@team-agent/installer 0.5.61 → 0.5.63

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 (250) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +5 -2
  4. package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +1 -0
  6. package/crates/team-agent/src/cli/emit.rs +41 -7
  7. package/crates/team-agent/src/cli/helpers.rs +1 -0
  8. package/crates/team-agent/src/cli/leader.rs +6 -1
  9. package/crates/team-agent/src/cli/leaders.rs +2 -0
  10. package/crates/team-agent/src/cli/mod.rs +33 -3
  11. package/crates/team-agent/src/cli/named_address.rs +8 -1
  12. package/crates/team-agent/src/cli/profile.rs +2 -1
  13. package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
  14. package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
  15. package/crates/team-agent/src/cli/send/persist.rs +1 -0
  16. package/crates/team-agent/src/cli/send/presentation.rs +1 -0
  17. package/crates/team-agent/src/cli/send/resolve.rs +206 -1
  18. package/crates/team-agent/src/cli/send.rs +2 -1
  19. package/crates/team-agent/src/cli/spec.rs +1 -0
  20. package/crates/team-agent/src/cli/status.rs +2 -1
  21. package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
  22. package/crates/team-agent/src/cli/status_port/format.rs +1 -0
  23. package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
  24. package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
  25. package/crates/team-agent/src/cli/types.rs +3 -2
  26. package/crates/team-agent/src/communication_mode/mod.rs +1 -0
  27. package/crates/team-agent/src/compiler.rs +1 -0
  28. package/crates/team-agent/src/conpty/backend.rs +1 -0
  29. package/crates/team-agent/src/conpty/mod.rs +1 -0
  30. package/crates/team-agent/src/coordinator/backoff.rs +5 -8
  31. package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
  32. package/crates/team-agent/src/coordinator/health.rs +1 -0
  33. package/crates/team-agent/src/coordinator/mod.rs +2 -2
  34. package/crates/team-agent/src/coordinator/orphan.rs +4 -116
  35. package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
  36. package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
  37. package/crates/team-agent/src/coordinator/steps/abnormal.rs +15 -866
  38. package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
  39. package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
  40. package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
  41. package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
  42. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
  43. package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
  44. package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
  45. package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +139 -375
  46. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
  47. package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -49
  49. package/crates/team-agent/src/coordinator/tick.rs +23 -40
  50. package/crates/team-agent/src/coordinator/types.rs +3 -176
  51. package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
  52. package/crates/team-agent/src/db/message_store.rs +2 -1
  53. package/crates/team-agent/src/db/migration.rs +5 -4
  54. package/crates/team-agent/src/db/mod.rs +1 -0
  55. package/crates/team-agent/src/db/schema.rs +1 -0
  56. package/crates/team-agent/src/diagnose/comms.rs +2 -1
  57. package/crates/team-agent/src/diagnose/mod.rs +1 -0
  58. package/crates/team-agent/src/diagnose/orphans.rs +1 -0
  59. package/crates/team-agent/src/layout/manager.rs +3 -7
  60. package/crates/team-agent/src/layout/mod.rs +2 -2
  61. package/crates/team-agent/src/layout/overlay.rs +6 -1
  62. package/crates/team-agent/src/layout/placement.rs +1 -0
  63. package/crates/team-agent/src/layout/recovery.rs +3 -0
  64. package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
  65. package/crates/team-agent/src/layout/sessions.rs +8 -1
  66. package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
  67. package/crates/team-agent/src/layout/worker_env.rs +3 -0
  68. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  69. package/crates/team-agent/src/leader/helpers.rs +1 -0
  70. package/crates/team-agent/src/leader/incident.rs +1 -0
  71. package/crates/team-agent/src/leader/lease.rs +27 -22
  72. package/crates/team-agent/src/leader/mod.rs +2 -3
  73. package/crates/team-agent/src/leader/owner_bind.rs +13 -3
  74. package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
  75. package/crates/team-agent/src/leader/rediscover.rs +4 -17
  76. package/crates/team-agent/src/leader/registry.rs +3 -15
  77. package/crates/team-agent/src/leader/start.rs +373 -125
  78. package/crates/team-agent/src/leader/takeover.rs +10 -232
  79. package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
  80. package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
  81. package/crates/team-agent/src/leader/tests/identity.rs +7 -2
  82. package/crates/team-agent/src/leader/tests/idle.rs +0 -193
  83. package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
  84. package/crates/team-agent/src/leader/tests/mod.rs +4 -41
  85. package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
  86. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
  87. package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
  88. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
  89. package/crates/team-agent/src/leader/types.rs +1 -28
  90. package/crates/team-agent/src/lib.rs +3 -0
  91. package/crates/team-agent/src/lifecycle/display.rs +7 -1
  92. package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
  93. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
  95. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
  96. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
  97. package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
  99. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
  100. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -27
  101. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
  102. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
  103. package/crates/team-agent/src/lifecycle/launch.rs +11 -12
  104. package/crates/team-agent/src/lifecycle/mod.rs +2 -1
  105. package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -66
  106. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
  107. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
  108. package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
  109. package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
  110. package/crates/team-agent/src/lifecycle/restart.rs +13 -15
  111. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
  112. package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
  113. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
  114. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
  115. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
  116. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
  117. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
  118. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
  119. package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
  120. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
  121. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
  122. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
  123. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
  124. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
  125. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
  126. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
  127. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
  128. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
  129. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
  130. package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
  131. package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
  132. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
  133. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
  134. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
  135. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
  136. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
  137. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
  138. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
  139. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
  140. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
  141. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
  142. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
  143. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
  144. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
  145. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
  146. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
  147. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
  148. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
  149. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
  150. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
  151. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
  152. package/crates/team-agent/src/lifecycle/tests.rs +59 -0
  153. package/crates/team-agent/src/lifecycle/types.rs +0 -9
  154. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  155. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
  156. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
  157. package/crates/team-agent/src/mcp_server/mod.rs +1 -0
  158. package/crates/team-agent/src/mcp_server/normalize.rs +4 -3
  159. package/crates/team-agent/src/mcp_server/tools.rs +1 -0
  160. package/crates/team-agent/src/mcp_server/types.rs +1 -18
  161. package/crates/team-agent/src/mcp_server/wire.rs +5 -5
  162. package/crates/team-agent/src/messaging/activity.rs +1 -0
  163. package/crates/team-agent/src/messaging/address.rs +1 -0
  164. package/crates/team-agent/src/messaging/delivery.rs +4 -3
  165. package/crates/team-agent/src/messaging/helpers.rs +2 -1
  166. package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
  167. package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
  168. package/crates/team-agent/src/messaging/mod.rs +10 -14
  169. package/crates/team-agent/src/messaging/peers.rs +2 -0
  170. package/crates/team-agent/src/messaging/persist.rs +3 -1
  171. package/crates/team-agent/src/messaging/presentation.rs +2 -1
  172. package/crates/team-agent/src/messaging/results.rs +1 -0
  173. package/crates/team-agent/src/messaging/scheduler.rs +1 -0
  174. package/crates/team-agent/src/messaging/selftest.rs +1 -1
  175. package/crates/team-agent/src/messaging/send.rs +41 -8
  176. package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
  177. package/crates/team-agent/src/messaging/types.rs +2 -2
  178. package/crates/team-agent/src/messaging/watchers.rs +19 -13
  179. package/crates/team-agent/src/model/enums.rs +1 -0
  180. package/crates/team-agent/src/model/errors.rs +1 -0
  181. package/crates/team-agent/src/model/ids.rs +1 -0
  182. package/crates/team-agent/src/model/mod.rs +1 -0
  183. package/crates/team-agent/src/model/name_similarity.rs +1 -0
  184. package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -0
  185. package/crates/team-agent/src/model/paths.rs +1 -0
  186. package/crates/team-agent/src/model/permissions.rs +3 -2
  187. package/crates/team-agent/src/model/routing.rs +1 -0
  188. package/crates/team-agent/src/model/spec.rs +1 -0
  189. package/crates/team-agent/src/model/task_graph.rs +1 -0
  190. package/crates/team-agent/src/model/yaml/tests.rs +1 -0
  191. package/crates/team-agent/src/model/yaml.rs +1 -0
  192. package/crates/team-agent/src/packaging/install.rs +9 -147
  193. package/crates/team-agent/src/packaging/migrate.rs +2 -0
  194. package/crates/team-agent/src/packaging/mod.rs +1 -0
  195. package/crates/team-agent/src/packaging/repair.rs +2 -0
  196. package/crates/team-agent/src/packaging/tests.rs +29 -218
  197. package/crates/team-agent/src/packaging/types.rs +8 -15
  198. package/crates/team-agent/src/platform/argv.rs +4 -0
  199. package/crates/team-agent/src/platform/errors.rs +10 -1
  200. package/crates/team-agent/src/platform/file_lock.rs +4 -157
  201. package/crates/team-agent/src/platform/mod.rs +0 -61
  202. package/crates/team-agent/src/platform/process.rs +1 -0
  203. package/crates/team-agent/src/provider/adapter.rs +1 -0
  204. package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
  205. package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
  206. package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
  207. package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
  208. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
  209. package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
  210. package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
  211. package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
  212. package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
  213. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
  214. package/crates/team-agent/src/provider/classify.rs +1 -0
  215. package/crates/team-agent/src/provider/faults.rs +1 -26
  216. package/crates/team-agent/src/provider/helpers.rs +1 -0
  217. package/crates/team-agent/src/provider/mod.rs +1 -1
  218. package/crates/team-agent/src/provider/session/capture.rs +1 -4
  219. package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
  220. package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
  221. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
  222. package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
  223. package/crates/team-agent/src/provider/session/mod.rs +2 -4
  224. package/crates/team-agent/src/provider/session/resume.rs +2 -209
  225. package/crates/team-agent/src/provider/session_scan/claude.rs +1 -0
  226. package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
  227. package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
  228. package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
  229. package/crates/team-agent/src/provider/session_scan.rs +1 -42
  230. package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
  231. package/crates/team-agent/src/provider/types.rs +1 -0
  232. package/crates/team-agent/src/provider/wire.rs +1 -0
  233. package/crates/team-agent/src/state/identity.rs +16 -5
  234. package/crates/team-agent/src/state/identity_keys.rs +1 -0
  235. package/crates/team-agent/src/state/mod.rs +2 -0
  236. package/crates/team-agent/src/state/owner_gate.rs +5 -0
  237. package/crates/team-agent/src/state/ownership.rs +10 -4
  238. package/crates/team-agent/src/state/paths.rs +12 -0
  239. package/crates/team-agent/src/state/persist.rs +9 -3
  240. package/crates/team-agent/src/state/projection.rs +15 -2
  241. package/crates/team-agent/src/state/repository/intent.rs +1 -0
  242. package/crates/team-agent/src/state/repository/tests.rs +1 -0
  243. package/crates/team-agent/src/state/repository.rs +7 -13
  244. package/crates/team-agent/src/state/selector.rs +1 -0
  245. package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
  246. package/crates/team-agent/src/tmux_backend.rs +63 -0
  247. package/crates/team-agent/src/transport.rs +15 -0
  248. package/package.json +4 -4
  249. package/crates/team-agent/src/leader/inject.rs +0 -33
  250. package/crates/team-agent/src/provider/command.rs +0 -80
@@ -0,0 +1,342 @@
1
+ //! Swallow-family batch 4: the two cr-gated semantic-change points.
2
+ //!
3
+ //! Basis: `.team/artifacts/swallow-family-slices.md` batch 4 +
4
+ //! `swallow-batch4-cr-verdict.md` (workspace artifacts; BOTH points APPROVE'd,
5
+ //! 8 constraints C1-C8 + 6 reverse cases — folded below).
6
+ //!
7
+ //! Point 1 (C1-C4): a corrupt provider-config JSON must FAIL EXPLICITLY — today
8
+ //! profile_launch.rs:639-649 `read_json_object` turns any parse failure into an
9
+ //! empty map and `write_json_object` then flattens the user's file (silent
10
+ //! destructive rewrite; MUST-NOT-9 / MUST-NOT-13 violation).
11
+ //! Point 2 (C5-C8): MCP scope validation must FAIL CLOSED when the runtime state is
12
+ //! unreadable — today `load_runtime_state(..).unwrap_or(json!({}))`
13
+ //! (mcp_server/tools.rs:574/:232) silently substitutes an empty state, so a
14
+ //! multi-team workspace degrades to "legacy single-team" and an owner-less client
15
+ //! slips through the scope gate (silent privilege widening; MUST-16).
16
+
17
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
18
+
19
+ #[path = "../../../tests/support/mcp_sim_harness.rs"]
20
+ #[allow(dead_code)]
21
+ mod mcp_sim_harness;
22
+
23
+ #[path = "../../../tests/support/hermetic.rs"]
24
+ mod hermetic_guard;
25
+ #[allow(dead_code)]
26
+ fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
27
+
28
+ use std::path::{Path, PathBuf};
29
+ use std::sync::atomic::{AtomicU64, Ordering};
30
+
31
+ use mcp_sim_harness::McpSimHarness;
32
+ use serde_json::json;
33
+ use serial_test::serial;
34
+ use team_agent::lifecycle::launch_with_transport;
35
+
36
+ /// C1-C4 + reverse cases `profile_invalid_json_returns_error_and_does_not_overwrite_file`
37
+ /// / `..._includes_path_and_parse_detail_and_next_action`: launching a claude
38
+ /// compatible_api profile over a CORRUPT `.claude.json` must return a structured error
39
+ /// (path + parse detail + next action) and leave the corrupt file byte-identical —
40
+ /// never rewrite it with a flattened empty object.
41
+ #[test]
42
+ #[serial(b4_mcp)]
43
+ fn b4_profile_invalid_json_fails_explicit_and_never_flattens_the_file() {
44
+ // Pin a neutral process ancestry so the launch path is deterministic regardless of
45
+ // which provider flags this test process inherited (same guard as the #264 batch).
46
+ std::env::set_var(
47
+ "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON",
48
+ "[\"/bin/zsh\"]",
49
+ );
50
+ let ws = tmp_ws("b4-profile");
51
+ let team_dir = write_claude_compatible_team(&ws);
52
+ let config_dir = ws.join(".team/runtime/provider-config/worker_a/claude");
53
+ std::fs::create_dir_all(&config_dir).unwrap();
54
+ let corrupt = b"{ this is the user's corrupt claude state".to_vec();
55
+ std::fs::write(config_dir.join(".claude.json"), &corrupt).unwrap();
56
+
57
+ let result = launch_with_transport(
58
+ &team_dir.join("team.spec.yaml"),
59
+ false,
60
+ true,
61
+ true,
62
+ &offline_transport::RecordingTransport::new(),
63
+ );
64
+
65
+ let mut failures = Vec::new();
66
+ match &result {
67
+ Ok(report) => failures.push(format!(
68
+ "C1/C2: a corrupt provider-config JSON must fail the launch with a \
69
+ structured profile_invalid_json error, not silently continue; got Ok({report:?})"
70
+ )),
71
+ Err(error) => {
72
+ let text = error.to_string();
73
+ if !text.contains(".claude.json") {
74
+ failures.push(format!(
75
+ "C1/N38: the error must name the offending path; got {text}"
76
+ ));
77
+ }
78
+ }
79
+ }
80
+ let after = std::fs::read(config_dir.join(".claude.json")).unwrap_or_default();
81
+ if after != corrupt {
82
+ failures.push(format!(
83
+ "C3/C4: the user's corrupt file must stay byte-identical (framework never \
84
+ flattens user files on load failure — MUST-NOT-9); before={} bytes, after={} bytes: {:?}",
85
+ corrupt.len(),
86
+ after.len(),
87
+ String::from_utf8_lossy(&after)
88
+ ));
89
+ }
90
+ std::env::remove_var("TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON");
91
+ assert!(
92
+ failures.is_empty(),
93
+ "batch4 point-1 profile fail-explicit contract failed:\n{}",
94
+ failures.join("\n")
95
+ );
96
+ }
97
+
98
+ /// C5-C8 + reverse cases `scope_unverifiable_when_state_read_fails_returns_structured_refusal`
99
+ /// / `scope_unverifiable_state_recovers_retry_passes`: with the runtime state
100
+ /// unreadable, an owner-less MCP client in a (really) multi-team workspace must be
101
+ /// REFUSED (scope unverifiable, fail-closed), not silently treated as legacy
102
+ /// single-team; once the state is restored the same call gets the honest multi-team
103
+ /// refusal path (verifiable again).
104
+ #[ignore = "ci-env: harness state fixture unavailable, tracked"]
105
+ #[test]
106
+ #[serial(b4_mcp)]
107
+ fn b4_scope_validation_fails_closed_when_state_unreadable() {
108
+ let harness = McpSimHarness::new();
109
+ let mut worker = harness.spawn_mcp_client("worker_b", "");
110
+ let state_path = harness.workspace_path().join(".team/runtime/state.json");
111
+ let good_state = std::fs::read(&state_path).unwrap();
112
+ std::fs::write(&state_path, "{ unreadable state").unwrap();
113
+
114
+ let broken = worker.call_tool(
115
+ "send_message",
116
+ json!({"to": "worker_a", "content": "b4 fail-closed probe"}),
117
+ );
118
+
119
+ let mut failures = Vec::new();
120
+ let broken_text = broken.body.to_string();
121
+ if !broken.is_error
122
+ || !(broken_text.contains("unverifiable") || broken_text.contains("scope"))
123
+ || broken_text.contains("internal_runtime_error")
124
+ {
125
+ failures.push(format!(
126
+ "C5/C6/N38: with state unreadable the refusal must be the STRUCTURED \
127
+ scope_unverifiable kind (reason='state read failed: ..' + retry next_action), not a \
128
+ silent legacy-single-team pass (tools.rs unwrap_or(json!({{}}))) and not an opaque \
129
+ internal_runtime_error leak; body={}",
130
+ broken.body
131
+ ));
132
+ }
133
+
134
+ // C7: restore the state — the same call must hit the honest, verifiable gate again
135
+ // (here: the multi-team owner-required refusal), proving the outage refusal was
136
+ // transient, not durable.
137
+ std::fs::write(&state_path, &good_state).unwrap();
138
+ let recovered = worker.call_tool(
139
+ "send_message",
140
+ json!({"to": "worker_a", "content": "b4 recovery probe"}),
141
+ );
142
+ let recovered_text = recovered.body.to_string();
143
+ if !recovered_text.contains("TEAM_AGENT_OWNER_TEAM_ID")
144
+ && !recovered_text.contains("scope_refused")
145
+ {
146
+ failures.push(format!(
147
+ "C7: after state recovery the same call must reach the verifiable multi-team \
148
+ gate (owner-required refusal), not some degraded path; body={}",
149
+ recovered.body
150
+ ));
151
+ }
152
+
153
+ assert!(
154
+ failures.is_empty(),
155
+ "batch4 point-2 fail-closed scope contract failed:\n{}",
156
+ failures.join("\n")
157
+ );
158
+ }
159
+
160
+ // ───────────────────────────────────── fixtures ─────────────────────────────────────
161
+
162
+ /// Compiled claude compatible_api team with a profile (the path that maintains the
163
+ /// managed `.claude.json`). Mirrors the #264 profile fixture, claude flavor.
164
+ fn write_claude_compatible_team(ws: &Path) -> PathBuf {
165
+ let team = ws.join("teamdir");
166
+ std::fs::create_dir_all(team.join("agents")).unwrap();
167
+ std::fs::create_dir_all(team.join("profiles")).unwrap();
168
+ std::fs::write(
169
+ team.join("TEAM.md"),
170
+ "---\nname: b4team\nobjective: batch4 profile fixture.\nprovider: claude\n---\n\nTeam.\n",
171
+ )
172
+ .unwrap();
173
+ std::fs::write(
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",
176
+ )
177
+ .unwrap();
178
+ std::fs::write(
179
+ team.join("profiles/relay.env"),
180
+ "AUTH_MODE=compatible_api\nBASE_URL=https://relay.example/v1\nAPI_KEY=sk-relay-b4\nMODEL=claude-haiku\n",
181
+ )
182
+ .unwrap();
183
+ let spec = team_agent::compiler::compile_team(&team).expect("compile fixture team");
184
+ std::fs::write(
185
+ team.join("team.spec.yaml"),
186
+ team_agent::model::yaml::dumps(&spec),
187
+ )
188
+ .unwrap();
189
+ team
190
+ }
191
+
192
+ mod offline_transport {
193
+ use super::*;
194
+ use std::collections::BTreeMap;
195
+ use std::sync::Mutex;
196
+ use team_agent::transport::{
197
+ AttachOutcome, BackendKind, CaptureRange, CapturedText, InjectPayload, InjectReport,
198
+ InjectStage, InjectVerification, Key, PaneField, PaneId, PaneInfo, SessionName,
199
+ SetEnvOutcome, SpawnResult, SubmitVerification, Target, Transport, TransportError,
200
+ TurnVerification, WindowName,
201
+ };
202
+
203
+ #[derive(Default)]
204
+ pub struct RecordingTransport {
205
+ session_present: Mutex<bool>,
206
+ }
207
+
208
+ impl RecordingTransport {
209
+ pub fn new() -> Self {
210
+ Self::default()
211
+ }
212
+ }
213
+
214
+ impl Transport for RecordingTransport {
215
+ fn kind(&self) -> BackendKind {
216
+ BackendKind::Tmux
217
+ }
218
+
219
+ fn spawn_first(
220
+ &self,
221
+ session: &SessionName,
222
+ window: &WindowName,
223
+ _argv: &[String],
224
+ _cwd: &Path,
225
+ _env: &BTreeMap<String, String>,
226
+ ) -> Result<SpawnResult, TransportError> {
227
+ *self.session_present.lock().unwrap() = true;
228
+ Ok(SpawnResult {
229
+ pane_id: PaneId::new("%1"),
230
+ session: session.clone(),
231
+ window: window.clone(),
232
+ child_pid: None,
233
+ })
234
+ }
235
+
236
+ fn spawn_into(
237
+ &self,
238
+ session: &SessionName,
239
+ window: &WindowName,
240
+ argv: &[String],
241
+ cwd: &Path,
242
+ env: &BTreeMap<String, String>,
243
+ ) -> Result<SpawnResult, TransportError> {
244
+ self.spawn_first(session, window, argv, cwd, env)
245
+ }
246
+
247
+ fn inject(
248
+ &self,
249
+ _target: &Target,
250
+ _payload: &InjectPayload,
251
+ _submit: Key,
252
+ _bracketed: bool,
253
+ ) -> Result<InjectReport, TransportError> {
254
+ Ok(InjectReport {
255
+ stage_reached: InjectStage::Submit,
256
+ inject_verification: InjectVerification::CaptureContainsToken,
257
+ submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
258
+ turn_verification: TurnVerification::NotYetObserved,
259
+ attempts: 1,
260
+ submit_diagnostics: None,
261
+ })
262
+ }
263
+
264
+ fn send_keys(&self, _target: &Target, _keys: &[Key]) -> Result<(), TransportError> {
265
+ Ok(())
266
+ }
267
+
268
+ fn capture(
269
+ &self,
270
+ _target: &Target,
271
+ range: CaptureRange,
272
+ ) -> Result<CapturedText, TransportError> {
273
+ Ok(CapturedText {
274
+ text: "Claude Code\n> ".to_string(),
275
+ range,
276
+ })
277
+ }
278
+
279
+ fn query(
280
+ &self,
281
+ _target: &Target,
282
+ field: PaneField,
283
+ ) -> Result<Option<String>, TransportError> {
284
+ match field {
285
+ PaneField::PaneWidth => Ok(Some("120".to_string())),
286
+ _ => Ok(None),
287
+ }
288
+ }
289
+
290
+ fn liveness(
291
+ &self,
292
+ _pane: &PaneId,
293
+ ) -> Result<team_agent::transport::PaneLiveness, TransportError> {
294
+ Ok(team_agent::transport::PaneLiveness::Live)
295
+ }
296
+
297
+ fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
298
+ Ok(Vec::new())
299
+ }
300
+
301
+ fn has_session(&self, _session: &SessionName) -> Result<bool, TransportError> {
302
+ Ok(*self.session_present.lock().unwrap())
303
+ }
304
+
305
+ fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
306
+ Ok(Vec::new())
307
+ }
308
+
309
+ fn set_session_env(
310
+ &self,
311
+ _session: &SessionName,
312
+ _key: &str,
313
+ _value: &str,
314
+ ) -> Result<SetEnvOutcome, TransportError> {
315
+ Ok(SetEnvOutcome::Applied)
316
+ }
317
+
318
+ fn kill_session(&self, _session: &SessionName) -> Result<(), TransportError> {
319
+ Ok(())
320
+ }
321
+
322
+ fn kill_window(&self, _target: &Target) -> Result<(), TransportError> {
323
+ Ok(())
324
+ }
325
+
326
+ fn attach_session(&self, _session: &SessionName) -> Result<AttachOutcome, TransportError> {
327
+ Ok(AttachOutcome::Attached)
328
+ }
329
+ }
330
+ }
331
+
332
+ fn tmp_ws(tag: &str) -> PathBuf {
333
+ static N: AtomicU64 = AtomicU64::new(0);
334
+ let dir = std::env::temp_dir().join(format!(
335
+ "ta-rs-swallow-{tag}-{}-{}",
336
+ std::process::id(),
337
+ N.fetch_add(1, Ordering::Relaxed)
338
+ ));
339
+ let _ = std::fs::remove_dir_all(&dir);
340
+ std::fs::create_dir_all(&dir).unwrap();
341
+ std::fs::canonicalize(dir).unwrap()
342
+ }