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>
This commit is contained in:
2026-07-26 23:54:27 +00:00
parent cf6bbc43da
commit 5777824d0b
21 changed files with 488 additions and 15 deletions
+21
View File
@@ -512,6 +512,27 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
compose ls`; firecracker cross-references its running gateway
containers against per-bottle metadata."""
@abstractmethod
def attach_bottled_agents_to_gateway(self) -> None:
"""Reconcile every already-running bottle against the freshly-(re)booted
gateway (PRD 0081). The host calls this on the gateway bring-up path —
the cold-boot branch only, never on an adopt of a healthy current
gateway — so a gateway rebuild/restart doesn't silently break running
bottles' gateway-dependent state.
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 every backend must implement it —
cross-backend by construction. Each per-bottle step is best-effort: one
unreachable or malformed bottle is logged and skipped so it never blocks
the others or the gateway coming up.
Currently reconciles the **CA**: it replaces each agent's trusted CA
with the current gateway CA (unconditional — there is one gateway, so no
fingerprint match is needed), which rotates the CA for free on every
bring-up. Git-gate re-provision and egress-token restore fold into this
same reconcile on the same trigger (see #516)."""
@classmethod
@abstractmethod
def is_available(cls) -> bool: