@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,624 @@
1
+ //! RED contract — 0.5.57 typed-state car · batch C (P0 upgrade gate)
2
+ //! Historical-inventory upgrade gate: pre-seed a multi-status, multi-era
3
+ //! fixture representing the storm production snapshot; after boot/claim,
4
+ //! the typed contract must produce EXACTLY the expected per-prior-state
5
+ //! transition count, and produce a per-status audit record.
6
+ //!
7
+ //! LINEAGE:
8
+ //! - Baseline: 5b847e4 (0.5.56 tested tip).
9
+ //! - Distillation §3.3 minimum matrix items #1, #6, #7 (historical
10
+ //! backlog / crash cut / upgrade inventory).
11
+ //! - Storm snapshot production shape (
12
+ //! `.team/artifacts/pipeline-runs/mailbox-flush-case/storm-p0-locate.md
13
+ //! §A1-A2`): 346 parked + 153 blocked (leader_not_attached) +
14
+ //! 51 `failed/send_unverified_exhausted` (of which 23 recipient=leader) +
15
+ //! 5 `queued_pane_missing` + 108 null-result historical watchers +
16
+ //! delivered rows preserved.
17
+ //! - Wiki hard rule: fixture predicates MUST derive from the product typed
18
+ //! enum catalog (`MessageRowStatus::ALL`), so no hand-written second state
19
+ //! semantics can exist.
20
+ //!
21
+ //! TEETH (RED at 5b847e4):
22
+ //! 1. `upgrade_gate_only_blocked_rows_flip` — with a full storm-shape
23
+ //! inventory (parked + blocked + delivered + submitted_unverified +
24
+ //! queued_pane_missing + null-result watcher + worker-recipient
25
+ //! parked), a single claim/attach must flip ONLY the truly-blocked
26
+ //! leader rows. Baseline 5b847e4 already passes this thanks to
27
+ //! 0.5.56, so this tooth is an INVARIANT LOCK (green). It's kept
28
+ //! here because the typed refactor MUST preserve it.
29
+ //! 2. `upgrade_gate_emits_per_prior_state_audit_count` — the
30
+ //! `leader_receiver.blocked_messages_requeued` event must carry a
31
+ //! per-prior-status breakdown (e.g. `{ blocked_leader_unbound: 1,
32
+ //! queued_until_leader_attach: 0 }`), not just a scalar `count`.
33
+ //! Baseline only emits `count` → red.
34
+ //! 3. `null_result_historical_watchers_never_rewritten_across_boot`
35
+ //! — 108 null-result historical watchers must be untouched: no
36
+ //! status change, no event write. Baseline 5b847e4 already passes
37
+ //! thanks to 0.5.56's `result_id is not null` guard → invariant
38
+ //! lock (green).
39
+ //! 4. `worker_recipient_parked_rows_have_typed_recovery_owner` — the
40
+ //! 5 `queued_pane_missing` rows (worker-recipient face) must be
41
+ //! pointed to a NAMED typed recovery incident, not left in
42
+ //! undefined-owner limbo. Baseline: no such surface exists → red.
43
+ //! 5. `upgrade_inventory_covers_full_enum` — the fixture itself
44
+ //! exercises every status in `MessageRowStatus::ALL`. A new product
45
+ //! status is therefore seeded automatically rather than copied into
46
+ //! a test-side list.
47
+ //! 6. `injected_awaiting_receipt_is_open_debt_but_not_requeueable` —
48
+ //! the typed parked row remains visible in the undelivered inventory,
49
+ //! while the same row cannot win a delivery claim.
50
+ //!
51
+ //! POSITIVE CONTROL:
52
+ //! 7. `newly_bound_leader_still_flushes_queued_row` — the legitimate
53
+ //! recovery path (blocked leader row → accepted → delivered) is
54
+ //! preserved through the upgrade gate.
55
+ //!
56
+ //! NEGATIVE CONTROL:
57
+ //! 8. `non_leader_parked_rows_are_never_flipped_by_leader_claim` — a
58
+ //! worker-recipient parked row (recipient!=leader, status=
59
+ //! submitted_pending_acceptance) is unaffected by any leader
60
+ //! claim; scope of blocked-leader recovery stays leader-only.
61
+ //!
62
+ //! FROZEN by verifier — do NOT modify without a new SHA256 signature.
63
+
64
+ #![cfg(unix)]
65
+ #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
66
+
67
+ use crate as team_agent;
68
+
69
+ #[path = "rehomed_env.rs"]
70
+ mod rehomed_env;
71
+
72
+ use std::collections::BTreeSet;
73
+ use std::path::PathBuf;
74
+ use std::sync::atomic::{AtomicU64, Ordering};
75
+
76
+ use rusqlite::params;
77
+ use serial_test::serial;
78
+ use team_agent::event_log::EventLog;
79
+ use team_agent::leader::{claim_lease_no_incident, LeaseStatus};
80
+ use team_agent::message_store::{MessageRowStatus, MessageStore};
81
+ use team_agent::messaging::enqueue_leader_mailbox_until_attach;
82
+ use team_agent::messaging::watchers::requeue_after_claim_leader;
83
+ use team_agent::model::enums::PaneLiveness;
84
+ use team_agent::model::ids::TeamKey;
85
+ use team_agent::state::owner_gate::PaneLivenessProbe;
86
+ use team_agent::transport::PaneId;
87
+
88
+ fn durable_status_catalog() -> BTreeSet<&'static str> {
89
+ MessageRowStatus::ALL
90
+ .iter()
91
+ .map(|status| status.as_str())
92
+ .collect()
93
+ }
94
+
95
+ const TEAM: &str = "current";
96
+ const PANE: &str = "%leader";
97
+
98
+ struct LiveCaller;
99
+ impl PaneLivenessProbe for LiveCaller {
100
+ fn liveness(&self, pane_id: &str) -> PaneLiveness {
101
+ if pane_id == PANE {
102
+ PaneLiveness::Live
103
+ } else {
104
+ PaneLiveness::Dead
105
+ }
106
+ }
107
+ }
108
+
109
+ fn bound_state() -> serde_json::Value {
110
+ serde_json::json!({
111
+ "session_name": "team-upgrade-gate",
112
+ "team_owner": {
113
+ "pane_id": PANE,
114
+ "provider": "codex",
115
+ "owner_epoch": 7,
116
+ "claimed_at": "2026-07-23T05:13:42Z",
117
+ "claimed_via": "claim-leader"
118
+ },
119
+ "leader_receiver": {
120
+ "pane_id": PANE,
121
+ "provider": "codex",
122
+ "owner_epoch": 7,
123
+ "status": "attached"
124
+ }
125
+ })
126
+ }
127
+
128
+ struct InventoryCase {
129
+ _env: rehomed_env::RehomedTestEnv,
130
+ workspace: PathBuf,
131
+ store: MessageStore,
132
+ event_log: EventLog,
133
+ }
134
+
135
+ impl InventoryCase {
136
+ fn new(tag: &str) -> Self {
137
+ static N: AtomicU64 = AtomicU64::new(0);
138
+ let env = rehomed_env::RehomedTestEnv::enter(tag);
139
+ let workspace = env.workspace(&format!(
140
+ "057c-inv-{tag}-{}",
141
+ N.fetch_add(1, Ordering::Relaxed)
142
+ ));
143
+ let store = MessageStore::open(&workspace).unwrap();
144
+ let event_log = EventLog::new(&workspace);
145
+ Self {
146
+ _env: env,
147
+ workspace,
148
+ store,
149
+ event_log,
150
+ }
151
+ }
152
+
153
+ fn seed_leader_row(&self, content: &str, status: &str, error: Option<&str>) -> String {
154
+ let mid = enqueue_leader_mailbox_until_attach(
155
+ &self.workspace,
156
+ TEAM,
157
+ content,
158
+ None,
159
+ "leader",
160
+ &self.event_log,
161
+ )
162
+ .unwrap()
163
+ .message_id
164
+ .expect("mailbox enqueue returns id");
165
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
166
+ conn.execute(
167
+ "update messages set status = ?2, error = ?3 where message_id = ?1",
168
+ params![mid, status, error],
169
+ )
170
+ .unwrap();
171
+ mid
172
+ }
173
+
174
+ fn seed_worker_row(&self, content: &str, status: &str, error: Option<&str>) -> String {
175
+ let mid = self.seed_leader_row(content, "accepted", None);
176
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
177
+ conn.execute(
178
+ "update messages set recipient = 'worker-a', status = ?2, error = ?3
179
+ where message_id = ?1",
180
+ params![mid, status, error],
181
+ )
182
+ .unwrap();
183
+ mid
184
+ }
185
+
186
+ fn seed_null_result_watcher(&self, i: usize) -> String {
187
+ let wid = format!("watch-null-hist-{i}");
188
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
189
+ conn.execute(
190
+ "insert into result_watchers
191
+ (watcher_id, owner_team_id, leader_id, status, created_at, completed_at,
192
+ result_id, notified_message_id)
193
+ values (?1, ?2, 'leader', 'notify_failed', '2026-07-21T00:00:00Z',
194
+ '2026-07-21T00:00:00Z', null, null)",
195
+ params![wid, TEAM],
196
+ )
197
+ .unwrap();
198
+ wid
199
+ }
200
+
201
+ fn row_status(&self, mid: &str) -> String {
202
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
203
+ conn.query_row(
204
+ "select status from messages where message_id = ?1",
205
+ params![mid],
206
+ |row| row.get(0),
207
+ )
208
+ .unwrap()
209
+ }
210
+
211
+ fn watcher_status(&self, wid: &str) -> String {
212
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
213
+ conn.query_row(
214
+ "select status from result_watchers where watcher_id = ?1",
215
+ params![wid],
216
+ |row| row.get(0),
217
+ )
218
+ .unwrap()
219
+ }
220
+
221
+ fn distinct_message_statuses(&self) -> BTreeSet<String> {
222
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
223
+ let mut statement = conn
224
+ .prepare("select distinct status from messages order by status")
225
+ .unwrap();
226
+ statement
227
+ .query_map([], |row| row.get::<_, String>(0))
228
+ .unwrap()
229
+ .map(|row| row.unwrap())
230
+ .collect()
231
+ }
232
+
233
+ fn last_blocked_requeue_event(&self) -> Option<serde_json::Value> {
234
+ let path = self
235
+ .workspace
236
+ .join(".team")
237
+ .join("logs")
238
+ .join("events.jsonl");
239
+ let text = std::fs::read_to_string(path).ok()?;
240
+ text.lines()
241
+ .rev()
242
+ .filter_map(|l| serde_json::from_str::<serde_json::Value>(l).ok())
243
+ .find(|e| {
244
+ e.get("event").and_then(serde_json::Value::as_str)
245
+ == Some("leader_receiver.blocked_messages_requeued")
246
+ })
247
+ }
248
+
249
+ fn claim(&self, state: &mut serde_json::Value) -> LeaseStatus {
250
+ let team = TeamKey::new(TEAM.to_string());
251
+ let pane = PaneId::new(PANE);
252
+ let result = claim_lease_no_incident(
253
+ &self.workspace,
254
+ state,
255
+ None,
256
+ &team,
257
+ &pane,
258
+ true,
259
+ &self.event_log,
260
+ &LiveCaller,
261
+ )
262
+ .unwrap();
263
+ if result.ok {
264
+ let _ = requeue_after_claim_leader(
265
+ &self.workspace,
266
+ &self.store,
267
+ &self.event_log,
268
+ &team,
269
+ result.bound_pane_id.as_ref().expect("pane"),
270
+ None,
271
+ )
272
+ .unwrap();
273
+ }
274
+ result.status
275
+ }
276
+ }
277
+
278
+ /// Seed a full storm-shape inventory and return the id map keyed by the
279
+ /// prior-state name so teeth can address rows by role.
280
+ fn seed_full_inventory(case: &InventoryCase) -> std::collections::BTreeMap<String, Vec<String>> {
281
+ let mut m: std::collections::BTreeMap<String, Vec<String>> = Default::default();
282
+ // parked (leader, submitted_pending_acceptance) ×3 — represents 346
283
+ for i in 0..3 {
284
+ m.entry("submitted_pending_acceptance".into())
285
+ .or_default()
286
+ .push(case.seed_leader_row(
287
+ &format!("parked history {i}"),
288
+ "submitted_pending_acceptance",
289
+ None,
290
+ ));
291
+ }
292
+ // blocked (failed + leader_not_attached) ×2 — represents 153
293
+ for i in 0..2 {
294
+ m.entry("failed:leader_not_attached".into())
295
+ .or_default()
296
+ .push(case.seed_leader_row(
297
+ &format!("blocked {i}"),
298
+ "failed",
299
+ Some("leader_not_attached"),
300
+ ));
301
+ }
302
+ // delivered ×1 — must never flip
303
+ m.entry("delivered".into())
304
+ .or_default()
305
+ .push(case.seed_leader_row("delivered history", "delivered", None));
306
+ // submitted_unverified ×1 — represents 51 (or the leader-subset 23)
307
+ m.entry("submitted_unverified".into())
308
+ .or_default()
309
+ .push(case.seed_leader_row("unverified history", "submitted_unverified", None));
310
+ // queued_pane_missing ×1 (worker-recipient face) — represents 5
311
+ m.entry("queued_pane_missing".into())
312
+ .or_default()
313
+ .push(case.seed_worker_row(
314
+ "worker pane missing",
315
+ "queued_pane_missing",
316
+ Some("tmux_target_missing"),
317
+ ));
318
+ // acknowledged ×1 — terminal, must not flip
319
+ m.entry("acknowledged".into())
320
+ .or_default()
321
+ .push(case.seed_leader_row("acked", "acknowledged", None));
322
+ // consumed ×1 — terminal, must not flip
323
+ m.entry("consumed".into())
324
+ .or_default()
325
+ .push(case.seed_leader_row("consumed", "consumed", None));
326
+ // stored_only ×1 — not an eligible transition
327
+ m.entry("stored_only".into())
328
+ .or_default()
329
+ .push(case.seed_leader_row("stored", "stored_only", None));
330
+ // queued_coordinator_unavailable ×1 — same, blocked but not by leader
331
+ m.entry("queued_coordinator_unavailable".into())
332
+ .or_default()
333
+ .push(case.seed_leader_row("coord-unavail", "queued_coordinator_unavailable", None));
334
+ // target_resolved ×1 — in-flight, must not flip
335
+ m.entry("target_resolved".into())
336
+ .or_default()
337
+ .push(case.seed_leader_row("resolved", "target_resolved", None));
338
+ // Non-leader parked (negative control body)
339
+ m.entry("worker:submitted_pending_acceptance".into())
340
+ .or_default()
341
+ .push(case.seed_worker_row("worker parked", "submitted_pending_acceptance", None));
342
+ // Add a queued_until_leader_attach positive control ×1
343
+ m.entry("queued_until_leader_attach".into())
344
+ .or_default()
345
+ .push(case.seed_leader_row("queued mailbox", "queued_until_leader_attach", None));
346
+ // accepted ×1 — freshly created, must remain accepted (not touched by claim)
347
+ m.entry("accepted".into())
348
+ .or_default()
349
+ .push(case.seed_leader_row("just accepted", "accepted", None));
350
+ // Drive the historical fixture from the product's unique typed directory.
351
+ // Any new durable wire status is seeded automatically, without a copied
352
+ // test-side list that can silently lag the product catalog.
353
+ let already_seeded = case.distinct_message_statuses();
354
+ for status in durable_status_catalog() {
355
+ if !already_seeded.contains(status) {
356
+ m.entry(status.to_string())
357
+ .or_default()
358
+ .push(case.seed_leader_row(
359
+ &format!("catalog-derived historical {status}"),
360
+ status,
361
+ None,
362
+ ));
363
+ }
364
+ }
365
+ m
366
+ }
367
+
368
+ // ---------------------------------------------------------------------------
369
+ // Tooth 1 — only truly-blocked rows flip (invariant lock at 5b847e4)
370
+ // ---------------------------------------------------------------------------
371
+
372
+ #[test]
373
+ #[serial(env)]
374
+ fn upgrade_gate_only_blocked_rows_flip() {
375
+ let case = InventoryCase::new("only-blocked-flip");
376
+ let map = seed_full_inventory(&case);
377
+ let mut state = bound_state();
378
+ let status = case.claim(&mut state);
379
+ assert_eq!(status, LeaseStatus::AlreadyBound);
380
+ // Blocked and queued_until_leader_attach must be accepted; everything
381
+ // else byte-stable at its seeded status.
382
+ for id in &map["failed:leader_not_attached"] {
383
+ assert_eq!(
384
+ case.row_status(id),
385
+ "accepted",
386
+ "blocked leader row must flip"
387
+ );
388
+ }
389
+ for id in &map["queued_until_leader_attach"] {
390
+ assert_eq!(
391
+ case.row_status(id),
392
+ "accepted",
393
+ "queued_until_leader_attach must flip (0.5.56 preserved)"
394
+ );
395
+ }
396
+ for (key, ids) in &map {
397
+ if key == "failed:leader_not_attached" || key == "queued_until_leader_attach" {
398
+ continue;
399
+ }
400
+ // Determine the expected preserved status.
401
+ let expected = key.split(':').next().unwrap();
402
+ // Special case: "worker:X" seeded status is X.
403
+ let expected = expected
404
+ .strip_prefix("worker")
405
+ .unwrap_or(expected)
406
+ .trim_start_matches(':');
407
+ let expected = if expected.is_empty() {
408
+ key.split(':').nth(1).unwrap()
409
+ } else {
410
+ expected
411
+ };
412
+ for id in ids {
413
+ assert_eq!(
414
+ case.row_status(id),
415
+ expected,
416
+ "row {id} (prior={key}) must be byte-stable across claim; upgrade gate leaks"
417
+ );
418
+ }
419
+ }
420
+ }
421
+
422
+ // ---------------------------------------------------------------------------
423
+ // Tooth 2 — per-prior-state audit count (RED at 5b847e4)
424
+ // ---------------------------------------------------------------------------
425
+
426
+ #[test]
427
+ #[serial(env)]
428
+ fn upgrade_gate_emits_per_prior_state_audit_count() {
429
+ let case = InventoryCase::new("audit-count");
430
+ let _map = seed_full_inventory(&case);
431
+ let mut state = bound_state();
432
+ let _ = case.claim(&mut state);
433
+ let event = case
434
+ .last_blocked_requeue_event()
435
+ .expect("blocked_messages_requeued event must exist");
436
+ // Distillation §D3 / §2.2#2 target: per-prior-status breakdown.
437
+ // The typed refactor must add a `by_prior_state` map keyed by typed
438
+ // variant. Baseline only has scalar `count` → red.
439
+ let by_prior = event.get("by_prior_state").and_then(|v| v.as_object());
440
+ assert!(
441
+ by_prior.is_some(),
442
+ "event missing `by_prior_state` breakdown; only scalar count exposed. \
443
+ audit-side conflation is what let the 494 batch hide 346/148 splits."
444
+ );
445
+ let by_prior = by_prior.unwrap();
446
+ // Every prior-status the fixture actually touched must be represented,
447
+ // even if 0 rows in that state were flipped.
448
+ for expected_key in ["blocked_leader_unbound", "queued_until_leader_attach"] {
449
+ assert!(
450
+ by_prior.contains_key(expected_key),
451
+ "audit map missing typed key `{expected_key}`; the typed refactor must \
452
+ expose 0-count entries too (so audits are self-describing)."
453
+ );
454
+ }
455
+ }
456
+
457
+ // ---------------------------------------------------------------------------
458
+ // Tooth 3 — null-result historical watchers are untouched (invariant lock)
459
+ // ---------------------------------------------------------------------------
460
+
461
+ #[test]
462
+ #[serial(env)]
463
+ fn null_result_historical_watchers_never_rewritten_across_boot() {
464
+ let case = InventoryCase::new("watcher-null");
465
+ let watchers: Vec<String> = (0..3).map(|i| case.seed_null_result_watcher(i)).collect();
466
+ let _map = seed_full_inventory(&case);
467
+ let before: Vec<String> = watchers.iter().map(|w| case.watcher_status(w)).collect();
468
+ let mut state = bound_state();
469
+ let _ = case.claim(&mut state);
470
+ let after: Vec<String> = watchers.iter().map(|w| case.watcher_status(w)).collect();
471
+ assert_eq!(
472
+ before, after,
473
+ "null-result historical watchers must never be rewritten (108-row write \
474
+ amplification of 0.5.55 storm). §2.3 watcher fan-in."
475
+ );
476
+ }
477
+
478
+ // ---------------------------------------------------------------------------
479
+ // Tooth 4 — worker-recipient parked rows must have a typed recovery owner
480
+ // ---------------------------------------------------------------------------
481
+
482
+ #[test]
483
+ #[serial(env)]
484
+ fn worker_recipient_parked_rows_have_typed_recovery_owner() {
485
+ // We probe by source scan: after refactor, a file inside
486
+ // src/messaging/ or src/recovery/ (name at implementer's discretion)
487
+ // must expose a typed API for pane-available recovery whose target
488
+ // is `BlockedWorkerPaneMissing` (§3.2 last-two-rows).
489
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
490
+ let src = manifest.join("src");
491
+ // Search for a symbol name pattern: `BlockedWorkerPaneMissing` variant
492
+ // + a recovery function whose name mentions pane_available.
493
+ let mut has_variant = false;
494
+ let mut has_recovery = false;
495
+ fn walk(dir: &std::path::Path, has_variant: &mut bool, has_recovery: &mut bool) {
496
+ let Ok(entries) = std::fs::read_dir(dir) else {
497
+ return;
498
+ };
499
+ for e in entries.flatten() {
500
+ let p = e.path();
501
+ if p.is_dir() {
502
+ walk(&p, has_variant, has_recovery);
503
+ } else if p.extension().and_then(|s| s.to_str()) == Some("rs") {
504
+ if let Ok(t) = std::fs::read_to_string(&p) {
505
+ if t.contains("BlockedWorkerPaneMissing") {
506
+ *has_variant = true;
507
+ }
508
+ if t.contains("pane_available")
509
+ && (t.contains("fn recover") || t.contains("fn requeue"))
510
+ {
511
+ *has_recovery = true;
512
+ }
513
+ }
514
+ }
515
+ }
516
+ }
517
+ walk(&src, &mut has_variant, &mut has_recovery);
518
+ assert!(
519
+ has_variant && has_recovery,
520
+ "worker-recipient parked rows (queued_pane_missing, §3.2 last-two-rows) \
521
+ have no typed recovery owner. Need: `BlockedWorkerPaneMissing` variant + \
522
+ a recovery function keyed on pane_available incident. Baseline: variant={} \
523
+ recovery={}. Currently these rows sit in undefined-owner limbo (locate \
524
+ §1.3 '23+5' subset).",
525
+ has_variant,
526
+ has_recovery
527
+ );
528
+ }
529
+
530
+ // ---------------------------------------------------------------------------
531
+ // Tooth 5 — fixture stays in lock-step with batch A's enum catalog
532
+ // ---------------------------------------------------------------------------
533
+
534
+ #[test]
535
+ #[serial(env)]
536
+ fn upgrade_inventory_covers_full_enum() {
537
+ let case = InventoryCase::new("catalog-lockstep");
538
+ let _map = seed_full_inventory(&case);
539
+ let seeded = case.distinct_message_statuses();
540
+ let catalog: BTreeSet<String> = durable_status_catalog()
541
+ .into_iter()
542
+ .map(str::to_string)
543
+ .collect();
544
+ assert_eq!(
545
+ seeded, catalog,
546
+ "historical fixture status coverage must be exactly the product \
547
+ MessageRowStatus::ALL wire catalog; no hand-copied subset may drift"
548
+ );
549
+ }
550
+
551
+ // ---------------------------------------------------------------------------
552
+ // Tooth 6 — parked delivery debt remains open but cannot requeue
553
+ // ---------------------------------------------------------------------------
554
+
555
+ #[test]
556
+ #[serial(env)]
557
+ fn injected_awaiting_receipt_is_open_debt_but_not_requeueable() {
558
+ let case = InventoryCase::new("injected-open-debt");
559
+ let status = durable_status_catalog()
560
+ .into_iter()
561
+ .find(|status| *status == "injected_awaiting_receipt")
562
+ .expect("product MessageRowStatus::ALL must expose injected_awaiting_receipt");
563
+ let message_id = case.seed_leader_row("receipt still owed", status, None);
564
+
565
+ let runtime_status =
566
+ team_agent::cli::status_port::status(&case.workspace, false, true).unwrap();
567
+ let undelivered = runtime_status
568
+ .pointer("/runtime/undelivered")
569
+ .and_then(serde_json::Value::as_i64);
570
+ let claim_won = case.store.claim_for_delivery(&message_id).unwrap();
571
+ assert!(
572
+ undelivered == Some(1) && !claim_won,
573
+ "injected_awaiting_receipt must be classified in two directions at once: \
574
+ nonterminal-for-delivery (runtime undelivered inventory=1) and \
575
+ terminal-for-requeue (claim_won=false). observed \
576
+ undelivered={undelivered:?} claim_won={claim_won}"
577
+ );
578
+ assert_eq!(
579
+ case.row_status(&message_id),
580
+ "injected_awaiting_receipt",
581
+ "a rejected claim must leave the typed parked fact unchanged"
582
+ );
583
+ }
584
+
585
+ // ---------------------------------------------------------------------------
586
+ // Tooth 7 — positive control: legitimate recovery path preserved
587
+ // ---------------------------------------------------------------------------
588
+
589
+ #[test]
590
+ #[serial(env)]
591
+ fn newly_bound_leader_still_flushes_queued_row() {
592
+ let case = InventoryCase::new("pos-newly-bound");
593
+ let queued = case.seed_leader_row("queued mailbox", "queued_until_leader_attach", None);
594
+ let mut state = serde_json::json!({ "session_name": "team-upgrade-pos-new" });
595
+ let status = case.claim(&mut state);
596
+ assert_eq!(
597
+ status,
598
+ LeaseStatus::Claimed,
599
+ "positive control exercises Claimed enum branch"
600
+ );
601
+ assert_eq!(
602
+ case.row_status(&queued),
603
+ "accepted",
604
+ "newly-bound path must still flush queued mailbox row"
605
+ );
606
+ }
607
+
608
+ // ---------------------------------------------------------------------------
609
+ // Tooth 8 — negative control: non-leader parked rows unaffected
610
+ // ---------------------------------------------------------------------------
611
+
612
+ #[test]
613
+ #[serial(env)]
614
+ fn non_leader_parked_rows_are_never_flipped_by_leader_claim() {
615
+ let case = InventoryCase::new("neg-worker-parked");
616
+ let worker_parked = case.seed_worker_row("worker parked", "submitted_pending_acceptance", None);
617
+ let mut state = bound_state();
618
+ let _ = case.claim(&mut state);
619
+ assert_eq!(
620
+ case.row_status(&worker_parked),
621
+ "submitted_pending_acceptance",
622
+ "recipient!=leader parked row must be unaffected by leader claim"
623
+ );
624
+ }
@@ -94,8 +94,13 @@ fn leader_start_plan_pins_mode_and_leader_env() {
94
94
  );
95
95
  assert!(!plan.is_external_leader);
96
96
  assert!(
97
- plan.argv.iter().any(|arg| arg == "attach-session"),
98
- "no-tmux managed launch attaches the user client to the leader window: {:?}",
97
+ plan.argv.len() >= 5
98
+ && plan.argv[0] == "tmux"
99
+ && plan.argv[1] == "-L"
100
+ && plan.argv[2].starts_with("ta-")
101
+ && plan.argv.iter().any(|arg| arg == "attach-session")
102
+ && plan.argv.iter().any(|arg| arg.ends_with(":fake")),
103
+ "no-tmux managed launch uses the workspace socket and attaches the user client to the leader window: {:?}",
99
104
  plan.argv
100
105
  );
101
106
  // plan 边界 detached 恒 false(`-d` 插入在 start_leader 层,非此处)。