Status: Accepted Date: 2026-10-02
Issue #742 (API-7) asks for predictable behavior under slow, concurrent,
malformed or overloaded clients: request timeout, header and body limits, a
concurrency cap, bounded queues, Retry-After on overload, graceful drain,
and a documented connection policy. Its multi-user note says a public or
network deployment must use a reviewed production transport adapter rather
than expose the stdlib development server.
Measured state of idkmesh/control_tower_ui.py before this decision:
ControlTowerServer is a stdlib ThreadingHTTPServer with
daemon_threads = True: one thread per connection and no cap.MAX_BODY_BYTES, 2 MiB), enforced on the
single endpoint that reads a body.serve_control_tower calls server_close() on exit but never waits for an
in-flight request, because daemon threads are not joined.max SSE clients has
nothing to bound.Apply the following limits to the Control Tower development server, publish them, and prove each one with a test.
408 request_timeout where
the socket still allows a response.503 overloaded with a
Retry-After header and the standard error envelope, without doing any
application work. GET /healthz is exempt so liveness stays cheap.request_queue_size = 16). The cap bounds concurrent request handling, not
accepted connections; this is stated rather than implied.503 shutting_down (including /readyz, so an orchestrator stops routing)
while /healthz still answers. serve_control_tower begins a drain on
shutdown and waits up to 5 s for in-flight requests before closing. Every
endpoint is read-only or a pure inspection, so no drain can leave a
partially committed mutation.429 in v0.1. 429 Retry-After signals that one client exceeded its
own rate. With a single local token there is no per-client identity to
attribute a rate to, so emitting 429 would be fabricated precision.
Server-wide saturation is 503. 429 is reserved for the enterprise
identity profile (ADR-0014/0016) and recorded as not implemented.operations.limits object reporting the limits actually in force.
Adding an optional property is a non-breaking change under ADR-0020, so the
frozen status schema is extended in place rather than versioned.Tunables are exposed as idkmesh control-tower --request-timeout SECONDS and
--max-concurrent-requests N, validated to sane ranges.
503 plus Retry-After.503. The backlog bound and the
loopback-only bind keep this acceptable for a development server.503.max SSE clients and 429 remain unimplemented until their prerequisites
(#741, enterprise identity) exist.gate-audit-ui, steward UIs); they stay as before.Rejected for this slice. It adds a dependency to a base install that #742 requires stay dependency-light, and the issue itself defers the network profile to a reviewed transport adapter.
429 for concurrency saturationRejected. It conflates server overload with a per-client quota and gives clients wrong retry semantics.
Rejected. An unbounded wait queue is an unbounded memory and latency commitment, which is exactly what #742 asks to remove.
Rejected. Rejecting before parsing cannot exempt /healthz or return the
standard envelope with the request id and security headers.
idkmesh/service_runtime.py owns the reusable limiter and limit constants;
idkmesh/control_tower_ui.py applies them;
docs/specifications/CONTROL_TOWER_LOCAL_API_V0_1.md documents them.Revisit this ADR if:
429 and per-principal quotas) or a production transport adapter (move the
limits there);