2.7 KiB
Service contract and architecture
Resource and lifecycle
A monitor has a server-generated UUID, name, absolute HTTP(S) URL, expected HTTP status,
timestamps, and a current status. State is process-local memory guarded by one asyncio.Lock;
it starts empty on every process start, is not shared across workers, and is lost on restart.
Run exactly one worker unless an external store is added. Returned models are frozen so state
cannot be mutated outside the store lock.
HTTP contract
| Method | Path | Success | Purpose |
|---|---|---|---|
| POST | /monitors |
201 | Create |
| GET | /monitors |
200 | List |
| GET | /monitors/{uuid} |
200 | Retrieve |
| PATCH | /monitors/{uuid} |
200 | Partial update |
| DELETE | /monitors/{uuid} |
204 | Delete |
| POST | /monitors/{uuid}/check |
200 | Run and atomically persist a check |
| GET | /monitors/{uuid}/status |
200 | Retrieve current status |
| GET | /health/live |
200 | Process liveness |
| GET | /health/ready |
200 | In-memory service readiness |
Missing resources return {"error":{"code":"not_found","message":"monitor not found"}}
with 404. Invalid UUIDs or bodies return a generic, input-redacting invalid_request with 422.
There is intentionally no authentication.
Status meanings: never_checked has no observation; up exactly matches expected_status;
down is a completed nonmatching HTTP response; error is timeout/protocol/network failure;
blocked means outbound policy rejected the destination. Check failures are check results (200),
not API transport failures. A monitor deleted while its check runs yields 404 rather than being
recreated.
Outbound security and logging
Only HTTP(S), credential-free URLs are accepted by the checker. Every initial and redirected hop is resolved immediately before request; all returned addresses must be globally routable. Redirects are handled manually and bounded, requests have bounded timeouts, bodies are streamed, and exceptions map to stable non-sensitive messages. This blocks direct, DNS, and redirect attempts to loopback/private/link-local/reserved addresses. Like most application-level DNS checks, there remains a small resolver-to-connect rebinding race; production high-assurance deployments should additionally enforce an egress proxy/firewall.
Structured JSON check logs include IDs, status, latency, and scheme/host/port/path only. Query, fragment, credentials, response bodies, and raw exception text are excluded.
Layout
app/api.py owns HTTP semantics, models.py schemas, store.py locked state, checker.py check
orchestration, security.py outbound policy, and config.py environment settings. Tests isolate
outbound traffic with httpx.MockTransport.