decomposer: generate deliverable files for Define the service contract and project architecture for the FastAPI endpoint monitoring service.; Implement the typed monitor CRUD API and concurrency-safe in-memory state according to the service design.; Implement secure on-demand endpoint checks with status updates, latency measurement, robust error handling, and redacted structured logs.; Add operational API endpoints and environment-driven runtime configuration to the monitoring service.; Create automated tests for the monitoring service.; Package the service with Docker and developer documentation.; Validate the complete project.
Some checks failed
ci / test (push) Has been cancelled

This commit is contained in:
2026-08-09 16:00:45 +00:00
parent d35adce9e7
commit f93cd54f29

View File

@@ -1,46 +1,37 @@
# Service design and contract # Endpoint Monitor service contract
## Architecture ## Scope and lifecycle
A single FastAPI process owns a `MonitorStore` guarded by an `asyncio.Lock`. Routes call the store and an `EndpointChecker`; the checker validates every initial/redirect URL, resolves every hostname, applies a total bounded timeout, and returns a typed result. No authentication is provided. State is intentionally neither durable nor shared. This is a single-process FastAPI service with an asynchronous, lock-protected in-memory repository. Data is lost at process exit and is neither shared nor replicated between workers; production deployments must therefore use exactly one worker. There is no authentication.
## Resource ## Resource and routes
A monitor has UUID `id`, unique `name`, HTTP(S) `url`, timestamps, monotonically increasing `revision`, and latest status fields. Create accepts name/url. Update is PUT-like for supplied fields. URL changes clear prior status. A monitor has `id` (UUID), `name`, HTTP(S) `url`, creation/update timestamps, and a current check status. Status starts as `unknown`. `up` means the last response was HTTP 200399, `down` means HTTP 400599, and `error` means no usable HTTP response (timeout, network/protocol failure, redirect-policy failure, or blocked target).
Status values: | Method | Route | Meaning |
|---|---|---|
| POST | `/v1/monitors` | Create; 201 |
| GET | `/v1/monitors` | List; 200 |
| GET | `/v1/monitors/{id}` | Retrieve; 200 |
| PATCH | `/v1/monitors/{id}` | Update name and/or URL; 200 |
| DELETE | `/v1/monitors/{id}` | Delete; 204 |
| POST | `/v1/monitors/{id}/checks` | Run one bounded check; 200, or 400 for an unsafe target |
| GET | `/v1/monitors/{id}/status` | Retrieve current status; 200 |
| GET | `/healthz` | Liveness; 200 |
| GET | `/readyz` | Readiness of process-local dependencies; 200 |
- `unknown`: never checked or target changed Missing resources return `404` with `{"detail":{"code":"monitor_not_found","message":"Monitor not found"}}`. Validation uses FastAPI's 422 response. Unsafe initial targets and unsafe redirect destinations return 400 with code `unsafe_target`; the monitor records `error`. Endpoint timeouts and transport failures are check outcomes, not service failures, and return a typed `error` result.
- `up`: final HTTP response is 200399
- `down`: final HTTP response is 400599
- `error`: DNS, policy, timeout, redirect, or transport failure
A check records UTC attempt time, elapsed milliseconds, final redacted URL, optional HTTP code, and a bounded non-secret detail. Results update the monitor under the store lock. A deletion racing with a check wins: the result is not resurrected and the check route returns 404. ## Check and concurrency semantics
## Routes Checks use configured total timeout, response-byte and redirect limits. Every initial target and redirect is HTTP(S), has no user information, and has all DNS answers checked; loopback, private, link-local, multicast, reserved, unspecified, and otherwise non-global addresses are blocked. Redirects are followed manually and revalidated. DNS validation materially reduces SSRF exposure, but this application-level approach cannot fully remove DNS time-of-check/time-of-use rebinding risk in the underlying client; a production egress proxy/firewall is required for a hard network boundary.
- `POST /v1/monitors` → 201 Repository operations are serialized with `asyncio.Lock`. A check captures the monitor revision before I/O. Its status update is a compare-and-set and is discarded if the monitor was updated or deleted while I/O was in flight, preventing stale results from overwriting newer state. Latency is monotonic elapsed time for the complete redirect chain.
- `GET /v1/monitors` → 200
- `GET /v1/monitors/{id}` → 200
- `PATCH /v1/monitors/{id}` → 200
- `DELETE /v1/monitors/{id}` → 204
- `POST /v1/monitors/{id}/check` → 200 result; 400 policy rejection
- `GET /v1/monitors/{id}/status` → 200 status snapshot
- `GET /healthz` → liveness
- `GET /readyz` → process/store readiness
Errors use FastAPI's stable `{"detail": ...}` envelope: validation 422, not found 404, duplicate/capacity conflict 409, blocked check 400. Transport failures are check outcomes rather than API failures. ## Logging and configuration
## SSRF policy Logs are one-line JSON. URLs are emitted without user information and with query/fragment replaced by redaction markers. Bodies and request headers are never logged. Environment settings use the `MONITOR_` prefix and are validated at startup; see README. Secrets in URL query strings are not persisted in logs, although the monitor resource itself necessarily stores the configured URL.
Only `http` and `https`, explicit hostnames, and no user-info are accepted. Every DNS answer must be globally routable according to Python `ipaddress`; loopback, private, link-local, multicast, reserved, unspecified, and documentation ranges are rejected. The same validation is repeated for each redirect, redirects are followed manually, and redirect count and total request time are bounded. URL query and fragment data are omitted from logs and persisted final URLs. ## Project structure
DNS validation followed by a separate client connection has an unavoidable DNS-rebinding time-of-check/time-of-use window in this compact implementation. Production environments should additionally enforce an egress proxy/firewall that only permits public destinations or use a transport that pins validated addresses while preserving TLS SNI. The application-level checks remain defense in depth, not the sole network boundary. `app/` contains settings, schemas, lock-protected storage, checker/security policy, logging, and API assembly. `tests/` contains API and focused unit tests. `docs/` contains this contract and an honest verification record. Runtime/package files live at repository root.
## Logging
Logs are one-line JSON. Check events contain monitor ID, outcome, latency, and a redacted URL only. User-info, query strings, fragments, and response bodies are never logged. Error details are normalized and bounded.
## Lifecycle
The store is created with the app and reports ready while available. A restart loses state. Exactly one Uvicorn worker is required; horizontal scaling requires replacing the store with shared durable storage and distributed check coordination.