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 / validate (push) Has been cancelled

This commit is contained in:
2026-08-09 15:46:36 +00:00
parent f829dbcd7d
commit 7ad0b34be5

34
SERVICE_DESIGN.md Normal file
View File

@@ -0,0 +1,34 @@
# Endpoint Monitor service contract
## 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.
## Resource and status semantics
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.
## API
- `POST /v1/monitors` creates a monitor (`201`).
- `GET /v1/monitors` lists monitors (`200`).
- `GET /v1/monitors/{id}` retrieves one (`200`).
- `PATCH /v1/monitors/{id}` changes provided name and/or URL (`200`).
- `DELETE /v1/monitors/{id}` deletes it (`204`).
- `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.
- `GET /v1/monitors/{id}/status` returns the latest status (`200`), including `unknown` before the first check.
- `GET /healthz` is liveness; `GET /readyz` confirms this process initialized its store.
Missing UUID resources return FastAPI's `404 {"detail":"monitor not found"}`. Invalid input returns `422`. Duplicate IDs cannot be supplied by clients.
## Concurrency and outbound security
`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 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.
## Logging and architecture
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.
`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.