Files
crucible-agent-build-fastap…/SERVICE_DESIGN.md

3.3 KiB
Raw Permalink Blame History

Endpoint Monitor service contract

Scope and lifecycle

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 model

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.

HTTP contract

  • POST /monitors -> 201 and a monitor.
  • GET /monitors -> 200 and all monitors, ordered by creation time.
  • GET /monitors/{id} -> 200, or 404.
  • PUT /monitors/{id} -> 200; replaces editable fields and preserves status, or 404.
  • DELETE /monitors/{id} -> 204, or 404.
  • POST /monitors/{id}/check -> 200 and the new current status; 400 when target/redirect is forbidden; 404 when absent.
  • GET /monitors/{id}/status -> 200 and current status, or 404.
  • GET /health/live -> 200 while the process serves requests.
  • GET /health/ready -> 200 after lifespan initialization; 503 otherwise.

FastAPI/Pydantic validation failures use 422. Explicit errors use {"detail": "..."}. UUID path validation also uses 422.

Check and security semantics

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.

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

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.

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.