@team-agent/installer 0.5.113 → 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.
- package/Cargo.lock +252 -84
- package/Cargo.toml +3 -1
- package/crates/team-agent/Cargo.toml +3 -0
- package/crates/team-agent/src/cli/adapters.rs +28 -15
- package/crates/team-agent/src/cli/diagnose.rs +36 -13
- package/crates/team-agent/src/cli/emit.rs +55 -20
- package/crates/team-agent/src/cli/leader.rs +16 -0
- package/crates/team-agent/src/cli/leader_prompt.rs +261 -0
- package/crates/team-agent/src/cli/mod.rs +152 -8
- package/crates/team-agent/src/cli/models.rs +68 -18
- package/crates/team-agent/src/cli/spec.rs +7 -1
- package/crates/team-agent/src/cli/status_port/brief.rs +268 -101
- package/crates/team-agent/src/cli/tests/base.rs +88 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +2 -0
- package/crates/team-agent/src/compiler.rs +66 -18
- package/crates/team-agent/src/contract_runtime/backend.rs +646 -0
- package/crates/team-agent/src/contract_runtime/config.rs +309 -0
- package/crates/team-agent/src/contract_runtime/discovery.rs +237 -0
- package/crates/team-agent/src/contract_runtime/forward.rs +229 -0
- package/crates/team-agent/src/contract_runtime/framework.rs +261 -0
- package/crates/team-agent/src/contract_runtime/mcp.rs +396 -0
- package/crates/team-agent/src/contract_runtime/mod.rs +14 -0
- package/crates/team-agent/src/contract_runtime/prompt.rs +216 -0
- package/crates/team-agent/src/contract_runtime/pump.rs +207 -0
- package/crates/team-agent/src/contract_runtime/registry.rs +30 -0
- package/crates/team-agent/src/coordinator/tick.rs +26 -8
- package/crates/team-agent/src/leader/mod.rs +1 -0
- package/crates/team-agent/src/leader/prompt.rs +906 -0
- package/crates/team-agent/src/leader/start.rs +260 -16
- package/crates/team-agent/src/leader/tests/mod.rs +2 -0
- package/crates/team-agent/src/leader/tests/prompt_contract.rs +268 -0
- package/crates/team-agent/src/leader/types.rs +6 -0
- package/crates/team-agent/src/lib.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +472 -388
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +38 -9
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +17 -0
- package/crates/team-agent/src/lifecycle/launch.rs +39 -28
- package/crates/team-agent/src/lifecycle/types.rs +4 -1
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +37 -27
- package/crates/team-agent/src/main.rs +6 -0
- package/crates/team-agent/src/messaging/delivery.rs +50 -1
- package/crates/team-agent/src/messaging/send.rs +10 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +131 -0
- package/crates/team-agent/src/model/spec.rs +24 -12
- package/crates/team-agent/src/state/persist.rs +73 -0
- 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(
|
|
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(_)
|
|
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) =
|
|
277
|
-
workspace,
|
|
278
|
-
|
|
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 =
|
|
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> {
|
|
@@ -434,7 +430,10 @@ fn persisted_binding_matches_verified_pane(
|
|
|
434
430
|
{
|
|
435
431
|
return false;
|
|
436
432
|
}
|
|
437
|
-
let Some(owner) = state
|
|
433
|
+
let Some(owner) = state
|
|
434
|
+
.get("team_owner")
|
|
435
|
+
.and_then(serde_json::Value::as_object)
|
|
436
|
+
else {
|
|
438
437
|
return false;
|
|
439
438
|
};
|
|
440
439
|
let Some(receiver) = state
|
|
@@ -488,10 +487,9 @@ fn bind_fresh_quick_start_leader_with<O: FreshQuickStartLeaderBindingOps>(
|
|
|
488
487
|
seeded_owner: Option<&serde_json::Value>,
|
|
489
488
|
ops: &mut O,
|
|
490
489
|
) -> Result<bool, LifecycleError> {
|
|
491
|
-
let Ok(resolved) =
|
|
492
|
-
workspace,
|
|
493
|
-
|
|
494
|
-
) else {
|
|
490
|
+
let Ok(resolved) =
|
|
491
|
+
crate::state::projection::resolve_runtime_team_scope(workspace, Some(team_key))
|
|
492
|
+
else {
|
|
495
493
|
return refuse_fresh_bind(workspace, team_key, "scope", "scope_unresolved");
|
|
496
494
|
};
|
|
497
495
|
if resolved.canonical_team_key != team_key {
|
|
@@ -550,27 +548,27 @@ fn bind_fresh_quick_start_leader_with<O: FreshQuickStartLeaderBindingOps>(
|
|
|
550
548
|
// projected state still contains that seed.
|
|
551
549
|
let current_receiver = state.get("leader_receiver");
|
|
552
550
|
let frozen_receipt = ops.frozen_seed_receipt();
|
|
553
|
-
let fresh_seeded_binding =
|
|
554
|
-
|
|
555
|
-
state
|
|
556
|
-
.get("team_owner")
|
|
557
|
-
.is_some_and(|current| current == frozen_owner)
|
|
558
|
-
&& current_receiver.is_some_and(|current| current == frozen_receiver)
|
|
559
|
-
} else {
|
|
560
|
-
let pending_seed_receiver = current_receiver.is_some_and(|receiver| {
|
|
561
|
-
receiver.get("status").and_then(serde_json::Value::as_str) == Some("pending")
|
|
562
|
-
&& receiver
|
|
563
|
-
.get("discovery")
|
|
564
|
-
.and_then(serde_json::Value::as_str)
|
|
565
|
-
== Some("quick_start_seed")
|
|
566
|
-
});
|
|
567
|
-
seeded_owner.is_some_and(|seed| {
|
|
551
|
+
let fresh_seeded_binding =
|
|
552
|
+
if let Some((frozen_owner, frozen_receiver)) = frozen_receipt.as_ref() {
|
|
568
553
|
state
|
|
569
554
|
.get("team_owner")
|
|
570
|
-
.is_some_and(|current| current ==
|
|
571
|
-
&&
|
|
572
|
-
}
|
|
573
|
-
|
|
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
|
+
};
|
|
574
572
|
let seeded_receiver = frozen_receipt
|
|
575
573
|
.as_ref()
|
|
576
574
|
.map(|(_, receiver)| receiver.clone())
|
|
@@ -721,9 +719,9 @@ fn combine_rollback_errors(
|
|
|
721
719
|
match (state, registry) {
|
|
722
720
|
(None, None) => None,
|
|
723
721
|
(Some(error), None) | (None, Some(error)) => Some(error),
|
|
724
|
-
(Some(state), Some(registry)) =>
|
|
725
|
-
"{state}; {registry}"
|
|
726
|
-
|
|
722
|
+
(Some(state), Some(registry)) => {
|
|
723
|
+
Some(LifecycleError::StatePersist(format!("{state}; {registry}")))
|
|
724
|
+
}
|
|
727
725
|
}
|
|
728
726
|
}
|
|
729
727
|
|
|
@@ -757,9 +755,11 @@ fn restore_this_attempt_state(
|
|
|
757
755
|
});
|
|
758
756
|
};
|
|
759
757
|
let previous = match snapshot.map(|snapshot| snapshot.bytes.as_deref()) {
|
|
760
|
-
Some(Some(bytes)) =>
|
|
761
|
-
|
|
762
|
-
|
|
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
|
+
}
|
|
763
763
|
Some(None) | None => serde_json::Value::Null,
|
|
764
764
|
};
|
|
765
765
|
let previous_owner = previous
|
|
@@ -879,9 +879,9 @@ fn clear_fresh_binding_on_refusal(
|
|
|
879
879
|
};
|
|
880
880
|
crate::state::repository::StateRepository::new(workspace)
|
|
881
881
|
.save(intent, state)
|
|
882
|
-
.map_err(|error|
|
|
883
|
-
"quick-start binding cleanup failed: {error}"
|
|
884
|
-
)
|
|
882
|
+
.map_err(|error| {
|
|
883
|
+
LifecycleError::StatePersist(format!("quick-start binding cleanup failed: {error}"))
|
|
884
|
+
})
|
|
885
885
|
}
|
|
886
886
|
|
|
887
887
|
fn existing_runtime_identity_view(
|
|
@@ -907,16 +907,14 @@ fn caller_read_transport() -> Option<Box<dyn Transport>> {
|
|
|
907
907
|
if !Path::new(&endpoint).exists() {
|
|
908
908
|
return None;
|
|
909
909
|
}
|
|
910
|
-
Some(Box::new(
|
|
911
|
-
&endpoint,
|
|
912
|
-
))
|
|
910
|
+
Some(Box::new(
|
|
911
|
+
crate::tmux_backend::TmuxBackend::for_tmux_endpoint(&endpoint),
|
|
912
|
+
))
|
|
913
913
|
}
|
|
914
914
|
|
|
915
915
|
fn query_caller_pane_command(transport: &dyn Transport, pane: &PaneId) -> Option<String> {
|
|
916
916
|
let caller_transport = caller_read_transport();
|
|
917
|
-
let query_transport: &dyn Transport = caller_transport
|
|
918
|
-
.as_deref()
|
|
919
|
-
.unwrap_or(transport);
|
|
917
|
+
let query_transport: &dyn Transport = caller_transport.as_deref().unwrap_or(transport);
|
|
920
918
|
query_transport
|
|
921
919
|
.query(&Target::Pane(pane.clone()), PaneField::PaneCurrentCommand)
|
|
922
920
|
.ok()
|
|
@@ -959,8 +957,8 @@ fn preflight_fresh_leader_identity(
|
|
|
959
957
|
let Some(pane) = pane else {
|
|
960
958
|
return None;
|
|
961
959
|
};
|
|
962
|
-
let command =
|
|
963
|
-
.unwrap_or_default();
|
|
960
|
+
let command =
|
|
961
|
+
query_caller_pane_command(transport, &PaneId::new(pane)).unwrap_or_default();
|
|
964
962
|
let explicit = std::env::var("TEAM_AGENT_LEADER_PROVIDER")
|
|
965
963
|
.ok()
|
|
966
964
|
.filter(|provider| !provider.is_empty());
|
|
@@ -968,16 +966,11 @@ fn preflight_fresh_leader_identity(
|
|
|
968
966
|
return Some((
|
|
969
967
|
"quick-start refused before spawn: command unobservable".to_string(),
|
|
970
968
|
vec!["stage=command_observation reason=command_unobservable".to_string()],
|
|
971
|
-
vec![
|
|
972
|
-
"run from a live provider pane; do not run claim-leader".to_string(),
|
|
973
|
-
],
|
|
969
|
+
vec!["run from a live provider pane; do not run claim-leader".to_string()],
|
|
974
970
|
));
|
|
975
971
|
}
|
|
976
|
-
if crate::leader::owner_bind::strict_owner_bind_provider(
|
|
977
|
-
|
|
978
|
-
&command,
|
|
979
|
-
)
|
|
980
|
-
.is_some()
|
|
972
|
+
if crate::leader::owner_bind::strict_owner_bind_provider(explicit.as_deref(), &command)
|
|
973
|
+
.is_some()
|
|
981
974
|
{
|
|
982
975
|
None
|
|
983
976
|
} else {
|
|
@@ -1097,12 +1090,7 @@ pub fn quick_start_in_workspace_with_backend(
|
|
|
1097
1090
|
if is_tmux_or_default {
|
|
1098
1091
|
let transport = quick_start_tmux_backend(&workspace);
|
|
1099
1092
|
return quick_start_with_transport_in_workspace(
|
|
1100
|
-
&workspace,
|
|
1101
|
-
agents_dir,
|
|
1102
|
-
name,
|
|
1103
|
-
yes,
|
|
1104
|
-
team_id,
|
|
1105
|
-
&transport,
|
|
1093
|
+
&workspace, agents_dir, name, yes, team_id, &transport,
|
|
1106
1094
|
);
|
|
1107
1095
|
}
|
|
1108
1096
|
// Explicit non-tmux backend: route through the factory. Parse the
|
|
@@ -1275,6 +1263,32 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
|
|
|
1275
1263
|
)));
|
|
1276
1264
|
}
|
|
1277
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
|
+
}
|
|
1278
1292
|
let workspace = workspace.to_path_buf();
|
|
1279
1293
|
let mut spec = crate::compiler::compile_team(agents_dir)
|
|
1280
1294
|
.map_err(|e| LifecycleError::Compile(e.to_string()))?;
|
|
@@ -1328,7 +1342,9 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
|
|
|
1328
1342
|
.get("tmux_endpoint")
|
|
1329
1343
|
.and_then(serde_json::Value::as_str)
|
|
1330
1344
|
.or_else(|| {
|
|
1331
|
-
identity
|
|
1345
|
+
identity
|
|
1346
|
+
.get("tmux_socket")
|
|
1347
|
+
.and_then(serde_json::Value::as_str)
|
|
1332
1348
|
})
|
|
1333
1349
|
.or_else(|| {
|
|
1334
1350
|
state
|
|
@@ -1450,10 +1466,7 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
|
|
|
1450
1466
|
.and_then(|teams| teams.get(&state_team_key))
|
|
1451
1467
|
.and_then(|team| team.get("team_owner"))
|
|
1452
1468
|
.filter(|owner| {
|
|
1453
|
-
owner
|
|
1454
|
-
.get("claimed_via")
|
|
1455
|
-
.and_then(serde_json::Value::as_str)
|
|
1456
|
-
== Some("quick-start")
|
|
1469
|
+
owner.get("claimed_via").and_then(serde_json::Value::as_str) == Some("quick-start")
|
|
1457
1470
|
})
|
|
1458
1471
|
.cloned();
|
|
1459
1472
|
let seeded_receiver = state
|
|
@@ -1517,12 +1530,18 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
|
|
|
1517
1530
|
quick_start_session_capture_incomplete_agents(&workspace, &state_team_key);
|
|
1518
1531
|
let coordinator_workspace = crate::coordinator::WorkspacePath::new(workspace.clone());
|
|
1519
1532
|
let coordinator_report = crate::coordinator::start_coordinator(&coordinator_workspace)
|
|
1520
|
-
.map_err(|error|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
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
|
+
);
|
|
1526
1545
|
require_quick_start_coordinator_started(&coordinator_report, &workspace)?;
|
|
1527
1546
|
let coordinator_action = "coordinator started";
|
|
1528
1547
|
// BUG-7: build an honest readiness verdict from the post-spawn runtime state.
|
|
@@ -1532,7 +1551,11 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
|
|
|
1532
1551
|
// loaded successfully (provider-side codex/claude schema rejections happen
|
|
1533
1552
|
// asynchronously after spawn), so the verdict is PendingToolLoad — never
|
|
1534
1553
|
// bare Ready.
|
|
1535
|
-
quick_start_phase_timer.emit(
|
|
1554
|
+
quick_start_phase_timer.emit(
|
|
1555
|
+
&workspace,
|
|
1556
|
+
"launch.phase",
|
|
1557
|
+
crate::lifecycle::restart::LifecyclePhase::ReadinessWait,
|
|
1558
|
+
);
|
|
1536
1559
|
let worker_readiness = quick_start_worker_readiness(&workspace, &state_team_key);
|
|
1537
1560
|
let attach_windows = load_runtime_state(&workspace)
|
|
1538
1561
|
.ok()
|
|
@@ -1572,7 +1595,11 @@ pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
|
|
|
1572
1595
|
));
|
|
1573
1596
|
}
|
|
1574
1597
|
next_actions.extend(attach_commands.iter().cloned());
|
|
1575
|
-
quick_start_phase_timer.emit(
|
|
1598
|
+
quick_start_phase_timer.emit(
|
|
1599
|
+
&workspace,
|
|
1600
|
+
"launch.phase",
|
|
1601
|
+
crate::lifecycle::restart::LifecyclePhase::Completed,
|
|
1602
|
+
);
|
|
1576
1603
|
Ok(QuickStartReport::Ready {
|
|
1577
1604
|
session_name,
|
|
1578
1605
|
launch: Box::new(launch),
|
|
@@ -1595,8 +1622,15 @@ fn preserve_quick_start_bootstrap_binding(
|
|
|
1595
1622
|
if let Some(receiver) = bootstrap.get("leader_receiver") {
|
|
1596
1623
|
binding = binding.with_leader_receiver(receiver.clone());
|
|
1597
1624
|
}
|
|
1598
|
-
if let Some(epoch) = bootstrap
|
|
1599
|
-
.
|
|
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
|
+
})
|
|
1600
1634
|
{
|
|
1601
1635
|
binding = binding.with_owner_epoch(epoch);
|
|
1602
1636
|
}
|
|
@@ -1682,13 +1716,19 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
1682
1716
|
assert_eq!(launched.get(key), bootstrap.get(key));
|
|
1683
1717
|
assert_eq!(launched["teams"]["current"].get(key), bootstrap.get(key));
|
|
1684
1718
|
}
|
|
1685
|
-
assert_eq!(
|
|
1719
|
+
assert_eq!(
|
|
1720
|
+
launched.get("leader_client"),
|
|
1721
|
+
bootstrap.get("leader_client")
|
|
1722
|
+
);
|
|
1686
1723
|
assert_eq!(launched.get("is_external_leader"), Some(&json!(true)));
|
|
1687
1724
|
assert_eq!(launched["agents"], json!({"worker": {}}));
|
|
1688
1725
|
let merged = super::super::state_projection::merge_workspace_team_state_with_key(
|
|
1689
1726
|
&bootstrap, &launched, "current",
|
|
1690
1727
|
);
|
|
1691
|
-
assert_eq!(
|
|
1728
|
+
assert_eq!(
|
|
1729
|
+
merged["teams"]["current"]["team_owner"],
|
|
1730
|
+
bootstrap["team_owner"]
|
|
1731
|
+
);
|
|
1692
1732
|
assert_eq!(merged["teams"]["current"]["owner_epoch"], json!(7));
|
|
1693
1733
|
}
|
|
1694
1734
|
|
|
@@ -1702,20 +1742,34 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
1702
1742
|
binary_path: Some("/tmp/candidate/team-agent".to_string()),
|
|
1703
1743
|
binary_version: Some("0.5.112".to_string()),
|
|
1704
1744
|
rotation_reason: Some("original rotation reason".to_string()),
|
|
1705
|
-
binary_identity_relation:
|
|
1745
|
+
binary_identity_relation:
|
|
1746
|
+
crate::coordinator::CoordinatorBinaryIdentityRelation::Unknown,
|
|
1706
1747
|
log: Some(PathBuf::from("/tmp/current/.team/runtime/coordinator.log")),
|
|
1707
|
-
schema_error: Some(crate::coordinator::SchemaError::InitFailed {
|
|
1748
|
+
schema_error: Some(crate::coordinator::SchemaError::InitFailed {
|
|
1749
|
+
message: "original database permission denied".to_string(),
|
|
1750
|
+
}),
|
|
1708
1751
|
action: Some("repair the exact database permissions".to_string()),
|
|
1709
1752
|
};
|
|
1710
1753
|
let error = require_quick_start_coordinator_started(&report, Path::new("/tmp/current"))
|
|
1711
|
-
.unwrap_err()
|
|
1712
|
-
|
|
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
|
+
] {
|
|
1713
1764
|
assert!(error.contains(detail), "lost {detail}: {error}");
|
|
1714
1765
|
}
|
|
1715
1766
|
report.ok = true;
|
|
1716
1767
|
report.status = crate::coordinator::StartOutcome::AlreadyRunning;
|
|
1717
|
-
report.binary_identity_relation =
|
|
1718
|
-
|
|
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
|
+
);
|
|
1719
1773
|
}
|
|
1720
1774
|
|
|
1721
1775
|
#[test]
|
|
@@ -1725,11 +1779,17 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
1725
1779
|
env.scrub_tmux();
|
|
1726
1780
|
env.assert_no_real_tmux();
|
|
1727
1781
|
let workspace = std::env::temp_dir().join(format!(
|
|
1728
|
-
"ta-current-bootstrap-{}-{}",
|
|
1782
|
+
"ta-current-bootstrap-{}-{}",
|
|
1783
|
+
std::process::id(),
|
|
1784
|
+
SEQUENCE.fetch_add(1, Ordering::Relaxed)
|
|
1729
1785
|
));
|
|
1730
1786
|
let team = workspace.join(".team/current");
|
|
1731
1787
|
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
1732
|
-
std::fs::write(
|
|
1788
|
+
std::fs::write(
|
|
1789
|
+
team.join("TEAM.md"),
|
|
1790
|
+
"---\nname: current\nobjective: Bootstrap fixture.\n---\n",
|
|
1791
|
+
)
|
|
1792
|
+
.unwrap();
|
|
1733
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();
|
|
1734
1794
|
let state_path = crate::state::persist::runtime_state_path(&workspace);
|
|
1735
1795
|
std::fs::create_dir_all(state_path.parent().unwrap()).unwrap();
|
|
@@ -1749,10 +1809,20 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
1749
1809
|
std::env::set_var("TMUX_PANE", "%0");
|
|
1750
1810
|
std::env::set_var(CALLER_PANE_ENV, "%0");
|
|
1751
1811
|
std::env::set_var(CALLER_PROVIDER_ENV, "pi");
|
|
1752
|
-
std::env::set_var(
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
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
|
+
);
|
|
1756
1826
|
assert_eq!(std::fs::read(&state_path).unwrap(), before);
|
|
1757
1827
|
assert!(!workspace.join(".team/team.db").exists());
|
|
1758
1828
|
assert!(!crate::model::paths::runtime_spec_path(&workspace, "current").exists());
|
|
@@ -1922,10 +1992,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
1922
1992
|
)
|
|
1923
1993
|
}
|
|
1924
1994
|
_ => crate::state::persist::save_runtime_state_with_receiver_authority(
|
|
1925
|
-
workspace,
|
|
1926
|
-
state,
|
|
1927
|
-
&team_key,
|
|
1928
|
-
None,
|
|
1995
|
+
workspace, state, &team_key, None,
|
|
1929
1996
|
),
|
|
1930
1997
|
};
|
|
1931
1998
|
if saved.is_ok() {
|
|
@@ -1941,9 +2008,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
1941
2008
|
}
|
|
1942
2009
|
|
|
1943
2010
|
fn frozen_seed_receipt(&self) -> Option<(serde_json::Value, serde_json::Value)> {
|
|
1944
|
-
self.frozen_owner
|
|
1945
|
-
.clone()
|
|
1946
|
-
.zip(self.frozen_receiver.clone())
|
|
2011
|
+
self.frozen_owner.clone().zip(self.frozen_receiver.clone())
|
|
1947
2012
|
}
|
|
1948
2013
|
|
|
1949
2014
|
fn this_attempt_extra_state_keys(&self) -> Vec<&'static str> {
|
|
@@ -1990,12 +2055,15 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
1990
2055
|
}
|
|
1991
2056
|
|
|
1992
2057
|
#[test]
|
|
1993
|
-
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
|
+
{
|
|
1994
2060
|
let workspace = workspace("positive");
|
|
1995
2061
|
let mut ops = MockOps::default();
|
|
1996
|
-
assert!(bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
|
|
1997
|
-
|
|
1998
|
-
|
|
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
|
+
);
|
|
1999
2067
|
assert_eq!(ops.attach_calls, 1);
|
|
2000
2068
|
assert_eq!(ops.register_calls, 1);
|
|
2001
2069
|
assert_eq!(ops.readback_calls, 1);
|
|
@@ -2030,11 +2098,13 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2030
2098
|
leader_session_uuid: "uuid-fresh".to_string(),
|
|
2031
2099
|
leader_session_uuid_source: "env".to_string(),
|
|
2032
2100
|
};
|
|
2033
|
-
assert!(
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2101
|
+
assert!(
|
|
2102
|
+
super::spec_state::seed_launched_owner_from_caller_with_provider_lookup(
|
|
2103
|
+
&mut state,
|
|
2104
|
+
caller,
|
|
2105
|
+
|_| None
|
|
2106
|
+
)
|
|
2107
|
+
);
|
|
2038
2108
|
let owner = state
|
|
2039
2109
|
.pointer("/teams/fresh/team_owner")
|
|
2040
2110
|
.cloned()
|
|
@@ -2061,13 +2131,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2061
2131
|
frozen_receiver: Some(frozen_receiver.clone()),
|
|
2062
2132
|
..MockOps::default()
|
|
2063
2133
|
};
|
|
2064
|
-
assert!(
|
|
2065
|
-
&workspace,
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
&mut ops
|
|
2069
|
-
)
|
|
2070
|
-
.unwrap());
|
|
2134
|
+
assert!(
|
|
2135
|
+
bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&owner), &mut ops)
|
|
2136
|
+
.unwrap()
|
|
2137
|
+
);
|
|
2071
2138
|
assert_eq!(ops.attach_calls, 1);
|
|
2072
2139
|
assert_eq!(ops.last_expected_receiver, Some(frozen_receiver));
|
|
2073
2140
|
let _ = std::fs::remove_dir_all(&workspace);
|
|
@@ -2084,10 +2151,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2084
2151
|
state["leader_receiver"] = newer.clone();
|
|
2085
2152
|
state["teams"]["fresh"]["leader_receiver"] = newer.clone();
|
|
2086
2153
|
crate::state::persist::save_runtime_state_with_receiver_authority(
|
|
2087
|
-
&workspace,
|
|
2088
|
-
&state,
|
|
2089
|
-
"fresh",
|
|
2090
|
-
None,
|
|
2154
|
+
&workspace, &state, "fresh", None,
|
|
2091
2155
|
)
|
|
2092
2156
|
.unwrap();
|
|
2093
2157
|
let on_disk = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
@@ -2104,13 +2168,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2104
2168
|
frozen_receiver: Some(frozen_receiver),
|
|
2105
2169
|
..MockOps::default()
|
|
2106
2170
|
};
|
|
2107
|
-
assert!(
|
|
2108
|
-
&workspace,
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
&mut ops
|
|
2112
|
-
)
|
|
2113
|
-
.unwrap());
|
|
2171
|
+
assert!(
|
|
2172
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&owner), &mut ops)
|
|
2173
|
+
.unwrap()
|
|
2174
|
+
);
|
|
2114
2175
|
assert_eq!(ops.attach_calls, 0);
|
|
2115
2176
|
assert_eq!(ops.last_expected_receiver, None);
|
|
2116
2177
|
let _ = std::fs::remove_dir_all(&workspace);
|
|
@@ -2127,10 +2188,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2127
2188
|
state["leader_receiver"] = winner.clone();
|
|
2128
2189
|
state["teams"]["fresh"]["leader_receiver"] = winner.clone();
|
|
2129
2190
|
crate::state::persist::save_runtime_state_with_receiver_authority(
|
|
2130
|
-
&workspace,
|
|
2131
|
-
&state,
|
|
2132
|
-
"fresh",
|
|
2133
|
-
None,
|
|
2191
|
+
&workspace, &state, "fresh", None,
|
|
2134
2192
|
)
|
|
2135
2193
|
.unwrap();
|
|
2136
2194
|
let on_disk = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
@@ -2147,13 +2205,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2147
2205
|
frozen_receiver: Some(frozen_receiver),
|
|
2148
2206
|
..MockOps::default()
|
|
2149
2207
|
};
|
|
2150
|
-
assert!(
|
|
2151
|
-
&workspace,
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
&mut ops
|
|
2155
|
-
)
|
|
2156
|
-
.unwrap());
|
|
2208
|
+
assert!(
|
|
2209
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&owner), &mut ops)
|
|
2210
|
+
.unwrap()
|
|
2211
|
+
);
|
|
2157
2212
|
assert_eq!(ops.attach_calls, 0);
|
|
2158
2213
|
assert_eq!(ops.last_expected_receiver, None);
|
|
2159
2214
|
let _ = std::fs::remove_dir_all(&workspace);
|
|
@@ -2163,7 +2218,11 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2163
2218
|
fn fresh_bind_refusal_event_keeps_first_safe_stage_and_reason() {
|
|
2164
2219
|
for (case, expected_stage, expected_reason) in [
|
|
2165
2220
|
("missing_pane", "caller_pane", "caller_pane_missing"),
|
|
2166
|
-
(
|
|
2221
|
+
(
|
|
2222
|
+
"empty_command",
|
|
2223
|
+
"command_observation",
|
|
2224
|
+
"command_unobservable",
|
|
2225
|
+
),
|
|
2167
2226
|
("unknown_provider", "strict_provider", "provider_unresolved"),
|
|
2168
2227
|
(
|
|
2169
2228
|
"existing_owner",
|
|
@@ -2200,12 +2259,16 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2200
2259
|
}
|
|
2201
2260
|
crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
|
|
2202
2261
|
|
|
2203
|
-
assert!(
|
|
2204
|
-
.unwrap()
|
|
2262
|
+
assert!(
|
|
2263
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap()
|
|
2264
|
+
);
|
|
2205
2265
|
let events = crate::event_log::EventLog::new(&workspace).tail(0).unwrap();
|
|
2206
2266
|
assert_eq!(events.len(), 1, "{case} must emit one first-refusal event");
|
|
2207
2267
|
assert_eq!(events[0]["event"], json!(FRESH_BIND_REFUSAL_EVENT));
|
|
2208
|
-
assert_eq!(
|
|
2268
|
+
assert_eq!(
|
|
2269
|
+
events[0]["operation"],
|
|
2270
|
+
json!("fresh_quick_start_leader_bind")
|
|
2271
|
+
);
|
|
2209
2272
|
assert_eq!(events[0]["team_key"], json!("fresh"));
|
|
2210
2273
|
assert_eq!(events[0]["stage"], json!(expected_stage));
|
|
2211
2274
|
assert_eq!(events[0]["reason"], json!(expected_reason));
|
|
@@ -2223,9 +2286,12 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2223
2286
|
));
|
|
2224
2287
|
std::fs::create_dir_all(&empty_workspace).unwrap();
|
|
2225
2288
|
let mut ops = MockOps::default();
|
|
2226
|
-
assert!(
|
|
2227
|
-
.unwrap()
|
|
2228
|
-
|
|
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();
|
|
2229
2295
|
assert_eq!(events.len(), 1);
|
|
2230
2296
|
assert_eq!(events[0]["stage"], json!("scope"));
|
|
2231
2297
|
assert_eq!(events[0]["reason"], json!("scope_unresolved"));
|
|
@@ -2240,8 +2306,9 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2240
2306
|
state["teams"] = json!({"fresh": {"team_key": "fresh", "agents": {}}});
|
|
2241
2307
|
crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
|
|
2242
2308
|
let mut ops = MockOps::default();
|
|
2243
|
-
assert!(
|
|
2244
|
-
.unwrap()
|
|
2309
|
+
assert!(
|
|
2310
|
+
!bind_fresh_quick_start_leader_with(&workspace, "current", None, &mut ops).unwrap()
|
|
2311
|
+
);
|
|
2245
2312
|
let events = crate::event_log::EventLog::new(&workspace).tail(0).unwrap();
|
|
2246
2313
|
assert_eq!(events.len(), 1);
|
|
2247
2314
|
assert_eq!(events[0]["stage"], json!("scope"));
|
|
@@ -2285,10 +2352,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2285
2352
|
ops.command = Some("codex".to_string());
|
|
2286
2353
|
}
|
|
2287
2354
|
"existing_owner" => state["team_owner"] = json!({"pane_id": "%old"}),
|
|
2288
|
-
"existing_receiver" => {
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2355
|
+
"existing_receiver" => state["leader_receiver"] = json!({"pane_id": "%old"}),
|
|
2356
|
+
"provider_mismatch"
|
|
2357
|
+
| "socket_mismatch"
|
|
2358
|
+
| "workspace_mismatch"
|
|
2292
2359
|
| "dual_state_mismatch" => {
|
|
2293
2360
|
state["workspace"] = json!(workspace);
|
|
2294
2361
|
state["team_owner"] = json!({
|
|
@@ -2303,8 +2370,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2303
2370
|
match case {
|
|
2304
2371
|
"provider_mismatch" => {
|
|
2305
2372
|
state["leader_receiver"]["provider"] = json!("claude")
|
|
2306
|
-
}
|
|
2307
|
-
"socket_mismatch" =>
|
|
2373
|
+
}
|
|
2374
|
+
"socket_mismatch" => {
|
|
2375
|
+
state["leader_receiver"]["tmux_socket"] = json!("/tmp/other")
|
|
2376
|
+
}
|
|
2308
2377
|
"workspace_mismatch" => state["workspace"] = json!(workspace.join("other")),
|
|
2309
2378
|
"dual_state_mismatch" => state["team_owner"]["owner_epoch"] = json!(2),
|
|
2310
2379
|
_ => unreachable!(),
|
|
@@ -2314,11 +2383,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2314
2383
|
_ => unreachable!(),
|
|
2315
2384
|
}
|
|
2316
2385
|
crate::state::persist::save_runtime_state(&workspace, &state).unwrap();
|
|
2317
|
-
let before =
|
|
2318
|
-
.unwrap();
|
|
2386
|
+
let before =
|
|
2387
|
+
std::fs::read(crate::state::persist::runtime_state_path(&workspace)).unwrap();
|
|
2319
2388
|
assert!(
|
|
2320
|
-
!bind_fresh_quick_start_leader_with(&workspace, team_key, None, &mut ops)
|
|
2321
|
-
.unwrap(),
|
|
2389
|
+
!bind_fresh_quick_start_leader_with(&workspace, team_key, None, &mut ops).unwrap(),
|
|
2322
2390
|
"{case} must refuse"
|
|
2323
2391
|
);
|
|
2324
2392
|
assert_eq!(
|
|
@@ -2490,15 +2558,23 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2490
2558
|
|| bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
|
|
2491
2559
|
)
|
|
2492
2560
|
.unwrap());
|
|
2493
|
-
assert_eq!(
|
|
2494
|
-
|
|
2495
|
-
"
|
|
2496
|
-
|
|
2497
|
-
)
|
|
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
|
+
));
|
|
2498
2571
|
let entry: crate::leader::registry::LeaderRegistryEntry =
|
|
2499
2572
|
serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap();
|
|
2500
2573
|
assert_eq!(
|
|
2501
|
-
entry
|
|
2574
|
+
entry
|
|
2575
|
+
.channel
|
|
2576
|
+
.get("tmux_socket")
|
|
2577
|
+
.and_then(serde_json::Value::as_str),
|
|
2502
2578
|
Some("/private/tmp/tmux-test/default")
|
|
2503
2579
|
);
|
|
2504
2580
|
}
|
|
@@ -2528,8 +2604,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2528
2604
|
let before = std::fs::read(crate::state::persist::runtime_state_path(&workspace)).unwrap();
|
|
2529
2605
|
let mut ops = MockOps::default();
|
|
2530
2606
|
|
|
2531
|
-
assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
|
|
2532
|
-
.unwrap());
|
|
2607
|
+
assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
|
|
2533
2608
|
assert_eq!(
|
|
2534
2609
|
before,
|
|
2535
2610
|
std::fs::read(crate::state::persist::runtime_state_path(&workspace)).unwrap(),
|
|
@@ -2709,10 +2784,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2709
2784
|
persisted["teams"][team_key]["leader_receiver"] = takeover.receiver.clone();
|
|
2710
2785
|
persisted["teams"][team_key]["owner_epoch"] = json!(takeover.epoch);
|
|
2711
2786
|
crate::state::persist::save_runtime_state_with_receiver_authority(
|
|
2712
|
-
workspace,
|
|
2713
|
-
&persisted,
|
|
2714
|
-
team_key,
|
|
2715
|
-
None,
|
|
2787
|
+
workspace, &persisted, team_key, None,
|
|
2716
2788
|
)
|
|
2717
2789
|
.unwrap();
|
|
2718
2790
|
assert_fresh_binding_kept(
|
|
@@ -2744,18 +2816,13 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2744
2816
|
}
|
|
2745
2817
|
}
|
|
2746
2818
|
|
|
2747
|
-
fn assert_fresh_binding_kept(
|
|
2748
|
-
persisted: &serde_json::Value,
|
|
2749
|
-
takeover: &ConcurrentTakeover,
|
|
2750
|
-
) {
|
|
2819
|
+
fn assert_fresh_binding_kept(persisted: &serde_json::Value, takeover: &ConcurrentTakeover) {
|
|
2751
2820
|
assert_eq!(
|
|
2752
|
-
persisted["teams"]["fresh"]["team_owner"],
|
|
2753
|
-
takeover.owner,
|
|
2821
|
+
persisted["teams"]["fresh"]["team_owner"], takeover.owner,
|
|
2754
2822
|
"concurrent team_owner must survive seeded cleanup"
|
|
2755
2823
|
);
|
|
2756
2824
|
assert_eq!(
|
|
2757
|
-
persisted["teams"]["fresh"]["leader_receiver"],
|
|
2758
|
-
takeover.receiver,
|
|
2825
|
+
persisted["teams"]["fresh"]["leader_receiver"], takeover.receiver,
|
|
2759
2826
|
"concurrent leader_receiver must survive seeded cleanup"
|
|
2760
2827
|
);
|
|
2761
2828
|
assert_eq!(
|
|
@@ -2769,13 +2836,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2769
2836
|
fn seeded_cleanup_clears_exact_seed_through_bind_repository_entry() {
|
|
2770
2837
|
let (workspace, seed) = workspace_with_fresh_seed("seed-cleanup-success");
|
|
2771
2838
|
let mut ops = failed_register_ops(None, false);
|
|
2772
|
-
assert!(
|
|
2773
|
-
&workspace,
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
&mut ops
|
|
2777
|
-
)
|
|
2778
|
-
.unwrap());
|
|
2839
|
+
assert!(
|
|
2840
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
2841
|
+
.unwrap()
|
|
2842
|
+
);
|
|
2779
2843
|
assert_eq!(ops.inner.attach_calls, 1);
|
|
2780
2844
|
assert_eq!(ops.inner.register_calls, 1);
|
|
2781
2845
|
assert!(ops
|
|
@@ -2794,11 +2858,9 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2794
2858
|
"matching seed must be cleared via bind/repository; got {}",
|
|
2795
2859
|
persisted["teams"]["fresh"]["team_owner"]
|
|
2796
2860
|
);
|
|
2797
|
-
assert!(
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
.is_some_and(serde_json::Value::is_null)
|
|
2801
|
-
);
|
|
2861
|
+
assert!(persisted
|
|
2862
|
+
.pointer("/teams/fresh/leader_receiver")
|
|
2863
|
+
.is_some_and(serde_json::Value::is_null));
|
|
2802
2864
|
assert_eq!(
|
|
2803
2865
|
persisted["teams"]["fresh"]["owner_epoch"],
|
|
2804
2866
|
json!(1),
|
|
@@ -2811,13 +2873,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2811
2873
|
let (workspace, seed) = workspace_with_matching_seed("seed-cleanup-concurrent");
|
|
2812
2874
|
let takeover = takeover_binding(1);
|
|
2813
2875
|
let mut ops = failed_register_ops(Some(takeover_binding(1)), false);
|
|
2814
|
-
assert!(
|
|
2815
|
-
&workspace,
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
&mut ops
|
|
2819
|
-
)
|
|
2820
|
-
.unwrap());
|
|
2876
|
+
assert!(
|
|
2877
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
2878
|
+
.unwrap()
|
|
2879
|
+
);
|
|
2821
2880
|
assert_fresh_binding_kept(
|
|
2822
2881
|
&crate::state::persist::load_runtime_state(&workspace).unwrap(),
|
|
2823
2882
|
&takeover,
|
|
@@ -2829,13 +2888,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2829
2888
|
let (workspace, seed) = workspace_with_matching_seed("seed-cleanup-epoch2");
|
|
2830
2889
|
let takeover = takeover_binding(2);
|
|
2831
2890
|
let mut ops = failed_register_ops(Some(takeover_binding(2)), false);
|
|
2832
|
-
assert!(
|
|
2833
|
-
&workspace,
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
&mut ops
|
|
2837
|
-
)
|
|
2838
|
-
.unwrap());
|
|
2891
|
+
assert!(
|
|
2892
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
2893
|
+
.unwrap()
|
|
2894
|
+
);
|
|
2839
2895
|
assert_fresh_binding_kept(
|
|
2840
2896
|
&crate::state::persist::load_runtime_state(&workspace).unwrap(),
|
|
2841
2897
|
&takeover,
|
|
@@ -2846,13 +2902,8 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2846
2902
|
fn seeded_cleanup_propagates_persist_error() {
|
|
2847
2903
|
let (workspace, seed) = workspace_with_fresh_seed("seed-cleanup-persist-err");
|
|
2848
2904
|
let mut ops = failed_register_ops(None, true);
|
|
2849
|
-
let error = bind_fresh_quick_start_leader_with(
|
|
2850
|
-
|
|
2851
|
-
"fresh",
|
|
2852
|
-
Some(&seed),
|
|
2853
|
-
&mut ops,
|
|
2854
|
-
)
|
|
2855
|
-
.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");
|
|
2856
2907
|
assert_eq!(ops.inner.attach_calls, 1);
|
|
2857
2908
|
assert_eq!(ops.inner.register_calls, 1);
|
|
2858
2909
|
assert!(ops
|
|
@@ -2932,7 +2983,9 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
2932
2983
|
"owner_epoch": 1
|
|
2933
2984
|
}
|
|
2934
2985
|
});
|
|
2935
|
-
serde_json::to_string_pretty(&expected)
|
|
2986
|
+
serde_json::to_string_pretty(&expected)
|
|
2987
|
+
.unwrap()
|
|
2988
|
+
.into_bytes()
|
|
2936
2989
|
}
|
|
2937
2990
|
|
|
2938
2991
|
fn assert_failure_rolls_back_this_attempt(
|
|
@@ -3031,8 +3084,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3031
3084
|
attach_ok: false,
|
|
3032
3085
|
..MockOps::default()
|
|
3033
3086
|
};
|
|
3034
|
-
assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
|
|
3035
|
-
.unwrap());
|
|
3087
|
+
assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
|
|
3036
3088
|
let after = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
3037
3089
|
assert_eq!(after.get("tasks"), Some(&json!({"keep": true})));
|
|
3038
3090
|
assert!(after.get("failed_attach_mutation").is_none());
|
|
@@ -3090,18 +3142,34 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3090
3142
|
let pane = PaneId::new("%0");
|
|
3091
3143
|
assert_ne!(socket.to_str(), Some(endpoint.as_str()));
|
|
3092
3144
|
assert!(persisted_binding_matches_verified_pane(
|
|
3093
|
-
&state,
|
|
3145
|
+
&state,
|
|
3146
|
+
workspace,
|
|
3147
|
+
&pane,
|
|
3148
|
+
Provider::Pi,
|
|
3149
|
+
Some(&endpoint),
|
|
3094
3150
|
));
|
|
3095
3151
|
assert_eq!(state, before);
|
|
3096
3152
|
assert!(!persisted_binding_matches_verified_pane(
|
|
3097
|
-
&state,
|
|
3153
|
+
&state,
|
|
3154
|
+
workspace,
|
|
3155
|
+
&PaneId::new("%1"),
|
|
3156
|
+
Provider::Pi,
|
|
3157
|
+
Some(&endpoint),
|
|
3098
3158
|
));
|
|
3099
3159
|
assert!(!persisted_binding_matches_verified_pane(
|
|
3100
|
-
&state,
|
|
3160
|
+
&state,
|
|
3161
|
+
workspace,
|
|
3162
|
+
&pane,
|
|
3163
|
+
Provider::Codex,
|
|
3164
|
+
Some(&endpoint),
|
|
3101
3165
|
));
|
|
3102
3166
|
for endpoint in [None, Some("")] {
|
|
3103
3167
|
assert!(!persisted_binding_matches_verified_pane(
|
|
3104
|
-
&state,
|
|
3168
|
+
&state,
|
|
3169
|
+
workspace,
|
|
3170
|
+
&pane,
|
|
3171
|
+
Provider::Pi,
|
|
3172
|
+
endpoint,
|
|
3105
3173
|
));
|
|
3106
3174
|
}
|
|
3107
3175
|
for (field, value) in [
|
|
@@ -3121,15 +3189,26 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3121
3189
|
] {
|
|
3122
3190
|
let mut conflict = state.clone();
|
|
3123
3191
|
*conflict.pointer_mut(field).unwrap() = value;
|
|
3124
|
-
assert!(
|
|
3125
|
-
|
|
3126
|
-
|
|
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
|
+
);
|
|
3127
3202
|
}
|
|
3128
3203
|
let mut unauthorized = state.clone();
|
|
3129
3204
|
unauthorized["leader_receiver"]["authorized_team_workspace"] =
|
|
3130
3205
|
json!(workspace.join("foreign"));
|
|
3131
3206
|
assert!(!persisted_binding_matches_verified_pane(
|
|
3132
|
-
&unauthorized,
|
|
3207
|
+
&unauthorized,
|
|
3208
|
+
workspace,
|
|
3209
|
+
&pane,
|
|
3210
|
+
Provider::Pi,
|
|
3211
|
+
Some(&endpoint),
|
|
3133
3212
|
));
|
|
3134
3213
|
}
|
|
3135
3214
|
|
|
@@ -3143,15 +3222,24 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3143
3222
|
let endpoint = transport.tmux_endpoint().unwrap();
|
|
3144
3223
|
let foreign_socket = workspace.join("foreign-socket-root").join(&endpoint);
|
|
3145
3224
|
let state = native_persisted_binding(workspace, foreign_socket.to_str().unwrap());
|
|
3146
|
-
assert_eq!(
|
|
3225
|
+
assert_eq!(
|
|
3226
|
+
foreign_socket.file_name().unwrap().to_str(),
|
|
3227
|
+
Some(endpoint.as_str())
|
|
3228
|
+
);
|
|
3147
3229
|
assert!(!persisted_binding_matches_verified_pane(
|
|
3148
|
-
&state,
|
|
3230
|
+
&state,
|
|
3231
|
+
workspace,
|
|
3232
|
+
&PaneId::new("%0"),
|
|
3233
|
+
Provider::Pi,
|
|
3234
|
+
Some(&endpoint),
|
|
3149
3235
|
));
|
|
3150
3236
|
assert!(!quick_start_tmux_endpoints_match(
|
|
3151
|
-
foreign_socket.to_str().unwrap(),
|
|
3237
|
+
foreign_socket.to_str().unwrap(),
|
|
3238
|
+
"default",
|
|
3152
3239
|
));
|
|
3153
3240
|
assert!(!quick_start_tmux_endpoints_match(
|
|
3154
|
-
foreign_socket.to_str().unwrap(),
|
|
3241
|
+
foreign_socket.to_str().unwrap(),
|
|
3242
|
+
"",
|
|
3155
3243
|
));
|
|
3156
3244
|
}
|
|
3157
3245
|
|
|
@@ -3169,7 +3257,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3169
3257
|
std::fs::write(&socket, "fixture endpoint; no tmux server").unwrap();
|
|
3170
3258
|
let state = native_persisted_binding(workspace, socket.to_str().unwrap());
|
|
3171
3259
|
assert!(persisted_binding_matches_verified_pane(
|
|
3172
|
-
&state,
|
|
3260
|
+
&state,
|
|
3261
|
+
workspace,
|
|
3262
|
+
&PaneId::new("%0"),
|
|
3263
|
+
Provider::Pi,
|
|
3173
3264
|
alias.join("ta-native.sock").to_str(),
|
|
3174
3265
|
));
|
|
3175
3266
|
}
|
|
@@ -3185,10 +3276,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3185
3276
|
state["teams"]["fresh"]["leader_receiver"] = receiver.clone();
|
|
3186
3277
|
state["teams"]["fresh"]["owner_epoch"] = json!(epoch);
|
|
3187
3278
|
crate::state::persist::save_runtime_state_with_receiver_authority(
|
|
3188
|
-
workspace,
|
|
3189
|
-
&state,
|
|
3190
|
-
"fresh",
|
|
3191
|
-
None,
|
|
3279
|
+
workspace, &state, "fresh", None,
|
|
3192
3280
|
)
|
|
3193
3281
|
.unwrap();
|
|
3194
3282
|
let raw = std::fs::read(crate::state::persist::runtime_state_path(workspace)).unwrap();
|
|
@@ -3248,7 +3336,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3248
3336
|
.unwrap();
|
|
3249
3337
|
let after = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
3250
3338
|
assert_eq!(after["teams"]["fresh"]["team_owner"], previous_owner);
|
|
3251
|
-
assert_eq!(
|
|
3339
|
+
assert_eq!(
|
|
3340
|
+
after["teams"]["fresh"]["leader_receiver"],
|
|
3341
|
+
previous_receiver
|
|
3342
|
+
);
|
|
3252
3343
|
assert_eq!(after["teams"]["fresh"]["owner_epoch"], json!(1));
|
|
3253
3344
|
assert_eq!(after.get("tasks"), Some(&json!({"keep": true})));
|
|
3254
3345
|
let _ = std::fs::remove_dir_all(&workspace);
|
|
@@ -3298,7 +3389,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3298
3389
|
assert_eq!(after["teams"]["fresh"]["leader_receiver"], winner_receiver);
|
|
3299
3390
|
assert_eq!(after["teams"]["fresh"]["owner_epoch"], json!(2));
|
|
3300
3391
|
assert_ne!(after["teams"]["fresh"]["team_owner"], previous_owner);
|
|
3301
|
-
assert_ne!(
|
|
3392
|
+
assert_ne!(
|
|
3393
|
+
after["teams"]["fresh"]["leader_receiver"],
|
|
3394
|
+
previous_receiver
|
|
3395
|
+
);
|
|
3302
3396
|
let _ = std::fs::remove_dir_all(&workspace);
|
|
3303
3397
|
}
|
|
3304
3398
|
|
|
@@ -3343,19 +3437,23 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3343
3437
|
let _home = HomeGuard::set(&home);
|
|
3344
3438
|
let state_path = crate::state::persist::runtime_state_path(&workspace);
|
|
3345
3439
|
let before = std::fs::read(&state_path).unwrap();
|
|
3346
|
-
let registry_path = crate::leader::registry::registry_dir()
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
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
|
+
));
|
|
3350
3446
|
let mut ops = AmbientRegistryOps {
|
|
3351
3447
|
readback_ok: false,
|
|
3352
3448
|
attach_calls: 0,
|
|
3353
3449
|
registry_receipt: None,
|
|
3354
3450
|
};
|
|
3355
|
-
assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
|
|
3356
|
-
.unwrap());
|
|
3451
|
+
assert!(!bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
|
|
3357
3452
|
assert_eq!(std::fs::read(state_path).unwrap(), before);
|
|
3358
|
-
assert!(
|
|
3453
|
+
assert!(
|
|
3454
|
+
!registry_path.exists(),
|
|
3455
|
+
"failed readback left registry bytes"
|
|
3456
|
+
);
|
|
3359
3457
|
}
|
|
3360
3458
|
|
|
3361
3459
|
struct RecordingRuntimeOps<'a> {
|
|
@@ -3563,7 +3661,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3563
3661
|
let mut focused = caller_target(parent.clone(), Some("shared-live-nonce"));
|
|
3564
3662
|
focused.active = true;
|
|
3565
3663
|
let (active_ok, active_reason) = bind_seeded_caller(focused, "runtime-focus-active");
|
|
3566
|
-
assert!(
|
|
3664
|
+
assert!(
|
|
3665
|
+
active_ok,
|
|
3666
|
+
"focused live caller must bind: {active_reason:?}"
|
|
3667
|
+
);
|
|
3567
3668
|
|
|
3568
3669
|
let mut background = caller_target(parent, Some("shared-live-nonce"));
|
|
3569
3670
|
background.active = false;
|
|
@@ -3599,8 +3700,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3599
3700
|
frozen_receiver: None,
|
|
3600
3701
|
nonce_writer: None,
|
|
3601
3702
|
};
|
|
3602
|
-
assert!(
|
|
3603
|
-
|
|
3703
|
+
assert!(
|
|
3704
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
3705
|
+
.unwrap()
|
|
3706
|
+
);
|
|
3604
3707
|
assert_eq!(ops.last_failure_reason, Some("caller_pane_not_live"));
|
|
3605
3708
|
}
|
|
3606
3709
|
|
|
@@ -3647,7 +3750,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3647
3750
|
.map(|agents| agents.keys().cloned().collect::<Vec<_>>())
|
|
3648
3751
|
.unwrap_or_default();
|
|
3649
3752
|
assert_eq!(ids, vec!["bob".to_string()]);
|
|
3650
|
-
assert_eq!(
|
|
3753
|
+
assert_eq!(
|
|
3754
|
+
view.get("session_name").and_then(serde_json::Value::as_str),
|
|
3755
|
+
Some("team-bob")
|
|
3756
|
+
);
|
|
3651
3757
|
}
|
|
3652
3758
|
|
|
3653
3759
|
#[test]
|
|
@@ -3750,11 +3856,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3750
3856
|
.with_tmux_endpoint("/tmp/tmux.sock")
|
|
3751
3857
|
.with_targets(vec![stale_target]);
|
|
3752
3858
|
assert!(matches!(
|
|
3753
|
-
crate::messaging::resolve_live_leader_channel(
|
|
3754
|
-
&workspace,
|
|
3755
|
-
&receiver,
|
|
3756
|
-
&stale_transport
|
|
3757
|
-
),
|
|
3859
|
+
crate::messaging::resolve_live_leader_channel(&workspace, &receiver, &stale_transport),
|
|
3758
3860
|
crate::messaging::LeaderChannelResolution::Unbound(
|
|
3759
3861
|
crate::messaging::LeaderChannelUnbound::PaneWorkspaceMismatch(_)
|
|
3760
3862
|
)
|
|
@@ -3792,10 +3894,17 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3792
3894
|
));
|
|
3793
3895
|
|
|
3794
3896
|
let mut foreign_receiver = receiver.clone();
|
|
3795
|
-
foreign_receiver["authorized_team_workspace"] =
|
|
3796
|
-
|
|
3897
|
+
foreign_receiver["authorized_team_workspace"] = json!(hermetic
|
|
3898
|
+
.root()
|
|
3899
|
+
.join("foreign")
|
|
3900
|
+
.to_string_lossy()
|
|
3901
|
+
.to_string());
|
|
3797
3902
|
assert!(matches!(
|
|
3798
|
-
crate::messaging::resolve_live_leader_channel(
|
|
3903
|
+
crate::messaging::resolve_live_leader_channel(
|
|
3904
|
+
&workspace,
|
|
3905
|
+
&foreign_receiver,
|
|
3906
|
+
&transport
|
|
3907
|
+
),
|
|
3799
3908
|
crate::messaging::LeaderChannelResolution::Unbound(
|
|
3800
3909
|
crate::messaging::LeaderChannelUnbound::PaneWorkspaceMismatch(_)
|
|
3801
3910
|
)
|
|
@@ -3998,10 +4107,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
3998
4107
|
let mut winner = crate::state::persist::load_runtime_state(workspace).unwrap();
|
|
3999
4108
|
winner["teams"][team_key]["leader_receiver"]["pane_id"] = json!("%winner");
|
|
4000
4109
|
crate::state::persist::save_runtime_state_with_receiver_authority(
|
|
4001
|
-
workspace,
|
|
4002
|
-
&winner,
|
|
4003
|
-
team_key,
|
|
4004
|
-
None,
|
|
4110
|
+
workspace, &winner, team_key, None,
|
|
4005
4111
|
)
|
|
4006
4112
|
.unwrap();
|
|
4007
4113
|
let _ = crate::leader::registry::register_binding_from_state_best_effort(
|
|
@@ -4037,10 +4143,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4037
4143
|
.with_tmux_endpoint("/tmp/tmux.sock")
|
|
4038
4144
|
.with_pane_current_command("%1", "bash")
|
|
4039
4145
|
.with_targets(vec![target_with_nonce.clone()])
|
|
4040
|
-
.with_target_snapshots(vec![
|
|
4041
|
-
vec![target_without_nonce],
|
|
4042
|
-
vec![target_with_nonce],
|
|
4043
|
-
]);
|
|
4146
|
+
.with_target_snapshots(vec![vec![target_without_nonce], vec![target_with_nonce]]);
|
|
4044
4147
|
let mut ops = RuntimeCommitFailureOps {
|
|
4045
4148
|
inner: RuntimeFreshQuickStartLeaderBindingOps {
|
|
4046
4149
|
transport: &transport,
|
|
@@ -4059,13 +4162,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4059
4162
|
break_registry_lock: false,
|
|
4060
4163
|
break_registry_read: false,
|
|
4061
4164
|
};
|
|
4062
|
-
assert!(
|
|
4063
|
-
&workspace,
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
&mut ops
|
|
4067
|
-
)
|
|
4068
|
-
.unwrap());
|
|
4165
|
+
assert!(
|
|
4166
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
4167
|
+
.unwrap()
|
|
4168
|
+
);
|
|
4069
4169
|
let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
4070
4170
|
assert!(persisted
|
|
4071
4171
|
.pointer("/teams/fresh/team_owner")
|
|
@@ -4080,7 +4180,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4080
4180
|
"{}__fresh.json",
|
|
4081
4181
|
crate::leader::registry::workspace_hash(&workspace)
|
|
4082
4182
|
));
|
|
4083
|
-
assert!(
|
|
4183
|
+
assert!(
|
|
4184
|
+
!registry_path.exists(),
|
|
4185
|
+
"failed child grant left registry row"
|
|
4186
|
+
);
|
|
4084
4187
|
let observed = transport.list_targets().unwrap();
|
|
4085
4188
|
assert_eq!(
|
|
4086
4189
|
observed[0]
|
|
@@ -4128,14 +4231,14 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4128
4231
|
break_registry_lock: false,
|
|
4129
4232
|
break_registry_read: false,
|
|
4130
4233
|
};
|
|
4131
|
-
assert!(
|
|
4132
|
-
&workspace,
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
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
|
+
);
|
|
4139
4242
|
let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
4140
4243
|
assert!(persisted
|
|
4141
4244
|
.pointer("/teams/fresh/team_owner")
|
|
@@ -4192,7 +4295,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4192
4295
|
|| bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
4193
4296
|
)
|
|
4194
4297
|
.unwrap());
|
|
4195
|
-
assert_eq!(
|
|
4298
|
+
assert_eq!(
|
|
4299
|
+
ops.last_failure_reason,
|
|
4300
|
+
Some("receiver_live_channel_unavailable")
|
|
4301
|
+
);
|
|
4196
4302
|
let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
4197
4303
|
assert!(persisted
|
|
4198
4304
|
.pointer("/teams/fresh/team_owner")
|
|
@@ -4245,13 +4351,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4245
4351
|
break_registry_lock: false,
|
|
4246
4352
|
break_registry_read: false,
|
|
4247
4353
|
};
|
|
4248
|
-
assert!(
|
|
4249
|
-
&workspace,
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
&mut ops
|
|
4253
|
-
)
|
|
4254
|
-
.unwrap());
|
|
4354
|
+
assert!(
|
|
4355
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
4356
|
+
.unwrap()
|
|
4357
|
+
);
|
|
4255
4358
|
let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
4256
4359
|
assert!(persisted
|
|
4257
4360
|
.pointer("/teams/fresh/team_owner")
|
|
@@ -4265,7 +4368,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4265
4368
|
"{}__fresh.json",
|
|
4266
4369
|
crate::leader::registry::workspace_hash(&workspace)
|
|
4267
4370
|
));
|
|
4268
|
-
assert!(
|
|
4371
|
+
assert!(
|
|
4372
|
+
!registry_path.exists(),
|
|
4373
|
+
"readback failure left registry row"
|
|
4374
|
+
);
|
|
4269
4375
|
}
|
|
4270
4376
|
|
|
4271
4377
|
#[test]
|
|
@@ -4304,13 +4410,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4304
4410
|
break_registry_lock: false,
|
|
4305
4411
|
break_registry_read: false,
|
|
4306
4412
|
};
|
|
4307
|
-
assert!(
|
|
4308
|
-
&workspace,
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
&mut ops,
|
|
4312
|
-
)
|
|
4313
|
-
.unwrap());
|
|
4413
|
+
assert!(
|
|
4414
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops,)
|
|
4415
|
+
.unwrap()
|
|
4416
|
+
);
|
|
4314
4417
|
let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
4315
4418
|
assert_eq!(
|
|
4316
4419
|
persisted
|
|
@@ -4348,13 +4451,11 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4348
4451
|
#[test]
|
|
4349
4452
|
#[serial_test::serial(env)]
|
|
4350
4453
|
fn seeded_runtime_registry_rollback_errors_are_visible_after_state_cleanup() {
|
|
4351
|
-
for (tag, break_registry_lock, break_registry_read) in
|
|
4352
|
-
("lock", true, false),
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
"runtime-seeded-registry-rollback-{tag}"
|
|
4357
|
-
));
|
|
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}"));
|
|
4358
4459
|
let workspace = runtime_workspace(&hermetic, "fresh");
|
|
4359
4460
|
let parent = hermetic.root().join("parent");
|
|
4360
4461
|
std::fs::create_dir_all(&parent).unwrap();
|
|
@@ -4387,13 +4488,9 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4387
4488
|
break_registry_lock,
|
|
4388
4489
|
break_registry_read,
|
|
4389
4490
|
};
|
|
4390
|
-
let error =
|
|
4391
|
-
&workspace,
|
|
4392
|
-
|
|
4393
|
-
Some(&seed),
|
|
4394
|
-
&mut ops,
|
|
4395
|
-
)
|
|
4396
|
-
.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");
|
|
4397
4494
|
assert!(error
|
|
4398
4495
|
.to_string()
|
|
4399
4496
|
.contains("quick-start registry rollback failed"));
|
|
@@ -4477,7 +4574,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4477
4574
|
)
|
|
4478
4575
|
.unwrap());
|
|
4479
4576
|
assert_eq!(ops.last_failure_reason, Some(expected_reason));
|
|
4480
|
-
assert_eq!(
|
|
4577
|
+
assert_eq!(
|
|
4578
|
+
refusal_reason(&workspace).map(|(_, reason)| reason),
|
|
4579
|
+
Some(expected_reason.to_string())
|
|
4580
|
+
);
|
|
4481
4581
|
let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
4482
4582
|
assert!(persisted
|
|
4483
4583
|
.pointer("/teams/fresh/team_owner")
|
|
@@ -4501,12 +4601,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4501
4601
|
let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
|
|
4502
4602
|
let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, "/tmp/tmux.sock");
|
|
4503
4603
|
let seed = seeded_runtime_owner(&workspace);
|
|
4504
|
-
let mut state =
|
|
4505
|
-
&workspace,
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
.unwrap()
|
|
4509
|
-
.state;
|
|
4604
|
+
let mut state =
|
|
4605
|
+
crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
|
|
4606
|
+
.unwrap()
|
|
4607
|
+
.state;
|
|
4510
4608
|
let expected_owner = state["team_owner"].clone();
|
|
4511
4609
|
let expected_receiver = state["leader_receiver"].clone();
|
|
4512
4610
|
let target = caller_target(parent.clone(), None);
|
|
@@ -4538,12 +4636,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4538
4636
|
|
|
4539
4637
|
let workspace = runtime_workspace(&hermetic, "writer-failure");
|
|
4540
4638
|
let seed = seeded_runtime_owner(&workspace);
|
|
4541
|
-
let mut state =
|
|
4542
|
-
&workspace,
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
.unwrap()
|
|
4546
|
-
.state;
|
|
4639
|
+
let mut state =
|
|
4640
|
+
crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
|
|
4641
|
+
.unwrap()
|
|
4642
|
+
.state;
|
|
4547
4643
|
let expected_owner = state["team_owner"].clone();
|
|
4548
4644
|
let expected_receiver = state["leader_receiver"].clone();
|
|
4549
4645
|
let target = caller_target(hermetic.root().join("parent"), None);
|
|
@@ -4583,27 +4679,22 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4583
4679
|
let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
|
|
4584
4680
|
let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, "/tmp/tmux.sock");
|
|
4585
4681
|
let seed = seeded_runtime_owner(&workspace);
|
|
4586
|
-
let concurrent_writer =
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
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
|
+
)
|
|
4591
4690
|
.map_err(crate::leader::LeaderError::State)?;
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
.map_err(crate::leader::LeaderError::State)?;
|
|
4600
|
-
let _ = crate::leader::registry::register_binding_from_state_best_effort(
|
|
4601
|
-
&workspace,
|
|
4602
|
-
Some("fresh"),
|
|
4603
|
-
"concurrent",
|
|
4604
|
-
);
|
|
4605
|
-
Ok("writer-winner".to_string())
|
|
4606
|
-
};
|
|
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
|
+
};
|
|
4607
4698
|
let target = caller_target(parent, None);
|
|
4608
4699
|
let transport = OfflineTransport::new()
|
|
4609
4700
|
.with_tmux_endpoint("/tmp/tmux.sock")
|
|
@@ -4619,30 +4710,35 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4619
4710
|
frozen_receiver: None,
|
|
4620
4711
|
nonce_writer: Some(&concurrent_writer),
|
|
4621
4712
|
};
|
|
4622
|
-
assert!(
|
|
4623
|
-
&workspace,
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
&mut ops
|
|
4627
|
-
)
|
|
4628
|
-
.unwrap());
|
|
4713
|
+
assert!(
|
|
4714
|
+
!bind_fresh_quick_start_leader_with(&workspace, "fresh", Some(&seed), &mut ops)
|
|
4715
|
+
.unwrap()
|
|
4716
|
+
);
|
|
4629
4717
|
assert!(ops.cas_conflict);
|
|
4630
4718
|
assert_eq!(ops.last_failure_reason, Some("attach_cas_conflict"));
|
|
4631
4719
|
let persisted = crate::state::persist::load_runtime_state(&workspace).unwrap();
|
|
4632
4720
|
assert_eq!(
|
|
4633
|
-
persisted
|
|
4721
|
+
persisted
|
|
4722
|
+
.pointer("/teams/fresh/team_owner/pane_id")
|
|
4723
|
+
.and_then(serde_json::Value::as_str),
|
|
4634
4724
|
Some("%1")
|
|
4635
4725
|
);
|
|
4636
4726
|
assert_eq!(
|
|
4637
|
-
persisted
|
|
4727
|
+
persisted
|
|
4728
|
+
.pointer("/teams/fresh/leader_receiver/pane_id")
|
|
4729
|
+
.and_then(serde_json::Value::as_str),
|
|
4638
4730
|
Some("%winner")
|
|
4639
4731
|
);
|
|
4640
4732
|
assert_eq!(
|
|
4641
|
-
persisted
|
|
4733
|
+
persisted
|
|
4734
|
+
.pointer("/teams/fresh/team_owner/owner_epoch")
|
|
4735
|
+
.and_then(serde_json::Value::as_u64),
|
|
4642
4736
|
Some(1)
|
|
4643
4737
|
);
|
|
4644
4738
|
assert_eq!(
|
|
4645
|
-
persisted
|
|
4739
|
+
persisted
|
|
4740
|
+
.pointer("/teams/fresh/leader_receiver/owner_epoch")
|
|
4741
|
+
.and_then(serde_json::Value::as_u64),
|
|
4646
4742
|
Some(1)
|
|
4647
4743
|
);
|
|
4648
4744
|
let registry_path = crate::leader::registry::registry_dir()
|
|
@@ -4651,7 +4747,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4651
4747
|
"{}__fresh.json",
|
|
4652
4748
|
crate::leader::registry::workspace_hash(&workspace)
|
|
4653
4749
|
));
|
|
4654
|
-
assert!(
|
|
4750
|
+
assert!(
|
|
4751
|
+
registry_path.exists(),
|
|
4752
|
+
"concurrent winner registry was removed"
|
|
4753
|
+
);
|
|
4655
4754
|
let winner_entry: crate::leader::registry::LeaderRegistryEntry =
|
|
4656
4755
|
serde_json::from_slice(&std::fs::read(registry_path).unwrap()).unwrap();
|
|
4657
4756
|
assert_eq!(winner_entry.channel["pane_id"], json!("%winner"));
|
|
@@ -4661,18 +4760,8 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4661
4760
|
#[serial_test::serial(env)]
|
|
4662
4761
|
fn runtime_socket_and_explicit_conflicts_refuse_without_command_fallback() {
|
|
4663
4762
|
for (tag, endpoint, leader, scoped) in [
|
|
4664
|
-
(
|
|
4665
|
-
|
|
4666
|
-
"/tmp/other.sock",
|
|
4667
|
-
None,
|
|
4668
|
-
"/tmp/tmux.sock",
|
|
4669
|
-
),
|
|
4670
|
-
(
|
|
4671
|
-
"scoped",
|
|
4672
|
-
"/tmp/tmux.sock",
|
|
4673
|
-
None,
|
|
4674
|
-
"/tmp/product.sock",
|
|
4675
|
-
),
|
|
4763
|
+
("socket", "/tmp/other.sock", None, "/tmp/tmux.sock"),
|
|
4764
|
+
("scoped", "/tmp/tmux.sock", None, "/tmp/product.sock"),
|
|
4676
4765
|
(
|
|
4677
4766
|
// The explicit leader provider intentionally conflicts with the
|
|
4678
4767
|
// caller provider; worker_env must reject this before command fallback.
|
|
@@ -4689,7 +4778,8 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4689
4778
|
let _provider = hermetic.with_env(CALLER_PROVIDER_ENV, "codex");
|
|
4690
4779
|
let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
|
|
4691
4780
|
let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, endpoint);
|
|
4692
|
-
let _leader =
|
|
4781
|
+
let _leader =
|
|
4782
|
+
leader.map(|value| hermetic.with_env("TEAM_AGENT_LEADER_PROVIDER", value));
|
|
4693
4783
|
let transport = OfflineTransport::new()
|
|
4694
4784
|
.with_tmux_endpoint(scoped)
|
|
4695
4785
|
.with_pane_current_command("%1", "codex");
|
|
@@ -4702,7 +4792,6 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4702
4792
|
}
|
|
4703
4793
|
}
|
|
4704
4794
|
|
|
4705
|
-
|
|
4706
4795
|
#[test]
|
|
4707
4796
|
fn pi_tool_category_is_transparently_accepted_by_compiler() {
|
|
4708
4797
|
let root = std::env::temp_dir().join(format!(
|
|
@@ -4729,9 +4818,9 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4729
4818
|
.get("agents")
|
|
4730
4819
|
.and_then(Value::as_list)
|
|
4731
4820
|
.and_then(|agents| {
|
|
4732
|
-
agents
|
|
4733
|
-
|
|
4734
|
-
|
|
4821
|
+
agents
|
|
4822
|
+
.iter()
|
|
4823
|
+
.find(|agent| agent.get("id").and_then(Value::as_str) == Some("worker"))
|
|
4735
4824
|
})
|
|
4736
4825
|
.expect("compiled spec must retain the worker role");
|
|
4737
4826
|
assert!(
|
|
@@ -4745,8 +4834,7 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4745
4834
|
fn fresh_binding_persists_then_registers_then_requires_canonical_readback() {
|
|
4746
4835
|
let workspace = workspace_with_provider("positive-pi", "pi");
|
|
4747
4836
|
let mut ops = MockOps::with_provider("pi");
|
|
4748
|
-
assert!(bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops)
|
|
4749
|
-
.unwrap());
|
|
4837
|
+
assert!(bind_fresh_quick_start_leader_with(&workspace, "fresh", None, &mut ops).unwrap());
|
|
4750
4838
|
assert_eq!(ops.attached_provider, Some(crate::provider::Provider::Pi));
|
|
4751
4839
|
assert_eq!(ops.attach_calls, 1);
|
|
4752
4840
|
assert_eq!(ops.register_calls, 1);
|
|
@@ -4803,12 +4891,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4803
4891
|
let _caller_pane = hermetic.with_env(CALLER_PANE_ENV, "%1");
|
|
4804
4892
|
let _caller_endpoint = hermetic.with_env(CALLER_ENDPOINT_ENV, "/tmp/tmux.sock");
|
|
4805
4893
|
let seed = seeded_runtime_owner(&workspace);
|
|
4806
|
-
let mut state =
|
|
4807
|
-
&workspace,
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
.unwrap()
|
|
4811
|
-
.state;
|
|
4894
|
+
let mut state =
|
|
4895
|
+
crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
|
|
4896
|
+
.unwrap()
|
|
4897
|
+
.state;
|
|
4812
4898
|
let expected_owner = state["team_owner"].clone();
|
|
4813
4899
|
let expected_receiver = state["leader_receiver"].clone();
|
|
4814
4900
|
let target = caller_target(parent.clone(), None);
|
|
@@ -4840,12 +4926,10 @@ mod fresh_quick_start_leader_binding_tests {
|
|
|
4840
4926
|
|
|
4841
4927
|
let workspace = runtime_workspace_with_provider(&hermetic, "writer-failure", "pi");
|
|
4842
4928
|
let seed = seeded_runtime_owner(&workspace);
|
|
4843
|
-
let mut state =
|
|
4844
|
-
&workspace,
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
.unwrap()
|
|
4848
|
-
.state;
|
|
4929
|
+
let mut state =
|
|
4930
|
+
crate::state::projection::resolve_runtime_team_scope(&workspace, Some("fresh"))
|
|
4931
|
+
.unwrap()
|
|
4932
|
+
.state;
|
|
4849
4933
|
let expected_owner = state["team_owner"].clone();
|
|
4850
4934
|
let expected_receiver = state["leader_receiver"].clone();
|
|
4851
4935
|
let target = caller_target(hermetic.root().join("parent"), None);
|