Files
crucible-agent-build-fastap…/docs/service-contract.md

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`.