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 / quality (push) Has been cancelled
ci / container (push) Has been cancelled

This commit is contained in:
2026-08-09 16:06:58 +00:00
parent ea7cdfa1a4
commit 87aa59f846

View File

@@ -2,33 +2,40 @@
## Scope and lifecycle ## Scope and lifecycle
This repository contains one FastAPI process that stores monitors and their latest check status in process-local memory. State starts empty, disappears on restart, and is neither shared nor replicated across workers. Run exactly one worker when consistent state is required. There is intentionally no authentication. This is an unauthenticated FastAPI service for creating process-local HTTP(S) endpoint monitors and running checks on demand. State is held in one concurrency-safe in-memory store and is lost at process exit. Each worker has an independent store; production must therefore run exactly one worker. This design intentionally has no scheduler, database, authentication, or cross-process coordination.
## Resource and status semantics ## Resource model
A monitor has an immutable UUID `id`, `name`, HTTP(S) `url`, timestamps, and a nullable `current_status`. Names need not be unique. `unknown` means no completed attempt; `up` means the final response was 200399; `down` means the final response was 400599; `error` means DNS, transport, timeout, redirect-policy, or SSRF rejection prevented a final response. Latency is wall-clock monotonic elapsed time for the complete attempt, including validated redirects. A monitor has a server-generated UUID, a human-readable `name`, an HTTP(S) `target_url`, creation/update timestamps, and `current_status`. Status starts as `unknown`. A completed check atomically replaces it with `up` (HTTP 100399), `down` (HTTP 400599), or `error` (DNS, policy, timeout, or transport failure), plus check time, latency, optional HTTP code, and a bounded error description.
## API ## HTTP contract
- `POST /v1/monitors` creates a monitor (`201`). - `POST /monitors` -> 201 and a monitor.
- `GET /v1/monitors` lists monitors (`200`). - `GET /monitors` -> 200 and all monitors, ordered by creation time.
- `GET /v1/monitors/{id}` retrieves one (`200`). - `GET /monitors/{id}` -> 200, or 404.
- `PATCH /v1/monitors/{id}` changes provided name and/or URL (`200`). - `PUT /monitors/{id}` -> 200; replaces editable fields and preserves status, or 404.
- `DELETE /v1/monitors/{id}` deletes it (`204`). - `DELETE /monitors/{id}` -> 204, or 404.
- `POST /v1/monitors/{id}/check` performs one bounded GET and atomically records the result (`200`). Policy-rejected destinations return `400` after recording an error; a monitor deleted while its request is running returns `404` and is not resurrected. - `POST /monitors/{id}/check` -> 200 and the new current status; 400 when target/redirect is forbidden; 404 when absent.
- `GET /v1/monitors/{id}/status` returns the latest status (`200`), including `unknown` before the first check. - `GET /monitors/{id}/status` -> 200 and current status, or 404.
- `GET /healthz` is liveness; `GET /readyz` confirms this process initialized its store. - `GET /health/live` -> 200 while the process serves requests.
- `GET /health/ready` -> 200 after lifespan initialization; 503 otherwise.
Missing UUID resources return FastAPI's `404 {"detail":"monitor not found"}`. Invalid input returns `422`. Duplicate IDs cannot be supplied by clients. FastAPI/Pydantic validation failures use 422. Explicit errors use `{"detail": "..."}`. UUID path validation also uses 422.
## Concurrency and outbound security ## Check and security semantics
`MonitorStore` serializes every state access with one `asyncio.Lock` and returns copies, preventing caller mutation. Network I/O never holds that lock. Status replacement is one locked operation. Only `http` and `https` are accepted; URL credentials are forbidden. Before every request, including every manually followed redirect, all resolved addresses are checked and the request is rejected if any address is non-global (loopback, private, link-local, multicast, reserved, unspecified, etc.). Literal IP hosts receive the same check. Redirect count, connect/read/write/pool timeouts, and response body use are bounded. Redirects without `Location` are treated according to their HTTP code. This DNS allow-listing closes ordinary private-address and redirect SSRF paths, but it cannot make process-local DNS validation and a later library connection perfectly atomic against a malicious DNS-rebinding authority; network egress policy remains a required production defense.
Only HTTP and HTTPS URLs without userinfo are accepted. Before each request—including every redirect hop—the checker resolves the hostname and requires every returned address to be globally routable. Literal loopback, private, link-local, multicast, reserved, and unspecified addresses are rejected. Redirects are manual, bounded, and revalidated. Timeouts and redirect counts are environment-controlled. This substantially reduces SSRF exposure; DNS rebinding between validation and the HTTP client's separate connection lookup remains a documented limitation, so production deployments should also enforce egress policy at the network layer. Latency is monotonic elapsed time for the complete attempt. Checks do not consume response bodies. A delete racing a check wins: the finished result is not resurrected. Updates under the store lock make each status replacement atomic.
## Logging and architecture ## Logging
Checks emit one JSON log event. URLs are normalized to omit credentials, fragments, and all query values (`?<redacted>`). Errors are bounded and do not include response bodies. Configuration is validated from `MONITOR_` environment variables. Logs are one-line JSON. Check events include monitor ID, outcome, latency, and a URL with user information and query/fragment removed. Raw URLs, query strings, and response bodies are never logged. Error text is bounded. Operators must still avoid embedding secrets in path segments because paths are retained for diagnosis.
`app/models.py` owns wire/domain types; `store.py` owns state; `security.py` owns destination policy; `checker.py` owns HTTP behavior; `main.py` composes routes; tests mock both DNS and HTTP. Packaging and validation evidence live at repository root. ## Project layout
- `app/`: settings, models, locked store, SSRF policy, checker, logging, and routes.
- `tests/`: API and unit tests with mocked outbound HTTP and DNS.
- `Dockerfile`, `compose.yaml`: non-root, single-worker runtime packaging.
- `pyproject.toml`, requirements files: dependency and quality-tool metadata.
- `README.md`: developer and operator guide.