@team-agent/installer 0.2.10 → 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 -83
  203. package/src/team_agent/coordinator/lifecycle.py +0 -363
  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 -200
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -111
  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 -254
  255. package/src/team_agent/messaging/delivery.py +0 -473
  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 -457
  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 -86
  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 -1239
  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 -143
  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 -602
  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,390 @@
1
+ use super::*;
2
+
3
+ // =========================================================================
4
+ // WAVE-2 NON-SUB CHECKPOINT — 9 MISSING CLI subcommands (ABSENT from cli/emit.rs dispatch).
5
+ // emit.rs:53-78 `dispatch` has NO arm for: sessions, peek, collect, e2e, diagnose, repair-state,
6
+ // validate-result, preflight, wait-ready -> they fall to `_ => Ok(ExitCode::Error)`. These REDs
7
+ // assert the dispatch ROUTES each subcommand: `run([sub,...]) == ExitCode::Ok` for a golden
8
+ // EXIT-0 scenario (today unrouted -> ExitCode::Error -> RED; green once the porter adds the
9
+ // dispatch arm + handler). Golden exit codes + JSON shapes probed via `python3 -m team_agent <sub>`.
10
+ //
11
+ // OBSERVABILITY NOTE: `run()` exposes only ExitCode (Ok=0/Error=1); it prints via println!, which
12
+ // libtest intercepts (thread-local capture), so an fd-level stdout byte-capture is unreliable
13
+ // under `cargo test`. The exact golden --json byte-shape is therefore LOCKED in each doc-comment
14
+ // as the porter's parity obligation; the in-process assertion is the routing (exit code). A
15
+ // follow-up can byte-lock output once each handler exists as a callable `cmd_*`/`*_port` symbol.
16
+ //
17
+ // Only golden-EXIT-0, tmux-SAFE scenarios make a clean RED (an exit-1 scenario's Error is
18
+ // indistinguishable from the unknown-subcommand Error -> false-green, forbidden). preflight/
19
+ // wait-ready/e2e/peek cannot reach golden-exit-0 on CI without real tmux/providers/a live team,
20
+ // so they are #[ignore] real-effect seams (documented shape), NOT false-green exit-1 asserts.
21
+ // =========================================================================
22
+
23
+ fn cli_argv(items: &[&str]) -> Vec<String> {
24
+ items.iter().map(|s| (*s).to_string()).collect()
25
+ }
26
+
27
+ /// The exact golden fake spec (cli/e2e.py `_fake_spec`, dumped via simple_yaml) with the literal
28
+ /// `/WS` placeholder substituted to the real workspace — the minimal VALID team.spec.yaml that
29
+ /// `collect`/`repair-state` (load_spec) accept. (load_spec rejects partial specs: requires
30
+ /// communication/context/leader/routing/runtime/tasks.)
31
+ const FAKE_SPEC_YAML: &str = r#"version: 1
32
+ team:
33
+ name: "fake-e2e"
34
+ mode: "supervisor_worker"
35
+ objective: "Exercise fake provider orchestration."
36
+ workspace: "/WS"
37
+ leader:
38
+ id: "leader"
39
+ role: "leader"
40
+ provider: "fake"
41
+ model: null
42
+ tools:
43
+ - "fs_read"
44
+ - "fs_list"
45
+ - "mcp_team"
46
+ context_policy:
47
+ keep_user_thread: true
48
+ receive_worker_outputs: "structured_only"
49
+ max_worker_result_tokens: 2000
50
+ agents:
51
+ - id: "fake_impl"
52
+ role: "implementation_engineer"
53
+ provider: "fake"
54
+ model: null
55
+ working_directory: "/WS"
56
+ system_prompt:
57
+ inline: "Handle fake implementation tasks."
58
+ file: null
59
+ tools:
60
+ - "fs_read"
61
+ - "fs_write"
62
+ - "fs_list"
63
+ - "execute_bash"
64
+ - "git_diff"
65
+ - "mcp_team"
66
+ - "provider_builtin"
67
+ permission_mode: "restricted"
68
+ preferred_for:
69
+ - "implementation"
70
+ avoid_for: []
71
+ output_contract:
72
+ format: "result_envelope_v1"
73
+ required_fields:
74
+ - "task_id"
75
+ - "status"
76
+ - "summary"
77
+ - "artifacts"
78
+ routing:
79
+ default_assignee: "leader"
80
+ rules:
81
+ - id: "implementation-to-fake"
82
+ match:
83
+ type:
84
+ - "implementation"
85
+ assign_to: "fake_impl"
86
+ priority: 10
87
+ communication:
88
+ protocol: "mcp_inbox"
89
+ topology: "leader_centered"
90
+ worker_to_worker: true
91
+ ack_timeout_sec: 2
92
+ result_format: "result_envelope_v1"
93
+ message_store:
94
+ sqlite: ".team/runtime/team.db"
95
+ mirror_files: ".team/messages"
96
+ runtime:
97
+ backend: "tmux"
98
+ display_backend: "none"
99
+ session_name: "team-agent-fake-e2e"
100
+ auto_launch: true
101
+ require_user_approval_before_launch: false
102
+ max_active_agents: 1
103
+ startup_order:
104
+ - "fake_impl"
105
+ context:
106
+ state_file: "team_state.md"
107
+ artifact_dir: ".team/artifacts"
108
+ log_dir: ".team/logs"
109
+ summarization:
110
+ worker_full_logs: "retain_outside_leader_context"
111
+ state_update: "after_each_result"
112
+ tasks:
113
+ - id: "task_impl"
114
+ title: "Fake implementation"
115
+ type: "implementation"
116
+ assignee: null
117
+ deps: []
118
+ acceptance:
119
+ - "fake result collected"
120
+ status: "pending"
121
+ requires_tools:
122
+ - "fs_write"
123
+ - "execute_bash"
124
+ files:
125
+ - "src/example.py"
126
+ risk: "low"
127
+ "#;
128
+
129
+ fn seed_team_spec(ws: &std::path::Path) {
130
+ let spec = FAKE_SPEC_YAML.replace("/WS", &ws.to_string_lossy());
131
+ std::fs::write(ws.join("team.spec.yaml"), spec).unwrap();
132
+ }
133
+
134
+ // ── sessions ── golden cli/parser.py:230 `cmd_sessions` -> runtime.sessions(ws). EXIT 0.
135
+ // `team-agent sessions --workspace <ws> --json` on an empty ws ->
136
+ // {"ok":true,"sessions":[],"workspace":"<ws>"} (--json sort_keys). RED: unrouted -> Error.
137
+ #[test]
138
+ fn dispatch_routes_sessions_subcommand() {
139
+ let ws = tmp_workspace();
140
+ let code = run(&cli_argv(&["sessions", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
141
+ assert_eq!(
142
+ code,
143
+ ExitCode::Ok,
144
+ "`sessions` must ROUTE to cmd_sessions (golden parser.py:230, exit 0 {{ok,sessions,workspace}}); \
145
+ today it falls to the unknown-subcommand arm (emit.rs:77) -> Error"
146
+ );
147
+ let _ = std::fs::remove_dir_all(&ws);
148
+ }
149
+
150
+ // ── validate-result ── golden parser.py:312 `cmd_validate_result` (commands.py:206). A FULL valid
151
+ // result_envelope_v1 -> {"agent_id":"a1","ok":true,"status":"success","task_id":"t1"} EXIT 0.
152
+ // RED: unrouted -> Error. (A partial envelope golden-exits 1 — that would be false-green, so the
153
+ // RED uses the complete envelope that golden accepts.)
154
+ #[test]
155
+ fn dispatch_routes_validate_result_valid_envelope() {
156
+ let envelope = r#"{"schema_version":"result_envelope_v1","task_id":"t1","agent_id":"a1","status":"success","summary":"done","artifacts":[],"changes":[],"tests":[],"risks":[],"next_actions":[]}"#;
157
+ let code = run(&cli_argv(&["validate-result", envelope, "--json"]), std::path::Path::new("."));
158
+ assert_eq!(
159
+ code,
160
+ ExitCode::Ok,
161
+ "`validate-result <valid envelope> --json` must ROUTE to cmd_validate_result (parser.py:312) \
162
+ and exit 0 with {{agent_id,ok,status,task_id}}; today -> unknown-subcommand Error"
163
+ );
164
+ }
165
+
166
+ // ── collect ── golden parser.py:292 `cmd_collect` -> runtime.collect(ws). With a valid
167
+ // team.spec.yaml present and nothing to collect -> EXIT 0, golden:
168
+ // {"collected":[],"collected_results":[],"coordinator":{"ok":false,"status":"not_required"},
169
+ // "delivered_messages":[],"invalid_results":[],"ok":true,"results":{...},"state_file":"<ws>/team_state.md"}
170
+ // RED: unrouted -> Error.
171
+ #[test]
172
+ fn dispatch_routes_collect_with_spec() {
173
+ let ws = tmp_workspace();
174
+ seed_team_spec(&ws);
175
+ let code = run(&cli_argv(&["collect", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
176
+ assert_eq!(
177
+ code,
178
+ ExitCode::Ok,
179
+ "`collect` must ROUTE to cmd_collect (parser.py:292); with a valid spec golden exits 0 \
180
+ {{collected,collected_results,coordinator,delivered_messages,invalid_results,ok,results,state_file}}; \
181
+ today -> unknown-subcommand Error"
182
+ );
183
+ let _ = std::fs::remove_dir_all(&ws);
184
+ }
185
+
186
+ // ── repair-state ── golden parser.py:303 `cmd_repair_state` -> runtime.repair_state (quick_start.py:285).
187
+ // `--task <id>` required; `--status` must be in TASK_STATUSES{blocked,cancelled,done,failed,
188
+ // needs_retry,pending,ready,running}. With a seeded task + --status done -> EXIT 0:
189
+ // {"after":{...},"before":{...},"ok":true,"state_file":"<ws>/team_state.md","task_id":"fake_impl"}
190
+ // RED: unrouted -> Error.
191
+ #[test]
192
+ fn dispatch_routes_repair_state_with_task() {
193
+ let ws = tmp_workspace();
194
+ seed_team_spec(&ws);
195
+ std::fs::write(
196
+ ws.join(".team").join("runtime").join("state.json"),
197
+ serde_json::to_vec(&json!({
198
+ "leader": {"id": "leader"},
199
+ "tasks": [{"id": "fake_impl", "title": "impl", "status": "open", "assignee": "fake_impl", "type": "implementation"}],
200
+ }))
201
+ .unwrap(),
202
+ )
203
+ .unwrap();
204
+ let code = run(
205
+ &cli_argv(&[
206
+ "repair-state", "--workspace", &ws.to_string_lossy(),
207
+ "--task", "fake_impl", "--status", "done", "--summary", "ok", "--json",
208
+ ]),
209
+ &ws,
210
+ );
211
+ assert_eq!(
212
+ code,
213
+ ExitCode::Ok,
214
+ "`repair-state --task <id> --status done` must ROUTE to cmd_repair_state (parser.py:303) and \
215
+ exit 0 {{after,before,ok,state_file,task_id}}; today -> unknown-subcommand Error"
216
+ );
217
+ let _ = std::fs::remove_dir_all(&ws);
218
+ }
219
+
220
+ // ── diagnose ── golden parser.py:298 `cmd_diagnose` -> runtime.diagnose(ws) (diagnose/health.py:19).
221
+ // ok:true only when there are zero issues. Seed leader_receiver=attached + NO session_name + NO
222
+ // agents -> issues=[] -> EXIT 0, golden top keys (sort): event_log,issues,ok,runtime,suggested_repairs.
223
+ // RED: unrouted -> Error.
224
+ #[test]
225
+ fn dispatch_routes_diagnose_healthy_leader() {
226
+ let ws = tmp_workspace();
227
+ std::fs::write(
228
+ ws.join(".team").join("runtime").join("state.json"),
229
+ serde_json::to_vec(&json!({
230
+ "leader": {"id": "leader"},
231
+ "leader_receiver": {"mode": "direct_tmux", "status": "attached", "pane_id": "%1", "provider": "codex"},
232
+ }))
233
+ .unwrap(),
234
+ )
235
+ .unwrap();
236
+ let code = run(&cli_argv(&["diagnose", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
237
+ assert_eq!(
238
+ code,
239
+ ExitCode::Ok,
240
+ "`diagnose` must ROUTE to cmd_diagnose (parser.py:298); a healthy (attached, no-session, \
241
+ no-agent) state yields zero issues -> exit 0 {{event_log,issues,ok,runtime,suggested_repairs}}; \
242
+ today -> unknown-subcommand Error"
243
+ );
244
+ let _ = std::fs::remove_dir_all(&ws);
245
+ }
246
+
247
+ // ── preflight (#[ignore] real-machine) ── golden parser.py:160 `cmd_preflight` -> runtime.preflight
248
+ // (Path(args.team)). Validates a team role-doc dir: checks compile(TEAM.md)/tmux/ghostty/rust_core/
249
+ // profile_dir; golden -> {"blockers":[...],"checks":[...],"details_log":...,"next_actions":[...],
250
+ // "ok":bool,"summary":...}. Reaches ok:true (exit 0) ONLY with a valid TEAM.md + ghostty installed,
251
+ // which CI cannot supply (ghostty absent -> blocker) -> not a clean in-process exit-0 RED.
252
+ #[test]
253
+ #[ignore = "real-machine: `preflight --team <dir>` needs a valid TEAM.md role-doc dir + ghostty to \
254
+ reach ok:true; on CI it golden-exits 1 (compile/ghostty blockers) which is \
255
+ indistinguishable from the unknown-subcommand Error (false-green). Routes to cmd_preflight \
256
+ (parser.py:160); shape {blockers,checks,details_log,next_actions,ok,summary}"]
257
+ fn dispatch_routes_preflight_real_machine() {
258
+ let ws = tmp_workspace();
259
+ let code = run(&cli_argv(&["preflight", "--team", &ws.to_string_lossy(), "--json"]), &ws);
260
+ assert_eq!(code, ExitCode::Ok, "preflight must ROUTE + exit 0 on a valid team dir");
261
+ let _ = std::fs::remove_dir_all(&ws);
262
+ }
263
+
264
+ // ── wait-ready (#[ignore] real-machine) ── golden parser.py:171 `cmd_wait_ready` -> runtime.wait_ready
265
+ // (ws, timeout). Polls worker readiness; golden -> {"details_log":...,"next_actions":[...],"ok":bool,
266
+ // "readiness":{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},"summary":...}.
267
+ // ok:true (exit 0) needs a LIVE ready team; CI has none -> golden-exits 1 (false-green vs unknown).
268
+ #[test]
269
+ #[ignore = "real-machine: `wait-ready` polls a LIVE team's readiness; with no workers golden-exits 1 \
270
+ (false-green vs unknown-subcommand Error). Routes to cmd_wait_ready (parser.py:171); shape \
271
+ {details_log,next_actions,ok,readiness{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},summary}"]
272
+ fn dispatch_routes_wait_ready_real_machine() {
273
+ let ws = tmp_workspace();
274
+ let code = run(&cli_argv(&["wait-ready", "--workspace", &ws.to_string_lossy(), "--timeout", "1", "--json"]), &ws);
275
+ assert_eq!(code, ExitCode::Ok, "wait-ready must ROUTE + exit 0 once the team is ready");
276
+ let _ = std::fs::remove_dir_all(&ws);
277
+ }
278
+
279
+ // ── e2e --providers fake (#[ignore] real-machine) ── golden parser.py:449 `cmd_e2e` (cli/e2e.py:12).
280
+ // `--providers fake` runs a REAL end-to-end: runtime.launch(spec, auto_approve=True) (spawns a tmux
281
+ // team) -> send_message -> sleep -> collect -> shutdown. Result envelope (e2e.py:16,48-56):
282
+ // {"workspace":str,"providers":{"fake":{"ok":bool,"launch":{...},"send":{...},"collect":{...},
283
+ // "shutdown":{...}}},"ok":bool}. Real tmux spawn -> #[ignore] (NOT runnable in-process).
284
+ #[test]
285
+ #[ignore = "real-machine: `e2e --providers fake` calls runtime.launch -> spawns a REAL tmux team \
286
+ (send/collect/shutdown). Routes to cmd_e2e (parser.py:449 / e2e.py:12); envelope \
287
+ {workspace,providers:{fake:{ok,launch,send,collect,shutdown}},ok}"]
288
+ fn dispatch_routes_e2e_fake_real_machine() {
289
+ let ws = tmp_workspace();
290
+ let code = run(&cli_argv(&["e2e", "--providers", "fake", "--workspace", &ws.to_string_lossy(), "--json"]), &ws);
291
+ assert_eq!(code, ExitCode::Ok, "e2e --providers fake must ROUTE + run the fake end-to-end (ok:true)");
292
+ let _ = std::fs::remove_dir_all(&ws);
293
+ }
294
+
295
+ // ── peek (#[ignore] real-machine) ── golden parser.py:201 `cmd_peek` (commands.py:118). Requires
296
+ // `--allow-raw-screen` (else TeamAgentError) + a mutually-exclusive --head/--tail/--search; then
297
+ // runtime.peek captures a LIVE tmux pane (tmux capture-pane). Real tmux -> #[ignore]. Routes to
298
+ // cmd_peek; --json returns the peek dict (text + capture metadata).
299
+ #[test]
300
+ #[ignore = "real-machine: `peek <agent> --tail N --allow-raw-screen` captures a LIVE tmux pane \
301
+ (tmux capture-pane); needs a running worker pane. Routes to cmd_peek (parser.py:201); \
302
+ --json returns the peek dict (raw screen text + metadata)"]
303
+ fn dispatch_routes_peek_real_machine() {
304
+ let ws = tmp_workspace();
305
+ let code = run(
306
+ &cli_argv(&["peek", "fake_impl", "--workspace", &ws.to_string_lossy(), "--tail", "20", "--allow-raw-screen", "--json"]),
307
+ &ws,
308
+ );
309
+ assert_eq!(code, ExitCode::Ok, "peek must ROUTE + capture the live pane (real machine)");
310
+ let _ = std::fs::remove_dir_all(&ws);
311
+ }
312
+
313
+ // CONTRACT (shared-root, real-machine-driven; golden = correct-behavior baseline): wait_readiness
314
+ // derives cli_prompt_ready from the LIFECYCLE status — an alive worker (status="running") IS
315
+ // cli_prompt_ready (golden quick_start.py:173 `status ∈ {running, busy}`). Rust (diagnose.rs:280)
316
+ // requires cli_prompt_ready flag / startup_prompts=="complete" / status=="ready" and does NOT accept
317
+ // "running" → an alive fake worker never becomes ready → wait_ready times out. Same shared root as the
318
+ // deferred_busy regression: lifecycle "running" is the authoritative alive/ready signal, not a
319
+ // turn-level flag. (process_started/mcp_ready/task_prompt_delivered are satisfied here so `ready`
320
+ // hinges solely on the cli_prompt_ready derivation.)
321
+ #[test]
322
+ fn contract_alive_worker_running_is_cli_prompt_ready_and_ready() {
323
+ let state = serde_json::json!({"agents": {"w1": {
324
+ "status": "running",
325
+ "pane_id": "%1",
326
+ "mcp_ready": true,
327
+ "first_send_at": "2026-01-01T00:00:00Z"
328
+ }}});
329
+ let r = crate::cli::diagnose::wait_readiness(&state);
330
+ assert_eq!(
331
+ r.get("cli_prompt_ready").and_then(serde_json::Value::as_bool),
332
+ Some(true),
333
+ "CONTRACT: an alive worker (lifecycle status=running) is cli_prompt_ready (golden status∈{{running,busy}}); got {r:?}"
334
+ );
335
+ assert_eq!(
336
+ r.get("ready").and_then(serde_json::Value::as_bool),
337
+ Some(true),
338
+ "status=running + pane_id + mcp_ready + first_send_at → all four readiness signals derive true → ready (no timeout); got {r:?}"
339
+ );
340
+ }
341
+
342
+ // CONTRACT (real-machine wait_ready product FAIL @ 8ea5df5): a live fake quick-start worker times out
343
+ // with process_started=false + mcp_ready=false. golden (quick_start.py:172/174):
344
+ // process_started = bool(last["tmux_session_present"]) (the live tmux session exists)
345
+ // mcp_ready = all(Path(agent["mcp_config"]).exists()) (each agent's mcp_config FILE exists)
346
+ // The Rust launch (launch.rs:220-228) persists status="running" + mcp_config (path) but NO pane_id/pid
347
+ // and NO mcp_ready flag. wait_readiness reads per-agent pane_id/pid for process_started (-> false) and an
348
+ // mcp_ready FLAG for mcp_ready (-> false), so a live worker never becomes ready. The shared-root fix only
349
+ // corrected cli_prompt_ready (status=running); these two signals still read the wrong source. This RED
350
+ // uses the REALISTIC post-launch state (no synthetic pane_id / mcp_ready flag).
351
+ #[test]
352
+ fn contract_wait_ready_derives_process_started_from_session_and_mcp_ready_from_file() {
353
+ let dir = std::env::temp_dir().join(format!(
354
+ "ta-wr-{}-{}",
355
+ std::process::id(),
356
+ std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
357
+ ));
358
+ std::fs::create_dir_all(&dir).unwrap();
359
+ let mcp = dir.join("mcp.json");
360
+ std::fs::write(&mcp, "{}").unwrap();
361
+ let state = serde_json::json!({
362
+ "session_name": "ta-fake",
363
+ "tmux_session_present": true, // golden status() top-level signal: the live tmux session exists
364
+ "agents": { "w1": {
365
+ "status": "running",
366
+ "mcp_config": mcp.to_string_lossy(),
367
+ "first_send_at": "2026-01-01T00:00:00Z"
368
+ }}
369
+ });
370
+ let r = crate::cli::diagnose::wait_readiness(&state);
371
+ assert_eq!(
372
+ r.get("process_started").and_then(serde_json::Value::as_bool),
373
+ Some(true),
374
+ "CONTRACT: process_started derives from tmux_session_present (golden quick_start.py:172), NOT \
375
+ per-agent pane_id/pid (the launch writes neither); got {r:?}"
376
+ );
377
+ assert_eq!(
378
+ r.get("mcp_ready").and_then(serde_json::Value::as_bool),
379
+ Some(true),
380
+ "CONTRACT: mcp_ready derives from each agent's mcp_config FILE existence (golden quick_start.py:174), \
381
+ NOT an mcp_ready flag (never set); got {r:?}"
382
+ );
383
+ assert_eq!(
384
+ r.get("ready").and_then(serde_json::Value::as_bool),
385
+ Some(true),
386
+ "a live fake quick-start worker (session present + status running + mcp_config file + task sent) \
387
+ must be ready, not timeout; got {r:?}"
388
+ );
389
+ let _ = std::fs::remove_dir_all(&dir);
390
+ }
@@ -0,0 +1,97 @@
1
+ //! cli · tests — RED 契约 lane(verbatim 从原 cli.rs 的 `#[cfg(test)] mod tests` 迁移)。
2
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
3
+
4
+ use super::*;
5
+ use serde_json::json;
6
+
7
+ fn tmp_workspace() -> std::path::PathBuf {
8
+ use std::sync::atomic::{AtomicU64, Ordering};
9
+ static CTR: AtomicU64 = AtomicU64::new(0);
10
+ let dir = std::env::temp_dir().join(format!(
11
+ "ta-cli-test-{}-{}",
12
+ std::process::id(),
13
+ CTR.fetch_add(1, Ordering::Relaxed)
14
+ ));
15
+ std::fs::create_dir_all(dir.join(".team").join("runtime")).unwrap();
16
+ dir
17
+ }
18
+
19
+ fn seed_status_workspace() -> std::path::PathBuf {
20
+ let dir = std::env::temp_dir().join(format!(
21
+ "ta-cli-status-{}-{}",
22
+ std::process::id(),
23
+ std::time::SystemTime::now()
24
+ .duration_since(std::time::UNIX_EPOCH)
25
+ .unwrap()
26
+ .as_nanos()
27
+ ));
28
+ let team = dir.join(".team");
29
+ std::fs::create_dir_all(team.join("runtime")).unwrap();
30
+ let state = json!({
31
+ "session_name": "seeded-team",
32
+ "leader": {"id": "leader"},
33
+ "leader_receiver": {"pane_id": "%3", "pane_current_command": "codex", "status": "running"},
34
+ "agents": {"a1": {"status": "running", "first_send_at": "2026-01-01T00:00:00Z"}},
35
+ "tasks": [{"id": "t1", "title": "demo", "status": "open", "assignee": "a1"}],
36
+ });
37
+ std::fs::write(
38
+ team.join("runtime").join("state.json"),
39
+ serde_json::to_vec_pretty(&state).unwrap(),
40
+ )
41
+ .unwrap();
42
+ dir
43
+ }
44
+
45
+ const DELEG_VALID_ROLE: &str = "---\nname: implementer\nrole: Implementation Engineer\nprovider: fake\nmodel: fake\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nImplement bounded tasks.\n";
46
+ const DELEG_INVALID_ROLE: &str = "---\nname: broken\nrole: Broken Worker\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nNo provider field.\n";
47
+ const DELEG_TEAM_MD: &str = "---\nname: clidelegteam\nobjective: Delegation probe.\nprovider: fake\n---\n\nteam.\n";
48
+
49
+ fn deleg_uniq_dir(tag: &str) -> std::path::PathBuf {
50
+ use std::sync::atomic::{AtomicU64, Ordering};
51
+ static N: AtomicU64 = AtomicU64::new(0);
52
+ let dir = std::env::temp_dir().join(format!(
53
+ "ta-cli-deleg-{}-{}-{}",
54
+ tag,
55
+ std::process::id(),
56
+ N.fetch_add(1, Ordering::Relaxed)
57
+ ));
58
+ std::fs::create_dir_all(&dir).unwrap();
59
+ dir
60
+ }
61
+
62
+ fn deleg_team_dir_with_healthy_coordinator() -> std::path::PathBuf {
63
+ let base = deleg_uniq_dir("qs");
64
+ let team = base.join("teamdir");
65
+ std::fs::create_dir_all(team.join("agents")).unwrap();
66
+ std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
67
+ std::fs::write(team.join("agents").join("implementer.md"), DELEG_VALID_ROLE).unwrap();
68
+ let wp = crate::coordinator::WorkspacePath::new(base.clone());
69
+ std::fs::create_dir_all(crate::model::paths::runtime_dir(&base)).unwrap();
70
+ let _ = crate::message_store::MessageStore::open(&base).unwrap();
71
+ let me = crate::coordinator::Pid::new(std::process::id());
72
+ crate::coordinator::write_coordinator_metadata(&wp, me, crate::coordinator::MetadataSource::Boot).unwrap();
73
+ std::fs::write(crate::coordinator::coordinator_pid_path(&wp), me.to_string()).unwrap();
74
+ team
75
+ }
76
+
77
+ fn outcome_text(r: Result<CmdResult, CliError>) -> String {
78
+ match r {
79
+ Ok(cmd) => format!("{cmd:?}"),
80
+ Err(e) => e.to_string(),
81
+ }
82
+ }
83
+
84
+ mod base;
85
+ mod compile;
86
+ mod divergence;
87
+ mod lane_c;
88
+ mod leader_watch;
89
+ mod missing_subcommands;
90
+ mod repair_state_byte_lock;
91
+ mod peer_allow;
92
+ mod run_delegation;
93
+ mod status_send;
94
+ mod verb_validate;
95
+ mod verb_settle;
96
+ mod verb_profile;
97
+ mod main_preserved;
@@ -0,0 +1,137 @@
1
+ use super::*;
2
+
3
+ // =============================================================================
4
+ // P0-1 LANE — `allow-peer-talk` byte-lock (was ZERO-test; a SHIPPED compat-noop).
5
+ //
6
+ // golden: cli/commands.py:422 -> messaging/leader.py:25 allow_peer_talk(ws,a,b)
7
+ // -> message_store/core.py:326 allow_peer (INSERT OR IGNORE BOTH (a,b) & (b,a))
8
+ // + events.py:27-35 EventLog.write (sort_keys=True -> .team/logs/events.jsonl).
9
+ //
10
+ // Golden bytes captured live (PYTHONPATH=.../src python3 /tmp/probe_app*.py):
11
+ // return (insertion order): {"ok":true,"a":..,"b":..,"status":"compat_noop",
12
+ // "reason":"team_scoped_peer_messages_enabled"}
13
+ // return (--json sort_keys): keys a,b,ok,reason,status
14
+ // event line (sort_keys): {"a":..,"b":..,"event":"communication.peer_allowed","ts":..}
15
+ // idempotent: 2nd call -> identical return + a SECOND event line
16
+ // (event fires per-call; DB rows are insert-or-ignore)
17
+ // unknown/undeclared agents: NO validation -> ok:true compat_noop, exit 0
18
+ //
19
+ // Rust impl: messaging/peers.rs:11 + message_store.rs:251 + adapters.rs:173.
20
+ // serde_json `preserve_order` is ON (Cargo.toml:24) so the default (human) output
21
+ // iterates dict keys in golden insertion order. These tests LOCK the contract so a
22
+ // porter/refactor cannot silently drift this live verb.
23
+ // =============================================================================
24
+
25
+ fn app_out(r: CmdResult) -> serde_json::Value {
26
+ match r.output {
27
+ CmdOutput::Json(v) => v,
28
+ _ => panic!("allow-peer-talk must emit a Json CmdOutput"),
29
+ }
30
+ }
31
+
32
+ fn app_events(ws: &std::path::Path) -> Vec<String> {
33
+ std::fs::read_to_string(ws.join(".team").join("logs").join("events.jsonl"))
34
+ .unwrap_or_default()
35
+ .lines()
36
+ .map(str::to_string)
37
+ .collect()
38
+ }
39
+
40
+ fn app_call(ws: &std::path::Path, a: &str, b: &str) -> CmdResult {
41
+ cmd_allow_peer_talk(&AllowPeerTalkArgs {
42
+ a: a.to_string(),
43
+ b: b.to_string(),
44
+ workspace: ws.to_path_buf(),
45
+ json: false,
46
+ })
47
+ .expect("allow-peer-talk is a compat-noop and must not error")
48
+ }
49
+
50
+ // ── return dict: exact compat_noop shape + golden insertion order (drives human emit) ──
51
+ #[test]
52
+ fn allow_peer_talk_output_byte_locks_golden_compat_noop() {
53
+ let ws = tmp_workspace();
54
+ let v = app_out(app_call(&ws, "alpha", "bravo"));
55
+ assert_eq!(
56
+ v,
57
+ json!({
58
+ "ok": true,
59
+ "a": "alpha",
60
+ "b": "bravo",
61
+ "status": "compat_noop",
62
+ "reason": "team_scoped_peer_messages_enabled"
63
+ }),
64
+ "golden leader.py:28 return dict (key set + values); got {v:?}"
65
+ );
66
+ let order: Vec<&str> = v.as_object().expect("object").keys().map(String::as_str).collect();
67
+ assert_eq!(
68
+ order,
69
+ vec!["ok", "a", "b", "status", "reason"],
70
+ "golden dict insertion order ok,a,b,status,reason drives the default human emit key order \
71
+ (helpers.py:16-19); got {order:?}"
72
+ );
73
+ let _ = std::fs::remove_dir_all(&ws);
74
+ }
75
+
76
+ // ── event: exactly one `communication.peer_allowed` line, sort_keys byte form, payload {a,b} ──
77
+ #[test]
78
+ fn allow_peer_talk_event_byte_locks_golden_line() {
79
+ let ws = tmp_workspace();
80
+ let _ = app_call(&ws, "alpha", "bravo");
81
+ let lines = app_events(&ws);
82
+ assert_eq!(lines.len(), 1, "exactly one event per call; got {lines:?}");
83
+ let v: serde_json::Value = serde_json::from_str(&lines[0]).expect("event line must be json");
84
+ let ts = v["ts"].as_str().expect("event has string ts").to_string();
85
+ let expected = format!(
86
+ "{{\"a\": \"alpha\", \"b\": \"bravo\", \"event\": \"communication.peer_allowed\", \"ts\": \"{ts}\"}}"
87
+ );
88
+ assert_eq!(
89
+ lines[0], expected,
90
+ "golden events.py:35 json.dumps(...,sort_keys=True) line: sorted keys a,b,event,ts with \
91
+ Python `, `/`: ` spacing; got {}",
92
+ lines[0]
93
+ );
94
+ let _ = std::fs::remove_dir_all(&ws);
95
+ }
96
+
97
+ // ── idempotency: identical return, event fires on EVERY call (rows are insert-or-ignore) ──
98
+ #[test]
99
+ fn allow_peer_talk_is_idempotent_and_fires_event_per_call() {
100
+ let ws = tmp_workspace();
101
+ let v1 = app_out(app_call(&ws, "alpha", "bravo"));
102
+ let v2 = app_out(app_call(&ws, "alpha", "bravo")); // duplicate pair
103
+ assert_eq!(v1, v2, "repeated allow returns identical compat_noop output");
104
+ let lines = app_events(&ws);
105
+ assert_eq!(
106
+ lines.len(),
107
+ 2,
108
+ "golden fires communication.peer_allowed on EVERY call (event NOT deduped; only DB rows \
109
+ are insert-or-ignore); got {lines:?}"
110
+ );
111
+ let _ = std::fs::remove_dir_all(&ws);
112
+ }
113
+
114
+ // ── permissive: undeclared/unknown agents still succeed (golden has NO validation path) ──
115
+ #[test]
116
+ fn allow_peer_talk_unknown_agents_succeed_no_validation() {
117
+ let ws = tmp_workspace(); // no state.json / no spec: agents never declared
118
+ let r = app_call(&ws, "ghost", "phantom-x");
119
+ assert_eq!(
120
+ r.exit,
121
+ ExitCode::Ok,
122
+ "golden leader.py:25-28 never validates agent ids -> no error, exit 0"
123
+ );
124
+ let v = app_out(r);
125
+ assert_eq!(
126
+ v,
127
+ json!({
128
+ "ok": true,
129
+ "a": "ghost",
130
+ "b": "phantom-x",
131
+ "status": "compat_noop",
132
+ "reason": "team_scoped_peer_messages_enabled"
133
+ }),
134
+ "unknown agents still get compat_noop (no unknown-agent error string/exit); got {v:?}"
135
+ );
136
+ let _ = std::fs::remove_dir_all(&ws);
137
+ }