53 lines
2.7 KiB
Markdown
53 lines
2.7 KiB
Markdown
# 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`.
|