From 7ad0b34be531c9f68e11501a8bbf3efdeb6b3988 Mon Sep 17 00:00:00 2001 From: demo-bot Date: Sun, 9 Aug 2026 15:46:36 +0000 Subject: [PATCH] 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. --- SERVICE_DESIGN.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 SERVICE_DESIGN.md diff --git a/SERVICE_DESIGN.md b/SERVICE_DESIGN.md new file mode 100644 index 0000000..351c7fa --- /dev/null +++ b/SERVICE_DESIGN.md @@ -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 200–399; `down` means the final response was 400–599; `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 (`?`). 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.