PRD: Host control server - list_live broker op + reconcile via broker (chunk 3/5) #507

Open
didericis-claude wants to merge 1 commits from host-control-server-3-list-live into host-control-server-2-secret
Collaborator

Chunk 3 of the host-control-server stack — grows the broker op vocabulary (PRD gap 3), starting with list_live, and inverts reconcile onto it.

Base: host-control-server-2-secret (#497) — stacked, review/merge after it.

What's here

  • Op vocabulary, split by kind. broker.py splits the closed vocabulary into mutation ops (launch/teardown — a bottle id + static launch flags) and query ops (list_live — carries nothing but its op name). verify_request now enforces a strict schema — resolving PRD open question 1 ("reject unknown claim keys?") to yes:
    • an unknown claim key is rejected outright, so the privileged surface can't widen by smuggling a claim past the fixed fields;
    • a mutation must name its bottle;
    • a query that smuggles any id/flag (bottle_id/source_ip/image_ref/slot) is refused — the "no arguments" rule, enforced, not just documented.
  • The list_live verb. LaunchBroker.list_live / SubmitBroker.list_live return the backend's live source IPs. DockerBroker enumerates its labelled containers; StubBroker derives the set from its recorded launches (or an explicit test override). A backend enumeration failure is converted to the single BrokerUnavailableError "live set unknown" signal.
  • Over the wire. POST /broker/live on the host controller verifies a signed list_live token and returns {"source_ips": [...]}; BrokerClient.list_live is its drop-in client. The verbs don't cross — a launch token routed to /broker/live (or vice-versa) is a fail-closed 401.
  • Reconcile inversion. OrchestratorCore.reconcile() drops the live_source_ips parameter and pulls the live set from the broker itself — the tell PRD 0070 called out (the orchestrator can't see the backend from inside its container) goes away. The /reconcile HTTP route and OrchestratorClient.reconcile become a bare trigger.

Safety property

Reconcile is fail-safe: if the broker can't return an authoritative live set (unreachable, timed out, enumeration failed), the sweep is skipped, never run against an empty/partial set. Reaping a healthy bottle's row (which bricks its egress at a recycled address) is far worse than leaving an orphan one cycle longer. Covered by test_reconcile_skipped_when_broker_cannot_enumerate.

Scope note (the "pull chunk 5 forward" call)

Per the steer to make launch coherent through the broker on macOS, the next stacked PR pulls chunk 5's launch-behind-broker forward: it moves the Apple-container container run behind the host controller so --broker http is coherent for all macOS ops, and wires macOS's existing enumeration (consolidated_launch.live_source_ips) in as the host controller's list_live. That piece is macOS-specific and can't be exercised in the Linux CI sandbox, so it's isolated here to keep this chunk fully green and reviewable. Until it lands, macOS's orchestrator broker is still the stub, whose live set is its recorded launches — so the interim sweep only under-reaps (leaves an orphan a cycle longer), never reaps a healthy bottle.

Tests + pyright clean; pylint 10.0 on broker.py.

Closes part of #468.

🤖 Generated with Claude Code

Chunk 3 of the host-control-server stack — grows the broker op vocabulary (PRD **gap 3**), starting with `list_live`, and inverts `reconcile` onto it. Base: `host-control-server-2-secret` (#497) — stacked, review/merge after it. ## What's here - **Op vocabulary, split by kind.** `broker.py` splits the closed vocabulary into **mutation** ops (`launch`/`teardown` — a bottle id + static launch flags) and **query** ops (`list_live` — carries nothing but its `op` name). `verify_request` now enforces a **strict schema** — resolving PRD **open question 1** ("reject unknown claim keys?") to **yes**: - an unknown claim key is rejected outright, so the privileged surface can't widen by smuggling a claim past the fixed fields; - a mutation must name its bottle; - a query that smuggles any id/flag (`bottle_id`/`source_ip`/`image_ref`/`slot`) is refused — the "no arguments" rule, enforced, not just documented. - **The `list_live` verb.** `LaunchBroker.list_live` / `SubmitBroker.list_live` return the backend's live source IPs. `DockerBroker` enumerates its labelled containers; `StubBroker` derives the set from its recorded launches (or an explicit test override). A backend enumeration failure is converted to the single `BrokerUnavailableError` "live set unknown" signal. - **Over the wire.** `POST /broker/live` on the host controller verifies a signed `list_live` token and returns `{"source_ips": [...]}`; `BrokerClient.list_live` is its drop-in client. The verbs don't cross — a launch token routed to `/broker/live` (or vice-versa) is a fail-closed 401. - **Reconcile inversion.** `OrchestratorCore.reconcile()` **drops the `live_source_ips` parameter** and pulls the live set from the broker itself — the tell PRD 0070 called out (the orchestrator can't see the backend from inside its container) goes away. The `/reconcile` HTTP route and `OrchestratorClient.reconcile` become a bare trigger. ## Safety property Reconcile is **fail-safe**: if the broker can't return an authoritative live set (unreachable, timed out, enumeration failed), the sweep is **skipped**, never run against an empty/partial set. Reaping a healthy bottle's row (which bricks its egress at a recycled address) is far worse than leaving an orphan one cycle longer. Covered by `test_reconcile_skipped_when_broker_cannot_enumerate`. ## Scope note (the "pull chunk 5 forward" call) Per the steer to make launch coherent through the broker on macOS, the **next stacked PR** pulls chunk 5's launch-behind-broker forward: it moves the Apple-container `container run` behind the host controller so `--broker http` is coherent for *all* macOS ops, and wires macOS's existing enumeration (`consolidated_launch.live_source_ips`) in as the host controller's `list_live`. That piece is macOS-specific and **can't be exercised in the Linux CI sandbox**, so it's isolated here to keep this chunk fully green and reviewable. Until it lands, macOS's orchestrator broker is still the stub, whose live set is its recorded launches — so the interim sweep only *under*-reaps (leaves an orphan a cycle longer), never reaps a healthy bottle. Tests + pyright clean; pylint 10.0 on `broker.py`. Closes part of #468. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
didericis force-pushed host-control-server-3-list-live from 9729407430 to 7005b22bcf 2026-07-26 18:16:05 -04:00 Compare
didericis added 1 commit 2026-07-26 18:36:11 -04:00
feat(orchestrator): list_live broker op + reconcile via broker (#468)
lint / lint (push) Successful in 1m5s
refresh-image-locks / refresh (push) Successful in 34s
test / unit (pull_request) Has started running
test / image-input-builds (pull_request) Has started running
test / coverage (pull_request) Blocked by required conditions
test / integration-docker (pull_request) Has been cancelled
tracker-policy-pr / check-pr (pull_request) Successful in 7s
76037f36a6
Chunk 3 of the host-control-server stack: grow the broker op vocabulary
(PRD gap 3), starting with `list_live`, and invert `reconcile` onto it.

- broker: split the closed op vocabulary into mutation (`launch`/`teardown`,
  carry a bottle id + static flags) and query (`list_live`, carries nothing
  but its op name) kinds. `verify_request` now enforces a **strict schema**
  (open question 1, resolved yes): unknown claim keys are rejected, a mutation
  must name its bottle, and a query that smuggles any id/flag is refused.
- broker verb: `LaunchBroker.list_live` / `SubmitBroker.list_live` return the
  backend's live source IPs; a backend enumeration failure is converted to the
  single `BrokerUnavailableError` "live set unknown" signal. `DockerBroker`
  enumerates its labelled containers; `StubBroker` derives from launches (or a
  test override).
- host controller: `POST /broker/live` verifies a signed `list_live` token and
  returns `{source_ips}`; `BrokerClient.list_live` is its drop-in client.
- reconcile: `OrchestratorCore.reconcile()` drops the `live_source_ips`
  parameter and pulls the live set from the broker itself — the tell that the
  orchestrator couldn't see the backend goes away. **Fail-safe**: if the broker
  can't return an authoritative set the sweep is skipped, never run against an
  empty/partial set (which would reap healthy rows). The `/reconcile` HTTP
  contract + `OrchestratorClient.reconcile` become a bare trigger.

The macOS launcher's Apple-container enumeration stays for now; it becomes the
host controller's `list_live` when launch itself moves behind the broker (the
pulled-forward chunk 5, next in the stack).

Tests + pyright clean; pylint 10.0 on broker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
didericis force-pushed host-control-server-3-list-live from 7005b22bcf to 76037f36a6 2026-07-26 18:36:11 -04:00 Compare
didericis-claude changed title from feat(orchestrator): list_live broker op + reconcile via broker (chunk 3/5) to feat(orchestrator): list_live broker op + reconcile via broker (chunk 3/3) 2026-07-26 23:17:23 -04:00
didericis-claude changed title from feat(orchestrator): list_live broker op + reconcile via broker (chunk 3/3) to PRD: Host control server - list_live broker op + reconcile via broker (chunk 3/5) 2026-07-26 23:18:50 -04:00
Some checks are pending
lint / lint (push) Successful in 1m5s
refresh-image-locks / refresh (push) Successful in 34s
test / unit (pull_request) Has started running
test / image-input-builds (pull_request) Has started running
test / coverage (pull_request) Blocked by required conditions
test / integration-docker (pull_request) Has been cancelled
tracker-policy-pr / check-pr (pull_request) Successful in 7s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin host-control-server-3-list-live:host-control-server-3-list-live
git checkout host-control-server-3-list-live
Sign in to join this conversation.