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

2.3 KiB

Endpoint Monitor Service Contract

Resource and lifecycle

A monitor has an immutable UUID id, name, HTTP(S) url, positive timeout_seconds, timestamps, and a current check snapshot. New and updated monitors are unknown until checked. State is process-local memory, guarded by one async lock; it disappears on restart and is neither shared nor replicated across workers. Run exactly one worker.

HTTP API

  • POST /monitors -> 201; create.
  • GET /monitors -> 200; list.
  • GET /monitors/{id} -> 200 or 404.
  • PUT /monitors/{id} -> 200 or 404; replace mutable fields and reset status.
  • DELETE /monitors/{id} -> 204 or 404.
  • POST /monitors/{id}/check -> 200 check result, 404 absent monitor, 400 blocked/invalid destination. Transport failures return a recorded error result rather than a gateway error.
  • GET /monitors/{id}/status -> 200 current snapshot or 404.
  • GET /healthz is liveness; GET /readyz reports readiness and storage mode.

Status is unknown, up (HTTP 200-399), down (HTTP 400-599), or error (DNS, policy, timeout, or transport failure). Latency is wall-clock monotonic elapsed milliseconds. Updates are committed atomically and only if the monitor still exists and has not changed during the check.

FastAPI/Pydantic validation errors use the framework 422 shape. Application errors use { "detail": "..." }. There is no authentication.

Security and logging

Only HTTP(S) URLs with hostnames are accepted. Every destination is DNS-resolved immediately before each request; all answers must be globally routable. Loopback, private, link-local, multicast, reserved, unspecified, and metadata-style non-global addresses are blocked. Redirects are followed manually up to the configured bound and every target is revalidated. Credentials in URLs are forbidden. Structured JSON logs include monitor id, event, status and a redacted URL (?REDACTED); query values and URL credentials are never logged.

Architecture

app/config.py validates environment settings; models.py is the wire/domain schema; store.py owns locked state; security.py enforces destination policy; checker.py performs bounded checks; main.py wires routes and exception behavior. Tests mock DNS and outbound HTTP. See README for runtime constraints and commands.