@team-agent/installer 0.5.65 → 0.5.66

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 (83) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +5 -0
  4. package/crates/team-agent/src/cli/adapters.rs +1 -1
  5. package/crates/team-agent/src/cli/tests/compile.rs +1 -1
  6. package/crates/team-agent/src/cli/tests/mod.rs +2 -2
  7. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
  8. package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
  9. package/crates/team-agent/src/compiler/tests.rs +23 -20
  10. package/crates/team-agent/src/compiler.rs +25 -5
  11. package/crates/team-agent/src/conpty/backend.rs +16 -0
  12. package/crates/team-agent/src/leader/start.rs +167 -2
  13. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
  14. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -2
  15. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +7 -2
  16. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +137 -2
  17. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -2
  18. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +5 -0
  19. package/crates/team-agent/src/lifecycle/launch/spawn.rs +39 -2
  20. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +105 -48
  21. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -2
  22. package/crates/team-agent/src/lifecycle/launch.rs +117 -21
  23. package/crates/team-agent/src/lifecycle/restart/agent.rs +89 -3
  24. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -9
  25. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +59 -15
  26. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
  27. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +1 -1
  28. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
  29. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
  30. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
  31. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +24 -32
  32. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
  33. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +13 -11
  34. package/crates/team-agent/src/lifecycle/tests/core.rs +0 -18
  35. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
  36. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
  37. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
  38. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
  39. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
  40. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +2 -2
  41. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +17 -30
  42. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +1 -1
  43. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +4 -4
  44. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
  45. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
  46. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
  47. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
  48. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1 -1
  49. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
  50. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
  51. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
  52. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1 -1
  53. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
  54. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
  55. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
  56. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
  57. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
  58. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
  59. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
  60. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +1 -1
  61. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
  62. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
  63. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +1 -1
  64. package/crates/team-agent/src/lifecycle/tests.rs +0 -2
  65. package/crates/team-agent/src/lifecycle/types.rs +0 -2
  66. package/crates/team-agent/src/lifecycle/worker_command_context.rs +115 -22
  67. package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
  68. package/crates/team-agent/src/model/spec.rs +129 -6
  69. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
  70. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
  71. package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
  72. package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
  73. package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
  74. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
  75. package/crates/team-agent/src/provider/bypass_flags.rs +80 -0
  76. package/crates/team-agent/src/provider/mod.rs +2 -0
  77. package/crates/team-agent/src/tmux_backend.rs +19 -0
  78. package/crates/team-agent/src/transport/test_support.rs +20 -0
  79. package/crates/team-agent/src/transport.rs +17 -0
  80. package/package.json +4 -4
  81. package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
  82. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
  83. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
@@ -270,7 +270,6 @@ fn restart_with_selected_team_and_transport(
270
270
  let spec_workspace = runtime_spec.parent().ok_or_else(|| {
271
271
  LifecycleError::TeamSelect("active team spec workspace not found".to_string())
272
272
  })?;
273
- let safety = crate::lifecycle::launch::effective_runtime_config(&spec)?;
274
273
  // Bug 1 (0.4.2 P0): the rebuilt spec is the single source of truth for the
275
274
  // active roster. Any agent in state.agents that is NOT in the rebuilt
276
275
  // spec is a stale leftover (role doc was deleted between sessions) and
@@ -526,7 +525,6 @@ fn restart_with_selected_team_and_transport(
526
525
  &selected.team_key,
527
526
  &session_name,
528
527
  transport,
529
- &safety,
530
528
  tmux_endpoint_source,
531
529
  &state,
532
530
  )
@@ -551,12 +549,25 @@ fn restart_with_selected_team_and_transport(
551
549
  if let Some(parallel_result) = parallel_result {
552
550
  match parallel_result {
553
551
  Ok(pspawn) => {
552
+ let raw_agent = state
553
+ .get("agents")
554
+ .and_then(|v| v.get(decision.agent_id.as_str()))
555
+ .cloned()
556
+ .unwrap_or(serde_json::Value::Null);
557
+ let rehydrated = rehydrate_agent_command_context_from_spec(
558
+ spec_workspace,
559
+ &decision.agent_id,
560
+ &raw_agent,
561
+ );
562
+ let launched_bypass = matches!(
563
+ rehydrated.get("dangerously_skip_permissions"),
564
+ Some(serde_json::Value::Bool(true))
565
+ );
554
566
  apply_marked_respawn(
555
567
  &selected.run_workspace,
556
568
  &selected.team_key,
557
569
  &mut state,
558
570
  transport,
559
- &safety,
560
571
  &phase_timer,
561
572
  decision,
562
573
  &pspawn.spawn,
@@ -565,6 +576,7 @@ fn restart_with_selected_team_and_transport(
565
576
  &mut successful_agents,
566
577
  &mut failed_agents,
567
578
  &mut fatal_resume_failure,
579
+ launched_bypass,
568
580
  );
569
581
  }
570
582
  Err(error) => {
@@ -709,7 +721,7 @@ fn restart_with_selected_team_and_transport(
709
721
  session_id,
710
722
  session_live,
711
723
  transport,
712
- Some(&safety),
724
+ None,
713
725
  layout_placement.as_ref(),
714
726
  None,
715
727
  tmux_endpoint_source,
@@ -737,6 +749,10 @@ fn restart_with_selected_team_and_transport(
737
749
  }
738
750
  };
739
751
  let verify_start = std::time::Instant::now();
752
+ let launched_bypass = matches!(
753
+ agent.get("dangerously_skip_permissions"),
754
+ Some(serde_json::Value::Bool(true))
755
+ );
740
756
  if let Err(error) = verify_spawned_agent_live(&decision.agent_id, &spawn, transport)
741
757
  .and_then(|_| {
742
758
  mark_agent_respawned(
@@ -745,7 +761,7 @@ fn restart_with_selected_team_and_transport(
745
761
  decision.restart_mode,
746
762
  &spawn,
747
763
  transport,
748
- &safety,
764
+ launched_bypass,
749
765
  )
750
766
  })
751
767
  {
@@ -787,7 +803,16 @@ fn restart_with_selected_team_and_transport(
787
803
  .and_then(|agents| agents.get_mut(decision.agent_id.as_str()))
788
804
  .and_then(serde_json::Value::as_object_mut)
789
805
  {
790
- persist_effective_approval_policy_for_restart(agent, &safety);
806
+ // 0.5.66 bypass 单源:policy 从 agent 行字段派生。
807
+ let agent_provider = agent
808
+ .get("provider")
809
+ .and_then(serde_json::Value::as_str)
810
+ .and_then(crate::provider::wire::parse_canonical_provider)
811
+ .unwrap_or(crate::model::enums::Provider::Codex);
812
+ crate::lifecycle::launch::persist_effective_approval_policy_from_agent(
813
+ agent,
814
+ agent_provider,
815
+ );
791
816
  }
792
817
  // 0.5.32 (`.team/artifacts/restart-resumed-stale-activity-locate.md` §5):
793
818
  // pair the state-side activity clear with a DB `agent_health` clear so
@@ -1958,7 +1983,7 @@ fn mark_agent_respawned(
1958
1983
  restart_mode: StartMode,
1959
1984
  spawn: &SpawnedAgentWindow,
1960
1985
  transport: &dyn crate::transport::Transport,
1961
- safety: &DangerousApproval,
1986
+ launched_bypass: bool,
1962
1987
  ) -> Result<(), LifecycleError> {
1963
1988
  let Some(agent) = state
1964
1989
  .get_mut("agents")
@@ -2081,7 +2106,18 @@ fn mark_agent_respawned(
2081
2106
  }
2082
2107
  }
2083
2108
  crate::lifecycle::launch::persist_command_plan_state(agent, &spawn.plan, &spawn.profile_launch);
2084
- persist_effective_approval_policy_for_restart(agent, safety);
2109
+ // 0.5.66 bypass 单源:policy 从 agent 行字段派生,不再用全队 `DangerousApproval`。
2110
+ let agent_provider = agent
2111
+ .get("provider")
2112
+ .and_then(serde_json::Value::as_str)
2113
+ .and_then(crate::provider::wire::parse_canonical_provider)
2114
+ .unwrap_or(crate::model::enums::Provider::Codex);
2115
+ crate::lifecycle::launch::persist_effective_approval_policy_from_agent(agent, agent_provider);
2116
+ // 0.5.66 bypass 单源 §2.6:fresh spawn(restart)记录运行面 bypass 值。
2117
+ agent.insert(
2118
+ "as_launched_dangerously_skip_permissions".to_string(),
2119
+ serde_json::json!(launched_bypass),
2120
+ );
2085
2121
  if let Some(placement) = spawn.layout_placement.as_ref() {
2086
2122
  agent.insert(
2087
2123
  "layout_window".to_string(),
@@ -2510,7 +2546,6 @@ fn run_bounded_parallel_worker_spawns(
2510
2546
  team_key: &str,
2511
2547
  session_name: &SessionName,
2512
2548
  transport: &(dyn crate::transport::Transport),
2513
- safety: &DangerousApproval,
2514
2549
  tmux_endpoint_source: Option<&str>,
2515
2550
  state: &serde_json::Value,
2516
2551
  ) -> Vec<Option<Result<ParallelSpawnResult, String>>> {
@@ -2589,7 +2624,7 @@ fn run_bounded_parallel_worker_spawns(
2589
2624
  first_input.session_id.as_ref(),
2590
2625
  first_session_live,
2591
2626
  transport,
2592
- Some(safety),
2627
+ None,
2593
2628
  first_input.layout_placement.as_ref(),
2594
2629
  None,
2595
2630
  tmux_endpoint_source,
@@ -2673,7 +2708,7 @@ fn run_bounded_parallel_worker_spawns(
2673
2708
  input.session_id.as_ref(),
2674
2709
  /* into_existing_session */ true,
2675
2710
  transport,
2676
- Some(safety),
2711
+ None,
2677
2712
  input.layout_placement.as_ref(),
2678
2713
  None,
2679
2714
  tmux_endpoint_source,
@@ -2716,7 +2751,6 @@ fn apply_marked_respawn(
2716
2751
  team_key: &str,
2717
2752
  state: &mut serde_json::Value,
2718
2753
  transport: &(dyn crate::transport::Transport),
2719
- safety: &DangerousApproval,
2720
2754
  phase_timer: &RestartPhaseTimer,
2721
2755
  decision: &RestartedAgent,
2722
2756
  spawn: &SpawnedAgentWindow,
@@ -2725,6 +2759,7 @@ fn apply_marked_respawn(
2725
2759
  successful_agents: &mut Vec<RestartedAgent>,
2726
2760
  failed_agents: &mut Vec<RestartFailedAgent>,
2727
2761
  fatal_resume_failure: &mut bool,
2762
+ launched_bypass: bool,
2728
2763
  ) {
2729
2764
  let verify_start = std::time::Instant::now();
2730
2765
  if let Err(error) =
@@ -2735,7 +2770,7 @@ fn apply_marked_respawn(
2735
2770
  decision.restart_mode,
2736
2771
  spawn,
2737
2772
  transport,
2738
- safety,
2773
+ launched_bypass,
2739
2774
  )
2740
2775
  })
2741
2776
  {
@@ -2776,7 +2811,16 @@ fn apply_marked_respawn(
2776
2811
  .and_then(|agents| agents.get_mut(decision.agent_id.as_str()))
2777
2812
  .and_then(serde_json::Value::as_object_mut)
2778
2813
  {
2779
- persist_effective_approval_policy_for_restart(agent, safety);
2814
+ // 0.5.66 bypass 单源:policy 从 agent 行字段派生。
2815
+ let agent_provider = agent
2816
+ .get("provider")
2817
+ .and_then(serde_json::Value::as_str)
2818
+ .and_then(crate::provider::wire::parse_canonical_provider)
2819
+ .unwrap_or(crate::model::enums::Provider::Codex);
2820
+ crate::lifecycle::launch::persist_effective_approval_policy_from_agent(
2821
+ agent,
2822
+ agent_provider,
2823
+ );
2780
2824
  }
2781
2825
  let _ = crate::db::agent_health_capture::clear_agent_health_observation(
2782
2826
  run_workspace,
@@ -3728,7 +3772,7 @@ tasks:
3728
3772
  StartMode::Resumed,
3729
3773
  &spawn,
3730
3774
  &RespawnEpochTransport,
3731
- &disabled_safety(),
3775
+ false,
3732
3776
  )
3733
3777
  .expect("mark respawned");
3734
3778
 
@@ -393,7 +393,7 @@ fn write_min_team(ws: &Path, name: &str) -> PathBuf {
393
393
  .unwrap();
394
394
  std::fs::write(
395
395
  team.join("agents/worker_a.md"),
396
- "---\nname: worker_a\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n",
396
+ "---\nname: worker_a\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n",
397
397
  )
398
398
  .unwrap();
399
399
  team
@@ -330,7 +330,7 @@ fn add_agent_joins_w2_into_running_roster_and_existing_session() {
330
330
  let role_file = team_dir.join("w2-role.md");
331
331
  std::fs::write(
332
332
  &role_file,
333
- "---\nname: w2\nrole: Worker Two\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker two.\n",
333
+ "---\nname: w2\nrole: Worker Two\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker two.\n",
334
334
  )
335
335
  .unwrap();
336
336
  let transport = OfflineTransport::new().with_session_present(true);
@@ -528,7 +528,7 @@ fn write_team(workspace: &Path, fixture: &TeamFixture<'_>) -> PathBuf {
528
528
  std::fs::write(
529
529
  team.join("agents").join(format!("{}.md", agent.id)),
530
530
  format!(
531
- "---\nname: {}\nrole: {}\nprovider: {}\n{model}auth_mode: {}\n{profile}tools:\n{tools}---\n\n{}\n",
531
+ "---\nname: {}\nrole: {}\nprovider: {}\n{model}auth_mode: {}\n{profile}dangerously_skip_permissions: false\ntools:\n{tools}---\n\n{}\n",
532
532
  agent.id, agent.role, agent.provider, agent.auth_mode, agent.body
533
533
  ),
534
534
  )
@@ -152,14 +152,14 @@ fn write_claude_profile_team(ws: &Path, team_key: &str, agent_id: &str) -> PathB
152
152
  std::fs::write(
153
153
  team.join("TEAM.md"),
154
154
  format!(
155
- "---\nname: {team_key}\nobjective: Claude compatible config contract.\nprovider: claude\nauth_mode: compatible_api\n---\n\nTeam.\n"
155
+ "---\nname: {team_key}\nobjective: Claude compatible config contract.\nprovider: claude\nauth_mode: compatible_api\ndangerously_skip_permissions: false\n---\n\nTeam.\n"
156
156
  ),
157
157
  )
158
158
  .unwrap();
159
159
  std::fs::write(
160
160
  team.join("agents").join(format!("{agent_id}.md")),
161
161
  format!(
162
- "---\nname: {agent_id}\nrole: Claude Worker\nprovider: claude\nauth_mode: compatible_api\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n"
162
+ "---\nname: {agent_id}\nrole: Claude Worker\nprovider: claude\nauth_mode: compatible_api\ndangerously_skip_permissions: false\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n"
163
163
  ),
164
164
  )
165
165
  .unwrap();
@@ -218,14 +218,14 @@ fn write_claude_profile_team(ws: &Path, team_key: &str, agent_id: &str) -> PathB
218
218
  std::fs::write(
219
219
  team.join("TEAM.md"),
220
220
  format!(
221
- "---\nname: {team_key}\nobjective: Claude compatible profile launch contract.\nprovider: claude\nauth_mode: compatible_api\n---\n\nTeam.\n"
221
+ "---\nname: {team_key}\nobjective: Claude compatible profile launch contract.\nprovider: claude\nauth_mode: compatible_api\ndangerously_skip_permissions: false\n---\n\nTeam.\n"
222
222
  ),
223
223
  )
224
224
  .unwrap();
225
225
  std::fs::write(
226
226
  team.join("agents").join(format!("{agent_id}.md")),
227
227
  format!(
228
- "---\nname: {agent_id}\nrole: Claude Worker\nprovider: claude\nauth_mode: compatible_api\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n"
228
+ "---\nname: {agent_id}\nrole: Claude Worker\nprovider: claude\nauth_mode: compatible_api\ndangerously_skip_permissions: false\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n"
229
229
  ),
230
230
  )
231
231
  .unwrap();
@@ -247,14 +247,14 @@ fn write_claude_subscription_profile_team(ws: &Path, team_key: &str, agent_id: &
247
247
  std::fs::write(
248
248
  team.join("TEAM.md"),
249
249
  format!(
250
- "---\nname: {team_key}\nobjective: Claude subscription profile launch contract.\nprovider: claude\nauth_mode: subscription\n---\n\nTeam.\n"
250
+ "---\nname: {team_key}\nobjective: Claude subscription profile launch contract.\nprovider: claude\nauth_mode: subscription\ndangerously_skip_permissions: false\n---\n\nTeam.\n"
251
251
  ),
252
252
  )
253
253
  .unwrap();
254
254
  std::fs::write(
255
255
  team.join("agents").join(format!("{agent_id}.md")),
256
256
  format!(
257
- "---\nname: {agent_id}\nrole: Claude Worker\nprovider: claude\nauth_mode: subscription\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n"
257
+ "---\nname: {agent_id}\nrole: Claude Worker\nprovider: claude\nauth_mode: subscription\ndangerously_skip_permissions: false\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n"
258
258
  ),
259
259
  )
260
260
  .unwrap();
@@ -304,7 +304,7 @@ fn write_team_docs(workspace: &Path, provider: &str) {
304
304
  std::fs::write(
305
305
  workspace.join("agents").join(format!("{SOURCE}.md")),
306
306
  format!(
307
- "---\nname: {SOURCE}\nrole: {SOURCE}\nprovider: {provider}\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{SOURCE} body.\n"
307
+ "---\nname: {SOURCE}\nrole: {SOURCE}\nprovider: {provider}\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{SOURCE} body.\n"
308
308
  ),
309
309
  )
310
310
  .expect("write source role doc");
@@ -323,7 +323,7 @@ fn write_copilot_team_docs(workspace: &Path) -> PathBuf {
323
323
  std::fs::write(
324
324
  team_dir.join("agents").join(format!("{SOURCE}.md")),
325
325
  format!(
326
- "---\nname: {SOURCE}\nrole: Copilot Worker\nprovider: copilot\nmodel: gpt-5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nCopilot source body.\n"
326
+ "---\nname: {SOURCE}\nrole: Copilot Worker\nprovider: copilot\nmodel: gpt-5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nCopilot source body.\n"
327
327
  ),
328
328
  )
329
329
  .expect("write source role doc");
@@ -502,20 +502,16 @@ fn r8_copilot_fork_is_no_longer_capability_refused() {
502
502
  }
503
503
 
504
504
  /// R7 permission-clamp GUARDRAIL — a fork's effective approval policy is derived
505
- /// from the LEADER and never escalates above it (MUST-16). Naturally green at
506
- /// baseline (the role model forces permission_mode=restricted and inherits
507
- /// approval from the leader); its teeth engage after implement recompiles the
508
- /// role via RoleSourceResolver any raw clone of a compiled permission above the
509
- /// leader trips it. The true escalation scenario (a source declaring capability
510
- /// above the leader) is a real-machine concern.
505
+ /// from the LEADER and never escalates above it (MUST-16). 0.5.66 bypass 单源:
506
+ /// fork approval policy 由源角色的 `dangerously_skip_permissions` 派生
507
+ /// (本 fixture = false `source=disabled`),不再依赖 leader argv 继承。任何
508
+ /// fork 都不得出现 `source=leader_process`(旧模型已删)或 capability 高于源。
511
509
  #[test]
512
510
  fn r7_fork_effective_permissions_are_clamped_to_leader_guardrail() {
513
511
  // Claude source + success-path backing shim so the fork COMPLETES and a NEW
514
- // row exists to inspect — no vacuous pass on a refused fork. The dangerous
515
- // leader is established by the PINNED ancestry seam (see ANCESTRY_ENV note):
516
- // `source=leader_process` is only correct when a dangerous ancestor is
517
- // actually detected, so the fixture must supply one instead of borrowing
518
- // the host's.
512
+ // row exists to inspect — no vacuous pass on a refused fork. The source role
513
+ // declares `dangerously_skip_permissions: false`, so the fork's policy must
514
+ // resolve to `disabled` (fail-closed) the new single-source model.
519
515
  let case = Case::start("cf-r7");
520
516
  let fork = case.fork(NEW);
521
517
  assert_eq!(
@@ -538,11 +534,17 @@ fn r7_fork_effective_permissions_are_clamped_to_leader_guardrail() {
538
534
  let above = policy
539
535
  .get("worker_capability_above_leader")
540
536
  .and_then(Value::as_bool);
541
- assert_eq!(
537
+ assert_ne!(
542
538
  source,
543
539
  Some("leader_process"),
544
- "guardrail: a fork's effective approval policy must be derived from the leader \
545
- (source=leader_process), not raw-cloned from the source; MUST-16. policy={policy}"
540
+ "0.5.66: fork policy must never be leader_process (old ancestry model removed); \
541
+ policy={policy}"
542
+ );
543
+ assert_eq!(
544
+ source,
545
+ Some("disabled"),
546
+ "guardrail: a fork's approval policy derives from the role's dangerously_skip_permissions \
547
+ (false → disabled), never raw-cloned escalation; MUST-16. policy={policy}"
546
548
  );
547
549
  assert_ne!(
548
550
  above,
@@ -552,18 +554,9 @@ fn r7_fork_effective_permissions_are_clamped_to_leader_guardrail() {
552
554
  );
553
555
  }
554
556
 
555
- /// R7b reverse positive control — with a pinned RESTRICTED leader ancestry the
556
- /// fork's policy must resolve to `source=disabled` (fail-closed), never
557
- /// `leader_process`, and still never escalate. This is the CI-truth face of the
558
- /// clamp (a clean runner == no dangerous ancestor) and proves r7 does not pass
559
- /// by constant behavior: the same fixture flips leader_process↔disabled purely
560
- /// on the pinned seam. Mirrors the deterministic product contract
561
- /// `lifecycle/tests/core.rs` (clean mocked ancestry → Disabled).
562
- ///
563
- /// NOTE deliberately NOT asserted with the seam UNSET: unpinned, the policy is
564
- /// a function of the host process tree (the exact non-hermeticity this
565
- /// revision removes), so an unset-seam assertion would be red on any host
566
- /// whose own leader runs dangerously.
557
+ /// R7b reverse positive control — same source role (dangerously_skip_permissions:
558
+ /// false) must also resolve to `source=disabled` under any ancestry seam. 0.5.66
559
+ /// policy leader argv 无关:source `disabled`,fork 恒不升级(MUST-16)。
567
560
  #[test]
568
561
  fn r7b_fork_policy_under_restricted_leader_ancestry_is_disabled() {
569
562
  let case = Case::start_with_ancestry("cf-r7b", RESTRICTED_LEADER_ARGV);
@@ -587,16 +580,15 @@ fn r7b_fork_policy_under_restricted_leader_ancestry_is_disabled() {
587
580
  assert_eq!(
588
581
  policy.get("source").and_then(Value::as_str),
589
582
  Some("disabled"),
590
- "reverse control: a restricted/clean leader ancestry must resolve to source=disabled \
591
- (fail-closed), never leader_process. policy={policy}"
583
+ "fail-closed: source role declares dangerously_skip_permissions=false fork policy must \
584
+ be disabled; policy={policy}"
592
585
  );
593
586
  assert_ne!(
594
587
  policy
595
588
  .get("worker_capability_above_leader")
596
589
  .and_then(Value::as_bool),
597
590
  Some(true),
598
- "reverse control: a restricted leader must never yield a fork above the leader; MUST-16. \
599
- policy={policy}"
591
+ "a fork must never yield a capability above the source; MUST-16. policy={policy}"
600
592
  );
601
593
  }
602
594
 
@@ -246,7 +246,7 @@ fn fixture(tag: &str, team_mode: Option<&str>) -> PathBuf {
246
246
  std::fs::write(
247
247
  root.join("agents").join(format!("worker_{id}.md")),
248
248
  format!(
249
- "---\nname: worker_{id}\nrole: Communication Worker\nprovider: claude\ncommunication_mode: {id}\ntools:\n - mcp_team\n---\n\nROLE BODY SENTINEL {id}\n"
249
+ "---\nname: worker_{id}\nrole: Communication Worker\nprovider: claude\ndangerously_skip_permissions: false\ncommunication_mode: {id}\ntools:\n - mcp_team\n---\n\nROLE BODY SENTINEL {id}\n"
250
250
  ),
251
251
  )
252
252
  .unwrap();
@@ -265,7 +265,7 @@ fn default_fixture(tag: &str) -> PathBuf {
265
265
  }
266
266
  std::fs::write(
267
267
  root.join("agents").join("worker_default.md"),
268
- "---\nname: worker_default\nrole: Communication Worker\nprovider: claude\ntools:\n - mcp_team\n---\n\nROLE BODY SENTINEL default\n",
268
+ "---\nname: worker_default\nrole: Communication Worker\nprovider: claude\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nROLE BODY SENTINEL default\n",
269
269
  )
270
270
  .unwrap();
271
271
  root
@@ -229,8 +229,9 @@ team_orchestrator or @<.team/runtime/mcp/worker_a.json>; got {value:?}"
229
229
  }
230
230
 
231
231
  /// Face 1 dangerous tier (C-5-1, verdict `dangerous_auto_approve_argv_contains_allow_all`):
232
- /// dangerous_auto_approve must map to `--allow-all` (claude --dangerously-skip-permissions
233
- /// parity, paths+urls included) — NOT the silently-narrower `--allow-all-tools`.
232
+ /// role `dangerously_skip_permissions: true` must map to `--allow-all` (claude
233
+ /// `--dangerously-skip-permissions` parity, paths+urls included) — NOT the
234
+ /// silently-narrower `--allow-all-tools`. 0.5.66 bypass 单源:bypass 由角色字段驱动。
234
235
  #[test]
235
236
  #[serial(env)]
236
237
  fn copilot_argv_dangerous_maps_to_allow_all() {
@@ -254,7 +255,7 @@ fn copilot_argv_dangerous_maps_to_allow_all() {
254
255
  let mut failures = Vec::new();
255
256
  if !argv.iter().any(|arg| arg == "--allow-all") {
256
257
  failures.push(format!(
257
- "C-5-1: dangerous_auto_approve must produce `--allow-all` (=--yolo, claude \
258
+ "C-5-1: dangerously_skip_permissions=true must produce `--allow-all` (=--yolo, claude \
258
259
  parity incl. paths+urls); argv={argv:?}"
259
260
  ));
260
261
  }
@@ -1101,8 +1102,8 @@ fn copilot_subscription_model_precedence_role_over_profile() {
1101
1102
  // point the agent at the profile
1102
1103
  let agent_md = team_dir.join("agents/worker_a.md");
1103
1104
  let body = std::fs::read_to_string(&agent_md).unwrap().replace(
1104
- "auth_mode: subscription\n",
1105
- "auth_mode: subscription\nprofile: subm\n",
1105
+ "auth_mode: subscription\ndangerously_skip_permissions: false\n",
1106
+ "auth_mode: subscription\ndangerously_skip_permissions: false\nprofile: subm\n",
1106
1107
  );
1107
1108
  std::fs::write(&agent_md, body).unwrap();
1108
1109
  seed_healthy_coordinator(&ws);
@@ -1387,7 +1388,7 @@ fn write_copilot_byok_team(ws: &Path, name: &str, model: Option<&str>) -> PathBu
1387
1388
  std::fs::write(
1388
1389
  team.join("agents/worker_a.md"),
1389
1390
  format!(
1390
- "---\nname: worker_a\nrole: Copilot Worker\nprovider: copilot\n{model_field}auth_mode: compatible_api\nprofile: byok\ntools:\n - mcp_team\n---\n\nCOPILOT ROLE BODY SENTINEL: BYOK worker.\n"
1391
+ "---\nname: worker_a\nrole: Copilot Worker\nprovider: copilot\n{model_field}auth_mode: compatible_api\ndangerously_skip_permissions: false\nprofile: byok\ntools:\n - mcp_team\n---\n\nCOPILOT ROLE BODY SENTINEL: BYOK worker.\n"
1391
1392
  ),
1392
1393
  )
1393
1394
  .unwrap();
@@ -1412,15 +1413,16 @@ fn write_copilot_team(ws: &Path, name: &str, tools: &[&str], dangerous: bool) ->
1412
1413
  let _ = TeamOpts;
1413
1414
  let team = ws.join(name);
1414
1415
  std::fs::create_dir_all(team.join("agents")).unwrap();
1415
- let dangerous_line = if dangerous {
1416
- "dangerous_auto_approve: true\n"
1416
+ // 0.5.66 bypass 单源:bypass 由角色字段驱动,不再是 TEAM.md dangerous_auto_approve。
1417
+ let role_bypass = if dangerous {
1418
+ "true"
1417
1419
  } else {
1418
- ""
1420
+ "false"
1419
1421
  };
1420
1422
  std::fs::write(
1421
1423
  team.join("TEAM.md"),
1422
1424
  format!(
1423
- "---\nname: {name}\nobjective: copilot provider contract fixture.\nprovider: copilot\n{dangerous_line}---\n\nTeam fixture.\n"
1425
+ "---\nname: {name}\nobjective: copilot provider contract fixture.\nprovider: copilot\n---\n\nTeam fixture.\n"
1424
1426
  ),
1425
1427
  )
1426
1428
  .unwrap();
@@ -1431,7 +1433,7 @@ fn write_copilot_team(ws: &Path, name: &str, tools: &[&str], dangerous: bool) ->
1431
1433
  std::fs::write(
1432
1434
  team.join("agents/worker_a.md"),
1433
1435
  format!(
1434
- "---\nname: worker_a\nrole: Copilot Worker\nprovider: copilot\nmodel: gpt-5\nauth_mode: subscription\ntools:\n{tool_lines}---\n\nCOPILOT ROLE BODY SENTINEL: serve the copilot adapter contract.\n"
1436
+ "---\nname: worker_a\nrole: Copilot Worker\nprovider: copilot\nmodel: gpt-5\nauth_mode: subscription\ndangerously_skip_permissions: {role_bypass}\ntools:\n{tool_lines}---\n\nCOPILOT ROLE BODY SENTINEL: serve the copilot adapter contract.\n"
1435
1437
  ),
1436
1438
  )
1437
1439
  .unwrap();
@@ -1010,24 +1010,6 @@ fn quick_start_empty_dir_returns_typed_report_not_error_path_only() {
1010
1010
  }
1011
1011
  }
1012
1012
 
1013
- // ───────────────────────────────────────────────────────────────────────
1014
- // detect_dangerous_approval — launch/config.py
1015
- // 默认进程(无 --dangerously-* 祖先)→ enabled=false / source=Disabled / inherited=false。
1016
- // launch 在 inherited=false 且无 --yes 时 raise DangerousApprovalRequired(core.py:120)。
1017
- // ───────────────────────────────────────────────────────────────────────
1018
-
1019
- #[test]
1020
- #[serial_test::serial(env)]
1021
- fn detect_dangerous_approval_clean_process_is_disabled() {
1022
- // Explicit mock ancestry keeps the test independent from the real Codex/CI
1023
- // process tree that runs cargo.
1024
- let _ancestry = EnvVarGuard::set("TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON", "[]");
1025
- let got = detect_dangerous_approval().expect("探测祖先链应 Ok");
1026
- assert!(!got.enabled, "干净进程不应启用危险审批");
1027
- assert_eq!(got.source, DangerousApprovalSource::Disabled);
1028
- assert!(!got.inherited);
1029
- }
1030
-
1031
1013
  #[test]
1032
1014
  #[serial_test::serial(env)]
1033
1015
  fn leader_pane_env_absent_pane_fails_fast() {
@@ -746,7 +746,7 @@ fn write_team(
746
746
  std::fs::write(
747
747
  team.join("agents").join(format!("{}.md", agent.id)),
748
748
  format!(
749
- "---\nname: {}\nrole: {}\nprovider: {}\nmodel: fake\nauth_mode: subscription\ntools:\n{}---\n\n{}\n",
749
+ "---\nname: {}\nrole: {}\nprovider: {}\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n{}---\n\n{}\n",
750
750
  agent.id, agent.role, agent.provider, tools, agent.body
751
751
  ),
752
752
  )
@@ -358,7 +358,7 @@ impl DisplayFixture {
358
358
  std::fs::write(
359
359
  team.join("agents").join(format!("{worker}.md")),
360
360
  format!(
361
- "---\nname: {worker}\nrole: Adaptive fake worker {worker}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n"
361
+ "---\nname: {worker}\nrole: Adaptive fake worker {worker}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n"
362
362
  ),
363
363
  )
364
364
  .unwrap();
@@ -27,7 +27,7 @@ static SEQ: AtomicU32 = AtomicU32::new(0);
27
27
 
28
28
  const TEAM_MD: &str =
29
29
  "---\nname: f032team\nobjective: F032 lifecycle prompt fault probe.\nprovider: codex\n---\n\nF032.\n";
30
- const ROLE_MD: &str = "---\nname: implementer\nrole: Implementation Engineer\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nImplement.\n";
30
+ const ROLE_MD: &str = "---\nname: implementer\nrole: Implementation Engineer\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nImplement.\n";
31
31
 
32
32
  #[test]
33
33
  fn f032_launch_spawn_path_treats_startup_prompt_panic_as_best_effort() {
@@ -422,7 +422,10 @@ check behind it = false green); it must reflect an actual doctor result"
422
422
  /// T3-2 (claude_auth_hint parse failure): when `claude auth status` output is
423
423
  /// unparseable / unsuccessful, the hint must not default to Present (auth misreported
424
424
  /// as available). PATH-shims a `claude` that prints garbage and exits non-zero.
425
+ /// Mutates the process-global PATH/HOME — must not race host_cotenant's ps/tmux
426
+ /// shims (P0 pid-probe isolation), hence `#[serial(pid_probe)]`.
425
427
  #[test]
428
+ #[serial(pid_probe)]
426
429
  fn t3_claude_auth_hint_parse_failure_is_not_present() {
427
430
  let home = tmp_ws("t3-auth-home");
428
431
  let shim_dir = home.join("bin");
@@ -65,7 +65,7 @@ const POLLUTED_CALLER_AUTHORITY_ENVS: &[&str] = &[
65
65
  ];
66
66
 
67
67
  #[test]
68
- #[serial(env)]
68
+ #[serial(env, pid_probe)]
69
69
  fn polluted_caller_env_never_authorizes_teardown_to_kill_foreign_tmux() {
70
70
  let env = hermetic_guard::HermeticTestEnv::enter("p0-caller-env-sentinel");
71
71
  let shim_dir = env.root().join("bin");
@@ -150,7 +150,7 @@ exec "$TEAM_AGENT_TEST_REAL_TMUX" "$@"
150
150
  }
151
151
 
152
152
  #[test]
153
- #[serial(env)]
153
+ #[serial(env, pid_probe)]
154
154
  fn restart_allow_fresh_does_not_leave_same_role_host_cotenants() {
155
155
  let mut failures = Vec::new();
156
156
  let (env, workspace) = team_case("p0-restart", &["developer", "test-engineer", "arch-reviewer"]);
@@ -677,7 +677,7 @@ fn write_team_docs(workspace: &Path, workers: &[(&str, &str)]) {
677
677
  fs::create_dir_all(workspace.join("agents")).unwrap();
678
678
  fs::write(workspace.join("TEAM.md"), "---\nname: current\nobjective: P0 host co-tenant contract.\nprovider: codex\n---\n").unwrap();
679
679
  for (worker, model) in workers {
680
- let role = format!("---\nname: {worker}\nrole: {worker}\nprovider: codex\nmodel: {model}\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{worker}.\n");
680
+ let role = format!("---\nname: {worker}\nrole: {worker}\nprovider: codex\nmodel: {model}\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{worker}.\n");
681
681
  fs::write(workspace.join("agents").join(format!("{worker}.md")), role).unwrap();
682
682
  }
683
683
  }
@@ -3,7 +3,7 @@ use super::*;
3
3
 
4
4
  // ═════════════════════════════════════════════════════════════════════════
5
5
 
6
- const DELEG_ROLE_ALPHA_COMPAT: &str = "---\nname: alpha\nrole: Alpha Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: compatible_api\nprofile: alpha-compat\ntools:\n - mcp_team\n---\n\nAlpha.\n";
6
+ const DELEG_ROLE_ALPHA_COMPAT: &str = "---\nname: alpha\nrole: Alpha Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: compatible_api\ndangerously_skip_permissions: false\nprofile: alpha-compat\ntools:\n - mcp_team\n---\n\nAlpha.\n";
7
7
 
8
8
  type LaneKills = std::sync::Arc<std::sync::Mutex<Vec<String>>>;
9
9
  pub(super) type LaneSpawns = std::sync::Arc<std::sync::Mutex<Vec<(String, Vec<String>)>>>;
@@ -816,7 +816,7 @@ fn current_alias_authority_does_not_leak_into_real_sibling_same_id_retirement()
816
816
  );
817
817
  }
818
818
 
819
- const CHARLIE_ROLE: &str = "---\nname: charlie\nrole: Charlie Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nCharlie.\n";
819
+ const CHARLIE_ROLE: &str = "---\nname: charlie\nrole: Charlie Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nCharlie.\n";
820
820
 
821
821
  struct HermeticTestEnv {
822
822
  root: std::path::PathBuf,