@team-agent/installer 0.5.65 → 0.5.68

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 (208) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +6 -0
  4. package/crates/team-agent/src/cli/adapters.rs +6 -1
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/compile.rs +1 -1
  17. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  18. package/crates/team-agent/src/cli/tests/mod.rs +2 -2
  19. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
  20. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  21. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  22. package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
  23. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  24. package/crates/team-agent/src/compiler/tests.rs +23 -20
  25. package/crates/team-agent/src/compiler.rs +84 -5
  26. package/crates/team-agent/src/conpty/backend.rs +16 -0
  27. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  28. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  29. package/crates/team-agent/src/coordinator/health.rs +173 -0
  30. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  31. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  32. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  33. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  34. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  35. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  36. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  37. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  38. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  39. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  40. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  41. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  42. package/crates/team-agent/src/coordinator/types.rs +49 -0
  43. package/crates/team-agent/src/db/message_store.rs +39 -5
  44. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  45. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  46. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  47. package/crates/team-agent/src/leader/registry.rs +65 -0
  48. package/crates/team-agent/src/leader/start.rs +1080 -58
  49. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
  50. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  51. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  52. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -14
  53. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
  54. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
  55. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  56. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  57. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  58. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -457
  59. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  60. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  61. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  62. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  63. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  64. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  65. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  66. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  67. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  68. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  69. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  70. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  71. package/crates/team-agent/src/lifecycle/launch/spawn.rs +101 -4
  72. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
  73. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
  74. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  75. package/crates/team-agent/src/lifecycle/launch.rs +209 -43
  76. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  77. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  78. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  79. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  80. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  81. package/crates/team-agent/src/lifecycle/restart/agent.rs +213 -3
  82. package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
  83. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  84. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  85. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -15
  86. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  87. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  88. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  89. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  90. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
  91. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
  92. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
  93. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
  95. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  96. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +159 -174
  97. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
  99. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
  100. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  102. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  103. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  104. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
  105. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
  106. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  107. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  108. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  109. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  110. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  111. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  112. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
  113. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
  114. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
  115. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
  116. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
  117. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  118. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +24 -23
  119. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
  120. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
  121. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
  122. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
  123. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
  124. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
  125. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
  126. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
  127. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
  128. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
  129. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
  130. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
  131. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
  132. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
  133. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
  134. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
  135. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
  136. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
  137. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
  138. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  139. package/crates/team-agent/src/lifecycle/tests.rs +23 -15
  140. package/crates/team-agent/src/lifecycle/types.rs +61 -2
  141. package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
  142. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  143. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  144. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  145. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  146. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  147. package/crates/team-agent/src/messaging/results.rs +4 -0
  148. package/crates/team-agent/src/messaging/send.rs +34 -0
  149. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  150. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  151. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  152. package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
  153. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  154. package/crates/team-agent/src/messaging/types.rs +4 -1
  155. package/crates/team-agent/src/model/enums.rs +14 -5
  156. package/crates/team-agent/src/model/permissions.rs +3 -0
  157. package/crates/team-agent/src/model/spec.rs +129 -6
  158. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
  159. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
  160. package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
  161. package/crates/team-agent/src/os_probe.rs +73 -2
  162. package/crates/team-agent/src/provider/adapter.rs +231 -4
  163. package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
  164. package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
  165. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
  166. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  167. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  168. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  169. package/crates/team-agent/src/provider/bypass_flags.rs +124 -0
  170. package/crates/team-agent/src/provider/classify.rs +4 -2
  171. package/crates/team-agent/src/provider/faults.rs +2 -1
  172. package/crates/team-agent/src/provider/mod.rs +13 -0
  173. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  174. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  175. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  176. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  177. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  178. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  179. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  180. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  181. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  182. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  183. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  184. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  185. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  186. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  187. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  188. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  189. package/crates/team-agent/src/provider/types.rs +4 -2
  190. package/crates/team-agent/src/provider/wire.rs +23 -1
  191. package/crates/team-agent/src/state/persist.rs +301 -8
  192. package/crates/team-agent/src/state/repository.rs +5 -0
  193. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  194. package/crates/team-agent/src/tmux_backend.rs +1083 -75
  195. package/crates/team-agent/src/transport/test_support.rs +20 -0
  196. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  197. package/crates/team-agent/src/transport.rs +265 -1
  198. package/npm/install.mjs +129 -73
  199. package/package.json +4 -4
  200. package/skills/team-agent/SKILL.md +33 -238
  201. package/skills/team-agent/command-coverage.json +31 -0
  202. package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
  203. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  204. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
  205. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  206. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
  207. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
  208. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
@@ -102,18 +102,10 @@ fn phase_b_golden_events_state_status_zero_drift() {
102
102
  team_key: "teamdir",
103
103
  lifecycle_op: phase_b_reset_discard_session,
104
104
  });
105
- if std::env::var_os("TEAM_AGENT_UPDATE_GOLDEN").is_some() {
106
- std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
107
- std::fs::write(&baseline, pretty(&actual)).unwrap();
108
- return;
109
- }
110
- let expected = std::fs::read_to_string(&baseline)
111
- .unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
112
- let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
105
+ let expected = load_phase_golden(&baseline);
113
106
  assert_eq!(
114
- actual,
115
- expected,
116
- "phase B golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
107
+ actual, expected,
108
+ "phase B golden drift; surgical key edits only — do not regenerate the file"
117
109
  );
118
110
  }
119
111
 
@@ -126,18 +118,10 @@ fn phase_c_golden_events_state_status_zero_drift() {
126
118
  team_key: "teamdir",
127
119
  lifecycle_op: phase_b_reset_discard_session,
128
120
  });
129
- if std::env::var_os("TEAM_AGENT_UPDATE_GOLDEN").is_some() {
130
- std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
131
- std::fs::write(&baseline, pretty(&actual)).unwrap();
132
- return;
133
- }
134
- let expected = std::fs::read_to_string(&baseline)
135
- .unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
136
- let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
121
+ let expected = load_phase_golden(&baseline);
137
122
  assert_eq!(
138
- actual,
139
- expected,
140
- "phase C golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
123
+ actual, expected,
124
+ "phase C golden drift; surgical key edits only — do not regenerate the file"
141
125
  );
142
126
  }
143
127
 
@@ -150,18 +134,10 @@ fn phase_d_golden_events_state_status_zero_drift() {
150
134
  team_key: "teamdir",
151
135
  lifecycle_op: phase_b_reset_discard_session,
152
136
  });
153
- if std::env::var_os("TEAM_AGENT_UPDATE_GOLDEN").is_some() {
154
- std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
155
- std::fs::write(&baseline, pretty(&actual)).unwrap();
156
- return;
157
- }
158
- let expected = std::fs::read_to_string(&baseline)
159
- .unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
160
- let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
137
+ let expected = load_phase_golden(&baseline);
161
138
  assert_eq!(
162
- actual,
163
- expected,
164
- "phase D golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
139
+ actual, expected,
140
+ "phase D golden drift; surgical key edits only — do not regenerate the file"
165
141
  );
166
142
  }
167
143
 
@@ -174,18 +150,10 @@ fn phase_e_golden_events_state_status_zero_drift() {
174
150
  team_key: "teamdir",
175
151
  lifecycle_op: phase_b_reset_discard_session,
176
152
  });
177
- if std::env::var_os("TEAM_AGENT_UPDATE_GOLDEN").is_some() {
178
- std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
179
- std::fs::write(&baseline, pretty(&actual)).unwrap();
180
- return;
181
- }
182
- let expected = std::fs::read_to_string(&baseline)
183
- .unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
184
- let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
153
+ let expected = load_phase_golden(&baseline);
185
154
  assert_eq!(
186
- actual,
187
- expected,
188
- "phase E golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
155
+ actual, expected,
156
+ "phase E golden drift; surgical key edits only — do not regenerate the file"
189
157
  );
190
158
  }
191
159
 
@@ -198,18 +166,39 @@ fn phase_f_golden_events_state_status_zero_drift() {
198
166
  team_key: "teamdir",
199
167
  lifecycle_op: phase_b_reset_discard_session,
200
168
  });
201
- if std::env::var_os("TEAM_AGENT_UPDATE_GOLDEN").is_some() {
202
- std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
203
- std::fs::write(&baseline, pretty(&actual)).unwrap();
204
- return;
205
- }
206
- let expected = std::fs::read_to_string(&baseline)
207
- .unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
208
- let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
169
+ let expected = load_phase_golden(&baseline);
209
170
  assert_eq!(
210
- actual,
211
- expected,
212
- "phase F golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
171
+ actual, expected,
172
+ "phase F golden drift; surgical key edits only — do not regenerate the file"
173
+ );
174
+ }
175
+
176
+ #[test]
177
+ #[serial_test::serial(env)]
178
+ fn phase_golden_unbound_not_ready_requires_claim_leader() {
179
+ // 已废除的行为:旧实现只说不 ready、不说怎么恢复,把人晾在原地。此断言证明它确实没了。
180
+ // Independent of golden.json so a later regenerate cannot erase it.
181
+ // Companion status keys grok_slot ← 77bddb95 / 0c319cca; next_action ← 080903a2.
182
+ let compact = run_compact_status_after_quick_start();
183
+ let not_ready = compact
184
+ .get("not_ready")
185
+ .and_then(Value::as_object)
186
+ .unwrap_or_else(|| panic!("compact must carry not_ready; got {compact}"));
187
+ let reasons = not_ready
188
+ .get("reasons")
189
+ .and_then(Value::as_array)
190
+ .cloned()
191
+ .unwrap_or_default();
192
+ assert!(
193
+ reasons
194
+ .iter()
195
+ .any(|reason| reason.as_str() == Some("leader_receiver_unbound")),
196
+ "tombstone fixture must still produce leader_receiver_unbound; not_ready={not_ready:?}"
197
+ );
198
+ assert_eq!(
199
+ not_ready.get("next_action").and_then(Value::as_str),
200
+ Some("claim-leader"),
201
+ "when not_ready.reasons contains leader_receiver_unbound, next_action must be claim-leader; not_ready={not_ready:?}"
213
202
  );
214
203
  }
215
204
 
@@ -387,7 +376,7 @@ fn codex_ready_transport() -> OfflineTransport {
387
376
 
388
377
  fn role_doc(id: &str) -> String {
389
378
  format!(
390
- "---\nname: {id}\nrole: {id} Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{id} worker.\n"
379
+ "---\nname: {id}\nrole: {id} Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{id} worker.\n"
391
380
  )
392
381
  }
393
382
 
@@ -466,6 +455,59 @@ fn phase_fixture_path(phase: &str) -> PathBuf {
466
455
  .join(phase)
467
456
  }
468
457
 
458
+ fn load_phase_golden(path: &Path) -> Value {
459
+ let text = std::fs::read_to_string(path)
460
+ .unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", path.display()));
461
+ let stripped = strip_full_line_json_comments(&text);
462
+ serde_json::from_str(&stripped).unwrap_or_else(|e| {
463
+ panic!(
464
+ "parse golden baseline {} after stripping // review comments: {e}",
465
+ path.display()
466
+ )
467
+ })
468
+ }
469
+
470
+ fn strip_full_line_json_comments(text: &str) -> String {
471
+ text.lines()
472
+ .filter(|line| !line.trim_start().starts_with("//"))
473
+ .collect::<Vec<_>>()
474
+ .join("\n")
475
+ }
476
+
477
+ fn run_compact_status_after_quick_start() -> Value {
478
+ let hermetic = HermeticTestEnv::enter("tombstone-claim-leader");
479
+ let _permission_mode = EnvVarGuard::set(ANCESTRY_ENV, "[]");
480
+ let team = two_worker_team_dir(&hermetic);
481
+ let workspace = team.parent().expect("workspace").to_path_buf();
482
+ seed_healthy_coordinator(&workspace);
483
+ let launch_transport = codex_ready_transport();
484
+ quick_start_with_transport_in_workspace_with_display(
485
+ &workspace,
486
+ &team,
487
+ None,
488
+ true,
489
+ None,
490
+ &launch_transport,
491
+ false,
492
+ )
493
+ .expect("tombstone fixture must quick-start");
494
+ let status = cmd_status(&StatusArgs {
495
+ agent: None,
496
+ workspace,
497
+ detail: false,
498
+ summary: false,
499
+ json: true,
500
+ team: Some("teamdir".to_string()),
501
+ });
502
+ match cmd_value(status) {
503
+ Value::Object(map) => map
504
+ .get("output")
505
+ .cloned()
506
+ .unwrap_or_else(|| panic!("compact status missing output")),
507
+ other => panic!("compact status must be a JSON object, got {other}"),
508
+ }
509
+ }
510
+
469
511
  struct EnvVarGuard {
470
512
  key: &'static str,
471
513
  previous: Option<String>,
@@ -843,12 +885,6 @@ fn key_is_id(key: &str, text: &str) -> bool {
843
885
  || text.starts_with("msg_")
844
886
  }
845
887
 
846
- fn pretty(value: &Value) -> String {
847
- let mut text = serde_json::to_string_pretty(value).unwrap();
848
- text.push('\n');
849
- text
850
- }
851
-
852
888
  #[test]
853
889
  fn phase_golden_normalizer_maps_custom_cargo_target_deps_binary_before_tmp_aliasing() {
854
890
  let ctx = NormalizeCtx {
@@ -38,9 +38,9 @@ use hermetic::HermeticTestEnv;
38
38
  const TEAM_MD: &str =
39
39
  "---\nname: bug7\nobjective: BUG-7 worker readiness honesty contract.\nprovider: codex\n---\n\nTeam.\n";
40
40
 
41
- const CODEX_ROLE: &str = "---\nname: codexer\nrole: Codex Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nCodex worker.\n";
41
+ const CODEX_ROLE: &str = "---\nname: codexer\nrole: Codex Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nCodex worker.\n";
42
42
 
43
- const CLAUDE_ROLE: &str = "---\nname: clauder\nrole: Claude Worker\nprovider: claude\nmodel: claude-sonnet-4-6\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nClaude worker.\n";
43
+ const CLAUDE_ROLE: &str = "---\nname: clauder\nrole: Claude Worker\nprovider: claude\nmodel: claude-sonnet-4-6\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nClaude worker.\n";
44
44
 
45
45
  #[test]
46
46
  #[ignore = "real-machine: quick-start worker readiness gate"]
@@ -218,7 +218,7 @@ fn write_team_docs(team_dir: &Path, workers: &[String], provider: ProviderShape)
218
218
  fs::write(
219
219
  team_dir.join("agents").join(format!("{worker}.md")),
220
220
  format!(
221
- "---\nname: {worker}\nrole: Worker {worker}\nprovider: {}\nmodel: {}\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{worker}.\n",
221
+ "---\nname: {worker}\nrole: Worker {worker}\nprovider: {}\nmodel: {}\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{worker}.\n",
222
222
  provider.provider(),
223
223
  provider.model()
224
224
  ),
@@ -492,7 +492,7 @@ impl RestartFixture {
492
492
  std::fs::write(
493
493
  team.join("agents").join(format!("{agent}.md")),
494
494
  format!(
495
- "---\nname: {agent}\nrole: Worker {agent}\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n"
495
+ "---\nname: {agent}\nrole: Worker {agent}\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n"
496
496
  ),
497
497
  )
498
498
  .unwrap();
@@ -342,7 +342,7 @@ impl HotfixCase {
342
342
  std::fs::write(
343
343
  dir.join("agents").join(format!("{agent_id}.md")),
344
344
  format!(
345
- "---\nname: {agent_id}\nrole: Greeter\nprovider: {}\nmodel: test\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n",
345
+ "---\nname: {agent_id}\nrole: Greeter\nprovider: {}\nmodel: test\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n",
346
346
  provider_name(provider)
347
347
  ),
348
348
  )
@@ -1066,6 +1066,8 @@ fn provider_name(provider: Provider) -> &'static str {
1066
1066
  Provider::Codex => "codex",
1067
1067
  Provider::Copilot => "copilot",
1068
1068
  Provider::GeminiCli => "gemini",
1069
+ Provider::Grok => "grok",
1070
+ Provider::CursorAgent => "cursor_agent",
1069
1071
  Provider::Fake => "fake",
1070
1072
  }
1071
1073
  }
@@ -1371,7 +1371,7 @@ impl RestartFixture {
1371
1371
  std::fs::write(
1372
1372
  team.join("agents/worker_a.md"),
1373
1373
  format!(
1374
- "---\nname: worker_a\nrole: Worker\nprovider: {provider}\nmodel: {}\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n",
1374
+ "---\nname: worker_a\nrole: Worker\nprovider: {provider}\nmodel: {}\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n",
1375
1375
  if provider == "claude" {
1376
1376
  "claude-sonnet-4-6"
1377
1377
  } else {
@@ -1409,7 +1409,7 @@ impl RestartFixture {
1409
1409
  std::fs::write(
1410
1410
  team.join("agents/worker_a.md"),
1411
1411
  format!(
1412
- "---\nname: worker_a\nrole: Worker\nprovider: {provider}\nmodel: {}\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n",
1412
+ "---\nname: worker_a\nrole: Worker\nprovider: {provider}\nmodel: {}\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n",
1413
1413
  if provider == "claude" {
1414
1414
  "claude-sonnet-4-6"
1415
1415
  } else {
@@ -151,7 +151,7 @@ impl ResumeFixture {
151
151
  .unwrap();
152
152
  std::fs::write(
153
153
  team.join("agents").join("worker_a.md"),
154
- "---\nname: worker_a\nrole: Worker A\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n",
154
+ "---\nname: worker_a\nrole: Worker A\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n",
155
155
  )
156
156
  .unwrap();
157
157
  let spec = team_agent::compiler::compile_team(&team).unwrap();
@@ -291,7 +291,7 @@ fn agent(id: &str, status: &str, topology: bool) -> Value {
291
291
  }
292
292
 
293
293
  fn role_doc(id: &str) -> String {
294
- format!("---\nname: {id}\nrole: {id}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{id}.\n")
294
+ format!("---\nname: {id}\nrole: {id}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{id}.\n")
295
295
  }
296
296
 
297
297
  fn assert_no_standards(case: &Case) {
@@ -1,3 +1,18 @@
1
+ //! ---
2
+ //! purpose: restart spawn overlap and identity contracts
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: restart_spawn_overlap_identity
6
+ //! what: controlled transport facts prove overlapping worker spawns and stable persisted identities
7
+ //! depends:
8
+ //! - crate::lifecycle::restart
9
+ //! - hermetic fake transport with spawn trace
10
+ //! boundary:
11
+ //! - overlap/max-in-flight facts replace scheduler-sensitive total-time thresholds
12
+ //! - no production scheduler, pool, or state-machine changes
13
+ //! maturity: wired
14
+ //! ---
15
+ //!
1
16
  //! 0.5.38 RED contract: startup latency instrumentation and bounded worker spawn.
2
17
  //!
3
18
  //! References:
@@ -83,6 +98,10 @@ fn restart_records_parallel_spawn_overlap_and_deterministic_state() {
83
98
  "expected at least two new-window spawns to overlap after the initial session creator; calls={calls:?}"
84
99
  ));
85
100
  }
101
+ assert!(
102
+ transport.max_in_flight() >= 2,
103
+ "expected at least two concurrent spawn calls; calls={calls:?}"
104
+ );
86
105
  let agent_keys = state
87
106
  .pointer("/agents")
88
107
  .and_then(Value::as_object)
@@ -272,7 +291,6 @@ fn eight_worker_restart_with_100ms_spawn_delay_beats_serial_baseline() {
272
291
  let delay = Duration::from_millis(100);
273
292
  let transport = StartupLatencyTransport::new().with_spawn_delay(delay);
274
293
 
275
- let started = Instant::now();
276
294
  let report = restart_with_transport_with_readiness_deadline(
277
295
  &case.workspace,
278
296
  true,
@@ -281,7 +299,6 @@ fn eight_worker_restart_with_100ms_spawn_delay_beats_serial_baseline() {
281
299
  Some(2_000),
282
300
  )
283
301
  .expect("R4 setup: restart should complete against delayed transport");
284
- let elapsed = started.elapsed();
285
302
  assert!(
286
303
  matches!(report, RestartReport::Restarted { .. }),
287
304
  "R4 setup: restart must reach Restarted; report={report:?}"
@@ -293,14 +310,18 @@ fn eight_worker_restart_with_100ms_spawn_delay_beats_serial_baseline() {
293
310
  8,
294
311
  "R4 setup: timing smoke must exercise all 8 workers; calls={calls:?}"
295
312
  );
296
- let serial_baseline = delay * calls.len() as u32;
297
313
  assert!(
298
- serial_baseline >= Duration::from_millis(800),
299
- "R4 setup: serial baseline must represent 8 workers * 100ms; baseline={serial_baseline:?}"
314
+ has_overlap(
315
+ &calls
316
+ .iter()
317
+ .filter(|call| call.kind == "spawn_into")
318
+ .collect::<Vec<_>>(),
319
+ ),
320
+ "R4: bounded-concurrency restart must overlap spawn calls; calls={calls:?}"
300
321
  );
301
322
  assert!(
302
- elapsed < serial_baseline - Duration::from_millis(150),
303
- "R4: bounded-concurrency restart should complete below the serial spawn sum; elapsed={elapsed:?} serial_baseline={serial_baseline:?} calls={calls:?}"
323
+ transport.max_in_flight() >= 2,
324
+ "R4: max in-flight spawn count must prove overlap; calls={calls:?}"
304
325
  );
305
326
  assert_phase_events(&case.events(), "restart.phase", &["spawn_all", "completed"]);
306
327
  }
@@ -488,7 +509,7 @@ fn write_team_docs(team_dir: &Path, workers: usize) {
488
509
 
489
510
  fn role_doc(worker: &str) -> String {
490
511
  format!(
491
- "---\nname: {worker}\nrole: Worker {worker}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{worker}.\n"
512
+ "---\nname: {worker}\nrole: Worker {worker}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{worker}.\n"
492
513
  )
493
514
  }
494
515
 
@@ -583,6 +604,8 @@ struct TransportState {
583
604
  calls: Vec<SpawnCall>,
584
605
  panes: BTreeMap<String, PaneInfo>,
585
606
  next_pane: usize,
607
+ in_flight: usize,
608
+ max_in_flight: usize,
586
609
  }
587
610
 
588
611
  #[derive(Clone)]
@@ -625,6 +648,10 @@ impl StartupLatencyTransport {
625
648
  self.state.lock().unwrap().calls.clone()
626
649
  }
627
650
 
651
+ fn max_in_flight(&self) -> usize {
652
+ self.state.lock().unwrap().max_in_flight
653
+ }
654
+
628
655
  fn pane_snapshot(&self) -> Vec<Value> {
629
656
  self.state
630
657
  .lock()
@@ -648,11 +675,17 @@ impl StartupLatencyTransport {
648
675
  window: &WindowName,
649
676
  ) -> Result<SpawnResult, TransportError> {
650
677
  let start = Instant::now();
678
+ {
679
+ let mut state = self.state.lock().unwrap();
680
+ state.in_flight += 1;
681
+ state.max_in_flight = state.max_in_flight.max(state.in_flight);
682
+ }
651
683
  if !self.spawn_delay.is_zero() {
652
684
  std::thread::sleep(self.spawn_delay);
653
685
  }
654
686
  let end = Instant::now();
655
687
  let mut state = self.state.lock().unwrap();
688
+ state.in_flight -= 1;
656
689
  let pane_id = PaneId::new(format!("%{}", state.next_pane));
657
690
  state.next_pane += 1;
658
691
  state.calls.push(SpawnCall {
@@ -368,7 +368,7 @@ fn red6_cli_restart_reports_and_top_level_error_logs_are_redacted() {
368
368
  }
369
369
 
370
370
  #[test]
371
- #[serial(env)]
371
+ #[serial(env, pid_probe)]
372
372
  fn red7_pane_and_profile_scrubbers_converge_without_losing_existing_policy() {
373
373
  let root = Path::new(env!("CARGO_MANIFEST_DIR"));
374
374
  let tmux = fs::read_to_string(root.join("src/tmux_backend.rs")).unwrap();
@@ -685,12 +685,12 @@ fn write_profile_fixture(root: &Path, base_url: &str, token: &str) -> PathBuf {
685
685
  .unwrap();
686
686
  fs::write(
687
687
  team.join("TEAM.md"),
688
- "---\nname: redact-profile\nobjective: Synthetic redaction profile contract.\nprovider: claude\nauth_mode: compatible_api\n---\n\nTeam.\n",
688
+ "---\nname: redact-profile\nobjective: Synthetic redaction profile contract.\nprovider: claude\nauth_mode: compatible_api\ndangerously_skip_permissions: false\n---\n\nTeam.\n",
689
689
  )
690
690
  .unwrap();
691
691
  fs::write(
692
692
  team.join("agents/worker.md"),
693
- "---\nname: worker\nrole: Worker\nprovider: claude\nauth_mode: compatible_api\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n",
693
+ "---\nname: worker\nrole: Worker\nprovider: claude\nauth_mode: compatible_api\ndangerously_skip_permissions: false\nprofile: local\nmodel: null\ntools:\n - mcp_team\n---\n\nWorker.\n",
694
694
  )
695
695
  .unwrap();
696
696
  team
@@ -861,7 +861,7 @@ fn contract_c_lifecycle_launch_command_carries_role_tools_and_real_mcp_context()
861
861
  .unwrap();
862
862
  std::fs::write(
863
863
  teamdir.join("agents").join("w1.md"),
864
- "---\nname: w1\nrole: Worker must call report_result exactly once.\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nAlways use report_result for completion.\n",
864
+ "---\nname: w1\nrole: Worker must call report_result exactly once.\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nAlways use report_result for completion.\n",
865
865
  )
866
866
  .unwrap();
867
867
  let spec = team_agent::compiler::compile_team(&teamdir).unwrap();
@@ -172,7 +172,7 @@ fn write_claude_compatible_team(ws: &Path) -> PathBuf {
172
172
  .unwrap();
173
173
  std::fs::write(
174
174
  team.join("agents/worker_a.md"),
175
- "---\nname: worker_a\nrole: Compat Worker\nprovider: claude\nmodel: claude-haiku\nauth_mode: compatible_api\nprofile: relay\ntools:\n - mcp_team\n---\n\nCompat worker.\n",
175
+ "---\nname: worker_a\nrole: Compat Worker\nprovider: claude\nmodel: claude-haiku\nauth_mode: compatible_api\ndangerously_skip_permissions: false\nprofile: relay\ntools:\n - mcp_team\n---\n\nCompat worker.\n",
176
176
  )
177
177
  .unwrap();
178
178
  std::fs::write(
@@ -244,7 +244,7 @@ fn write_team_dir(root: &Path, dir_name: &str, agent: &str) -> PathBuf {
244
244
 
245
245
  fn role_doc(agent: &str) -> String {
246
246
  format!(
247
- "---\nname: {agent}\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n"
247
+ "---\nname: {agent}\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n"
248
248
  )
249
249
  }
250
250
 
@@ -881,7 +881,7 @@ fn team_dir_with_name(root: &Path, dir_name: &str, spec_name: &str, agent_id: &s
881
881
 
882
882
  fn role_doc(name: &str) -> String {
883
883
  format!(
884
- "---\nname: {name}\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n"
884
+ "---\nname: {name}\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n"
885
885
  )
886
886
  }
887
887
 
@@ -1057,7 +1057,7 @@ fn team_dir(root: &Path, name: &str, agents: &[(&str, &str)]) -> PathBuf {
1057
1057
 
1058
1058
  fn role_doc(name: &str, role: &str) -> String {
1059
1059
  format!(
1060
- "---\nname: {name}\nrole: {role}\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{role}.\n"
1060
+ "---\nname: {name}\nrole: {role}\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{role}.\n"
1061
1061
  )
1062
1062
  }
1063
1063
 
@@ -390,7 +390,7 @@ impl RollbackFixture {
390
390
 
391
391
  fn agent_doc(name: &str, role: &str) -> String {
392
392
  format!(
393
- "---\nname: {name}\nrole: {role}\nprovider: codex\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{role}.\n"
393
+ "---\nname: {name}\nrole: {role}\nprovider: codex\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{role}.\n"
394
394
  )
395
395
  }
396
396
 
@@ -1,3 +1,7 @@
1
+ //! purpose: contracts that tests touching product side effects stay inside one hermetic HOME/registry/socket/env boundary
2
+ //! contract: R1-R6 source guards; R6 needles match as identifiers, not as substrings of longer names
3
+ //! boundary: does not own HermeticTestEnv; does not drop or whitelist R6 needles
4
+ //!
1
5
  //! task#8 RED contract: tests that exercise Team Agent product side effects
2
6
  //! must run inside one HOME/registry/socket/env hermetic boundary.
3
7
  //!
@@ -233,6 +237,40 @@ fn r6_static_guard_rejects_dangerous_tests_without_hermetic_boundary() {
233
237
  );
234
238
  }
235
239
 
240
+ #[test]
241
+ fn r6_mcp_tool_name_literal_is_not_message_plane_send() {
242
+ let source = r#"
243
+ assert!(prompt.contains("mcp__team_orchestrator__send_message"));
244
+ assert!(prompt.contains("team_orchestrator__send_message"));
245
+ "#;
246
+ assert!(
247
+ static_guard_offenders([(
248
+ "synthetic_mcp_tool_name_literal.rs".to_string(),
249
+ source.to_string()
250
+ )])
251
+ .is_empty(),
252
+ "MCP tool-name literals are not the send_message surface"
253
+ );
254
+ }
255
+
256
+ #[test]
257
+ fn r6_bare_send_message_identifier_without_hermetic_is_still_offender() {
258
+ let source = r#"
259
+ fn tap() {
260
+ let _ = send_message("to", "body");
261
+ }
262
+ "#;
263
+ let offenders = static_guard_offenders([(
264
+ "synthetic_bare_send_message.rs".to_string(),
265
+ source.to_string(),
266
+ )]);
267
+ assert!(
268
+ offenders.iter().any(|offender| offender.path == "synthetic_bare_send_message.rs"
269
+ && offender.signals.contains(&"send_message")),
270
+ "bare send_message identifier without HermeticTestEnv must remain an offender; offenders={offenders:?}"
271
+ );
272
+ }
273
+
236
274
  fn assert_contains_all(label: &str, source: &str, needles: &[&str], message: &str) {
237
275
  let missing = needles
238
276
  .iter()
@@ -315,10 +353,30 @@ fn dangerous_signals(source: &str) -> Vec<&'static str> {
315
353
  "attach-leader",
316
354
  ]
317
355
  .into_iter()
318
- .filter(|needle| source.contains(needle))
356
+ .filter(|needle| contains_identifier(source, needle))
319
357
  .collect()
320
358
  }
321
359
 
360
+ fn contains_identifier(source: &str, ident: &str) -> bool {
361
+ let bytes = source.as_bytes();
362
+ let mut start = 0;
363
+ while let Some(rel) = source[start..].find(ident) {
364
+ let idx = start + rel;
365
+ let before_ok = idx == 0 || !is_ident_char(bytes[idx - 1]);
366
+ let after = idx + ident.len();
367
+ let after_ok = after == bytes.len() || !is_ident_char(bytes[after]);
368
+ if before_ok && after_ok {
369
+ return true;
370
+ }
371
+ start = idx + 1;
372
+ }
373
+ false
374
+ }
375
+
376
+ fn is_ident_char(b: u8) -> bool {
377
+ matches!(b, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_')
378
+ }
379
+
322
380
  fn dangerous_test_files() -> Vec<(String, String)> {
323
381
  let root = repo_root();
324
382
  let mut files = Vec::new();
@@ -393,7 +451,7 @@ impl RestartAutoAttachCase {
393
451
  std::fs::write(
394
452
  workspace.join("agents").join(format!("{WORKER}.md")),
395
453
  format!(
396
- "---\nname: {WORKER}\nrole: Worker {WORKER}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n"
454
+ "---\nname: {WORKER}\nrole: Worker {WORKER}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n"
397
455
  ),
398
456
  )
399
457
  .expect("write worker role");
@@ -456,7 +456,7 @@ impl UpgradeFixture {
456
456
  std::fs::create_dir_all(self.team_dir.join("agents")).unwrap();
457
457
  std::fs::write(
458
458
  self.team_dir.join("agents").join("upgrade_worker.md"),
459
- "---\nname: upgrade_worker\nrole: Upgrade Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n",
459
+ "---\nname: upgrade_worker\nrole: Upgrade Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n",
460
460
  )
461
461
  .unwrap();
462
462
  let spec = team_agent::compiler::compile_team(&self.team_dir).unwrap();
@@ -39,9 +39,9 @@ use hermetic::HermeticTestEnv;
39
39
  const TEAM_MD: &str =
40
40
  "---\nname: verify031\nobjective: Verify mixed provider windows.\nprovider: codex\n---\n\nTeam.\n";
41
41
 
42
- const CODEX_ROLE: &str = "---\nname: codexer\nrole: Codex Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nCodex worker.\n";
42
+ const CODEX_ROLE: &str = "---\nname: codexer\nrole: Codex Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nCodex worker.\n";
43
43
 
44
- const CLAUDE_ROLE: &str = "---\nname: clauder\nrole: Claude Worker\nprovider: claude\nmodel: claude-sonnet-4-6\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nClaude worker.\n";
44
+ const CLAUDE_ROLE: &str = "---\nname: clauder\nrole: Claude Worker\nprovider: claude\nmodel: claude-sonnet-4-6\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nClaude worker.\n";
45
45
 
46
46
  #[test]
47
47
  #[serial(env)]