@team-agent/installer 0.5.112 → 0.5.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/Cargo.lock +252 -84
  2. package/Cargo.toml +3 -1
  3. package/crates/team-agent/Cargo.toml +3 -0
  4. package/crates/team-agent/src/cli/adapters.rs +119 -50
  5. package/crates/team-agent/src/cli/diagnose.rs +36 -13
  6. package/crates/team-agent/src/cli/emit.rs +173 -109
  7. package/crates/team-agent/src/cli/leader.rs +16 -0
  8. package/crates/team-agent/src/cli/leader_prompt.rs +261 -0
  9. package/crates/team-agent/src/cli/leaders.rs +32 -32
  10. package/crates/team-agent/src/cli/mod.rs +158 -14
  11. package/crates/team-agent/src/cli/models.rs +68 -18
  12. package/crates/team-agent/src/cli/named_address.rs +17 -16
  13. package/crates/team-agent/src/cli/route.rs +4 -4
  14. package/crates/team-agent/src/cli/send/presentation.rs +19 -18
  15. package/crates/team-agent/src/cli/send/resolve.rs +11 -11
  16. package/crates/team-agent/src/cli/send.rs +4 -4
  17. package/crates/team-agent/src/cli/spec.rs +35 -29
  18. package/crates/team-agent/src/cli/status.rs +7 -7
  19. package/crates/team-agent/src/cli/status_port/brief.rs +268 -101
  20. package/crates/team-agent/src/cli/tests/base.rs +94 -6
  21. package/crates/team-agent/src/cli/tests/divergence.rs +3 -2
  22. package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
  23. package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
  24. package/crates/team-agent/src/cli/tests/status_send.rs +9 -7
  25. package/crates/team-agent/src/cli/triage.rs +53 -2
  26. package/crates/team-agent/src/cli/types.rs +8 -8
  27. package/crates/team-agent/src/compiler/tests.rs +12 -0
  28. package/crates/team-agent/src/compiler.rs +65 -26
  29. package/crates/team-agent/src/contract_runtime/backend.rs +646 -0
  30. package/crates/team-agent/src/contract_runtime/config.rs +309 -0
  31. package/crates/team-agent/src/contract_runtime/discovery.rs +237 -0
  32. package/crates/team-agent/src/contract_runtime/forward.rs +229 -0
  33. package/crates/team-agent/src/contract_runtime/framework.rs +261 -0
  34. package/crates/team-agent/src/contract_runtime/mcp.rs +396 -0
  35. package/crates/team-agent/src/contract_runtime/mod.rs +14 -0
  36. package/crates/team-agent/src/contract_runtime/prompt.rs +216 -0
  37. package/crates/team-agent/src/contract_runtime/pump.rs +207 -0
  38. package/crates/team-agent/src/contract_runtime/registry.rs +30 -0
  39. package/crates/team-agent/src/coordinator/tick.rs +26 -8
  40. package/crates/team-agent/src/leader/mod.rs +1 -0
  41. package/crates/team-agent/src/leader/prompt.rs +906 -0
  42. package/crates/team-agent/src/leader/start.rs +265 -16
  43. package/crates/team-agent/src/leader/tests/identity.rs +21 -0
  44. package/crates/team-agent/src/leader/tests/mod.rs +2 -0
  45. package/crates/team-agent/src/leader/tests/prompt_contract.rs +268 -0
  46. package/crates/team-agent/src/leader/types.rs +6 -0
  47. package/crates/team-agent/src/lib.rs +4 -0
  48. package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
  49. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +760 -365
  50. package/crates/team-agent/src/lifecycle/launch/spawn.rs +38 -9
  51. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +17 -0
  52. package/crates/team-agent/src/lifecycle/launch.rs +39 -28
  53. package/crates/team-agent/src/lifecycle/profile_launch.rs +58 -46
  54. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +97 -52
  55. package/crates/team-agent/src/lifecycle/restart.rs +1 -1
  56. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
  57. package/crates/team-agent/src/lifecycle/types.rs +4 -1
  58. package/crates/team-agent/src/lifecycle/worker_command_context.rs +37 -27
  59. package/crates/team-agent/src/main.rs +6 -0
  60. package/crates/team-agent/src/messaging/delivery.rs +50 -1
  61. package/crates/team-agent/src/messaging/send.rs +10 -0
  62. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +131 -0
  63. package/crates/team-agent/src/model/spec.rs +24 -12
  64. package/crates/team-agent/src/provider/adapter.rs +8 -59
  65. package/crates/team-agent/src/provider/adapters/claude.rs +6 -14
  66. package/crates/team-agent/src/provider/adapters/codex.rs +8 -18
  67. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -22
  68. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +8 -45
  69. package/crates/team-agent/src/provider/adapters/grok.rs +7 -31
  70. package/crates/team-agent/src/provider/adapters/pi.rs +7 -18
  71. package/crates/team-agent/src/provider/command_helpers.rs +45 -0
  72. package/crates/team-agent/src/provider/mod.rs +1 -0
  73. package/crates/team-agent/src/provider/model_catalog.rs +21 -28
  74. package/crates/team-agent/src/provider/tests/adapter.rs +70 -0
  75. package/crates/team-agent/src/provider/tests/command_helpers.rs +55 -0
  76. package/crates/team-agent/src/state/persist.rs +73 -0
  77. package/package.json +4 -4
@@ -192,7 +192,9 @@ impl FreshQuickStartLeaderBindingOps for RuntimeFreshQuickStartLeaderBindingOps<
192
192
  let (error, applied_grant) = error.into_parts();
193
193
  self.applied_grant = applied_grant.map(|grant| (grant.owner, grant.receiver));
194
194
  self.cas_conflict = match &error {
195
- crate::leader::LeaderError::State(crate::state::StateError::SaveConflict(_)) => true,
195
+ crate::leader::LeaderError::State(crate::state::StateError::SaveConflict(
196
+ _,
197
+ )) => true,
196
198
  _ => false,
197
199
  };
198
200
  self.last_failure_reason = Some(if self.cas_conflict {
@@ -206,7 +208,8 @@ impl FreshQuickStartLeaderBindingOps for RuntimeFreshQuickStartLeaderBindingOps<
206
208
  crate::leader::LeaderError::Io(_)
207
209
  | crate::leader::LeaderError::Json(_)
208
210
  | crate::leader::LeaderError::MessageStore(_)
209
- | crate::leader::LeaderError::Start(_) => "attach_failed",
211
+ | crate::leader::LeaderError::Start(_)
212
+ | crate::leader::LeaderError::Prompt(_) => "attach_failed",
210
213
  crate::leader::LeaderError::EventLog(_) => "attach_event_log_failed",
211
214
  crate::leader::LeaderError::Messaging(_) => "attach_watcher_failed",
212
215
  }
@@ -273,16 +276,13 @@ impl FreshQuickStartLeaderBindingOps for RuntimeFreshQuickStartLeaderBindingOps<
273
276
  });
274
277
  return false;
275
278
  }
276
- let Ok(resolved) = crate::state::projection::resolve_runtime_team_scope(
277
- workspace,
278
- Some(team_key),
279
- ) else {
279
+ let Ok(resolved) =
280
+ crate::state::projection::resolve_runtime_team_scope(workspace, Some(team_key))
281
+ else {
280
282
  self.last_failure_reason = Some("receiver_scope_unavailable");
281
283
  return false;
282
284
  };
283
- let Some(receiver) =
284
- super::selected_team_leader_receiver(&resolved.state, team_key)
285
- else {
285
+ let Some(receiver) = super::selected_team_leader_receiver(&resolved.state, team_key) else {
286
286
  self.last_failure_reason = Some("receiver_missing");
287
287
  return false;
288
288
  };
@@ -291,9 +291,8 @@ impl FreshQuickStartLeaderBindingOps for RuntimeFreshQuickStartLeaderBindingOps<
291
291
  .and_then(serde_json::Value::as_str)
292
292
  .filter(|endpoint| !endpoint.is_empty())
293
293
  .map(crate::transport_factory::leader_endpoint_transport);
294
- let live_channel_transport: &dyn Transport = leader_transport
295
- .as_deref()
296
- .unwrap_or(self.transport);
294
+ let live_channel_transport: &dyn Transport =
295
+ leader_transport.as_deref().unwrap_or(self.transport);
297
296
  if !matches!(
298
297
  crate::messaging::resolve_live_leader_channel(
299
298
  workspace,
@@ -314,9 +313,7 @@ impl FreshQuickStartLeaderBindingOps for RuntimeFreshQuickStartLeaderBindingOps<
314
313
  }
315
314
 
316
315
  fn frozen_seed_receipt(&self) -> Option<(serde_json::Value, serde_json::Value)> {
317
- self.frozen_owner
318
- .clone()
319
- .zip(self.frozen_receiver.clone())
316
+ self.frozen_owner.clone().zip(self.frozen_receiver.clone())
320
317
  }
321
318
  }
322
319
 
@@ -376,7 +373,6 @@ impl BindingFileSnapshot {
376
373
  let bytes = std::fs::read(&path).ok();
377
374
  Self { path, bytes }
378
375
  }
379
-
380
376
  }
381
377
 
382
378
  fn binding_snapshots(workspace: &Path, team_key: &str) -> Vec<BindingFileSnapshot> {
@@ -397,6 +393,25 @@ fn same_workspace(left: &Path, right: &Path) -> bool {
397
393
  == std::fs::canonicalize(right).unwrap_or_else(|_| right.to_path_buf())
398
394
  }
399
395
 
396
+ fn quick_start_tmux_endpoints_match(bound: &str, endpoint: &str) -> bool {
397
+ if bound == endpoint {
398
+ return true;
399
+ }
400
+ // A workspace transport uses a short `-L` name, while the native launcher
401
+ // persists an absolute `-S` path. Compare full resolved paths, never basenames.
402
+ let socket_path = |value: &str| {
403
+ let path = if Path::new(value).is_absolute() {
404
+ Some(PathBuf::from(value))
405
+ } else {
406
+ crate::tmux_backend::socket_path_for_name(value)
407
+ };
408
+ path.map(|path| crate::state::owner_gate::realpath_like(&path))
409
+ };
410
+ socket_path(bound)
411
+ .zip(socket_path(endpoint))
412
+ .is_some_and(|(bound, endpoint)| bound == endpoint)
413
+ }
414
+
400
415
  fn persisted_binding_matches_verified_pane(
401
416
  state: &serde_json::Value,
402
417
  workspace: &Path,
@@ -415,7 +430,10 @@ fn persisted_binding_matches_verified_pane(
415
430
  {
416
431
  return false;
417
432
  }
418
- let Some(owner) = state.get("team_owner").and_then(serde_json::Value::as_object) else {
433
+ let Some(owner) = state
434
+ .get("team_owner")
435
+ .and_then(serde_json::Value::as_object)
436
+ else {
419
437
  return false;
420
438
  };
421
439
  let Some(receiver) = state
@@ -455,7 +473,7 @@ fn persisted_binding_matches_verified_pane(
455
473
  && receiver
456
474
  .get("tmux_socket")
457
475
  .and_then(serde_json::Value::as_str)
458
- == Some(endpoint)
476
+ .is_some_and(|bound| quick_start_tmux_endpoints_match(bound, endpoint))
459
477
  && receiver
460
478
  .get("authorized_team_workspace")
461
479
  .and_then(serde_json::Value::as_str)
@@ -469,10 +487,9 @@ fn bind_fresh_quick_start_leader_with<O: FreshQuickStartLeaderBindingOps>(
469
487
  seeded_owner: Option<&serde_json::Value>,
470
488
  ops: &mut O,
471
489
  ) -> Result<bool, LifecycleError> {
472
- let Ok(resolved) = crate::state::projection::resolve_runtime_team_scope(
473
- workspace,
474
- Some(team_key),
475
- ) else {
490
+ let Ok(resolved) =
491
+ crate::state::projection::resolve_runtime_team_scope(workspace, Some(team_key))
492
+ else {
476
493
  return refuse_fresh_bind(workspace, team_key, "scope", "scope_unresolved");
477
494
  };
478
495
  if resolved.canonical_team_key != team_key {
@@ -531,27 +548,27 @@ fn bind_fresh_quick_start_leader_with<O: FreshQuickStartLeaderBindingOps>(
531
548
  // projected state still contains that seed.
532
549
  let current_receiver = state.get("leader_receiver");
533
550
  let frozen_receipt = ops.frozen_seed_receipt();
534
- let fresh_seeded_binding = if let Some((frozen_owner, frozen_receiver)) = frozen_receipt.as_ref()
535
- {
536
- state
537
- .get("team_owner")
538
- .is_some_and(|current| current == frozen_owner)
539
- && current_receiver.is_some_and(|current| current == frozen_receiver)
540
- } else {
541
- let pending_seed_receiver = current_receiver.is_some_and(|receiver| {
542
- receiver.get("status").and_then(serde_json::Value::as_str) == Some("pending")
543
- && receiver
544
- .get("discovery")
545
- .and_then(serde_json::Value::as_str)
546
- == Some("quick_start_seed")
547
- });
548
- seeded_owner.is_some_and(|seed| {
551
+ let fresh_seeded_binding =
552
+ if let Some((frozen_owner, frozen_receiver)) = frozen_receipt.as_ref() {
549
553
  state
550
554
  .get("team_owner")
551
- .is_some_and(|current| current == seed)
552
- && pending_seed_receiver
553
- })
554
- };
555
+ .is_some_and(|current| current == frozen_owner)
556
+ && current_receiver.is_some_and(|current| current == frozen_receiver)
557
+ } else {
558
+ let pending_seed_receiver = current_receiver.is_some_and(|receiver| {
559
+ receiver.get("status").and_then(serde_json::Value::as_str) == Some("pending")
560
+ && receiver
561
+ .get("discovery")
562
+ .and_then(serde_json::Value::as_str)
563
+ == Some("quick_start_seed")
564
+ });
565
+ seeded_owner.is_some_and(|seed| {
566
+ state
567
+ .get("team_owner")
568
+ .is_some_and(|current| current == seed)
569
+ && pending_seed_receiver
570
+ })
571
+ };
555
572
  let seeded_receiver = frozen_receipt
556
573
  .as_ref()
557
574
  .map(|(_, receiver)| receiver.clone())
@@ -702,9 +719,9 @@ fn combine_rollback_errors(
702
719
  match (state, registry) {
703
720
  (None, None) => None,
704
721
  (Some(error), None) | (None, Some(error)) => Some(error),
705
- (Some(state), Some(registry)) => Some(LifecycleError::StatePersist(format!(
706
- "{state}; {registry}"
707
- ))),
722
+ (Some(state), Some(registry)) => {
723
+ Some(LifecycleError::StatePersist(format!("{state}; {registry}")))
724
+ }
708
725
  }
709
726
  }
710
727
 
@@ -738,9 +755,11 @@ fn restore_this_attempt_state(
738
755
  });
739
756
  };
740
757
  let previous = match snapshot.map(|snapshot| snapshot.bytes.as_deref()) {
741
- Some(Some(bytes)) => serde_json::from_slice::<serde_json::Value>(bytes).map_err(|error| {
742
- LifecycleError::StatePersist(format!("quick-start binding cleanup failed: {error}"))
743
- })?,
758
+ Some(Some(bytes)) => {
759
+ serde_json::from_slice::<serde_json::Value>(bytes).map_err(|error| {
760
+ LifecycleError::StatePersist(format!("quick-start binding cleanup failed: {error}"))
761
+ })?
762
+ }
744
763
  Some(None) | None => serde_json::Value::Null,
745
764
  };
746
765
  let previous_owner = previous
@@ -860,9 +879,9 @@ fn clear_fresh_binding_on_refusal(
860
879
  };
861
880
  crate::state::repository::StateRepository::new(workspace)
862
881
  .save(intent, state)
863
- .map_err(|error| LifecycleError::StatePersist(format!(
864
- "quick-start binding cleanup failed: {error}"
865
- )))
882
+ .map_err(|error| {
883
+ LifecycleError::StatePersist(format!("quick-start binding cleanup failed: {error}"))
884
+ })
866
885
  }
867
886
 
868
887
  fn existing_runtime_identity_view(
@@ -888,16 +907,14 @@ fn caller_read_transport() -> Option<Box<dyn Transport>> {
888
907
  if !Path::new(&endpoint).exists() {
889
908
  return None;
890
909
  }
891
- Some(Box::new(crate::tmux_backend::TmuxBackend::for_tmux_endpoint(
892
- &endpoint,
893
- )))
910
+ Some(Box::new(
911
+ crate::tmux_backend::TmuxBackend::for_tmux_endpoint(&endpoint),
912
+ ))
894
913
  }
895
914
 
896
915
  fn query_caller_pane_command(transport: &dyn Transport, pane: &PaneId) -> Option<String> {
897
916
  let caller_transport = caller_read_transport();
898
- let query_transport: &dyn Transport = caller_transport
899
- .as_deref()
900
- .unwrap_or(transport);
917
+ let query_transport: &dyn Transport = caller_transport.as_deref().unwrap_or(transport);
901
918
  query_transport
902
919
  .query(&Target::Pane(pane.clone()), PaneField::PaneCurrentCommand)
903
920
  .ok()
@@ -940,8 +957,8 @@ fn preflight_fresh_leader_identity(
940
957
  let Some(pane) = pane else {
941
958
  return None;
942
959
  };
943
- let command = query_caller_pane_command(transport, &PaneId::new(pane))
944
- .unwrap_or_default();
960
+ let command =
961
+ query_caller_pane_command(transport, &PaneId::new(pane)).unwrap_or_default();
945
962
  let explicit = std::env::var("TEAM_AGENT_LEADER_PROVIDER")
946
963
  .ok()
947
964
  .filter(|provider| !provider.is_empty());
@@ -949,16 +966,11 @@ fn preflight_fresh_leader_identity(
949
966
  return Some((
950
967
  "quick-start refused before spawn: command unobservable".to_string(),
951
968
  vec!["stage=command_observation reason=command_unobservable".to_string()],
952
- vec![
953
- "run from a live provider pane; do not run claim-leader".to_string(),
954
- ],
969
+ vec!["run from a live provider pane; do not run claim-leader".to_string()],
955
970
  ));
956
971
  }
957
- if crate::leader::owner_bind::strict_owner_bind_provider(
958
- explicit.as_deref(),
959
- &command,
960
- )
961
- .is_some()
972
+ if crate::leader::owner_bind::strict_owner_bind_provider(explicit.as_deref(), &command)
973
+ .is_some()
962
974
  {
963
975
  None
964
976
  } else {
@@ -1078,12 +1090,7 @@ pub fn quick_start_in_workspace_with_backend(
1078
1090
  if is_tmux_or_default {
1079
1091
  let transport = quick_start_tmux_backend(&workspace);
1080
1092
  return quick_start_with_transport_in_workspace(
1081
- &workspace,
1082
- agents_dir,
1083
- name,
1084
- yes,
1085
- team_id,
1086
- &transport,
1093
+ &workspace, agents_dir, name, yes, team_id, &transport,
1087
1094
  );
1088
1095
  }
1089
1096
  // Explicit non-tmux backend: route through the factory. Parse the
@@ -1256,6 +1263,32 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1256
1263
  )));
1257
1264
  }
1258
1265
  crate::lifecycle::launch::run_pi_catalog_preflight(agents_dir, discover)?;
1266
+ #[cfg(unix)]
1267
+ {
1268
+ let input = crate::cli::QuickStartArgs {
1269
+ workspace: workspace.into(),
1270
+ agents_dir: agents_dir.into(),
1271
+ name: name.map(str::to_owned),
1272
+ team_id: team_id.map(str::to_owned),
1273
+ yes: false,
1274
+ json: false,
1275
+ detail: false,
1276
+ backend: Some(
1277
+ if matches!(transport.kind(), crate::transport::BackendKind::Tmux) {
1278
+ "tmux"
1279
+ } else {
1280
+ "conpty"
1281
+ }
1282
+ .into(),
1283
+ ),
1284
+ };
1285
+ if let Some(config) = crate::contract_runtime::config::read_team(&input)
1286
+ .map_err(|error| LifecycleError::Compile(error.to_string()))?
1287
+ {
1288
+ crate::contract_runtime::backend::preflight(&config)
1289
+ .map_err(|error| LifecycleError::Provider(error.to_string()))?;
1290
+ }
1291
+ }
1259
1292
  let workspace = workspace.to_path_buf();
1260
1293
  let mut spec = crate::compiler::compile_team(agents_dir)
1261
1294
  .map_err(|e| LifecycleError::Compile(e.to_string()))?;
@@ -1286,6 +1319,7 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1286
1319
  )));
1287
1320
  }
1288
1321
  let state_path = crate::state::persist::runtime_state_path(&workspace);
1322
+ let mut leader_bootstrap = None;
1289
1323
  if state_path.exists() {
1290
1324
  let state = crate::state::persist::load_runtime_state(&workspace)
1291
1325
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
@@ -1308,7 +1342,9 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1308
1342
  .get("tmux_endpoint")
1309
1343
  .and_then(serde_json::Value::as_str)
1310
1344
  .or_else(|| {
1311
- identity.get("tmux_socket").and_then(serde_json::Value::as_str)
1345
+ identity
1346
+ .get("tmux_socket")
1347
+ .and_then(serde_json::Value::as_str)
1312
1348
  })
1313
1349
  .or_else(|| {
1314
1350
  state
@@ -1363,6 +1399,16 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1363
1399
  agent_ids,
1364
1400
  });
1365
1401
  }
1402
+ let entry = state
1403
+ .get("teams")
1404
+ .and_then(serde_json::Value::as_object)
1405
+ .and_then(|teams| teams.get(&canonical_team_key))
1406
+ .unwrap_or(&state);
1407
+ if is_quick_start_leader_bootstrap(entry) {
1408
+ let mut bootstrap = entry.clone();
1409
+ bootstrap["team_key"] = serde_json::json!(canonical_team_key);
1410
+ leader_bootstrap = Some(bootstrap);
1411
+ }
1366
1412
  }
1367
1413
  // CR-040/042: repeated quick-start from one template with distinct --team-id/--name
1368
1414
  // must NOT collide on the template-derived tmux session. Override the compiled
@@ -1384,12 +1430,20 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1384
1430
  attach_commands: Vec::new(),
1385
1431
  });
1386
1432
  }
1433
+ if let Some(bootstrap) = &leader_bootstrap {
1434
+ // Allow initialization, not takeover of the existing leader binding.
1435
+ ensure_owner_allowed_for_state(bootstrap, None)?;
1436
+ }
1387
1437
  // E5 spec 迁移:spec 写到 .team/runtime/<team_key>/(中间产物,绝不落用户目录 agents_dir)。
1388
1438
  // Bug2:原子写(tmp+rename),避免半截 spec。
1389
1439
  let spec_path = crate::model::paths::runtime_spec_path(&workspace, &state_team_key);
1390
1440
  write_spec_atomic(&spec_path, &spec)?;
1391
- let _store = crate::message_store::MessageStore::open(&workspace)
1392
- .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
1441
+ let _store = crate::message_store::MessageStore::open(&workspace).map_err(|error| {
1442
+ LifecycleError::StatePersist(format!(
1443
+ "database initialization failed at {}: {error}; coordinator start was not attempted",
1444
+ workspace.join(".team/team.db").display()
1445
+ ))
1446
+ })?;
1393
1447
  let resolved_spec_path =
1394
1448
  std::fs::canonicalize(&spec_path).unwrap_or_else(|_| spec_path.clone());
1395
1449
  let scoped_endpoint = transport.tmux_endpoint();
@@ -1401,6 +1455,9 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1401
1455
  &state_team_key,
1402
1456
  scoped_endpoint.as_deref(),
1403
1457
  );
1458
+ if let Some(bootstrap) = &leader_bootstrap {
1459
+ preserve_quick_start_bootstrap_binding(&mut state, bootstrap, &state_team_key);
1460
+ }
1404
1461
  // Keep this attempt-local seed separate from persisted `claimed_via`;
1405
1462
  // historical quick-start rows are never sufficient cleanup authority.
1406
1463
  let seeded_owner = state
@@ -1409,10 +1466,7 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1409
1466
  .and_then(|teams| teams.get(&state_team_key))
1410
1467
  .and_then(|team| team.get("team_owner"))
1411
1468
  .filter(|owner| {
1412
- owner
1413
- .get("claimed_via")
1414
- .and_then(serde_json::Value::as_str)
1415
- == Some("quick-start")
1469
+ owner.get("claimed_via").and_then(serde_json::Value::as_str) == Some("quick-start")
1416
1470
  })
1417
1471
  .cloned();
1418
1472
  let seeded_receiver = state
@@ -1475,15 +1529,21 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1475
1529
  launch.session_capture_incomplete_agents =
1476
1530
  quick_start_session_capture_incomplete_agents(&workspace, &state_team_key);
1477
1531
  let coordinator_workspace = crate::coordinator::WorkspacePath::new(workspace.clone());
1478
- let coordinator_started = crate::coordinator::start_coordinator(&coordinator_workspace)
1479
- .map(|report| report.ok)
1480
- .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
1481
- quick_start_phase_timer.emit(&workspace, "launch.phase", "coordinator_start");
1482
- let coordinator_action = if coordinator_started {
1483
- "coordinator started"
1484
- } else {
1485
- "coordinator not started"
1486
- };
1532
+ let coordinator_report = crate::coordinator::start_coordinator(&coordinator_workspace)
1533
+ .map_err(|error| {
1534
+ LifecycleError::StatePersist(format!(
1535
+ "coordinator start failed for {}: {error}; inspect {} before retrying",
1536
+ workspace.display(),
1537
+ crate::coordinator::coordinator_log_path(&coordinator_workspace).display()
1538
+ ))
1539
+ })?;
1540
+ quick_start_phase_timer.emit(
1541
+ &workspace,
1542
+ "launch.phase",
1543
+ crate::lifecycle::restart::LifecyclePhase::CoordinatorStart,
1544
+ );
1545
+ require_quick_start_coordinator_started(&coordinator_report, &workspace)?;
1546
+ let coordinator_action = "coordinator started";
1487
1547
  // BUG-7: build an honest readiness verdict from the post-spawn runtime state.
1488
1548
  // - If persist_spawn_agent_state (BUG-2 fix) marked any agent non-running, the
1489
1549
  // team is observably Degraded.
@@ -1491,7 +1551,11 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1491
1551
  // loaded successfully (provider-side codex/claude schema rejections happen
1492
1552
  // asynchronously after spawn), so the verdict is PendingToolLoad — never
1493
1553
  // bare Ready.
1494
- quick_start_phase_timer.emit(&workspace, "launch.phase", "readiness_wait");
1554
+ quick_start_phase_timer.emit(
1555
+ &workspace,
1556
+ "launch.phase",
1557
+ crate::lifecycle::restart::LifecyclePhase::ReadinessWait,
1558
+ );
1495
1559
  let worker_readiness = quick_start_worker_readiness(&workspace, &state_team_key);
1496
1560
  let attach_windows = load_runtime_state(&workspace)
1497
1561
  .ok()
@@ -1531,7 +1595,11 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1531
1595
  ));
1532
1596
  }
1533
1597
  next_actions.extend(attach_commands.iter().cloned());
1534
- quick_start_phase_timer.emit(&workspace, "launch.phase", "completed");
1598
+ quick_start_phase_timer.emit(
1599
+ &workspace,
1600
+ "launch.phase",
1601
+ crate::lifecycle::restart::LifecyclePhase::Completed,
1602
+ );
1535
1603
  Ok(QuickStartReport::Ready {
1536
1604
  session_name,
1537
1605
  launch: Box::new(launch),
@@ -1542,8 +1610,80 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
1542
1610
  })
1543
1611
  }
1544
1612
 
1613
+ fn preserve_quick_start_bootstrap_binding(
1614
+ state: &mut serde_json::Value,
1615
+ bootstrap: &serde_json::Value,
1616
+ team_key: &str,
1617
+ ) {
1618
+ let mut binding = crate::state::ownership::OwnershipWrite::new();
1619
+ if let Some(owner) = bootstrap.get("team_owner") {
1620
+ binding = binding.with_team_owner(owner.clone());
1621
+ }
1622
+ if let Some(receiver) = bootstrap.get("leader_receiver") {
1623
+ binding = binding.with_leader_receiver(receiver.clone());
1624
+ }
1625
+ if let Some(epoch) = bootstrap
1626
+ .get("owner_epoch")
1627
+ .and_then(serde_json::Value::as_u64)
1628
+ .or_else(|| {
1629
+ bootstrap
1630
+ .get("team_owner")
1631
+ .and_then(|owner| owner.get("owner_epoch"))
1632
+ .and_then(serde_json::Value::as_u64)
1633
+ })
1634
+ {
1635
+ binding = binding.with_owner_epoch(epoch);
1636
+ }
1637
+ crate::state::ownership::write_owner(state, team_key, binding);
1638
+ // This is the new launch's transient view, not the persisted bootstrap.
1639
+ // The canonical writer intentionally leaves root fields alone; remove the
1640
+ // provisional epoch-1 seed so save_launched_team_state cannot merge it back
1641
+ // over the preserved canonical binding. Reuse its existing view promotion.
1642
+ if let Some(root) = state.as_object_mut() {
1643
+ for key in ["team_owner", "leader_receiver", "owner_epoch"] {
1644
+ root.remove(key);
1645
+ }
1646
+ }
1647
+ super::state_projection::promote_launched_binding_from_team_entry(state, team_key);
1648
+ for key in ["leader_client", "is_external_leader"] {
1649
+ if let Some(value) = bootstrap.get(key) {
1650
+ state[key] = value.clone();
1651
+ }
1652
+ }
1653
+ }
1654
+
1655
+ fn require_quick_start_coordinator_started(
1656
+ report: &crate::coordinator::StartReport,
1657
+ workspace: &Path,
1658
+ ) -> Result<(), LifecycleError> {
1659
+ if report.ok {
1660
+ return Ok(());
1661
+ }
1662
+ let details = serde_json::json!({
1663
+ "ok": report.ok,
1664
+ "status": report.status,
1665
+ "pid": report.pid,
1666
+ "previous_pid": report.previous_pid,
1667
+ "binary_path": report.binary_path,
1668
+ "binary_version": report.binary_version,
1669
+ "rotation_reason": report.rotation_reason,
1670
+ "binary_identity_relation": report.binary_identity_relation,
1671
+ "log": report.log,
1672
+ "schema_error": report.schema_error.as_ref().map(|error| format!("{error:?}")),
1673
+ "action": report.action,
1674
+ "workspace": workspace,
1675
+ "coordinator_log_path": crate::coordinator::coordinator_log_path(
1676
+ &crate::coordinator::WorkspacePath::new(workspace.to_path_buf())
1677
+ ),
1678
+ });
1679
+ Err(LifecycleError::StatePersist(format!(
1680
+ "coordinator start refused: {details}; workers may already be started; inspect the report before retrying quick-start"
1681
+ )))
1682
+ }
1683
+
1545
1684
  #[cfg(test)]
1546
1685
  mod fresh_quick_start_leader_binding_tests {
1686
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
1547
1687
  use super::*;
1548
1688
  use crate::layout::worker_env::{CALLER_ENDPOINT_ENV, CALLER_PANE_ENV, CALLER_PROVIDER_ENV};
1549
1689
  use crate::transport::test_support::OfflineTransport;
@@ -1556,6 +1696,139 @@ mod fresh_quick_start_leader_binding_tests {
1556
1696
 
1557
1697
  static SEQUENCE: AtomicU64 = AtomicU64::new(0);
1558
1698
 
1699
+ #[test]
1700
+ fn bootstrap_binding_is_preserved_without_epoch_reset_or_takeover() {
1701
+ let bootstrap = json!({
1702
+ "team_owner": {"pane_id": "%0", "provider": "pi", "owner_epoch": 7, "claimed_via": "claim-leader"},
1703
+ "leader_receiver": {"pane_id": "%0", "status": "attached", "owner_epoch": 7},
1704
+ "owner_epoch": 7,
1705
+ "leader_client": {"diagnostic_only": true},
1706
+ "is_external_leader": true
1707
+ });
1708
+ let mut launched = json!({
1709
+ "team_key": "current", "agents": {"worker": {}},
1710
+ "team_owner": {"owner_epoch": 1, "claimed_via": "quick-start"},
1711
+ "leader_receiver": {"status": "pending"}, "owner_epoch": 1,
1712
+ "is_external_leader": false
1713
+ });
1714
+ preserve_quick_start_bootstrap_binding(&mut launched, &bootstrap, "current");
1715
+ for key in ["team_owner", "leader_receiver", "owner_epoch"] {
1716
+ assert_eq!(launched.get(key), bootstrap.get(key));
1717
+ assert_eq!(launched["teams"]["current"].get(key), bootstrap.get(key));
1718
+ }
1719
+ assert_eq!(
1720
+ launched.get("leader_client"),
1721
+ bootstrap.get("leader_client")
1722
+ );
1723
+ assert_eq!(launched.get("is_external_leader"), Some(&json!(true)));
1724
+ assert_eq!(launched["agents"], json!({"worker": {}}));
1725
+ let merged = super::super::state_projection::merge_workspace_team_state_with_key(
1726
+ &bootstrap, &launched, "current",
1727
+ );
1728
+ assert_eq!(
1729
+ merged["teams"]["current"]["team_owner"],
1730
+ bootstrap["team_owner"]
1731
+ );
1732
+ assert_eq!(merged["teams"]["current"]["owner_epoch"], json!(7));
1733
+ }
1734
+
1735
+ #[test]
1736
+ fn coordinator_negative_report_retains_actual_details_and_does_not_become_ready() {
1737
+ let mut report = crate::coordinator::StartReport {
1738
+ ok: false,
1739
+ pid: None,
1740
+ status: crate::coordinator::StartOutcome::SchemaIncompatible,
1741
+ previous_pid: Some(crate::coordinator::Pid(17)),
1742
+ binary_path: Some("/tmp/candidate/team-agent".to_string()),
1743
+ binary_version: Some("0.5.112".to_string()),
1744
+ rotation_reason: Some("original rotation reason".to_string()),
1745
+ binary_identity_relation:
1746
+ crate::coordinator::CoordinatorBinaryIdentityRelation::Unknown,
1747
+ log: Some(PathBuf::from("/tmp/current/.team/runtime/coordinator.log")),
1748
+ schema_error: Some(crate::coordinator::SchemaError::InitFailed {
1749
+ message: "original database permission denied".to_string(),
1750
+ }),
1751
+ action: Some("repair the exact database permissions".to_string()),
1752
+ };
1753
+ let error = require_quick_start_coordinator_started(&report, Path::new("/tmp/current"))
1754
+ .unwrap_err()
1755
+ .to_string();
1756
+ for detail in [
1757
+ "schema_incompatible",
1758
+ "original database permission denied",
1759
+ "repair the exact database permissions",
1760
+ "/tmp/candidate/team-agent",
1761
+ "/tmp/current/.team/runtime/coordinator.log",
1762
+ "workers may already be started",
1763
+ ] {
1764
+ assert!(error.contains(detail), "lost {detail}: {error}");
1765
+ }
1766
+ report.ok = true;
1767
+ report.status = crate::coordinator::StartOutcome::AlreadyRunning;
1768
+ report.binary_identity_relation =
1769
+ crate::coordinator::CoordinatorBinaryIdentityRelation::DaemonNewerThanCaller;
1770
+ assert!(
1771
+ require_quick_start_coordinator_started(&report, Path::new("/tmp/current")).is_ok()
1772
+ );
1773
+ }
1774
+
1775
+ #[test]
1776
+ #[serial_test::serial(env)]
1777
+ fn same_current_leader_bootstrap_reaches_fresh_preflight_without_mutating_seed() {
1778
+ let env = HermeticTestEnv::enter("current-bootstrap-preflight");
1779
+ env.scrub_tmux();
1780
+ env.assert_no_real_tmux();
1781
+ let workspace = std::env::temp_dir().join(format!(
1782
+ "ta-current-bootstrap-{}-{}",
1783
+ std::process::id(),
1784
+ SEQUENCE.fetch_add(1, Ordering::Relaxed)
1785
+ ));
1786
+ let team = workspace.join(".team/current");
1787
+ std::fs::create_dir_all(team.join("agents")).unwrap();
1788
+ std::fs::write(
1789
+ team.join("TEAM.md"),
1790
+ "---\nname: current\nobjective: Bootstrap fixture.\n---\n",
1791
+ )
1792
+ .unwrap();
1793
+ std::fs::write(team.join("agents/worker.md"), "---\nname: worker\nrole: Worker\nprovider: codex\nauth_mode: subscription\ndangerously_skip_permissions: false\n---\nBounded fixture.\n").unwrap();
1794
+ let state_path = crate::state::persist::runtime_state_path(&workspace);
1795
+ std::fs::create_dir_all(state_path.parent().unwrap()).unwrap();
1796
+ let state = json!({
1797
+ "active_team_key": "current", "team_key": "current", "tasks": [],
1798
+ "teams": {"current": {
1799
+ "agents": {}, "tasks": [], "session_name": null,
1800
+ "team_owner": {"pane_id": "%0", "provider": "pi", "owner_epoch": 1},
1801
+ "leader_receiver": {"pane_id": "%0", "status": "attached"}
1802
+ }}
1803
+ });
1804
+ std::fs::write(&state_path, serde_json::to_vec(&state).unwrap()).unwrap();
1805
+ let before = std::fs::read(&state_path).unwrap();
1806
+ // The real entrypoint must reach its existing strict identity preflight,
1807
+ // not return ExistingRuntime merely because the launcher registered current.
1808
+ std::env::set_var("TMUX", "/private/tmp/bootstrap-test/default,1,0");
1809
+ std::env::set_var("TMUX_PANE", "%0");
1810
+ std::env::set_var(CALLER_PANE_ENV, "%0");
1811
+ std::env::set_var(CALLER_PROVIDER_ENV, "pi");
1812
+ std::env::set_var(
1813
+ CALLER_ENDPOINT_ENV,
1814
+ "/private/tmp/different-bootstrap/default",
1815
+ );
1816
+ let transport =
1817
+ OfflineTransport::new().with_tmux_endpoint("/private/tmp/bootstrap-test/default");
1818
+ let result = quick_start_with_transport_in_workspace(
1819
+ &workspace, &team, None, true, None, &transport,
1820
+ )
1821
+ .unwrap();
1822
+ assert!(
1823
+ matches!(result, QuickStartReport::PreflightBlocked { .. }),
1824
+ "unexpected outcome: {result:?}"
1825
+ );
1826
+ assert_eq!(std::fs::read(&state_path).unwrap(), before);
1827
+ assert!(!workspace.join(".team/team.db").exists());
1828
+ assert!(!crate::model::paths::runtime_spec_path(&workspace, "current").exists());
1829
+ std::fs::remove_dir_all(&workspace).unwrap();
1830
+ }
1831
+
1559
1832
  fn workspace_with_provider(tag: &str, provider: &str) -> PathBuf {
1560
1833
  let path = std::env::temp_dir().join(format!(
1561
1834
  "ta-quick-start-bind-{tag}-{}-{}",
@@ -1719,10 +1992,7 @@ mod fresh_quick_start_leader_binding_tests {
1719
1992
  )
1720
1993
  }
1721
1994
  _ => crate::state::persist::save_runtime_state_with_receiver_authority(
1722
- workspace,
1723
- state,
1724
- &team_key,
1725
- None,
1995
+ workspace, state, &team_key, None,
1726
1996
  ),
1727
1997
  };
1728
1998
  if saved.is_ok() {
@@ -1738,9 +2008,7 @@ mod fresh_quick_start_leader_binding_tests {
1738
2008
  }
1739
2009
 
1740
2010
  fn frozen_seed_receipt(&self) -> Option<(serde_json::Value, serde_json::Value)> {
1741
- self.frozen_owner
1742
- .clone()
1743
- .zip(self.frozen_receiver.clone())
2011
+ self.frozen_owner.clone().zip(self.frozen_receiver.clone())
1744
2012
  }
1745
2013
 
1746
2014
  fn this_attempt_extra_state_keys(&self) -> Vec<&'static str> {
@@ -1787,12 +2055,15 @@ mod fresh_quick_start_leader_binding_tests {
1787
2055
  }
1788
2056
 
1789
2057
  #[test]
1790
- fn fresh_binding_persists_then_registers_then_requires_canonical_readback_provider_independent() {
2058
+ fn fresh_binding_persists_then_registers_then_requires_canonical_readback_provider_independent()
2059
+ {
1791
2060
  let workspace = workspace("positive");
1792
2061
  let mut ops = MockOps::default();
1793
- assert!(bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
1794
- .unwrap());
1795
- assert_eq!(ops.attached_provider, Some(crate::provider::Provider::Codex));
2062
+ assert!(bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
2063
+ assert_eq!(
2064
+ ops.attached_provider,
2065
+ Some(crate::provider::Provider::Codex)
2066
+ );
1796
2067
  assert_eq!(ops.attach_calls, 1);
1797
2068
  assert_eq!(ops.register_calls, 1);
1798
2069
  assert_eq!(ops.readback_calls, 1);
@@ -1827,11 +2098,13 @@ mod fresh_quick_start_leader_binding_tests {
1827
2098
  leader_session_uuid: "uuid-fresh".to_string(),
1828
2099
  leader_session_uuid_source: "env".to_string(),
1829
2100
  };
1830
- assert!(super::spec_state::seed_launched_owner_from_caller_with_provider_lookup(
1831
- &mut state,
1832
- caller,
1833
- |_| None
1834
- ));
2101
+ assert!(
2102
+ super::spec_state::seed_launched_owner_from_caller_with_provider_lookup(
2103
+ &mut state,
2104
+ caller,
2105
+ |_| None
2106
+ )
2107
+ );
1835
2108
  let owner = state
1836
2109
  .pointer("/teams/fresh/team_owner")
1837
2110
  .cloned()
@@ -1858,13 +2131,10 @@ mod fresh_quick_start_leader_binding_tests {
1858
2131
  frozen_receiver: Some(frozen_receiver.clone()),
1859
2132
  ..MockOps::default()
1860
2133
  };
1861
- assert!(bind_fresh_quick_start_leader_with(
1862
- &workspace,
1863
- "fresh",
1864
- Some(&owner),
1865
- &mut ops
1866
- )
1867
- .unwrap());
2134
+ assert!(
2135
+ bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&owner), &mut ops)
2136
+ .unwrap()
2137
+ );
1868
2138
  assert_eq!(ops.attach_calls, 1);
1869
2139
  assert_eq!(ops.last_expected_receiver, Some(frozen_receiver));
1870
2140
  let _ = std::fs::remove_dir_all(&workspace);
@@ -1881,10 +2151,7 @@ mod fresh_quick_start_leader_binding_tests {
1881
2151
  state["leader_receiver"] = newer.clone();
1882
2152
  state["teams"]["fresh"]["leader_receiver"] = newer.clone();
1883
2153
  crate::state::persist::save_runtime_state_with_receiver_authority(
1884
- &workspace,
1885
- &state,
1886
- "fresh",
1887
- None,
2154
+ &workspace, &state, "fresh", None,
1888
2155
  )
1889
2156
  .unwrap();
1890
2157
  let on_disk = crate::state::persist::load_runtime_state(&workspace).unwrap();
@@ -1901,13 +2168,10 @@ mod fresh_quick_start_leader_binding_tests {
1901
2168
  frozen_receiver: Some(frozen_receiver),
1902
2169
  ..MockOps::default()
1903
2170
  };
1904
- assert!(!bind_fresh_quick_start_leader_with(
1905
- &workspace,
1906
- "fresh",
1907
- Some(&owner),
1908
- &mut ops
1909
- )
1910
- .unwrap());
2171
+ assert!(
2172
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&owner), &mut ops)
2173
+ .unwrap()
2174
+ );
1911
2175
  assert_eq!(ops.attach_calls, 0);
1912
2176
  assert_eq!(ops.last_expected_receiver, None);
1913
2177
  let _ = std::fs::remove_dir_all(&workspace);
@@ -1924,10 +2188,7 @@ mod fresh_quick_start_leader_binding_tests {
1924
2188
  state["leader_receiver"] = winner.clone();
1925
2189
  state["teams"]["fresh"]["leader_receiver"] = winner.clone();
1926
2190
  crate::state::persist::save_runtime_state_with_receiver_authority(
1927
- &workspace,
1928
- &state,
1929
- "fresh",
1930
- None,
2191
+ &workspace, &state, "fresh", None,
1931
2192
  )
1932
2193
  .unwrap();
1933
2194
  let on_disk = crate::state::persist::load_runtime_state(&workspace).unwrap();
@@ -1944,13 +2205,10 @@ mod fresh_quick_start_leader_binding_tests {
1944
2205
  frozen_receiver: Some(frozen_receiver),
1945
2206
  ..MockOps::default()
1946
2207
  };
1947
- assert!(!bind_fresh_quick_start_leader_with(
1948
- &workspace,
1949
- "fresh",
1950
- Some(&owner),
1951
- &mut ops
1952
- )
1953
- .unwrap());
2208
+ assert!(
2209
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&owner), &mut ops)
2210
+ .unwrap()
2211
+ );
1954
2212
  assert_eq!(ops.attach_calls, 0);
1955
2213
  assert_eq!(ops.last_expected_receiver, None);
1956
2214
  let _ = std::fs::remove_dir_all(&workspace);
@@ -1960,7 +2218,11 @@ mod fresh_quick_start_leader_binding_tests {
1960
2218
  fn fresh_bind_refusal_event_keeps_first_safe_stage_and_reason() {
1961
2219
  for (case, expected_stage, expected_reason) in [
1962
2220
  ("missing_pane", "caller_pane", "caller_pane_missing"),
1963
- ("empty_command", "command_observation", "command_unobservable"),
2221
+ (
2222
+ "empty_command",
2223
+ "command_observation",
2224
+ "command_unobservable",
2225
+ ),
1964
2226
  ("unknown_provider", "strict_provider", "provider_unresolved"),
1965
2227
  (
1966
2228
  "existing_owner",
@@ -1997,12 +2259,16 @@ mod fresh_quick_start_leader_binding_tests {
1997
2259
  }
1998
2260
  crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
1999
2261
 
2000
- assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
2001
- .unwrap());
2262
+ assert!(
2263
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap()
2264
+ );
2002
2265
  let events = crate::event_log::EventLog::new(&workspace).tail(0).unwrap();
2003
2266
  assert_eq!(events.len(), 1, "{case} must emit one first-refusal event");
2004
2267
  assert_eq!(events[0]["event"], json!(FRESH_BIND_REFUSAL_EVENT));
2005
- assert_eq!(events[0]["operation"], json!("fresh_quick_start_leader_bind"));
2268
+ assert_eq!(
2269
+ events[0]["operation"],
2270
+ json!("fresh_quick_start_leader_bind")
2271
+ );
2006
2272
  assert_eq!(events[0]["team_key"], json!("fresh"));
2007
2273
  assert_eq!(events[0]["stage"], json!(expected_stage));
2008
2274
  assert_eq!(events[0]["reason"], json!(expected_reason));
@@ -2020,9 +2286,12 @@ mod fresh_quick_start_leader_binding_tests {
2020
2286
  ));
2021
2287
  std::fs::create_dir_all(&empty_workspace).unwrap();
2022
2288
  let mut ops = MockOps::default();
2023
- assert!(!bind_fresh_quick_start_leader_with(&empty_workspace, "fresh", None, &mut ops)
2024
- .unwrap());
2025
- let events = crate::event_log::EventLog::new(&empty_workspace).tail(0).unwrap();
2289
+ assert!(
2290
+ !bind_fresh_quick_start_leader_with(&empty_workspace, "fresh", None, &mut ops).unwrap()
2291
+ );
2292
+ let events = crate::event_log::EventLog::new(&empty_workspace)
2293
+ .tail(0)
2294
+ .unwrap();
2026
2295
  assert_eq!(events.len(), 1);
2027
2296
  assert_eq!(events[0]["stage"], json!("scope"));
2028
2297
  assert_eq!(events[0]["reason"], json!("scope_unresolved"));
@@ -2037,8 +2306,9 @@ mod fresh_quick_start_leader_binding_tests {
2037
2306
  state["teams"] = json!({"fresh": {"team_key": "fresh", "agents": {}}});
2038
2307
  crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
2039
2308
  let mut ops = MockOps::default();
2040
- assert!(!bind_fresh_quick_start_leader_with(&workspace, "current", None, &mut ops)
2041
- .unwrap());
2309
+ assert!(
2310
+ !bind_fresh_quick_start_leader_with(&workspace, "current", None, &mut ops).unwrap()
2311
+ );
2042
2312
  let events = crate::event_log::EventLog::new(&workspace).tail(0).unwrap();
2043
2313
  assert_eq!(events.len(), 1);
2044
2314
  assert_eq!(events[0]["stage"], json!("scope"));
@@ -2082,10 +2352,10 @@ mod fresh_quick_start_leader_binding_tests {
2082
2352
  ops.command = Some("codex".to_string());
2083
2353
  }
2084
2354
  "existing_owner" => state["team_owner"] = json!({"pane_id": "%old"}),
2085
- "existing_receiver" => {
2086
- state["leader_receiver"] = json!({"pane_id": "%old"})
2087
- }
2088
- "provider_mismatch" | "socket_mismatch" | "workspace_mismatch"
2355
+ "existing_receiver" => state["leader_receiver"] = json!({"pane_id": "%old"}),
2356
+ "provider_mismatch"
2357
+ | "socket_mismatch"
2358
+ | "workspace_mismatch"
2089
2359
  | "dual_state_mismatch" => {
2090
2360
  state["workspace"] = json!(workspace);
2091
2361
  state["team_owner"] = json!({
@@ -2100,8 +2370,10 @@ mod fresh_quick_start_leader_binding_tests {
2100
2370
  match case {
2101
2371
  "provider_mismatch" => {
2102
2372
  state["leader_receiver"]["provider"] = json!("claude")
2103
- },
2104
- "socket_mismatch" => state["leader_receiver"]["tmux_socket"] = json!("/tmp/other"),
2373
+ }
2374
+ "socket_mismatch" => {
2375
+ state["leader_receiver"]["tmux_socket"] = json!("/tmp/other")
2376
+ }
2105
2377
  "workspace_mismatch" => state["workspace"] = json!(workspace.join("other")),
2106
2378
  "dual_state_mismatch" => state["team_owner"]["owner_epoch"] = json!(2),
2107
2379
  _ => unreachable!(),
@@ -2111,11 +2383,10 @@ mod fresh_quick_start_leader_binding_tests {
2111
2383
  _ => unreachable!(),
2112
2384
  }
2113
2385
  crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
2114
- let before = std::fs::read(crate::state::persist::runtime_state_path(&workspace))
2115
- .unwrap();
2386
+ let before =
2387
+ std::fs::read(crate::state::persist::runtime_state_path(&workspace)).unwrap();
2116
2388
  assert!(
2117
- !bind_fresh_quick_start_leader_with(&workspace, team_key, None, &mut ops)
2118
- .unwrap(),
2389
+ !bind_fresh_quick_start_leader_with(&workspace, team_key, None, &mut ops).unwrap(),
2119
2390
  "{case} must refuse"
2120
2391
  );
2121
2392
  assert_eq!(
@@ -2287,15 +2558,23 @@ mod fresh_quick_start_leader_binding_tests {
2287
2558
  || bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
2288
2559
  )
2289
2560
  .unwrap());
2290
- assert_eq!(ops.attach_calls, 0, "preseeded dual state must not reattach");
2291
- let path = crate::leader::registry::registry_dir().unwrap().join(format!(
2292
- "{}__fresh.json",
2293
- crate::leader::registry::workspace_hash(&workspace)
2294
- ));
2561
+ assert_eq!(
2562
+ ops.attach_calls, 0,
2563
+ "preseeded dual state must not reattach"
2564
+ );
2565
+ let path = crate::leader::registry::registry_dir()
2566
+ .unwrap()
2567
+ .join(format!(
2568
+ "{}__fresh.json",
2569
+ crate::leader::registry::workspace_hash(&workspace)
2570
+ ));
2295
2571
  let entry: crate::leader::registry::LeaderRegistryEntry =
2296
2572
  serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap();
2297
2573
  assert_eq!(
2298
- entry.channel.get("tmux_socket").and_then(serde_json::Value::as_str),
2574
+ entry
2575
+ .channel
2576
+ .get("tmux_socket")
2577
+ .and_then(serde_json::Value::as_str),
2299
2578
  Some("/private/tmp/tmux-test/default")
2300
2579
  );
2301
2580
  }
@@ -2325,8 +2604,7 @@ mod fresh_quick_start_leader_binding_tests {
2325
2604
  let before = std::fs::read(crate::state::persist::runtime_state_path(&workspace)).unwrap();
2326
2605
  let mut ops = MockOps::default();
2327
2606
 
2328
- assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
2329
- .unwrap());
2607
+ assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
2330
2608
  assert_eq!(
2331
2609
  before,
2332
2610
  std::fs::read(crate::state::persist::runtime_state_path(&workspace)).unwrap(),
@@ -2506,10 +2784,7 @@ mod fresh_quick_start_leader_binding_tests {
2506
2784
  persisted["teams"][team_key]["leader_receiver"] = takeover.receiver.clone();
2507
2785
  persisted["teams"][team_key]["owner_epoch"] = json!(takeover.epoch);
2508
2786
  crate::state::persist::save_runtime_state_with_receiver_authority(
2509
- workspace,
2510
- &persisted,
2511
- team_key,
2512
- None,
2787
+ workspace, &persisted, team_key, None,
2513
2788
  )
2514
2789
  .unwrap();
2515
2790
  assert_fresh_binding_kept(
@@ -2541,18 +2816,13 @@ mod fresh_quick_start_leader_binding_tests {
2541
2816
  }
2542
2817
  }
2543
2818
 
2544
- fn assert_fresh_binding_kept(
2545
- persisted: &serde_json::Value,
2546
- takeover: &ConcurrentTakeover,
2547
- ) {
2819
+ fn assert_fresh_binding_kept(persisted: &serde_json::Value, takeover: &ConcurrentTakeover) {
2548
2820
  assert_eq!(
2549
- persisted["teams"]["fresh"]["team_owner"],
2550
- takeover.owner,
2821
+ persisted["teams"]["fresh"]["team_owner"], takeover.owner,
2551
2822
  "concurrent team_owner must survive seeded cleanup"
2552
2823
  );
2553
2824
  assert_eq!(
2554
- persisted["teams"]["fresh"]["leader_receiver"],
2555
- takeover.receiver,
2825
+ persisted["teams"]["fresh"]["leader_receiver"], takeover.receiver,
2556
2826
  "concurrent leader_receiver must survive seeded cleanup"
2557
2827
  );
2558
2828
  assert_eq!(
@@ -2566,13 +2836,10 @@ mod fresh_quick_start_leader_binding_tests {
2566
2836
  fn seeded_cleanup_clears_exact_seed_through_bind_repository_entry() {
2567
2837
  let (workspace, seed) = workspace_with_fresh_seed("seed-cleanup-success");
2568
2838
  let mut ops = failed_register_ops(None, false);
2569
- assert!(!bind_fresh_quick_start_leader_with(
2570
- &workspace,
2571
- "fresh",
2572
- Some(&seed),
2573
- &mut ops
2574
- )
2575
- .unwrap());
2839
+ assert!(
2840
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
2841
+ .unwrap()
2842
+ );
2576
2843
  assert_eq!(ops.inner.attach_calls, 1);
2577
2844
  assert_eq!(ops.inner.register_calls, 1);
2578
2845
  assert!(ops
@@ -2591,11 +2858,9 @@ mod fresh_quick_start_leader_binding_tests {
2591
2858
  "matching seed must be cleared via bind/repository; got {}",
2592
2859
  persisted["teams"]["fresh"]["team_owner"]
2593
2860
  );
2594
- assert!(
2595
- persisted
2596
- .pointer("/teams/fresh/leader_receiver")
2597
- .is_some_and(serde_json::Value::is_null)
2598
- );
2861
+ assert!(persisted
2862
+ .pointer("/teams/fresh/leader_receiver")
2863
+ .is_some_and(serde_json::Value::is_null));
2599
2864
  assert_eq!(
2600
2865
  persisted["teams"]["fresh"]["owner_epoch"],
2601
2866
  json!(1),
@@ -2608,13 +2873,10 @@ mod fresh_quick_start_leader_binding_tests {
2608
2873
  let (workspace, seed) = workspace_with_matching_seed("seed-cleanup-concurrent");
2609
2874
  let takeover = takeover_binding(1);
2610
2875
  let mut ops = failed_register_ops(Some(takeover_binding(1)), false);
2611
- assert!(!bind_fresh_quick_start_leader_with(
2612
- &workspace,
2613
- "fresh",
2614
- Some(&seed),
2615
- &mut ops
2616
- )
2617
- .unwrap());
2876
+ assert!(
2877
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
2878
+ .unwrap()
2879
+ );
2618
2880
  assert_fresh_binding_kept(
2619
2881
  &crate::state::persist::load_runtime_state(&workspace).unwrap(),
2620
2882
  &takeover,
@@ -2626,13 +2888,10 @@ mod fresh_quick_start_leader_binding_tests {
2626
2888
  let (workspace, seed) = workspace_with_matching_seed("seed-cleanup-epoch2");
2627
2889
  let takeover = takeover_binding(2);
2628
2890
  let mut ops = failed_register_ops(Some(takeover_binding(2)), false);
2629
- assert!(!bind_fresh_quick_start_leader_with(
2630
- &workspace,
2631
- "fresh",
2632
- Some(&seed),
2633
- &mut ops
2634
- )
2635
- .unwrap());
2891
+ assert!(
2892
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
2893
+ .unwrap()
2894
+ );
2636
2895
  assert_fresh_binding_kept(
2637
2896
  &crate::state::persist::load_runtime_state(&workspace).unwrap(),
2638
2897
  &takeover,
@@ -2643,13 +2902,8 @@ mod fresh_quick_start_leader_binding_tests {
2643
2902
  fn seeded_cleanup_propagates_persist_error() {
2644
2903
  let (workspace, seed) = workspace_with_fresh_seed("seed-cleanup-persist-err");
2645
2904
  let mut ops = failed_register_ops(None, true);
2646
- let error = bind_fresh_quick_start_leader_with(
2647
- &workspace,
2648
- "fresh",
2649
- Some(&seed),
2650
- &mut ops,
2651
- )
2652
- .expect_err("poisoned state.json must surface cleanup persist failure");
2905
+ let error = bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
2906
+ .expect_err("poisoned state.json must surface cleanup persist failure");
2653
2907
  assert_eq!(ops.inner.attach_calls, 1);
2654
2908
  assert_eq!(ops.inner.register_calls, 1);
2655
2909
  assert!(ops
@@ -2729,7 +2983,9 @@ mod fresh_quick_start_leader_binding_tests {
2729
2983
  "owner_epoch": 1
2730
2984
  }
2731
2985
  });
2732
- serde_json::to_string_pretty(&expected).unwrap().into_bytes()
2986
+ serde_json::to_string_pretty(&expected)
2987
+ .unwrap()
2988
+ .into_bytes()
2733
2989
  }
2734
2990
 
2735
2991
  fn assert_failure_rolls_back_this_attempt(
@@ -2828,8 +3084,7 @@ mod fresh_quick_start_leader_binding_tests {
2828
3084
  attach_ok: false,
2829
3085
  ..MockOps::default()
2830
3086
  };
2831
- assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
2832
- .unwrap());
3087
+ assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
2833
3088
  let after = crate::state::persist::load_runtime_state(&workspace).unwrap();
2834
3089
  assert_eq!(after.get("tasks"), Some(&json!({"keep": true})));
2835
3090
  assert!(after.get("failed_attach_mutation").is_none());
@@ -2863,6 +3118,153 @@ mod fresh_quick_start_leader_binding_tests {
2863
3118
  })
2864
3119
  }
2865
3120
 
3121
+ fn native_persisted_binding(workspace: &Path, socket: &str) -> serde_json::Value {
3122
+ let uuid = "9e1ceba443966bc206a776a7c621e808";
3123
+ json!({
3124
+ "workspace": workspace,
3125
+ "agents": {}, "tasks": [], "session_name": null,
3126
+ "team_owner": complete_owner("%0", "pi", uuid, 1),
3127
+ "leader_receiver": complete_receiver("%0", "pi", uuid, 1, socket)
3128
+ })
3129
+ }
3130
+
3131
+ #[cfg(unix)]
3132
+ #[test]
3133
+ #[serial_test::serial(env)]
3134
+ fn native_persisted_binding_matches_workspace_short_socket_without_weakening_identity() {
3135
+ let hermetic = HermeticTestEnv::enter("native-persisted-socket");
3136
+ let workspace = hermetic.root();
3137
+ let transport = crate::tmux_backend::TmuxBackend::for_workspace(workspace);
3138
+ let endpoint = transport.tmux_endpoint().unwrap();
3139
+ let socket = crate::tmux_backend::socket_path_for_workspace(workspace).unwrap();
3140
+ let state = native_persisted_binding(workspace, socket.to_str().unwrap());
3141
+ let before = state.clone();
3142
+ let pane = PaneId::new("%0");
3143
+ assert_ne!(socket.to_str(), Some(endpoint.as_str()));
3144
+ assert!(persisted_binding_matches_verified_pane(
3145
+ &state,
3146
+ workspace,
3147
+ &pane,
3148
+ Provider::Pi,
3149
+ Some(&endpoint),
3150
+ ));
3151
+ assert_eq!(state, before);
3152
+ assert!(!persisted_binding_matches_verified_pane(
3153
+ &state,
3154
+ workspace,
3155
+ &PaneId::new("%1"),
3156
+ Provider::Pi,
3157
+ Some(&endpoint),
3158
+ ));
3159
+ assert!(!persisted_binding_matches_verified_pane(
3160
+ &state,
3161
+ workspace,
3162
+ &pane,
3163
+ Provider::Codex,
3164
+ Some(&endpoint),
3165
+ ));
3166
+ for endpoint in [None, Some("")] {
3167
+ assert!(!persisted_binding_matches_verified_pane(
3168
+ &state,
3169
+ workspace,
3170
+ &pane,
3171
+ Provider::Pi,
3172
+ endpoint,
3173
+ ));
3174
+ }
3175
+ for (field, value) in [
3176
+ ("/team_owner/pane_id", json!("%1")),
3177
+ ("/leader_receiver/pane_id", json!("%1")),
3178
+ ("/team_owner/provider", json!("codex")),
3179
+ ("/leader_receiver/provider", json!("codex")),
3180
+ ("/team_owner/owner_epoch", json!(0)),
3181
+ ("/leader_receiver/owner_epoch", json!(2)),
3182
+ ("/team_owner/leader_session_uuid", json!("")),
3183
+ ("/leader_receiver/leader_session_uuid", json!("foreign")),
3184
+ ("/leader_receiver/status", json!("pending")),
3185
+ ("/leader_receiver/mode", json!("external")),
3186
+ ("/leader_receiver/tmux_socket", json!(null)),
3187
+ ("/leader_receiver/tmux_socket", json!("")),
3188
+ ("/workspace", json!(workspace.join("foreign"))),
3189
+ ] {
3190
+ let mut conflict = state.clone();
3191
+ *conflict.pointer_mut(field).unwrap() = value;
3192
+ assert!(
3193
+ !persisted_binding_matches_verified_pane(
3194
+ &conflict,
3195
+ workspace,
3196
+ &pane,
3197
+ Provider::Pi,
3198
+ Some(&endpoint),
3199
+ ),
3200
+ "conflicting {field} must refuse"
3201
+ );
3202
+ }
3203
+ let mut unauthorized = state.clone();
3204
+ unauthorized["leader_receiver"]["authorized_team_workspace"] =
3205
+ json!(workspace.join("foreign"));
3206
+ assert!(!persisted_binding_matches_verified_pane(
3207
+ &unauthorized,
3208
+ workspace,
3209
+ &pane,
3210
+ Provider::Pi,
3211
+ Some(&endpoint),
3212
+ ));
3213
+ }
3214
+
3215
+ #[cfg(unix)]
3216
+ #[test]
3217
+ #[serial_test::serial(env)]
3218
+ fn native_persisted_binding_rejects_same_socket_basename_in_foreign_root() {
3219
+ let hermetic = HermeticTestEnv::enter("native-persisted-foreign-root");
3220
+ let workspace = hermetic.root();
3221
+ let transport = crate::tmux_backend::TmuxBackend::for_workspace(workspace);
3222
+ let endpoint = transport.tmux_endpoint().unwrap();
3223
+ let foreign_socket = workspace.join("foreign-socket-root").join(&endpoint);
3224
+ let state = native_persisted_binding(workspace, foreign_socket.to_str().unwrap());
3225
+ assert_eq!(
3226
+ foreign_socket.file_name().unwrap().to_str(),
3227
+ Some(endpoint.as_str())
3228
+ );
3229
+ assert!(!persisted_binding_matches_verified_pane(
3230
+ &state,
3231
+ workspace,
3232
+ &PaneId::new("%0"),
3233
+ Provider::Pi,
3234
+ Some(&endpoint),
3235
+ ));
3236
+ assert!(!quick_start_tmux_endpoints_match(
3237
+ foreign_socket.to_str().unwrap(),
3238
+ "default",
3239
+ ));
3240
+ assert!(!quick_start_tmux_endpoints_match(
3241
+ foreign_socket.to_str().unwrap(),
3242
+ "",
3243
+ ));
3244
+ }
3245
+
3246
+ #[cfg(unix)]
3247
+ #[test]
3248
+ #[serial_test::serial(env)]
3249
+ fn native_persisted_binding_matches_full_socket_path_symlink_alias() {
3250
+ let hermetic = HermeticTestEnv::enter("native-persisted-path-alias");
3251
+ let workspace = hermetic.root();
3252
+ let root = workspace.join("socket-root");
3253
+ std::fs::create_dir_all(&root).unwrap();
3254
+ let alias = workspace.join("socket-alias");
3255
+ std::os::unix::fs::symlink(&root, &alias).unwrap();
3256
+ let socket = root.join("ta-native.sock");
3257
+ std::fs::write(&socket, "fixture endpoint; no tmux server").unwrap();
3258
+ let state = native_persisted_binding(workspace, socket.to_str().unwrap());
3259
+ assert!(persisted_binding_matches_verified_pane(
3260
+ &state,
3261
+ workspace,
3262
+ &PaneId::new("%0"),
3263
+ Provider::Pi,
3264
+ alias.join("ta-native.sock").to_str(),
3265
+ ));
3266
+ }
3267
+
2866
3268
  fn commit_fresh_binding(
2867
3269
  workspace: &Path,
2868
3270
  owner: &serde_json::Value,
@@ -2874,10 +3276,7 @@ mod fresh_quick_start_leader_binding_tests {
2874
3276
  state["teams"]["fresh"]["leader_receiver"] = receiver.clone();
2875
3277
  state["teams"]["fresh"]["owner_epoch"] = json!(epoch);
2876
3278
  crate::state::persist::save_runtime_state_with_receiver_authority(
2877
- workspace,
2878
- &state,
2879
- "fresh",
2880
- None,
3279
+ workspace, &state, "fresh", None,
2881
3280
  )
2882
3281
  .unwrap();
2883
3282
  let raw = std::fs::read(crate::state::persist::runtime_state_path(workspace)).unwrap();
@@ -2937,7 +3336,10 @@ mod fresh_quick_start_leader_binding_tests {
2937
3336
  .unwrap();
2938
3337
  let after = crate::state::persist::load_runtime_state(&workspace).unwrap();
2939
3338
  assert_eq!(after["teams"]["fresh"]["team_owner"], previous_owner);
2940
- assert_eq!(after["teams"]["fresh"]["leader_receiver"], previous_receiver);
3339
+ assert_eq!(
3340
+ after["teams"]["fresh"]["leader_receiver"],
3341
+ previous_receiver
3342
+ );
2941
3343
  assert_eq!(after["teams"]["fresh"]["owner_epoch"], json!(1));
2942
3344
  assert_eq!(after.get("tasks"), Some(&json!({"keep": true})));
2943
3345
  let _ = std::fs::remove_dir_all(&workspace);
@@ -2987,7 +3389,10 @@ mod fresh_quick_start_leader_binding_tests {
2987
3389
  assert_eq!(after["teams"]["fresh"]["leader_receiver"], winner_receiver);
2988
3390
  assert_eq!(after["teams"]["fresh"]["owner_epoch"], json!(2));
2989
3391
  assert_ne!(after["teams"]["fresh"]["team_owner"], previous_owner);
2990
- assert_ne!(after["teams"]["fresh"]["leader_receiver"], previous_receiver);
3392
+ assert_ne!(
3393
+ after["teams"]["fresh"]["leader_receiver"],
3394
+ previous_receiver
3395
+ );
2991
3396
  let _ = std::fs::remove_dir_all(&workspace);
2992
3397
  }
2993
3398
 
@@ -3032,19 +3437,23 @@ mod fresh_quick_start_leader_binding_tests {
3032
3437
  let _home = HomeGuard::set(&home);
3033
3438
  let state_path = crate::state::persist::runtime_state_path(&workspace);
3034
3439
  let before = std::fs::read(&state_path).unwrap();
3035
- let registry_path = crate::leader::registry::registry_dir().unwrap().join(format!(
3036
- "{}__fresh.json",
3037
- crate::leader::registry::workspace_hash(&workspace)
3038
- ));
3440
+ let registry_path = crate::leader::registry::registry_dir()
3441
+ .unwrap()
3442
+ .join(format!(
3443
+ "{}__fresh.json",
3444
+ crate::leader::registry::workspace_hash(&workspace)
3445
+ ));
3039
3446
  let mut ops = AmbientRegistryOps {
3040
3447
  readback_ok: false,
3041
3448
  attach_calls: 0,
3042
3449
  registry_receipt: None,
3043
3450
  };
3044
- assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
3045
- .unwrap());
3451
+ assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
3046
3452
  assert_eq!(std::fs::read(state_path).unwrap(), before);
3047
- assert!(!registry_path.exists(), "failed readback left registry bytes");
3453
+ assert!(
3454
+ !registry_path.exists(),
3455
+ "failed readback left registry bytes"
3456
+ );
3048
3457
  }
3049
3458
 
3050
3459
  struct RecordingRuntimeOps<'a> {
@@ -3252,7 +3661,10 @@ mod fresh_quick_start_leader_binding_tests {
3252
3661
  let mut focused = caller_target(parent.clone(), Some("shared-live-nonce"));
3253
3662
  focused.active = true;
3254
3663
  let (active_ok, active_reason) = bind_seeded_caller(focused, "runtime-focus-active");
3255
- assert!(active_ok, "focused live caller must bind: {active_reason:?}");
3664
+ assert!(
3665
+ active_ok,
3666
+ "focused live caller must bind: {active_reason:?}"
3667
+ );
3256
3668
 
3257
3669
  let mut background = caller_target(parent, Some("shared-live-nonce"));
3258
3670
  background.active = false;
@@ -3288,8 +3700,10 @@ mod fresh_quick_start_leader_binding_tests {
3288
3700
  frozen_receiver: None,
3289
3701
  nonce_writer: None,
3290
3702
  };
3291
- assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
3292
- .unwrap());
3703
+ assert!(
3704
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
3705
+ .unwrap()
3706
+ );
3293
3707
  assert_eq!(ops.last_failure_reason, Some("caller_pane_not_live"));
3294
3708
  }
3295
3709
 
@@ -3336,7 +3750,10 @@ mod fresh_quick_start_leader_binding_tests {
3336
3750
  .map(|agents| agents.keys().cloned().collect::<Vec<_>>())
3337
3751
  .unwrap_or_default();
3338
3752
  assert_eq!(ids, vec!["bob".to_string()]);
3339
- assert_eq!(view.get("session_name").and_then(serde_json::Value::as_str), Some("team-bob"));
3753
+ assert_eq!(
3754
+ view.get("session_name").and_then(serde_json::Value::as_str),
3755
+ Some("team-bob")
3756
+ );
3340
3757
  }
3341
3758
 
3342
3759
  #[test]
@@ -3439,11 +3856,7 @@ mod fresh_quick_start_leader_binding_tests {
3439
3856
  .with_tmux_endpoint("/tmp/tmux.sock")
3440
3857
  .with_targets(vec![stale_target]);
3441
3858
  assert!(matches!(
3442
- crate::messaging::resolve_live_leader_channel(
3443
- &workspace,
3444
- &receiver,
3445
- &stale_transport
3446
- ),
3859
+ crate::messaging::resolve_live_leader_channel(&workspace, &receiver, &stale_transport),
3447
3860
  crate::messaging::LeaderChannelResolution::Unbound(
3448
3861
  crate::messaging::LeaderChannelUnbound::PaneWorkspaceMismatch(_)
3449
3862
  )
@@ -3481,10 +3894,17 @@ mod fresh_quick_start_leader_binding_tests {
3481
3894
  ));
3482
3895
 
3483
3896
  let mut foreign_receiver = receiver.clone();
3484
- foreign_receiver["authorized_team_workspace"] =
3485
- json!(hermetic.root().join("foreign").to_string_lossy().to_string());
3897
+ foreign_receiver["authorized_team_workspace"] = json!(hermetic
3898
+ .root()
3899
+ .join("foreign")
3900
+ .to_string_lossy()
3901
+ .to_string());
3486
3902
  assert!(matches!(
3487
- crate::messaging::resolve_live_leader_channel(&workspace, &foreign_receiver, &transport),
3903
+ crate::messaging::resolve_live_leader_channel(
3904
+ &workspace,
3905
+ &foreign_receiver,
3906
+ &transport
3907
+ ),
3488
3908
  crate::messaging::LeaderChannelResolution::Unbound(
3489
3909
  crate::messaging::LeaderChannelUnbound::PaneWorkspaceMismatch(_)
3490
3910
  )
@@ -3687,10 +4107,7 @@ mod fresh_quick_start_leader_binding_tests {
3687
4107
  let mut winner = crate::state::persist::load_runtime_state(workspace).unwrap();
3688
4108
  winner["teams"][team_key]["leader_receiver"]["pane_id"] = json!("%winner");
3689
4109
  crate::state::persist::save_runtime_state_with_receiver_authority(
3690
- workspace,
3691
- &winner,
3692
- team_key,
3693
- None,
4110
+ workspace, &winner, team_key, None,
3694
4111
  )
3695
4112
  .unwrap();
3696
4113
  let _ = crate::leader::registry::register_binding_from_state_best_effort(
@@ -3726,10 +4143,7 @@ mod fresh_quick_start_leader_binding_tests {
3726
4143
  .with_tmux_endpoint("/tmp/tmux.sock")
3727
4144
  .with_pane_current_command("%1", "bash")
3728
4145
  .with_targets(vec![target_with_nonce.clone()])
3729
- .with_target_snapshots(vec![
3730
- vec![target_without_nonce],
3731
- vec![target_with_nonce],
3732
- ]);
4146
+ .with_target_snapshots(vec![vec![target_without_nonce], vec![target_with_nonce]]);
3733
4147
  let mut ops = RuntimeCommitFailureOps {
3734
4148
  inner: RuntimeFreshQuickStartLeaderBindingOps {
3735
4149
  transport: &transport,
@@ -3748,13 +4162,10 @@ mod fresh_quick_start_leader_binding_tests {
3748
4162
  break_registry_lock: false,
3749
4163
  break_registry_read: false,
3750
4164
  };
3751
- assert!(!bind_fresh_quick_start_leader_with(
3752
- &workspace,
3753
- "fresh",
3754
- Some(&seed),
3755
- &mut ops
3756
- )
3757
- .unwrap());
4165
+ assert!(
4166
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
4167
+ .unwrap()
4168
+ );
3758
4169
  let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
3759
4170
  assert!(persisted
3760
4171
  .pointer("/teams/fresh/team_owner")
@@ -3769,7 +4180,10 @@ mod fresh_quick_start_leader_binding_tests {
3769
4180
  "{}__fresh.json",
3770
4181
  crate::leader::registry::workspace_hash(&workspace)
3771
4182
  ));
3772
- assert!(!registry_path.exists(), "failed child grant left registry row");
4183
+ assert!(
4184
+ !registry_path.exists(),
4185
+ "failed child grant left registry row"
4186
+ );
3773
4187
  let observed = transport.list_targets().unwrap();
3774
4188
  assert_eq!(
3775
4189
  observed[0]
@@ -3817,14 +4231,14 @@ mod fresh_quick_start_leader_binding_tests {
3817
4231
  break_registry_lock: false,
3818
4232
  break_registry_read: false,
3819
4233
  };
3820
- assert!(!bind_fresh_quick_start_leader_with(
3821
- &workspace,
3822
- "fresh",
3823
- Some(&seed),
3824
- &mut ops,
3825
- )
3826
- .unwrap());
3827
- assert_eq!(ops.inner.last_failure_reason, Some("attach_event_log_failed"));
4234
+ assert!(
4235
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops,)
4236
+ .unwrap()
4237
+ );
4238
+ assert_eq!(
4239
+ ops.inner.last_failure_reason,
4240
+ Some("attach_event_log_failed")
4241
+ );
3828
4242
  let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
3829
4243
  assert!(persisted
3830
4244
  .pointer("/teams/fresh/team_owner")
@@ -3881,7 +4295,10 @@ mod fresh_quick_start_leader_binding_tests {
3881
4295
  || bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
3882
4296
  )
3883
4297
  .unwrap());
3884
- assert_eq!(ops.last_failure_reason, Some("receiver_live_channel_unavailable"));
4298
+ assert_eq!(
4299
+ ops.last_failure_reason,
4300
+ Some("receiver_live_channel_unavailable")
4301
+ );
3885
4302
  let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
3886
4303
  assert!(persisted
3887
4304
  .pointer("/teams/fresh/team_owner")
@@ -3934,13 +4351,10 @@ mod fresh_quick_start_leader_binding_tests {
3934
4351
  break_registry_lock: false,
3935
4352
  break_registry_read: false,
3936
4353
  };
3937
- assert!(!bind_fresh_quick_start_leader_with(
3938
- &workspace,
3939
- "fresh",
3940
- Some(&seed),
3941
- &mut ops
3942
- )
3943
- .unwrap());
4354
+ assert!(
4355
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
4356
+ .unwrap()
4357
+ );
3944
4358
  let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
3945
4359
  assert!(persisted
3946
4360
  .pointer("/teams/fresh/team_owner")
@@ -3954,7 +4368,10 @@ mod fresh_quick_start_leader_binding_tests {
3954
4368
  "{}__fresh.json",
3955
4369
  crate::leader::registry::workspace_hash(&workspace)
3956
4370
  ));
3957
- assert!(!registry_path.exists(), "readback failure left registry row");
4371
+ assert!(
4372
+ !registry_path.exists(),
4373
+ "readback failure left registry row"
4374
+ );
3958
4375
  }
3959
4376
 
3960
4377
  #[test]
@@ -3993,13 +4410,10 @@ mod fresh_quick_start_leader_binding_tests {
3993
4410
  break_registry_lock: false,
3994
4411
  break_registry_read: false,
3995
4412
  };
3996
- assert!(!bind_fresh_quick_start_leader_with(
3997
- &workspace,
3998
- "fresh",
3999
- Some(&seed),
4000
- &mut ops,
4001
- )
4002
- .unwrap());
4413
+ assert!(
4414
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops,)
4415
+ .unwrap()
4416
+ );
4003
4417
  let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
4004
4418
  assert_eq!(
4005
4419
  persisted
@@ -4037,13 +4451,11 @@ mod fresh_quick_start_leader_binding_tests {
4037
4451
  #[test]
4038
4452
  #[serial_test::serial(env)]
4039
4453
  fn seeded_runtime_registry_rollback_errors_are_visible_after_state_cleanup() {
4040
- for (tag, break_registry_lock, break_registry_read) in [
4041
- ("lock", true, false),
4042
- ("read", false, true),
4043
- ] {
4044
- let hermetic = HermeticTestEnv::enter(&format!(
4045
- "runtime-seeded-registry-rollback-{tag}"
4046
- ));
4454
+ for (tag, break_registry_lock, break_registry_read) in
4455
+ [("lock", true, false), ("read", false, true)]
4456
+ {
4457
+ let hermetic =
4458
+ HermeticTestEnv::enter(&format!("runtime-seeded-registry-rollback-{tag}"));
4047
4459
  let workspace = runtime_workspace(&hermetic, "fresh");
4048
4460
  let parent = hermetic.root().join("parent");
4049
4461
  std::fs::create_dir_all(&parent).unwrap();
@@ -4076,13 +4488,9 @@ mod fresh_quick_start_leader_binding_tests {
4076
4488
  break_registry_lock,
4077
4489
  break_registry_read,
4078
4490
  };
4079
- let error = bind_fresh_quick_start_leader_with(
4080
- &workspace,
4081
- "fresh",
4082
- Some(&seed),
4083
- &mut ops,
4084
- )
4085
- .expect_err("registry rollback failure must be visible");
4491
+ let error =
4492
+ bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
4493
+ .expect_err("registry rollback failure must be visible");
4086
4494
  assert!(error
4087
4495
  .to_string()
4088
4496
  .contains("quick-start registry rollback failed"));
@@ -4166,7 +4574,10 @@ mod fresh_quick_start_leader_binding_tests {
4166
4574
  )
4167
4575
  .unwrap());
4168
4576
  assert_eq!(ops.last_failure_reason, Some(expected_reason));
4169
- assert_eq!(refusal_reason(&workspace).map(|(_, reason)| reason), Some(expected_reason.to_string()));
4577
+ assert_eq!(
4578
+ refusal_reason(&workspace).map(|(_, reason)| reason),
4579
+ Some(expected_reason.to_string())
4580
+ );
4170
4581
  let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
4171
4582
  assert!(persisted
4172
4583
  .pointer("/teams/fresh/team_owner")
@@ -4190,12 +4601,10 @@ mod fresh_quick_start_leader_binding_tests {
4190
4601
  let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
4191
4602
  let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, "/tmp/tmux.sock");
4192
4603
  let seed = seeded_runtime_owner(&workspace);
4193
- let mut state = crate::state::projection::resolve_runtime_team_scope(
4194
- &workspace,
4195
- Some("fresh"),
4196
- )
4197
- .unwrap()
4198
- .state;
4604
+ let mut state =
4605
+ crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
4606
+ .unwrap()
4607
+ .state;
4199
4608
  let expected_owner = state["team_owner"].clone();
4200
4609
  let expected_receiver = state["leader_receiver"].clone();
4201
4610
  let target = caller_target(parent.clone(), None);
@@ -4227,12 +4636,10 @@ mod fresh_quick_start_leader_binding_tests {
4227
4636
 
4228
4637
  let workspace = runtime_workspace(&hermetic, "writer-failure");
4229
4638
  let seed = seeded_runtime_owner(&workspace);
4230
- let mut state = crate::state::projection::resolve_runtime_team_scope(
4231
- &workspace,
4232
- Some("fresh"),
4233
- )
4234
- .unwrap()
4235
- .state;
4639
+ let mut state =
4640
+ crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
4641
+ .unwrap()
4642
+ .state;
4236
4643
  let expected_owner = state["team_owner"].clone();
4237
4644
  let expected_receiver = state["leader_receiver"].clone();
4238
4645
  let target = caller_target(hermetic.root().join("parent"), None);
@@ -4272,27 +4679,22 @@ mod fresh_quick_start_leader_binding_tests {
4272
4679
  let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
4273
4680
  let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, "/tmp/tmux.sock");
4274
4681
  let seed = seeded_runtime_owner(&workspace);
4275
- let concurrent_writer = |_: &str,
4276
- _: &PaneId,
4277
- _: &str|
4278
- -> Result<String, crate::leader::LeaderError> {
4279
- let mut winner = crate::state::persist::load_runtime_state(&workspace)
4682
+ let concurrent_writer =
4683
+ |_: &str, _: &PaneId, _: &str| -> Result<String, crate::leader::LeaderError> {
4684
+ let mut winner = crate::state::persist::load_runtime_state(&workspace)
4685
+ .map_err(crate::leader::LeaderError::State)?;
4686
+ winner["teams"]["fresh"]["leader_receiver"]["pane_id"] = json!("%winner");
4687
+ crate::state::persist::save_runtime_state_with_receiver_authority(
4688
+ &workspace, &winner, "fresh", None,
4689
+ )
4280
4690
  .map_err(crate::leader::LeaderError::State)?;
4281
- winner["teams"]["fresh"]["leader_receiver"]["pane_id"] = json!("%winner");
4282
- crate::state::persist::save_runtime_state_with_receiver_authority(
4283
- &workspace,
4284
- &winner,
4285
- "fresh",
4286
- None,
4287
- )
4288
- .map_err(crate::leader::LeaderError::State)?;
4289
- let _ = crate::leader::registry::register_binding_from_state_best_effort(
4290
- &workspace,
4291
- Some("fresh"),
4292
- "concurrent",
4293
- );
4294
- Ok("writer-winner".to_string())
4295
- };
4691
+ let _ = crate::leader::registry::register_binding_from_state_best_effort(
4692
+ &workspace,
4693
+ Some("fresh"),
4694
+ "concurrent",
4695
+ );
4696
+ Ok("writer-winner".to_string())
4697
+ };
4296
4698
  let target = caller_target(parent, None);
4297
4699
  let transport = OfflineTransport::new()
4298
4700
  .with_tmux_endpoint("/tmp/tmux.sock")
@@ -4308,30 +4710,35 @@ mod fresh_quick_start_leader_binding_tests {
4308
4710
  frozen_receiver: None,
4309
4711
  nonce_writer: Some(&concurrent_writer),
4310
4712
  };
4311
- assert!(!bind_fresh_quick_start_leader_with(
4312
- &workspace,
4313
- "fresh",
4314
- Some(&seed),
4315
- &mut ops
4316
- )
4317
- .unwrap());
4713
+ assert!(
4714
+ !bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
4715
+ .unwrap()
4716
+ );
4318
4717
  assert!(ops.cas_conflict);
4319
4718
  assert_eq!(ops.last_failure_reason, Some("attach_cas_conflict"));
4320
4719
  let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
4321
4720
  assert_eq!(
4322
- persisted.pointer("/teams/fresh/team_owner/pane_id").and_then(serde_json::Value::as_str),
4721
+ persisted
4722
+ .pointer("/teams/fresh/team_owner/pane_id")
4723
+ .and_then(serde_json::Value::as_str),
4323
4724
  Some("%1")
4324
4725
  );
4325
4726
  assert_eq!(
4326
- persisted.pointer("/teams/fresh/leader_receiver/pane_id").and_then(serde_json::Value::as_str),
4727
+ persisted
4728
+ .pointer("/teams/fresh/leader_receiver/pane_id")
4729
+ .and_then(serde_json::Value::as_str),
4327
4730
  Some("%winner")
4328
4731
  );
4329
4732
  assert_eq!(
4330
- persisted.pointer("/teams/fresh/team_owner/owner_epoch").and_then(serde_json::Value::as_u64),
4733
+ persisted
4734
+ .pointer("/teams/fresh/team_owner/owner_epoch")
4735
+ .and_then(serde_json::Value::as_u64),
4331
4736
  Some(1)
4332
4737
  );
4333
4738
  assert_eq!(
4334
- persisted.pointer("/teams/fresh/leader_receiver/owner_epoch").and_then(serde_json::Value::as_u64),
4739
+ persisted
4740
+ .pointer("/teams/fresh/leader_receiver/owner_epoch")
4741
+ .and_then(serde_json::Value::as_u64),
4335
4742
  Some(1)
4336
4743
  );
4337
4744
  let registry_path = crate::leader::registry::registry_dir()
@@ -4340,7 +4747,10 @@ mod fresh_quick_start_leader_binding_tests {
4340
4747
  "{}__fresh.json",
4341
4748
  crate::leader::registry::workspace_hash(&workspace)
4342
4749
  ));
4343
- assert!(registry_path.exists(), "concurrent winner registry was removed");
4750
+ assert!(
4751
+ registry_path.exists(),
4752
+ "concurrent winner registry was removed"
4753
+ );
4344
4754
  let winner_entry: crate::leader::registry::LeaderRegistryEntry =
4345
4755
  serde_json::from_slice(&std::fs::read(registry_path).unwrap()).unwrap();
4346
4756
  assert_eq!(winner_entry.channel["pane_id"], json!("%winner"));
@@ -4350,18 +4760,8 @@ mod fresh_quick_start_leader_binding_tests {
4350
4760
  #[serial_test::serial(env)]
4351
4761
  fn runtime_socket_and_explicit_conflicts_refuse_without_command_fallback() {
4352
4762
  for (tag, endpoint, leader, scoped) in [
4353
- (
4354
- "socket",
4355
- "/tmp/other.sock",
4356
- None,
4357
- "/tmp/tmux.sock",
4358
- ),
4359
- (
4360
- "scoped",
4361
- "/tmp/tmux.sock",
4362
- None,
4363
- "/tmp/product.sock",
4364
- ),
4763
+ ("socket", "/tmp/other.sock", None, "/tmp/tmux.sock"),
4764
+ ("scoped", "/tmp/tmux.sock", None, "/tmp/product.sock"),
4365
4765
  (
4366
4766
  // The explicit leader provider intentionally conflicts with the
4367
4767
  // caller provider; worker_env must reject this before command fallback.
@@ -4378,7 +4778,8 @@ mod fresh_quick_start_leader_binding_tests {
4378
4778
  let _provider = hermetic.with_env(CALLER_PROVIDER_ENV, "codex");
4379
4779
  let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
4380
4780
  let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, endpoint);
4381
- let _leader = leader.map(|value| hermetic.with_env("TEAM_AGENT_LEADER_PROVIDER", value));
4781
+ let _leader =
4782
+ leader.map(|value| hermetic.with_env("TEAM_AGENT_LEADER_PROVIDER", value));
4382
4783
  let transport = OfflineTransport::new()
4383
4784
  .with_tmux_endpoint(scoped)
4384
4785
  .with_pane_current_command("%1", "codex");
@@ -4391,7 +4792,6 @@ mod fresh_quick_start_leader_binding_tests {
4391
4792
  }
4392
4793
  }
4393
4794
 
4394
-
4395
4795
  #[test]
4396
4796
  fn pi_tool_category_is_transparently_accepted_by_compiler() {
4397
4797
  let root = std::env::temp_dir().join(format!(
@@ -4418,9 +4818,9 @@ mod fresh_quick_start_leader_binding_tests {
4418
4818
  .get("agents")
4419
4819
  .and_then(Value::as_list)
4420
4820
  .and_then(|agents| {
4421
- agents.iter().find(|agent| {
4422
- agent.get("id").and_then(Value::as_str) == Some("worker")
4423
- })
4821
+ agents
4822
+ .iter()
4823
+ .find(|agent| agent.get("id").and_then(Value::as_str) == Some("worker"))
4424
4824
  })
4425
4825
  .expect("compiled spec must retain the worker role");
4426
4826
  assert!(
@@ -4434,8 +4834,7 @@ mod fresh_quick_start_leader_binding_tests {
4434
4834
  fn fresh_binding_persists_then_registers_then_requires_canonical_readback() {
4435
4835
  let workspace = workspace_with_provider("positive-pi", "pi");
4436
4836
  let mut ops = MockOps::with_provider("pi");
4437
- assert!(bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
4438
- .unwrap());
4837
+ assert!(bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
4439
4838
  assert_eq!(ops.attached_provider, Some(crate::provider::Provider::Pi));
4440
4839
  assert_eq!(ops.attach_calls, 1);
4441
4840
  assert_eq!(ops.register_calls, 1);
@@ -4492,12 +4891,10 @@ mod fresh_quick_start_leader_binding_tests {
4492
4891
  let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
4493
4892
  let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, "/tmp/tmux.sock");
4494
4893
  let seed = seeded_runtime_owner(&workspace);
4495
- let mut state = crate::state::projection::resolve_runtime_team_scope(
4496
- &workspace,
4497
- Some("fresh"),
4498
- )
4499
- .unwrap()
4500
- .state;
4894
+ let mut state =
4895
+ crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
4896
+ .unwrap()
4897
+ .state;
4501
4898
  let expected_owner = state["team_owner"].clone();
4502
4899
  let expected_receiver = state["leader_receiver"].clone();
4503
4900
  let target = caller_target(parent.clone(), None);
@@ -4529,12 +4926,10 @@ mod fresh_quick_start_leader_binding_tests {
4529
4926
 
4530
4927
  let workspace = runtime_workspace_with_provider(&hermetic, "writer-failure", "pi");
4531
4928
  let seed = seeded_runtime_owner(&workspace);
4532
- let mut state = crate::state::projection::resolve_runtime_team_scope(
4533
- &workspace,
4534
- Some("fresh"),
4535
- )
4536
- .unwrap()
4537
- .state;
4929
+ let mut state =
4930
+ crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
4931
+ .unwrap()
4932
+ .state;
4538
4933
  let expected_owner = state["team_owner"].clone();
4539
4934
  let expected_receiver = state["leader_receiver"].clone();
4540
4935
  let target = caller_target(hermetic.root().join("parent"), None);