PRD: reprovision on gateway startup - bottle ca (chunk 1/5) #519

Open
didericis-claude wants to merge 14 commits from prd-0081-reconcile-1-ca into main

14 Commits

Author SHA1 Message Date
didericis-codex 3dc901c057 fix(docker): enumerate the active infra network
tracker-policy-pr / check-pr (pull_request) Successful in 21s
test / image-input-builds (pull_request) Successful in 59s
test / unit (pull_request) Successful in 1m2s
test / integration-docker (pull_request) Successful in 1m2s
test / coverage (pull_request) Successful in 15s
lint / lint (push) Successful in 58s
2026-07-27 16:44:00 +00:00
didericis-codex e49f9a4e53 fix(docker): fail closed on bottle enumeration
lint / lint (push) Successful in 1m2s
test / integration-docker (pull_request) Failing after 56s
test / image-input-builds (pull_request) Successful in 57s
test / unit (pull_request) Successful in 1m3s
test / coverage (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Successful in 11s
2026-07-27 16:41:17 +00:00
didericis a401310865 docs: renumber PRD 0083 -> 0081 across the reconcile chunk
test / image-input-builds (pull_request) Successful in 59s
test / integration-docker (pull_request) Successful in 1m3s
lint / lint (push) Successful in 3m32s
test / unit (pull_request) Successful in 2m47s
test / coverage (pull_request) Successful in 49s
tracker-policy-pr / check-pr (pull_request) Successful in 13s
Follows the base branch back to 0081. 0083 was already claimed by
feat/pinned-infra-artifacts (docs/prds/0083-packaged-infra-artifacts.md), so
the two PRDs would have collided on whichever merged second; 0081 is free.

29 references across 21 files — the "PRD 0083" prose in the backends, the
shared gateway_attach flow, the tests, and the ADR — plus the PRD filename in
the ADR's reference link, which now resolves again.

requirements.gateway.lock matches "0083" inside a sha256 hash and is
deliberately untouched.

Unit suite unchanged: same 13 pre-existing failures as main
(test_cli_start_selector, test_firecracker_cleanup).
2026-07-27 11:35:07 -04:00
didericis-claude 31b6724a52 docs: renumber PRD 0081 -> 0083 across the reconcile chunk
Follows the base PR's renumber (0081 was already taken; main is on 0082). Update
every `PRD 0081` reference in the gateway-attach code, tests, and ADR 0006, plus
ADR 0006's link to the PRD file. No behaviour change.

Refs #516, #519.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 11:33:35 -04:00
didericis-claude 6f3b221823 fix(backend): finish infra_launch rename + base cap after rebase onto main
Reconciles the chunk-1 rebase onto the current main:
- `_network_container_ips` (added on main) raised the pre-rename
  `ConsolidatedLaunchError`; renamed to `InfraLaunchError` to match the rest of
  the rename.
- main grew `backend/base.py` to 563 lines; with the gateway-attach contract
  (39 lines) it is 602, so the guardrail cap moves 600 -> 615. The reconcile
  flow still lives in `backend/gateway_attach.py`, not base.py.

Refs #516, #519.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 11:33:35 -04:00
didericis c73d6508bc fix(backend): reconcile reads the booted gateway's CA, not a default (0081)
The gateway-attach reconcile (PRD 0081) gathered its resources via a fresh
default DockerInfraService(), so on a cold-boot bring-up of a NON-default
gateway instance — e.g. an isolated integration test's `-itest-` gateway — it
read the default `bot-bottle-orch-gateway`, which doesn't exist for that
instance, and setUpClass died with "No such container: bot-bottle-orch-gateway"
(the 0081 PR's integration-docker job).

Thread the infra service whose gateway just cold-booted from
DockerInfraService.ensure_running() into DockerBottleBackend so
_gateway_attach_resources() reads THAT gateway's CA. Ordinary construction (no
infra) still falls back to the per-host default singleton — production is
unchanged — and the resources-first / fail-hard reconcile ordering is kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 11:33:35 -04:00
didericis-claude ffffe66c95 refactor(backend): base owns the gateway-attach flow, fail hard (0081 review)
Addresses review on #519 (@didericis 6143 + the fail-hard direction over the
codex skip).

Base owns the flow (6143 / ADR 0006). `attach_bottled_agents_to_gateway()` is
now a concrete method on `BottleBackend` that delegates to
`gateway_attach.reconcile_running_bottles`; backends override only three
primitives — `_gateway_attach_resources()`, `_running_bottles()`,
`_attach_bottle_to_gateway()`. The shared control flow + error policy live in
one place so a backend can't drift onto a bespoke loop or a silent skip. Keeps
the reconcile flow + `GatewayAttachResources` out of `backend/base.py` (the
size guardrail) in a dedicated `backend/gateway_attach.py`. New ADR 0006 records
the "shared behaviour in the base backend, subclasses override primitives"
theme.

Fail hard, never skip (the maintainer's direction over the codex review's
skip). Any attach failure now aborts bring-up instead of being logged and
skipped: a bottle that silently can't reach the fresh gateway (its egress just
starts failing TLS) is worse than a loud failure. Every bottle is attempted and
the failures are raised together (aggregate `InfraLaunchError`) so one bring-up
surfaces the full blast radius. Resource gathering (the CA fetch) also fails
hard. PRD 0081 goal + design updated to match (reversed from the earlier
"tolerate per-bottle failures" draft).

Bumps the base.py guardrail cap 580->600 for the new contract surface (the
delegator + 3 abstract primitives); the flow itself lives in gateway_attach.py.

Refs #516, #519.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 11:33:35 -04:00
didericis-claude 56cbd4a257 refactor(backend): rename consolidated_launch -> infra_launch (0081 review)
Mechanical rename addressing review 6141 on #519: "consolidated launch" was
unclear about what it composes. Rename the per-backend module
`consolidated_launch.py` -> `infra_launch.py` and the error
`ConsolidatedLaunchError` -> `InfraLaunchError` across all three backends and
their callers/tests. Unify the three identical per-backend error classes into
one `InfraLaunchError` defined in `backend/base.py` (re-exported by each
`infra_launch`) so the base backend can raise and catch a single shared type —
groundwork for the base owning the gateway-attach flow.

Add a glossary entry defining **infra** = the per-host gateway + orchestrator
service pair every bottle attaches to.

No behavior change.

Refs #516, #519.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 11:33:35 -04:00
didericis-claude 36f594b770 feat(backend): reconcile running bottles' CA on gateway bring-up (0081)
Add `attach_bottled_agents_to_gateway()` as an `@abc.abstractmethod` on
`BottleBackend` and implement the first reconcile step across all three
backends: on a gateway cold boot, replace every already-running agent's
trusted CA with the freshly-minted gateway CA. Reconciling running bottles
is a backend responsibility (only the backend can enumerate its agents and
reach them — firecracker over SSH, docker/macOS over exec/cp), so making it
an abstract method keeps the fix cross-backend by construction.

The gateway rootfs is ephemeral, so a rebuild mints a new CA that every
running bottle distrusts (SSL verification fails — #510). This reconcile is
what distributes the fresh CA, so a routine gateway rebuild now doubles as a
free CA rotation. Per-bottle steps are best-effort: one unreachable or
malformed bottle is logged and skipped, never blocking the others.

Trigger — `Gateway.connect_to_orchestrator` now returns a cold-boot bool
(True when it actually (re)brought the gateway up, False when a healthy
current gateway was left untouched); each infra `ensure_running` gates the
reconcile on it, so it fires exactly on cold boot and never on an adopt.

Git-gate re-provision and egress-token restore fold into this same reconcile
on the same trigger in follow-up PRs (#516).

Refs #516. Addresses #510.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 11:33:35 -04:00
didericis 314b30c013 docs(prd): renumber 0083 -> 0081, and point at the umbrella issue
prd-number-check / require-numbered-prds (pull_request) Successful in 14s
test / image-input-builds (pull_request) Successful in 46s
test / unit (pull_request) Successful in 1m0s
test / integration-docker (pull_request) Successful in 1m8s
test / coverage (pull_request) Successful in 18s
tracker-policy-pr / check-pr (pull_request) Successful in 14s
cf6bbc43 renumbered this PRD 0081 -> 0083 as "0081 already taken", but 0081 is
free: main goes 0080, 0082, and no other branch claims it. 0083 is the number
that is actually taken — feat/pinned-infra-artifacts holds
docs/prds/0083-packaged-infra-artifacts.md, renamed there from its prd-new-
form, so both PRDs were sitting on 0083 and whichever merged second would have
collided. Move back to 0081 and leave 0083 to the infra-artifacts PRD.

The Issue header pointed at #510 (CA) and #512 (git-gate), which have since
been folded into #516 and closed. #516 carries both symptoms plus the shared
root cause, so it is the reference that stays useful.

requirements.gateway.lock also matches "0083" — inside a sha256 hash, left
alone.
2026-07-27 11:33:23 -04:00
didericis-claude cf6bbc43da docs(prd): renumber 0081 -> 0083 (0081 already taken)
test / integration-docker (pull_request) Has been cancelled
prd-number-check / require-numbered-prds (pull_request) Successful in 14s
lint / lint (push) Successful in 1m4s
test / image-input-builds (pull_request) Successful in 59s
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / unit (pull_request) Failing after 13m55s
test / coverage (pull_request) Has been skipped
PRD number 0081 was claimed by another PRD before this one merged (main is on
0082); renumber this design to the next free slot, 0083. File + title only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:14:54 +00:00
didericis 4e26e075c8 docs(prd): reconcile via a backend-ABC method across all backends (0081 review)
Address review: target all backends, not firecracker-only. Make the reconcile
an @abc.abstractmethod `attach_bottled_agents_to_gateway()` on BottleBackend so
every backend implements it (cross-backend by construction) and the host calls
it on gateway bring-up. Drop the docker/macOS non-goal; add the open question of
retiring docker's now-redundant CA bind-mount.

Refs #516
2026-07-27 15:14:33 +00:00
didericis cca724244e feat(orchestrator): update a single egress secret in place (0081)
Add a single-secret counterpart to reprovision_from_secret's restore-all:
update ONE egress token for a running bottle without a relaunch, for
refreshing a short-lived host credential (e.g. the Codex access token)
whose launch-time snapshot has expired.

- registry_store.store_agent_secret: per-key upsert (delete+insert of the
  one row), the counterpart of store_agent_secrets' replace-all.
- OrchestratorCore.update_agent_secret: set the in-memory token AND upsert
  the re-encrypted row under the bottle's env_var_secret, leaving other
  tokens untouched.
- POST /bottles/<id>/secret (cli-only) + client.update_agent_secret.

Refs #510, #512

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:14:33 +00:00
didericis cecc1b4d60 docs(prd): reprovision gateway-dependent state on gateway bring-up (0081)
Reconcile every running bottle against a freshly-booted gateway (replace
each agent's CA, re-provision git-gate, restore egress tokens) instead of
persisting gateway state on volumes. Supersedes the abandoned CA-volume
(#511) and git-gate-volume (#513) PRs.

Refs #510, #512

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 15:14:33 +00:00