# Service contract and architecture ## Resource A monitor has UUID `id`, `name`, absolute HTTP(S) `url`, timestamps, and `current_status`. Status is initially `unknown`; an HTTP 200–399 result is `up`, any other HTTP response is `down`, and timeout/network/security failures set `error`. Status records include check time and latency; successful HTTP exchanges also include the status code. Secrets or response bodies are never retained. ## HTTP API | Method/path | Result | |---|---| | `POST /monitors` | Create, 201 | | `GET /monitors` | List, 200 | | `GET /monitors/{id}` | Read, 200 | | `PUT /monitors/{id}` | Replace name/url, 200; resets status if URL changes | | `DELETE /monitors/{id}` | Delete, 204 | | `POST /monitors/{id}/check` | Check now and atomically update status, 200 | | `GET /monitors/{id}/status` | Current status, 200 | | `GET /healthz` | Liveness, 200 | | `GET /readyz` | Readiness, 200 after state/checker initialization | Unknown resources return 404. Invalid requests return 422. Capacity returns 409. Unsafe targets return 400, outbound network failures 502, and timeout 504. Errors use `{"error":{"code":"...","message":"..."}}`. No authentication is provided; deploy behind an authenticated trusted gateway if exposed. ## Check security Only HTTP(S), host-bearing URLs without userinfo are accepted. A dedicated resolver rejects every non-global address from every DNS answer. Redirects are followed manually, resolved relative to the previous URL, and revalidated before each hop. The production aiohttp connector uses that same resolver, disables DNS caching, and closes connections, so connection resolution cannot bypass filtering. Total timeout and redirect count are bounded. The service does not return response bodies. ## Concurrency and lifecycle `MonitorStore` owns one dictionary guarded by an `asyncio.Lock`. Mutations and status publication are atomic. Status publication uses the checked URL as a compare condition: a concurrent URL change cannot publish a stale result and produces a 409. Memory is per process, starts empty, and disappears on restart. A one-worker deployment is required unless the store is replaced. ## Logging and structure Application events are single-line JSON. Logged URLs are transformed to scheme/host/path only; credentials, query, and fragment are removed. Error text from remote systems is not logged. `main` owns routing/lifespan, `store` owns state, `checker` owns outbound security, `models` owns wire types, `config` owns environment validation, and `logging_config` owns JSON/redaction behavior.