@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,417 @@
1
+ //! RED contract — 0.5.57 typed-state car · batch F
2
+ //! Terminal-state immutability + framework-replay vs provider-re-render
3
+ //! diagnostic separation.
4
+ //!
5
+ //! LINEAGE:
6
+ //! - Baseline: 5b847e4 (0.5.56 tested tip).
7
+ //! - Distillation §1.4 "两种历史消息重现必须分诊" and §3.3#5
8
+ //! "Submitted→Delivered 后任何 claim/attach/restart 都不可逆".
9
+ //! - Storm remote-control export proved that `delivered` was never
10
+ //! re-injected (attempts / delivered_at unchanged); the provider
11
+ //! re-rendered the historical transcript on `codex resume`. Any
12
+ //! diagnostic that conflates the two will misattribute
13
+ //! provider-side redraw as a framework replay.
14
+ //!
15
+ //! TEETH (RED at 5b847e4):
16
+ //! 1. `delivered_row_immutable_under_any_transition`
17
+ //! — a `delivered` row's `delivered_at`, `delivery_attempts`,
18
+ //! `status` and delivery token must be BYTE-STABLE across any
19
+ //! claim/attach/requeue call from the typed-state surface.
20
+ //! Baseline: passes (invariant lock) → green.
21
+ //! 2. `acknowledged_row_immutable_under_generic_mark`
22
+ //! — an `acknowledged` row must not be overwritten by a generic
23
+ //! `store.mark` call that could regress it to a delivery status.
24
+ //! Baseline: distillation §1.2 acknowledged row notes "current
25
+ //! guard still allows other status to overwrite; typed car needs
26
+ //! to tighten." → red.
27
+ //! 3. `consumed_row_immutable_under_any_transition`
28
+ //! — a `consumed` row must not re-enter any recovery arm.
29
+ //! Baseline: source scan — no explicit exclusion. → red.
30
+ //! 4. `typed_event_carries_replay_vs_rerender_kind`
31
+ //! — the framework must emit a typed marker distinguishing
32
+ //! `framework_replay` (new attempt) from `provider_rerender`
33
+ //! (transcript redraw, no new attempt). Baseline: no such
34
+ //! marker; every `send.injected` looks the same regardless of
35
+ //! whether it is a fresh submit or a resume redraw. → red.
36
+ //! 5. `restart_never_marks_delivered_as_accepted`
37
+ //! — a restart / resume path must not translate a re-rendered
38
+ //! transcript token into a new `messages.status = 'accepted'`
39
+ //! write for an already-`delivered` row. Baseline (invariant
40
+ //! lock, storm proved): green.
41
+ //!
42
+ //! POSITIVE CONTROL:
43
+ //! 6. `delivered_to_acknowledged_transition_still_allowed` — the
44
+ //! legitimate business terminal transition (delivered →
45
+ //! acknowledged via the ack API) must remain functional.
46
+ //!
47
+ //! NEGATIVE CONTROL:
48
+ //! 7. `stray_ack_call_does_not_promote_pending_row_to_acknowledged`
49
+ //! — a mis-scoped ack call must not skip a row's delivery lifecycle
50
+ //! and jump straight from `accepted` to `acknowledged`.
51
+ //!
52
+ //! FROZEN by verifier — do NOT modify without a new SHA256 signature.
53
+
54
+ #![cfg(unix)]
55
+ #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
56
+
57
+ use crate as team_agent;
58
+
59
+ #[path = "rehomed_env.rs"]
60
+ mod rehomed_env;
61
+
62
+ use std::path::PathBuf;
63
+ use std::sync::atomic::{AtomicU64, Ordering};
64
+
65
+ use rusqlite::params;
66
+ use serial_test::serial;
67
+ use team_agent::event_log::EventLog;
68
+ use team_agent::leader::{claim_lease_no_incident, LeaseStatus};
69
+ use team_agent::message_store::MessageStore;
70
+ use team_agent::messaging::enqueue_leader_mailbox_until_attach;
71
+ use team_agent::messaging::watchers::requeue_after_claim_leader;
72
+ use team_agent::model::enums::PaneLiveness;
73
+ use team_agent::model::ids::TeamKey;
74
+ use team_agent::state::owner_gate::PaneLivenessProbe;
75
+ use team_agent::transport::PaneId;
76
+
77
+ const TEAM: &str = "current";
78
+ const PANE: &str = "%leader";
79
+
80
+ struct LiveCaller;
81
+ impl PaneLivenessProbe for LiveCaller {
82
+ fn liveness(&self, pane_id: &str) -> PaneLiveness {
83
+ if pane_id == PANE {
84
+ PaneLiveness::Live
85
+ } else {
86
+ PaneLiveness::Dead
87
+ }
88
+ }
89
+ }
90
+
91
+ fn bound_state() -> serde_json::Value {
92
+ serde_json::json!({
93
+ "session_name": "team-terminal-inv",
94
+ "team_owner": {
95
+ "pane_id": PANE, "provider": "codex", "owner_epoch": 7,
96
+ "claimed_at": "2026-07-24T00:00:00Z", "claimed_via": "claim-leader"
97
+ },
98
+ "leader_receiver": {
99
+ "pane_id": PANE, "provider": "codex", "owner_epoch": 7, "status": "attached"
100
+ }
101
+ })
102
+ }
103
+
104
+ struct TermCase {
105
+ _env: rehomed_env::RehomedTestEnv,
106
+ workspace: PathBuf,
107
+ store: MessageStore,
108
+ event_log: EventLog,
109
+ }
110
+
111
+ impl TermCase {
112
+ fn new(tag: &str) -> Self {
113
+ static N: AtomicU64 = AtomicU64::new(0);
114
+ let env = rehomed_env::RehomedTestEnv::enter(tag);
115
+ let workspace = env.workspace(&format!(
116
+ "057f-term-{tag}-{}",
117
+ N.fetch_add(1, Ordering::Relaxed)
118
+ ));
119
+ let store = MessageStore::open(&workspace).unwrap();
120
+ let event_log = EventLog::new(&workspace);
121
+ Self {
122
+ _env: env,
123
+ workspace,
124
+ store,
125
+ event_log,
126
+ }
127
+ }
128
+
129
+ fn seed(&self, content: &str, status: &str, delivered_at: Option<&str>) -> String {
130
+ let mid = enqueue_leader_mailbox_until_attach(
131
+ &self.workspace,
132
+ TEAM,
133
+ content,
134
+ None,
135
+ "leader",
136
+ &self.event_log,
137
+ )
138
+ .unwrap()
139
+ .message_id
140
+ .expect("mailbox enqueue");
141
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
142
+ conn.execute(
143
+ "update messages set status = ?2, delivered_at = ?3, delivery_attempts = 1
144
+ where message_id = ?1",
145
+ params![mid, status, delivered_at],
146
+ )
147
+ .unwrap();
148
+ mid
149
+ }
150
+
151
+ fn full(&self, mid: &str) -> (String, Option<String>, i64) {
152
+ let conn = team_agent::db::schema::open_db(self.store.db_path()).unwrap();
153
+ conn.query_row(
154
+ "select status, delivered_at, delivery_attempts from messages where message_id = ?1",
155
+ params![mid],
156
+ |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
157
+ )
158
+ .unwrap()
159
+ }
160
+
161
+ fn claim_and_requeue(&self) -> LeaseStatus {
162
+ let team = TeamKey::new(TEAM.to_string());
163
+ let pane = PaneId::new(PANE);
164
+ let mut state = bound_state();
165
+ let result = claim_lease_no_incident(
166
+ &self.workspace,
167
+ &mut state,
168
+ None,
169
+ &team,
170
+ &pane,
171
+ true,
172
+ &self.event_log,
173
+ &LiveCaller,
174
+ )
175
+ .unwrap();
176
+ if result.ok {
177
+ let _ = requeue_after_claim_leader(
178
+ &self.workspace,
179
+ &self.store,
180
+ &self.event_log,
181
+ &team,
182
+ result.bound_pane_id.as_ref().expect("pane"),
183
+ None,
184
+ )
185
+ .unwrap();
186
+ }
187
+ result.status
188
+ }
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Tooth 1 — delivered row is byte-stable across claim (invariant lock)
193
+ // ---------------------------------------------------------------------------
194
+
195
+ #[test]
196
+ #[serial(env)]
197
+ fn delivered_row_immutable_under_any_transition() {
198
+ let case = TermCase::new("delivered-lock");
199
+ let mid = case.seed(
200
+ "delivered canary",
201
+ "delivered",
202
+ Some("2026-07-21T12:00:00Z"),
203
+ );
204
+ let before = case.full(&mid);
205
+ let _ = case.claim_and_requeue();
206
+ let after = case.full(&mid);
207
+ assert_eq!(
208
+ before, after,
209
+ "delivered row must be byte-stable across any typed-state transition; \
210
+ a re-enqueue would create a second physical submit for a message the \
211
+ framework already saw succeed. §3.3#5 + storm proof."
212
+ );
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Tooth 2 — acknowledged row cannot be regressed by generic mark
217
+ // ---------------------------------------------------------------------------
218
+
219
+ #[test]
220
+ #[serial(env)]
221
+ fn acknowledged_row_immutable_under_generic_mark() {
222
+ let case = TermCase::new("ack-lock");
223
+ let mid = case.seed("acked canary", "acknowledged", Some("2026-07-21T12:00:00Z"));
224
+ let before = case.full(&mid);
225
+ // Baseline weakness (distillation §1.2 acknowledged note): a generic
226
+ // `store.mark(mid, "target_resolved", None)` currently succeeds and
227
+ // silently regresses the row. Typed refactor MUST make this a
228
+ // hard-refused transition (typed error).
229
+ let mark_result = case.store.mark(&mid, "target_resolved", None);
230
+ let after = case.full(&mid);
231
+ assert!(
232
+ mark_result.is_err() || after == before,
233
+ "acknowledged row was regressed by a generic mark call: before={:?} \
234
+ after={:?} mark_result={:?}. §1.2 requires terminal states to refuse \
235
+ any downgrade from a non-ack API; typed refactor must return a typed \
236
+ error rather than silently overwrite.",
237
+ before,
238
+ after,
239
+ mark_result
240
+ );
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // Tooth 3 — consumed row is untouchable by any recovery arm
245
+ // ---------------------------------------------------------------------------
246
+
247
+ #[test]
248
+ #[serial(env)]
249
+ fn consumed_row_immutable_under_any_transition() {
250
+ let case = TermCase::new("consumed-lock");
251
+ let mid = case.seed("consumed canary", "consumed", None);
252
+ let before = case.full(&mid);
253
+ // A generic mark attempt should also refuse to regress a `consumed`
254
+ // row. Baseline: the same generic mark path applies, distillation
255
+ // §1.2 already flags this. Also verify claim/requeue does not touch
256
+ // it.
257
+ let mark_result = case.store.mark(&mid, "accepted", None);
258
+ let _ = case.claim_and_requeue();
259
+ let after = case.full(&mid);
260
+ assert!(
261
+ (mark_result.is_err() || after.0 == "consumed") && after.0 == "consumed",
262
+ "consumed row was affected: before={:?} after={:?} mark_result={:?}. \
263
+ §1.2 consumed is a terminal disposition; typed refactor must refuse \
264
+ any downgrade or requeue.",
265
+ before,
266
+ after,
267
+ mark_result
268
+ );
269
+ }
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // Tooth 4 — typed event carries framework_replay vs provider_rerender kind
273
+ // ---------------------------------------------------------------------------
274
+
275
+ #[test]
276
+ #[serial(env)]
277
+ fn typed_event_carries_replay_vs_rerender_kind() {
278
+ // Source-shape probe: the delivery + acceptance event schemas must
279
+ // include a typed `redisplay_kind` (or equivalent) enum with at
280
+ // least the two values.
281
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
282
+ let src = manifest.join("src");
283
+ let mut has_marker = false;
284
+ fn walk(dir: &std::path::Path, has_marker: &mut bool) {
285
+ let Ok(es) = std::fs::read_dir(dir) else {
286
+ return;
287
+ };
288
+ for e in es.flatten() {
289
+ let p = e.path();
290
+ if p.is_dir() {
291
+ walk(&p, has_marker);
292
+ } else if p.extension().and_then(|s| s.to_str()) == Some("rs") {
293
+ if let Ok(t) = std::fs::read_to_string(&p) {
294
+ if (t.contains("framework_replay") && t.contains("provider_rerender"))
295
+ || t.contains("RedisplayKind::FrameworkReplay")
296
+ || t.contains("RedisplayKind::ProviderRerender")
297
+ {
298
+ *has_marker = true;
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+ walk(&src, &mut has_marker);
305
+ assert!(
306
+ has_marker,
307
+ "no typed redisplay-kind marker found (framework_replay / provider_rerender). \
308
+ §1.4 requires diagnostics to distinguish framework replay (new attempt) from \
309
+ provider re-render (transcript redraw, no new attempt). Baseline: every \
310
+ 'send.injected' event looks identical."
311
+ );
312
+ }
313
+
314
+ // ---------------------------------------------------------------------------
315
+ // Tooth 5 — restart never marks delivered as accepted (invariant lock)
316
+ // ---------------------------------------------------------------------------
317
+
318
+ #[test]
319
+ #[serial(env)]
320
+ fn restart_never_marks_delivered_as_accepted() {
321
+ // Source-scan: no file may contain a SQL literal that translates a
322
+ // `delivered` row to `accepted`. We check for the substring
323
+ // `set status = 'accepted'` inside any block that also mentions
324
+ // `delivered`. This is a coarse guard; the more precise test lives
325
+ // in tooth 1 (behavioral). Both are cheap and complementary.
326
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
327
+ let src = manifest.join("src");
328
+ let mut violations: Vec<PathBuf> = Vec::new();
329
+ fn walk(dir: &std::path::Path, out: &mut Vec<PathBuf>) {
330
+ let Ok(es) = std::fs::read_dir(dir) else {
331
+ return;
332
+ };
333
+ for e in es.flatten() {
334
+ let p = e.path();
335
+ if p.is_dir() {
336
+ if p.file_name().and_then(|s| s.to_str()) == Some("tests") {
337
+ continue;
338
+ }
339
+ walk(&p, out);
340
+ } else if p.extension().and_then(|s| s.to_str()) == Some("rs") {
341
+ if let Ok(t) = std::fs::read_to_string(&p) {
342
+ let lines: Vec<&str> = t.lines().collect();
343
+ for (i, l) in lines.iter().enumerate() {
344
+ if l.contains("set status = 'accepted'") {
345
+ let start = i.saturating_sub(20);
346
+ let end = (i + 20).min(lines.len());
347
+ let window = lines[start..end].join("\n");
348
+ // Trigger only if `delivered` (not
349
+ // `delivered_at` alone) is treated as
350
+ // eligible.
351
+ if window.contains("status = 'delivered'")
352
+ || window.contains("'delivered'")
353
+ {
354
+ out.push(p.clone());
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ walk(&src, &mut violations);
363
+ // 0.5.55 storm proved this invariant held (delivered was untouched).
364
+ // Green at baseline; locked here.
365
+ assert!(
366
+ violations.is_empty(),
367
+ "regression: SQL that flips a `delivered` row to `accepted` found: {:?}. \
368
+ §3.3#5 storm proof — delivered must be irreversibly terminal.",
369
+ violations
370
+ );
371
+ }
372
+
373
+ // ---------------------------------------------------------------------------
374
+ // Tooth 6 — positive control: delivered→acknowledged is still supported
375
+ // ---------------------------------------------------------------------------
376
+
377
+ #[test]
378
+ #[serial(env)]
379
+ fn delivered_to_acknowledged_transition_still_allowed() {
380
+ let case = TermCase::new("pos-ack");
381
+ let mid = case.seed("business ack", "delivered", Some("2026-07-24T00:01:00Z"));
382
+ // Business ack path: mark the row acknowledged via the store API.
383
+ let r = case.store.mark(&mid, "acknowledged", None);
384
+ let (status, _, _) = case.full(&mid);
385
+ assert!(
386
+ r.is_ok() && status == "acknowledged",
387
+ "delivered → acknowledged legitimate business transition must remain \
388
+ functional (mark_result={:?}, status={})",
389
+ r,
390
+ status
391
+ );
392
+ }
393
+
394
+ // ---------------------------------------------------------------------------
395
+ // Tooth 7 — negative control: stray ack cannot leapfrog delivery
396
+ // ---------------------------------------------------------------------------
397
+
398
+ #[test]
399
+ #[serial(env)]
400
+ fn stray_ack_call_does_not_promote_pending_row_to_acknowledged() {
401
+ let case = TermCase::new("neg-stray-ack");
402
+ let mid = case.seed("pending row", "accepted", None);
403
+ let mark_result = case.store.mark(&mid, "acknowledged", None);
404
+ let (status, _, _) = case.full(&mid);
405
+ // Baseline weakness: generic mark permits a jump. Typed refactor
406
+ // must refuse. If mark succeeds today, this tooth is red; if it
407
+ // refuses, tooth stays green.
408
+ assert!(
409
+ mark_result.is_err() || status != "acknowledged",
410
+ "generic mark leap-frogged an accepted row directly to acknowledged \
411
+ (mark_result={:?}, status={}). §1.2 acknowledged transition must be \
412
+ enforced through the ack API's lifecycle guard, not permitted by \
413
+ any writer.",
414
+ mark_result,
415
+ status
416
+ );
417
+ }
@@ -1,94 +1,5 @@
1
1
  use super::*;
2
2
 
3
- // =====================================================================
4
- // 4. wake 层纯函数(unimplemented → RED)。golden:probe_leader.py 5 分支 + boundary。
5
- // =====================================================================
6
-
7
- #[test]
8
- fn should_reread_no_file_when_current_mtime_none() {
9
- // current_mtime is None → {reread:false, no_file}(wake.py:31-32)。
10
- let d = should_reread(None, None, None, 100.0, 60.0);
11
- assert!(!d.reread);
12
- assert_eq!(d.reason, RereadReason::NoFile);
13
- }
14
-
15
- #[test]
16
- fn should_reread_never_classified() {
17
- // last_classified_mtime is None → {reread:true, never_classified}。
18
- let d = should_reread(None, Some(10.0), None, 100.0, 60.0);
19
- assert!(d.reread);
20
- assert_eq!(d.reason, RereadReason::NeverClassified);
21
- }
22
-
23
- #[test]
24
- fn should_reread_file_changed() {
25
- // current != last_classified → {reread:true, file_changed}。
26
- let d = should_reread(Some(5.0), Some(20.0), Some(10.0), 100.0, 60.0);
27
- assert!(d.reread);
28
- assert_eq!(d.reason, RereadReason::FileChanged);
29
- }
30
-
31
- #[test]
32
- fn should_reread_quiescent_already_classified_at_and_past_debounce() {
33
- // current==last_classified 且 silent_for >= debounce → quiescent(不再重读)。
34
- // silent_for = max(0, now-current_mtime) = 100-10 = 90 >= 60。
35
- let d = should_reread(Some(10.0), Some(10.0), Some(10.0), 100.0, 60.0);
36
- assert!(!d.reread);
37
- assert_eq!(d.reason, RereadReason::QuiescentAlreadyClassified);
38
- // 边界:silent_for 恰 == debounce(now-current=60)→ 仍 quiescent(>= 比较)。
39
- let b = should_reread(Some(40.0), Some(40.0), Some(40.0), 100.0, 60.0);
40
- assert!(!b.reread);
41
- assert_eq!(b.reason, RereadReason::QuiescentAlreadyClassified);
42
- }
43
-
44
- #[test]
45
- fn should_reread_unchanged_within_debounce() {
46
- // current==last_classified 且 silent_for < debounce → unchanged。
47
- // now-current = 100-95 = 5 < 60。注:last_mtime 在 Python body 中未被使用。
48
- let d = should_reread(Some(10.0), Some(95.0), Some(95.0), 100.0, 60.0);
49
- assert!(!d.reread);
50
- assert_eq!(d.reason, RereadReason::Unchanged);
51
- }
52
-
53
- #[test]
54
- fn on_file_changed_adds_node_sorted_and_records_mtime() {
55
- // golden probe_leader.py:add b → pending ["b"];add a → sorted ["a","b"]。
56
- let s0 = on_file_changed(None, "b", 1.0);
57
- assert_eq!(s0.pending, vec!["b".to_string()]);
58
- assert_eq!(s0.mtimes.get("b"), Some(&1.0));
59
- let s1 = on_file_changed(Some(&s0), "a", 2.0);
60
- assert_eq!(
61
- s1.pending,
62
- vec!["a".to_string(), "b".to_string()],
63
- "pending 必须 sorted"
64
- );
65
- assert_eq!(s1.mtimes.get("a"), Some(&2.0));
66
- // 重复 add b:pending 仍是 set(不重复),mtime 被更新到 3.0。
67
- let s2 = on_file_changed(Some(&s1), "b", 3.0);
68
- assert_eq!(s2.pending, vec!["a".to_string(), "b".to_string()]);
69
- assert_eq!(s2.mtimes.get("b"), Some(&3.0));
70
- }
71
-
72
- #[test]
73
- fn take_pending_drains_sorted_and_clears_pending_but_keeps_mtimes() {
74
- // golden probe_leader.py:drain → (["a","b"], state{pending:[], mtimes 保留})。
75
- let mut st = on_file_changed(None, "b", 3.0);
76
- st = on_file_changed(Some(&st), "a", 2.0);
77
- let (drained, after) = take_pending(Some(&st));
78
- assert_eq!(drained, vec!["a".to_string(), "b".to_string()]);
79
- assert!(after.pending.is_empty());
80
- // mtimes 不被 drain 清空。
81
- assert_eq!(after.mtimes.get("a"), Some(&2.0));
82
- assert_eq!(after.mtimes.get("b"), Some(&3.0));
83
- // 再 drain → 空。
84
- let (drained2, _after2) = take_pending(Some(&after));
85
- assert!(drained2.is_empty());
86
- // drain None → ([], default state)。
87
- let (none_drained, none_state) = take_pending(None);
88
- assert!(none_drained.is_empty());
89
- assert!(none_state.pending.is_empty());
90
- }
91
-
92
3
  // =====================================================================
93
4
  // 5. leader_session_name — sha1 派生 + 文件夹消毒(unimplemented → RED)
94
5
  // =====================================================================
@@ -194,28 +105,33 @@ fn owner_bind_refused_event_name_is_owner_bind_refused() {
194
105
  assert_eq!(LeaderEvent::OwnerBindRefused.name(), "owner.bind_refused");
195
106
  }
196
107
 
197
- // emit_owner_bound_event:成功绑定 hook(owner.bound_from_caller_pane;leader_binding.py:162-183)。
108
+ // bind_owner_from_caller_pane:成功绑定后调用 owner.bound_from_caller_pane 审计 hook
109
+ // (leader_binding.py:162-183)。
198
110
  // 强化(no-full-uuid-leak 命门):事件只写 derived_uuid_prefix == derived[:12](12 hex),
199
111
  // old uuid 为 None → old_uuid_prefix == ""(空串,非缺省);全 32 hex uuid 绝不出现在任何字段。
200
112
  // unimplemented → RED。
201
113
  #[test]
202
114
  fn emit_owner_bound_event_logs_prefix_only_never_full_uuid() {
115
+ let _lock = ENV_LOCK.lock().unwrap();
203
116
  let ws = std::env::temp_dir().join(format!("ta_rs_emit_{}", std::process::id()));
204
117
  std::fs::create_dir_all(&ws).unwrap();
205
- let caller = PaneId::new("%7");
206
- let derived = uuid("fp", "/ws", "u", "default");
207
- let full = derived.as_str().to_string();
118
+ let _env = EnvGuard::apply(&[
119
+ ("TMUX_PANE", Some("%7")),
120
+ ("TEAM_AGENT_MACHINE_FINGERPRINT", Some("fp")),
121
+ ("TEAM_AGENT_LEADER_PROVIDER", Some("codex")),
122
+ ("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
123
+ ]);
124
+ let result = bind_owner_from_caller_pane(&ws, &TeamKey::new("default"), None).unwrap();
125
+ assert!(result.ok, "caller pane should produce an owner binding");
126
+ let owner = result.owner.as_ref().expect("successful bind has owner");
127
+ let full = owner
128
+ .leader_session_uuid
129
+ .as_ref()
130
+ .expect("successful bind has leader session uuid")
131
+ .as_str()
132
+ .to_string();
208
133
  assert_eq!(full.len(), 32, "derive 产 32 hex");
209
134
  let prefix12 = full[..12].to_string();
210
- emit_owner_bound_event(
211
- &ws,
212
- &caller,
213
- "codex",
214
- &derived,
215
- &TeamKey::new("default"),
216
- None,
217
- )
218
- .unwrap();
219
135
  // 读回审计事件:恰一条 owner.bound_from_caller_pane。
220
136
  let events = crate::event_log::EventLog::new(&ws).tail(50).unwrap();
221
137
  let ev = events
@@ -223,7 +139,10 @@ fn emit_owner_bound_event_logs_prefix_only_never_full_uuid() {
223
139
  .find(|e| e["event"] == serde_json::json!("owner.bound_from_caller_pane"))
224
140
  .expect("必写 owner.bound_from_caller_pane");
225
141
  assert_eq!(ev["caller_pane_id"], serde_json::json!("%7"));
226
- assert_eq!(ev["caller_current_command"], serde_json::json!("codex"));
142
+ assert_eq!(
143
+ ev["caller_current_command"],
144
+ serde_json::json!(result.caller_current_command)
145
+ );
227
146
  assert_eq!(ev["team_id"], serde_json::json!("default"));
228
147
  // derived_uuid_prefix == derived[:12](只前缀,12 hex)。
229
148
  assert_eq!(ev["derived_uuid_prefix"], serde_json::json!(prefix12));
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! leader 数据层 — lease/owner/incident 枚举 + struct + 错误 + cross-lane 占位/trait + 锁名常量。
2
3
 
3
4
  use std::collections::BTreeMap;
@@ -472,34 +473,6 @@ pub enum LeaderError {
472
473
  Messaging(#[from] crate::messaging::MessagingError),
473
474
  }
474
475
 
475
- // ===========================================================================
476
- // CROSS-LANE PLACEHOLDERS — 其他并行 lane 的 typed 依赖,leader 集成时 reconcile。
477
- // (本骨架定义 MINIMAL 本地占位,不猜对方精确命名;见 cross_deps_or_placeholders。)
478
- // ===========================================================================
479
-
480
- /// 命名互斥锁句柄占位(step 11 messaging / runtime 的 `_runtime_lock(workspace, name)`)。
481
- /// `LEADER_OWNERSHIP_LOCK = "send"`:attach/claim/takeover/autobind 共享同一临界区。
482
- /// **PLACEHOLDER**:真锁类型(文件锁 / RAII guard)在 runtime/messaging lane 落地。
483
- pub struct RuntimeLockGuard {
484
- pub _name: String,
485
- }
486
-
487
- /// MessageStore 占位引用(step 7 已有 `message_store::MessageStore`,但 requeue 方法
488
- /// `requeue_delivery_exhausted_watchers` / `requeue_after_claim_leader` 属 step 11 messaging)。
489
- /// **PLACEHOLDER**:claim/attach 后 requeue exhausted result watchers 的接口签名,
490
- /// 由 messaging lane 落地。
491
- pub struct RequeueOutcome {
492
- pub watcher_ids: Vec<String>,
493
- }
494
-
495
- /// `_resolve_leader_pane` 返回的 pane 信息占位(step 9 transport `PaneInfo` 的相邻产物)。
496
- /// `__init__.py:276` 调 `runtime._resolve_leader_pane` 返 `(pane_info, discovery)`。
497
- /// **PLACEHOLDER**:实际 pane_info 字段映射到 `transport::PaneInfo`,resolve 逻辑在 step 9/11。
498
- pub struct ResolvedLeaderPane {
499
- pub pane_info: Value,
500
- pub discovery: Discovery,
501
- }
502
-
503
476
  // ===========================================================================
504
477
  // 常量 / 锁名
505
478
  // ===========================================================================
@@ -35,6 +35,9 @@
35
35
  //! provider client crate,测试断言 mock 调用计数 = 0。
36
36
 
37
37
  // step 2 (model) — typed bedrock。其余 step 落地时在此挂载。
38
+ #[cfg(test)]
39
+ extern crate self as team_agent;
40
+
38
41
  pub mod model;
39
42
 
40
43
  // step 3 (db) — team.db SQLite schema/migration(rusqlite bundled)。
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! lifecycle::display —— 能力门 / 后端解析 / 开关 / rebind 后重建。
2
3
  //!
3
4
  //! 0.5.39 Slice 1 (tmux-server-death-locate §7 Slice 1): all tmux
@@ -19,6 +20,7 @@ use super::*;
19
20
 
20
21
  // ── lifecycle::display —— 能力门 / 后端解析 / 开关 / rebind 后重建 ────────────
21
22
 
23
+ ///
22
24
  /// `resolve_display_backend(requested, recorded, source)`(`display/backend.py`)。默认
23
25
  /// adaptive;显式 none 是逃生口。
24
26
  pub fn resolve_display_backend(
@@ -32,6 +34,7 @@ pub fn resolve_display_backend(
32
34
  }
33
35
  }
34
36
 
37
+ ///
35
38
  /// `probe_display_capabilities(...)`(`display/adaptive.py:31`,C13)。能力探测,分支只看
36
39
  /// 结果不看 `cfg!(target_os)`;Windows/WSL → `NotImplementedThisPlatform`。
37
40
  pub fn probe_display_capabilities(_workspace: &Path) -> Result<DisplayProbe, LifecycleError> {
@@ -68,10 +71,11 @@ pub fn probe_display_capabilities(_workspace: &Path) -> Result<DisplayProbe, Lif
68
71
  })
69
72
  }
70
73
 
74
+ ///
71
75
  /// `open_worker_displays(workspace, session_name, jobs, backend, capability_probe)`
72
76
  /// (`display/worker_window.py`)。按后端分派 adaptive / ghostty_workspace / ghostty_window;
73
77
  /// 显示失败**不阻塞** team readiness(C14)。
74
- pub fn open_worker_displays(
78
+ pub(crate) fn open_worker_displays(
75
79
  workspace: &Path,
76
80
  session_name: &SessionName,
77
81
  backend: DisplayBackend,
@@ -91,6 +95,7 @@ pub fn open_worker_displays(
91
95
  Ok(OpenDisplaysReport { backend, displays })
92
96
  }
93
97
 
98
+ ///
94
99
  /// `close_team_display_backends(state, event_log)`(`display/close.py`,C9)。按 state
95
100
  /// 记录的后端关显示;adaptive 只删带 team tag 的窗口(C2 leader pane 安全)+ orphan 清理。
96
101
  pub fn close_team_display_backends(
@@ -100,6 +105,7 @@ pub fn close_team_display_backends(
100
105
  close_adaptive_displays(workspace, session_name)
101
106
  }
102
107
 
108
+ ///
103
109
  /// `rebuild_adaptive_display_after_rebind(...)`(`display/rebuild.py`,C12)。restart 在
104
110
  /// leader rebind **之后**重建 adaptive 窗口(读 `leader_receiver.rebind_applied` 事件)。
105
111
  pub fn rebuild_adaptive_display_after_rebind(