fix(mission_runtime): per-mission auto-pair via container log scrape (C3 auth)
ci / gates (push) Successful in 10s
ci / rust (push) Failing after 23s
ci / frontend (push) Successful in 38s
ci / e2e (push) Skipped
ci / publish (push) Skipped

The seed-mount approach didnt work: even with the shared runtimes
data dir bind-mounted, a fresh gateway instance mints a new pairing
key and requires re-pairing. The topology_worker connect returned
401 forever.

New approach — per-mission gateways self-pair:
- Provisioner tails container logs after start, extracts the
  X-Pairing-Code from the boot banner
- Persists it on missions.runtime_pairing_code (migration 0059)
- topology_worker constructs ZeroClawDriveExecutor with THAT code
  via from_env_for_gateway_with_code, which triggers the lazy
  /pair handshake on first turn and caches the returned bearer

Drops the shared-runtime data-dir mount — each per-mission gateway
now owns its own state, restoring the C3 isolation guarantee.
This commit is contained in:
Omar Sobh
2026-07-22 13:06:25 -07:00
parent 0210f5bf51
commit b569688e04
7 changed files with 173 additions and 44 deletions
+6 -3
View File
@@ -72,14 +72,15 @@ pub async fn on_launch(
// shared runtime endpoint in that case.
if let Some(prov) = crate::mission_runtime::MissionRuntimeProvisioner::from_env() {
match prov.ensure_container(mission_id).await {
Ok(endpoint) => {
Ok(ec) => {
let container_name = crate::mission_runtime::container_name(mission_id);
if let Err(e) = cm_db::repo::missions::set_runtime_binding(
pool,
mission_id,
workspace_id.as_uuid(),
Some(&container_name),
Some(&endpoint),
Some(&ec.endpoint),
ec.pairing_code.as_deref(),
)
.await
{
@@ -88,7 +89,9 @@ pub async fn on_launch(
);
} else {
eprintln!(
"mission_orchestrator: runtime container {container_name}{endpoint} for mission {mission_id}"
"mission_orchestrator: runtime container {container_name}{} (paired={}) for mission {mission_id}",
ec.endpoint,
ec.pairing_code.is_some()
);
}
}