@team-agent/installer 0.2.11 → 0.3.0

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 (326) hide show
  1. package/Cargo.lock +744 -0
  2. package/Cargo.toml +34 -0
  3. package/crates/team-agent/Cargo.toml +33 -0
  4. package/crates/team-agent/src/cli/adapters.rs +1343 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +554 -0
  6. package/crates/team-agent/src/cli/emit.rs +1077 -0
  7. package/crates/team-agent/src/cli/helpers.rs +88 -0
  8. package/crates/team-agent/src/cli/leader.rs +216 -0
  9. package/crates/team-agent/src/cli/mod.rs +1141 -0
  10. package/crates/team-agent/src/cli/profile.rs +306 -0
  11. package/crates/team-agent/src/cli/send.rs +215 -0
  12. package/crates/team-agent/src/cli/status.rs +179 -0
  13. package/crates/team-agent/src/cli/status_port.rs +502 -0
  14. package/crates/team-agent/src/cli/tests/base.rs +616 -0
  15. package/crates/team-agent/src/cli/tests/compile.rs +96 -0
  16. package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
  21. package/crates/team-agent/src/cli/tests/mod.rs +97 -0
  22. package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
  23. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
  25. package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
  27. package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
  28. package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
  29. package/crates/team-agent/src/cli/types.rs +605 -0
  30. package/crates/team-agent/src/compiler/tests.rs +701 -0
  31. package/crates/team-agent/src/compiler.rs +489 -0
  32. package/crates/team-agent/src/coordinator/backoff.rs +153 -0
  33. package/crates/team-agent/src/coordinator/health.rs +436 -0
  34. package/crates/team-agent/src/coordinator/mod.rs +80 -0
  35. package/crates/team-agent/src/coordinator/orphan.rs +179 -0
  36. package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
  37. package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
  38. package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
  39. package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
  40. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
  41. package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
  42. package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
  43. package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
  44. package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
  45. package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
  46. package/crates/team-agent/src/coordinator/tick.rs +2032 -0
  47. package/crates/team-agent/src/coordinator/types.rs +584 -0
  48. package/crates/team-agent/src/db/migration.rs +716 -0
  49. package/crates/team-agent/src/db/mod.rs +23 -0
  50. package/crates/team-agent/src/db/schema.rs +378 -0
  51. package/crates/team-agent/src/event_log.rs +375 -0
  52. package/crates/team-agent/src/fake_worker.rs +253 -0
  53. package/crates/team-agent/src/leader/helpers.rs +190 -0
  54. package/crates/team-agent/src/leader/inject.rs +33 -0
  55. package/crates/team-agent/src/leader/lease.rs +1063 -0
  56. package/crates/team-agent/src/leader/mod.rs +99 -0
  57. package/crates/team-agent/src/leader/owner_bind.rs +292 -0
  58. package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1099 -0
  60. package/crates/team-agent/src/leader/start.rs +273 -0
  61. package/crates/team-agent/src/leader/takeover.rs +235 -0
  62. package/crates/team-agent/src/leader/tests/basics.rs +183 -0
  63. package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +271 -0
  66. package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
  67. package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
  68. package/crates/team-agent/src/leader/tests/mod.rs +125 -0
  69. package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
  70. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
  71. package/crates/team-agent/src/leader/types.rs +487 -0
  72. package/crates/team-agent/src/lib.rs +85 -0
  73. package/crates/team-agent/src/lifecycle/display.rs +228 -0
  74. package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
  75. package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
  76. package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
  77. package/crates/team-agent/src/lifecycle/mod.rs +62 -0
  78. package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
  79. package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
  80. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
  81. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
  82. package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
  83. package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
  84. package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
  85. package/crates/team-agent/src/lifecycle/restart.rs +76 -0
  86. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
  88. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
  89. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
  90. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
  91. package/crates/team-agent/src/lifecycle/tests.rs +27 -0
  92. package/crates/team-agent/src/lifecycle/types.rs +685 -0
  93. package/crates/team-agent/src/main.rs +41 -0
  94. package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
  95. package/crates/team-agent/src/mcp_server/mod.rs +183 -0
  96. package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
  97. package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
  98. package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
  99. package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
  100. package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
  101. package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
  102. package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
  103. package/crates/team-agent/src/mcp_server/tests.rs +38 -0
  104. package/crates/team-agent/src/mcp_server/tools.rs +603 -0
  105. package/crates/team-agent/src/mcp_server/types.rs +421 -0
  106. package/crates/team-agent/src/mcp_server/wire.rs +388 -0
  107. package/crates/team-agent/src/message_store.rs +767 -0
  108. package/crates/team-agent/src/messaging/activity.rs +433 -0
  109. package/crates/team-agent/src/messaging/delivery.rs +542 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
  112. package/crates/team-agent/src/messaging/mod.rs +147 -0
  113. package/crates/team-agent/src/messaging/peers.rs +32 -0
  114. package/crates/team-agent/src/messaging/results.rs +537 -0
  115. package/crates/team-agent/src/messaging/scheduler.rs +344 -0
  116. package/crates/team-agent/src/messaging/selftest.rs +100 -0
  117. package/crates/team-agent/src/messaging/send.rs +582 -0
  118. package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
  119. package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
  121. package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
  122. package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
  123. package/crates/team-agent/src/messaging/trust.rs +192 -0
  124. package/crates/team-agent/src/messaging/types.rs +355 -0
  125. package/crates/team-agent/src/messaging/watchers.rs +591 -0
  126. package/crates/team-agent/src/model/enums.rs +311 -0
  127. package/crates/team-agent/src/model/errors.rs +17 -0
  128. package/crates/team-agent/src/model/ids.rs +155 -0
  129. package/crates/team-agent/src/model/mod.rs +22 -0
  130. package/crates/team-agent/src/model/paths.rs +228 -0
  131. package/crates/team-agent/src/model/permissions.rs +567 -0
  132. package/crates/team-agent/src/model/routing.rs +340 -0
  133. package/crates/team-agent/src/model/spec.rs +680 -0
  134. package/crates/team-agent/src/model/task_graph.rs +380 -0
  135. package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
  136. package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
  137. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
  138. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
  139. package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
  140. package/crates/team-agent/src/model/yaml/tests.rs +288 -0
  141. package/crates/team-agent/src/model/yaml.rs +800 -0
  142. package/crates/team-agent/src/packaging/install.rs +305 -0
  143. package/crates/team-agent/src/packaging/migrate.rs +30 -0
  144. package/crates/team-agent/src/packaging/mod.rs +82 -0
  145. package/crates/team-agent/src/packaging/repair.rs +24 -0
  146. package/crates/team-agent/src/packaging/tests.rs +829 -0
  147. package/crates/team-agent/src/packaging/types.rs +369 -0
  148. package/crates/team-agent/src/provider/adapter.rs +801 -0
  149. package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
  150. package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
  151. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
  152. package/crates/team-agent/src/provider/classify.rs +456 -0
  153. package/crates/team-agent/src/provider/faults.rs +136 -0
  154. package/crates/team-agent/src/provider/helpers.rs +41 -0
  155. package/crates/team-agent/src/provider/mod.rs +53 -0
  156. package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
  157. package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
  158. package/crates/team-agent/src/provider/tests/classify.rs +240 -0
  159. package/crates/team-agent/src/provider/tests/faults.rs +120 -0
  160. package/crates/team-agent/src/provider/tests/idle.rs +208 -0
  161. package/crates/team-agent/src/provider/tests/wire.rs +213 -0
  162. package/crates/team-agent/src/provider/tests.rs +31 -0
  163. package/crates/team-agent/src/provider/types.rs +424 -0
  164. package/crates/team-agent/src/state/identity.rs +656 -0
  165. package/crates/team-agent/src/state/mod.rs +58 -0
  166. package/crates/team-agent/src/state/owner_gate.rs +423 -0
  167. package/crates/team-agent/src/state/persist.rs +712 -0
  168. package/crates/team-agent/src/state/projection.rs +657 -0
  169. package/crates/team-agent/src/state/selector.rs +105 -0
  170. package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
  171. package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
  172. package/crates/team-agent/src/tmux_backend.rs +758 -0
  173. package/crates/team-agent/src/transport/test_support.rs +252 -0
  174. package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
  175. package/crates/team-agent/src/transport/tests/mod.rs +199 -0
  176. package/crates/team-agent/src/transport/tests/wire.rs +527 -0
  177. package/crates/team-agent/src/transport.rs +774 -0
  178. package/npm/install.mjs +90 -106
  179. package/package.json +15 -13
  180. package/crates/team-agent-core/Cargo.toml +0 -12
  181. package/crates/team-agent-core/src/lib.rs +0 -332
  182. package/crates/team-agent-core/src/main.rs +0 -152
  183. package/pyproject.toml +0 -18
  184. package/scripts/install.py +0 -88
  185. package/scripts/run_regression_tests.py +0 -83
  186. package/src/team_agent/__init__.py +0 -3
  187. package/src/team_agent/__main__.py +0 -5
  188. package/src/team_agent/_legacy_pane_discovery.py +0 -186
  189. package/src/team_agent/abnormal_track.py +0 -253
  190. package/src/team_agent/approvals/__init__.py +0 -65
  191. package/src/team_agent/approvals/constants.py +0 -6
  192. package/src/team_agent/approvals/parsing.py +0 -176
  193. package/src/team_agent/approvals/runtime_prompts.py +0 -171
  194. package/src/team_agent/approvals/status.py +0 -176
  195. package/src/team_agent/cli/__init__.py +0 -137
  196. package/src/team_agent/cli/commands.py +0 -481
  197. package/src/team_agent/cli/e2e.py +0 -202
  198. package/src/team_agent/cli/helpers.py +0 -226
  199. package/src/team_agent/cli/parser.py +0 -540
  200. package/src/team_agent/compiler.py +0 -334
  201. package/src/team_agent/coordinator/__init__.py +0 -53
  202. package/src/team_agent/coordinator/__main__.py +0 -119
  203. package/src/team_agent/coordinator/lifecycle.py +0 -411
  204. package/src/team_agent/coordinator/metadata.py +0 -61
  205. package/src/team_agent/coordinator/paths.py +0 -17
  206. package/src/team_agent/diagnose/__init__.py +0 -48
  207. package/src/team_agent/diagnose/checks.py +0 -101
  208. package/src/team_agent/diagnose/comms.py +0 -213
  209. package/src/team_agent/diagnose/health.py +0 -241
  210. package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
  211. package/src/team_agent/diagnose/preflight.py +0 -194
  212. package/src/team_agent/diagnose/quick_start.py +0 -324
  213. package/src/team_agent/display/__init__.py +0 -92
  214. package/src/team_agent/display/adaptive.py +0 -511
  215. package/src/team_agent/display/backend.py +0 -46
  216. package/src/team_agent/display/close.py +0 -154
  217. package/src/team_agent/display/ghostty.py +0 -77
  218. package/src/team_agent/display/rebuild.py +0 -102
  219. package/src/team_agent/display/tiling.py +0 -156
  220. package/src/team_agent/display/worker_window.py +0 -114
  221. package/src/team_agent/display/workspace.py +0 -382
  222. package/src/team_agent/errors.py +0 -10
  223. package/src/team_agent/events.py +0 -84
  224. package/src/team_agent/fake_worker.py +0 -80
  225. package/src/team_agent/idle_predicate.py +0 -218
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -114
  228. package/src/team_agent/launch/__init__.py +0 -41
  229. package/src/team_agent/launch/bootstrap.py +0 -85
  230. package/src/team_agent/launch/config.py +0 -106
  231. package/src/team_agent/launch/core.py +0 -301
  232. package/src/team_agent/launch/requirements.py +0 -57
  233. package/src/team_agent/leader/__init__.py +0 -926
  234. package/src/team_agent/leader_binding.py +0 -183
  235. package/src/team_agent/lifecycle/__init__.py +0 -5
  236. package/src/team_agent/lifecycle/agents.py +0 -278
  237. package/src/team_agent/lifecycle/operations.py +0 -411
  238. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
  239. package/src/team_agent/lifecycle/start.py +0 -363
  240. package/src/team_agent/mcp_server/__init__.py +0 -42
  241. package/src/team_agent/mcp_server/__main__.py +0 -7
  242. package/src/team_agent/mcp_server/contracts.py +0 -148
  243. package/src/team_agent/mcp_server/normalize.py +0 -257
  244. package/src/team_agent/mcp_server/server.py +0 -150
  245. package/src/team_agent/mcp_server/tools.py +0 -352
  246. package/src/team_agent/message_store/__init__.py +0 -23
  247. package/src/team_agent/message_store/agent_health.py +0 -113
  248. package/src/team_agent/message_store/core.py +0 -497
  249. package/src/team_agent/message_store/leader_notification_log.py +0 -198
  250. package/src/team_agent/message_store/result_watchers.py +0 -251
  251. package/src/team_agent/message_store/schema.py +0 -308
  252. package/src/team_agent/message_store/schema_migration.py +0 -448
  253. package/src/team_agent/messaging/__init__.py +0 -1
  254. package/src/team_agent/messaging/activity_detector.py +0 -262
  255. package/src/team_agent/messaging/delivery.py +0 -504
  256. package/src/team_agent/messaging/deps.py +0 -247
  257. package/src/team_agent/messaging/idle_alerts.py +0 -423
  258. package/src/team_agent/messaging/internal_delivery.py +0 -46
  259. package/src/team_agent/messaging/leader.py +0 -497
  260. package/src/team_agent/messaging/leader_api_errors.py +0 -216
  261. package/src/team_agent/messaging/leader_panes.py +0 -673
  262. package/src/team_agent/messaging/owner_bypass.py +0 -29
  263. package/src/team_agent/messaging/result_delivery.py +0 -539
  264. package/src/team_agent/messaging/results.py +0 -447
  265. package/src/team_agent/messaging/scheduler.py +0 -450
  266. package/src/team_agent/messaging/send.py +0 -532
  267. package/src/team_agent/messaging/session_drift.py +0 -94
  268. package/src/team_agent/messaging/tmux_io.py +0 -506
  269. package/src/team_agent/messaging/tmux_prompt.py +0 -338
  270. package/src/team_agent/messaging/trust_auto_answer.py +0 -52
  271. package/src/team_agent/orchestrator/__init__.py +0 -376
  272. package/src/team_agent/orchestrator/plan.py +0 -122
  273. package/src/team_agent/orchestrator/state.py +0 -128
  274. package/src/team_agent/paths.py +0 -45
  275. package/src/team_agent/permissions.py +0 -123
  276. package/src/team_agent/profiles/__init__.py +0 -82
  277. package/src/team_agent/profiles/constants.py +0 -19
  278. package/src/team_agent/profiles/core.py +0 -407
  279. package/src/team_agent/profiles/helpers.py +0 -69
  280. package/src/team_agent/profiles/provider_env.py +0 -188
  281. package/src/team_agent/profiles/smoke.py +0 -201
  282. package/src/team_agent/provider_cli/__init__.py +0 -43
  283. package/src/team_agent/provider_cli/adapter.py +0 -172
  284. package/src/team_agent/provider_cli/base.py +0 -48
  285. package/src/team_agent/provider_cli/claude.py +0 -503
  286. package/src/team_agent/provider_cli/codex.py +0 -336
  287. package/src/team_agent/provider_cli/copilot.py +0 -8
  288. package/src/team_agent/provider_cli/fake.py +0 -39
  289. package/src/team_agent/provider_cli/gemini.py +0 -95
  290. package/src/team_agent/provider_cli/opencode.py +0 -8
  291. package/src/team_agent/provider_cli/prompt.py +0 -62
  292. package/src/team_agent/provider_cli/registry.py +0 -18
  293. package/src/team_agent/provider_cli/unsupported.py +0 -32
  294. package/src/team_agent/provider_state/README.md +0 -78
  295. package/src/team_agent/provider_state/__init__.py +0 -91
  296. package/src/team_agent/provider_state/claude.py +0 -86
  297. package/src/team_agent/provider_state/codex.py +0 -84
  298. package/src/team_agent/provider_state/common.py +0 -207
  299. package/src/team_agent/provider_state/registry.py +0 -118
  300. package/src/team_agent/providers.py +0 -163
  301. package/src/team_agent/quality_gates.py +0 -104
  302. package/src/team_agent/restart/__init__.py +0 -34
  303. package/src/team_agent/restart/orchestration.py +0 -554
  304. package/src/team_agent/restart/selection.py +0 -89
  305. package/src/team_agent/restart/snapshot.py +0 -70
  306. package/src/team_agent/routing.py +0 -84
  307. package/src/team_agent/runtime.py +0 -1243
  308. package/src/team_agent/rust_core.py +0 -327
  309. package/src/team_agent/sessions/__init__.py +0 -25
  310. package/src/team_agent/sessions/capture.py +0 -144
  311. package/src/team_agent/sessions/inventory.py +0 -44
  312. package/src/team_agent/sessions/resume.py +0 -135
  313. package/src/team_agent/simple_yaml.py +0 -236
  314. package/src/team_agent/spec.py +0 -370
  315. package/src/team_agent/state.py +0 -693
  316. package/src/team_agent/status/__init__.py +0 -63
  317. package/src/team_agent/status/approvals.py +0 -52
  318. package/src/team_agent/status/compact.py +0 -158
  319. package/src/team_agent/status/constants.py +0 -18
  320. package/src/team_agent/status/inbox.py +0 -58
  321. package/src/team_agent/status/peek.py +0 -117
  322. package/src/team_agent/status/queries.py +0 -199
  323. package/src/team_agent/task_graph.py +0 -80
  324. package/src/team_agent/terminal.py +0 -57
  325. package/src/team_agent/wake.py +0 -58
  326. package/src/team_agent/watch/__init__.py +0 -145
@@ -0,0 +1,253 @@
1
+ use super::*;
2
+
3
+ // = RED. The porter must reproduce the EXACT LeaseResult shape per outcome.
4
+ // We seed state.json directly and inject a PaneLivenessProbe so no real tmux
5
+ // is needed for the no-pane / live-owner / dead-owner gate decisions.
6
+
7
+ /// Injectable liveness probe (`state::owner_gate::PaneLivenessProbe`): a pane
8
+ /// in the seeded set is `Live`, otherwise `Dead`. The porter's real probe
9
+ /// (step 9 tmux) replaces it; here it drives the no-incident lease gate
10
+ /// (bound-pane liveness + caller eligibility) deterministically with no tmux.
11
+ #[test]
12
+ #[serial_test::serial(env)]
13
+ fn claim_lease_no_incident_vacant_acquire_advances_epoch_and_dual_writes() {
14
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
15
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
16
+ let ws = p2_temp_ws("claim_vacant");
17
+ let team_id = TeamKey::new("current");
18
+ let caller = PaneId::new("%5");
19
+ // empty (vacant) state with a session so dual-state writes both files.
20
+ let mut state = serde_json::json!({"session_name": "team-agent-x"});
21
+ let event_log = crate::event_log::EventLog::new(&ws);
22
+ let live = seeded_liveness(&["%5"]);
23
+ let r = claim_lease_no_incident(
24
+ &ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
25
+ )
26
+ .unwrap();
27
+ assert!(r.ok);
28
+ assert_eq!(r.status, LeaseStatus::Claimed);
29
+ assert_eq!(r.reason, Some(LeaseReason::VacantAcquired));
30
+ assert_eq!(r.owner_epoch, Some(OwnerEpoch(1)), "vacant acquire 0→1");
31
+ let owner = r.owner.as_ref().expect("claimed → owner");
32
+ assert_eq!(owner.pane_id, caller);
33
+ assert_eq!(owner.owner_epoch, OwnerEpoch(1));
34
+ assert_eq!(owner.claimed_via, ClaimedVia::ClaimLeader);
35
+ let receiver = r.receiver.as_ref().expect("claimed → receiver");
36
+ assert_eq!(receiver.pane_id, caller);
37
+ assert_eq!(receiver.discovery, Some(Discovery::ClaimLeader));
38
+ // dual-state: BOTH workspace state.json + team/<session> snapshot carry %5/epoch 1.
39
+ let ws_path = crate::state::persist::runtime_state_path(&ws);
40
+ let ws_state: serde_json::Value =
41
+ serde_json::from_str(&std::fs::read_to_string(&ws_path).unwrap()).unwrap();
42
+ assert_eq!(ws_state["team_owner"]["pane_id"], serde_json::json!("%5"));
43
+ assert_eq!(ws_state["team_owner"]["owner_epoch"], serde_json::json!(1));
44
+ let snap_path = crate::model::paths::runtime_dir(&ws)
45
+ .join("teams").join("team-agent-x").join("state.json");
46
+ let snap: serde_json::Value =
47
+ serde_json::from_str(&std::fs::read_to_string(&snap_path).unwrap()).unwrap();
48
+ assert_eq!(snap["team_owner"]["pane_id"], serde_json::json!("%5"), "dual-state snapshot owner");
49
+ assert_eq!(snap["team_owner"]["owner_epoch"], serde_json::json!(1));
50
+ }
51
+
52
+ // RED — DEAD-OWNER RECOVER: bound pane %1 absent from live set, caller %5
53
+ // eligible, confirm=false → still acquires (dead owner never blocks),
54
+ // reason=previous_owner_pane_dead, epoch 3→4. golden /tmp/probe_confirm.py.
55
+ #[test]
56
+ #[serial_test::serial(env)]
57
+ fn claim_lease_no_incident_dead_owner_recovers_without_confirm() {
58
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
59
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
60
+ let ws = p2_temp_ws("claim_dead");
61
+ let team_id = TeamKey::new("current");
62
+ let caller = PaneId::new("%5");
63
+ let mut state = serde_json::json!({
64
+ "session_name": "team-agent-x",
65
+ "team_owner": {"pane_id":"%1","provider":"codex","machine_fingerprint":"fp","leader_session_uuid":"OLDUUID","owner_epoch":3,"claimed_at":"t","claimed_via":"claim-leader"},
66
+ "leader_receiver": {"pane_id":"%1","owner_epoch":3,"leader_session_uuid":"OLDUUID"},
67
+ });
68
+ let event_log = crate::event_log::EventLog::new(&ws);
69
+ // %1 (the recorded owner) is NOT live; only the caller %5 is live.
70
+ let live = seeded_liveness(&["%5"]);
71
+ let r = claim_lease_no_incident(
72
+ &ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
73
+ )
74
+ .unwrap();
75
+ assert!(r.ok);
76
+ assert_eq!(r.status, LeaseStatus::Claimed);
77
+ assert_eq!(r.reason, Some(LeaseReason::PreviousOwnerPaneDead));
78
+ assert_eq!(r.owner_epoch, Some(OwnerEpoch(4)), "dead-owner recover 3→4");
79
+ assert_eq!(r.owner.as_ref().unwrap().pane_id, caller);
80
+ }
81
+
82
+ // RED — LIVE-OWNER REFUSAL (no --confirm): bound pane %1 live, caller %5,
83
+ // confirm=false → refused force_confirm_required + action + bound_pane_id=%1
84
+ // + owner_epoch=2 (precheck epoch, NOT advanced). golden /tmp/probe_confirm.py.
85
+ #[test]
86
+ #[serial_test::serial(env)]
87
+ fn claim_lease_no_incident_live_owner_refuses_without_confirm() {
88
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
89
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
90
+ let ws = p2_temp_ws("claim_live");
91
+ let team_id = TeamKey::new("current");
92
+ let caller = PaneId::new("%5");
93
+ let mut state = serde_json::json!({
94
+ "session_name": "team-agent-x",
95
+ "team_owner": {"pane_id":"%1","provider":"codex","machine_fingerprint":"fp","leader_session_uuid":"OWNERUUID","owner_epoch":2,"claimed_at":"t","claimed_via":"claim-leader"},
96
+ "leader_receiver": {"pane_id":"%1","owner_epoch":2,"leader_session_uuid":"OWNERUUID"},
97
+ });
98
+ let event_log = crate::event_log::EventLog::new(&ws);
99
+ // both %1 (live owner, matching uuid) and %5 (eligible caller) live.
100
+ let live = seeded_liveness(&["%1", "%5"]);
101
+ let r = claim_lease_no_incident(
102
+ &ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
103
+ )
104
+ .unwrap();
105
+ assert!(!r.ok);
106
+ assert_eq!(r.status, LeaseStatus::Refused);
107
+ assert_eq!(r.reason, Some(LeaseReason::ForceConfirmRequired));
108
+ assert_eq!(
109
+ r.action.as_deref(),
110
+ Some("rerun with --confirm to take over the live leader pane")
111
+ );
112
+ assert_eq!(r.bound_pane_id, Some(PaneId::new("%1")), "refusal carries bound pane");
113
+ assert_eq!(r.owner_epoch, Some(OwnerEpoch(2)), "refused → precheck epoch, NOT advanced");
114
+ }
115
+
116
+ // RED — ALREADY-BOUND: caller pane == bound pane → status=AlreadyBound,
117
+ // ok=true, owner_epoch unchanged (no advance, no rewrite). golden probe_claimed.py.
118
+ #[test]
119
+ #[serial_test::serial(env)]
120
+ fn claim_lease_no_incident_already_bound_when_caller_is_bound_pane() {
121
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
122
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
123
+ let ws = p2_temp_ws("claim_bound");
124
+ let team_id = TeamKey::new("current");
125
+ let caller = PaneId::new("%5");
126
+ let mut state = serde_json::json!({
127
+ "session_name": "team-agent-x",
128
+ "team_owner": {"pane_id":"%5","provider":"codex","machine_fingerprint":"fp","leader_session_uuid":"U","owner_epoch":1,"claimed_at":"t","claimed_via":"claim-leader"},
129
+ "leader_receiver": {"pane_id":"%5","owner_epoch":1,"leader_session_uuid":"U"},
130
+ });
131
+ let event_log = crate::event_log::EventLog::new(&ws);
132
+ let live = seeded_liveness(&["%5"]);
133
+ let r = claim_lease_no_incident(
134
+ &ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
135
+ )
136
+ .unwrap();
137
+ assert!(r.ok);
138
+ assert_eq!(r.status, LeaseStatus::AlreadyBound);
139
+ assert_eq!(r.owner_epoch, Some(OwnerEpoch(1)), "already-bound → epoch unchanged");
140
+ assert!(r.reason.is_none(), "already-bound path carries no acquire reason");
141
+ }
142
+
143
+ // RED — NOT-IN-TMUX-PANE: empty caller pane → refused not_in_tmux_pane with
144
+ // the EXACT golden action string (differs from the current claim_leader stub
145
+ // string). golden /tmp/probe_claim.py _lease_refused("not_in_tmux_pane",...).
146
+ #[test]
147
+ #[serial_test::serial(env)]
148
+ fn claim_lease_no_incident_empty_caller_refuses_not_in_tmux_pane() {
149
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
150
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
151
+ let ws = p2_temp_ws("claim_nopane");
152
+ let team_id = TeamKey::new("current");
153
+ let empty = PaneId::new("");
154
+ let mut state = serde_json::json!({});
155
+ let event_log = crate::event_log::EventLog::new(&ws);
156
+ let live = seeded_liveness(&[]);
157
+ let r = claim_lease_no_incident(
158
+ &ws, &mut state, None, &team_id, &empty, false, &event_log, &live,
159
+ )
160
+ .unwrap();
161
+ assert!(!r.ok);
162
+ assert_eq!(r.status, LeaseStatus::Refused);
163
+ assert_eq!(r.reason, Some(LeaseReason::NotInTmuxPane));
164
+ assert_eq!(
165
+ r.action.as_deref(),
166
+ Some("run team-agent claim-leader from the leader's tmux pane"),
167
+ "byte-parity: not_in_tmux_pane action string from _lease_refused (probe_claim.py)"
168
+ );
169
+ }
170
+
171
+ // OLD (Python parity, pre-Bug-3): caller pane present but NOT in the live
172
+ // leader-shape set → refused with CallerNotLeaderShaped + eligibility action
173
+ // ("from a leader (claude/codex) tmux pane").
174
+ // NEW (Bug 3 / I-RN-3 / tests/explicit_claim_takeover_any_live_pane_red):
175
+ // the hard leader-shape gate is removed by design. claim_lease_no_incident
176
+ // now accepts any LIVE caller pane (Codex / Claude / any worker shape). The
177
+ // only remaining caller refusal on this no-incident path is "caller pane is
178
+ // not live at all"; with seeded_liveness(&[]) the caller %7 is therefore
179
+ // refused as CallerPaneNotLive (CallerNotLeaderShaped is no longer produced
180
+ // on this code path).
181
+ #[test]
182
+ #[serial_test::serial(env)]
183
+ fn claim_lease_no_incident_caller_not_live_refused_under_n_rn3_any_live_pane() {
184
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
185
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
186
+ let ws = p2_temp_ws("claim_notleader");
187
+ let team_id = TeamKey::new("current");
188
+ let caller = PaneId::new("%7");
189
+ let mut state = serde_json::json!({});
190
+ let event_log = crate::event_log::EventLog::new(&ws);
191
+ // Empty live-set → caller %7 is not a live pane at all.
192
+ let live = seeded_liveness(&[]);
193
+ let r = claim_lease_no_incident(
194
+ &ws, &mut state, None, &team_id, &caller, false, &event_log, &live,
195
+ )
196
+ .unwrap();
197
+ assert!(!r.ok);
198
+ assert_eq!(r.status, LeaseStatus::Refused);
199
+ assert_eq!(r.reason, Some(LeaseReason::CallerPaneNotLive));
200
+ }
201
+
202
+ // ── 14b. leader_identity dict — workspace_abspath key (golden __init__.py:363) ──
203
+ //
204
+ // LOCK→RED: leader_identity IS implemented (owner_bind.rs:22) but its dict
205
+ // OMITS the golden `workspace_abspath` key (probe_lid.py shows it sits between
206
+ // machine_fingerprint and os_user). This pins the missing key so the porter
207
+ // must add it. Fails today (key absent → null) = RED against the omission.
208
+ #[test]
209
+ #[serial_test::serial(env)]
210
+ fn leader_identity_dict_includes_workspace_abspath_key() {
211
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
212
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
213
+ let ws = p2_temp_ws("lid_wsabs");
214
+ let v = leader_identity(&ws, None).unwrap();
215
+ let ctx = leader_identity_context(&ws, None, Some(&serde_json::json!({}))).unwrap();
216
+ // golden key present + equals the resolved workspace abspath from context.
217
+ assert!(
218
+ v.get("workspace_abspath").is_some(),
219
+ "golden leader_identity dict carries 'workspace_abspath' (probe_lid.py)"
220
+ );
221
+ assert_eq!(
222
+ v["workspace_abspath"].as_str().unwrap(),
223
+ ctx.workspace_abspath.to_string_lossy(),
224
+ "workspace_abspath must equal the context's resolved abspath"
225
+ );
226
+ }
227
+
228
+ // LOCK — leader_identity key ORDER & full key set (golden probe_lid.py):
229
+ // [ok, uuid_prefix, machine_fingerprint, workspace_abspath, os_user, team_id,
230
+ // current_pane_id, last_seen_at, source]. Pins the 9-key surface CLI emits.
231
+ #[test]
232
+ #[serial_test::serial(env)]
233
+ fn leader_identity_dict_has_exact_nine_golden_keys() {
234
+ let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
235
+ let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
236
+ let ws = p2_temp_ws("lid_keys");
237
+ let v = leader_identity(&ws, None).unwrap();
238
+ let obj = v.as_object().expect("leader_identity → JSON object");
239
+ for key in [
240
+ "ok", "uuid_prefix", "machine_fingerprint", "workspace_abspath",
241
+ "os_user", "team_id", "current_pane_id", "last_seen_at", "source",
242
+ ] {
243
+ assert!(obj.contains_key(key), "golden leader_identity dict must carry '{key}'");
244
+ }
245
+ }
246
+
247
+ // ═════════════════════════════════════════════════════════════════════════
248
+ // WAVE-2 Lane B — leader-lease DIVERGENCE round (adversarial review @ 2cd71ce).
249
+ // The lease gate is green but NOT byte-parity. Golden: leader/__init__.py
250
+ // (_claim_lease_no_incident:598, _receiver_from_claim_target:861, new_owner:686,
251
+ // _lease_epoch:400, _emit_lease_refusal:469, audit :712-729). These REDs lock the EXACT
252
+ // golden shape per divergence; driven via claim_lease_no_incident(+seeded_liveness), OS-safe.
253
+ // ═════════════════════════════════════════════════════════════════════════
@@ -0,0 +1,125 @@
1
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
2
+ use super::*;
3
+ use std::cell::Cell;
4
+
5
+ // ── helpers ────────────────────────────────────────────────────────────
6
+ /// derive 一个真 LeaderSessionUuid(骨架无公开裸构造器,仅 `derive`)。
7
+ fn uuid(fp: &str, ws: &str, user: &str, team: &str) -> LeaderSessionUuid {
8
+ LeaderSessionUuid::derive(fp, ws, user, team).unwrap()
9
+ }
10
+
11
+ /// 注入式 turn-state 分类器:MUST-NOT-13 命门 —— 统计 classify 调用次数,
12
+ /// 据此断言 idle 面经此 trait 分类、绝不直连任何 provider client。
13
+ struct CountingClassifier {
14
+ calls: Cell<usize>,
15
+ result: TurnState,
16
+ }
17
+ impl CountingClassifier {
18
+ fn new(result: TurnState) -> Self {
19
+ Self { calls: Cell::new(0), result }
20
+ }
21
+ }
22
+ impl TurnStateClassifier for CountingClassifier {
23
+ fn classify(
24
+ &self,
25
+ _provider: Provider,
26
+ session_log_text: &str,
27
+ ) -> Result<TurnClassification, LeaderError> {
28
+ self.calls.set(self.calls.get() + 1);
29
+ // 空 session-log 文本 → unknown(bug-085:None rollout_path 漏穿后读到空串)。
30
+ let state = if session_log_text.is_empty() { TurnState::Unknown } else { self.result };
31
+ Ok(TurnClassification {
32
+ state,
33
+ turn_id: None,
34
+ annotations: vec![],
35
+ reason: if state == TurnState::Unknown { Some("empty_session_log".into()) } else { None },
36
+ })
37
+ }
38
+ }
39
+
40
+ static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
41
+
42
+ /// RAII env override: restores prior values on drop (incl. panic unwind), so a
43
+ /// RED assertion-panic cannot leak a dirty env into other tests.
44
+ struct EnvGuard {
45
+ saved: Vec<(String, Option<String>)>,
46
+ }
47
+ impl EnvGuard {
48
+ fn apply(vars: &[(&str, Option<&str>)]) -> Self {
49
+ let saved = vars.iter().map(|(k, _)| ((*k).to_string(), std::env::var(k).ok())).collect();
50
+ for (k, v) in vars {
51
+ match v {
52
+ Some(val) => std::env::set_var(k, val),
53
+ None => std::env::remove_var(k),
54
+ }
55
+ }
56
+ Self { saved }
57
+ }
58
+ }
59
+ impl Drop for EnvGuard {
60
+ fn drop(&mut self) {
61
+ for (k, v) in &self.saved {
62
+ match v {
63
+ Some(val) => std::env::set_var(k, val),
64
+ None => std::env::remove_var(k),
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ fn p2_temp_ws(tag: &str) -> PathBuf {
71
+ let ws = std::env::temp_dir().join(format!("ta_rs_p2leader_{}_{}", tag, std::process::id()));
72
+ std::fs::create_dir_all(&ws).unwrap();
73
+ ws
74
+ }
75
+
76
+ // P1 — state-supplied leader_session_uuid → source 'derived' (NOT 'env'); Python's
77
+ // source is binary override-vs-derived (leader/__init__.py:206).
78
+ struct SeededLiveness {
79
+ live_panes: std::collections::BTreeSet<String>,
80
+ }
81
+ impl crate::state::owner_gate::PaneLivenessProbe for SeededLiveness {
82
+ fn liveness(&self, pane_id: &str) -> crate::model::enums::PaneLiveness {
83
+ if self.live_panes.contains(pane_id) {
84
+ crate::model::enums::PaneLiveness::Live
85
+ } else {
86
+ crate::model::enums::PaneLiveness::Dead
87
+ }
88
+ }
89
+ }
90
+
91
+ fn seeded_liveness(panes: &[&str]) -> SeededLiveness {
92
+ SeededLiveness { live_panes: panes.iter().map(|p| (*p).to_string()).collect() }
93
+ }
94
+
95
+ // RED — VACANT ACQUIRE: empty state + eligible caller → status=Claimed,
96
+ // reason=vacant_acquired, owner_epoch 0→1, owner+receiver bound to caller,
97
+ // claimed_via=claim-leader, BOTH state files written (dual-state).
98
+ // golden /tmp/probe_claimed.py: epoch 1, claimed_via "claim-leader",
99
+ // discovery "claim_leader", reason "vacant_acquired".
100
+ /// Event names from the workspace event log (events.jsonl), in write order.
101
+ fn event_names(ws: &std::path::Path) -> Vec<String> {
102
+ let path = crate::model::paths::logs_dir(ws).join("events.jsonl");
103
+ std::fs::read_to_string(&path)
104
+ .map(|t| {
105
+ t.lines()
106
+ .filter_map(|l| serde_json::from_str::<serde_json::Value>(l).ok())
107
+ .filter_map(|e| e.get("event").and_then(|v| v.as_str()).map(String::from))
108
+ .collect()
109
+ })
110
+ .unwrap_or_default()
111
+ }
112
+
113
+ // D1 [BLOCK] — claim-path team_owner is EXACTLY golden's 7 keys (NO os_user). Golden new_owner
114
+ // (__init__.py:686-694) = pane_id,provider,machine_fingerprint,leader_session_uuid,owner_epoch,
115
+ // claimed_at,claimed_via; only Family-A leader_binding writes os_user, NEVER the claim path. Rust
116
+ // make_owner sets os_user:Some(..) + TeamOwner has no skip_serializing_if -> 8 keys. RED.
117
+
118
+ mod basics;
119
+ mod wake_start_owner;
120
+ mod lease_api;
121
+ mod idle;
122
+ mod identity;
123
+ mod lease_claim;
124
+ mod byte_findings;
125
+ mod rediscover;