Codex review on #496:
- **High — ambiguous delivery no longer orphans a launched bottle.** A
timeout / dropped response from the host controller is now the ambiguous
BrokerUnavailableError (distinct from the definite BrokerAuthError /
BrokerClientError). OrchestratorCore.launch_bottle keeps the registry
row on the ambiguous case instead of deregistering — deregistering would
orphan a running container with no record (reconcile reaps rows, never
containers). The row is left for reconcile to reap iff the bottle is not
actually live. Definite failures still roll back, so a real failure
leaves no orphan row.
- **Medium — the privileged endpoint bounds request bodies.** The host
server rejects an oversized Content-Length with 413 before reading it,
and sets a per-request socket timeout, so a caller that can merely reach
the socket (no signed token) can't exhaust memory or a handler thread.
Tests: ambiguous-keep vs definite-rollback in the launch path; the
BrokerUnavailableError/BrokerClientError split in BrokerClient; the 413
body cap + handler error paths (driven in-thread, since daemon request
threads lose coverage) plus a deterministic real-socket check that
declares an oversized Content-Length but sends a sliver (rejection on the
header, no unread-body reset race); and the __main__ entrypoint broker
selection. Diff-coverage 98%; pyright clean; pylint 9.8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chunk 1 of the host-control-server stack: close the PRD's **transport**
gap. Today LaunchBroker.submit(token) is an in-process method call from
OrchestratorCore; this makes it a real out-of-process service reached
over HTTP.
- host_server.py: the host control server. A pure dispatch() (POST
/broker verifies a signed token via the existing verify_request +
_launch/_teardown path, GET /health) wrapped by a thin http.server
adapter, mirroring orchestrator/server.py. Only the signed token
crosses the wire; provenance/schema failures are fail-closed 401s that
never touch the backend, a backend launch failure is a 502.
- broker_client.py: BrokerClient — a drop-in submit(token) that POSTs the
signed token to the host controller. A 401 re-raises as BrokerAuthError
so the launch path's rollback is identical local or remote.
- broker.py: SubmitBroker Protocol — the one method OrchestratorCore
depends on, satisfied by both LaunchBroker and BrokerClient, so the
core is unchanged (service.py annotation only).
- __main__.py: wire `--broker http` behind the shared-secret env var
(BOT_BOTTLE_BROKER_SECRET, hex) — a chunk-1 stopgap the durable
TrustDomain key (chunk 2, #476) replaces.
Tested: pure-dispatch cases, BrokerClient with HTTP mocked, and a
real-socket sign -> POST -> verify -> act round-trip (incl. fail-closed
forged token). pyright clean; pylint 9.86.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>