@team-agent/installer 0.5.60 → 0.5.62

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 (265) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +49 -3
  4. package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +184 -0
  6. package/crates/team-agent/src/cli/emit.rs +104 -21
  7. package/crates/team-agent/src/cli/helpers.rs +1 -0
  8. package/crates/team-agent/src/cli/leader.rs +14 -2
  9. package/crates/team-agent/src/cli/leaders.rs +2 -0
  10. package/crates/team-agent/src/cli/mod.rs +296 -8
  11. package/crates/team-agent/src/cli/named_address.rs +26 -2
  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 +3 -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 +5 -1
  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/status_port/store.rs +7 -3
  25. package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
  27. package/crates/team-agent/src/cli/types.rs +36 -2
  28. package/crates/team-agent/src/communication_mode/mod.rs +54 -0
  29. package/crates/team-agent/src/compiler/tests.rs +5 -5
  30. package/crates/team-agent/src/compiler.rs +54 -1
  31. package/crates/team-agent/src/conpty/backend.rs +1 -0
  32. package/crates/team-agent/src/conpty/mod.rs +1 -0
  33. package/crates/team-agent/src/coordinator/backoff.rs +5 -8
  34. package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
  35. package/crates/team-agent/src/coordinator/health.rs +1 -0
  36. package/crates/team-agent/src/coordinator/mod.rs +2 -2
  37. package/crates/team-agent/src/coordinator/orphan.rs +4 -116
  38. package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
  39. package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
  40. package/crates/team-agent/src/coordinator/steps/abnormal.rs +6 -5
  41. package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
  42. package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
  43. package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
  44. package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
  45. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
  46. package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
  47. package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
  48. package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +0 -9
  49. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
  50. package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
  51. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -42
  52. package/crates/team-agent/src/coordinator/tick.rs +24 -28
  53. package/crates/team-agent/src/coordinator/types.rs +3 -176
  54. package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
  55. package/crates/team-agent/src/db/message_store.rs +47 -2
  56. package/crates/team-agent/src/db/migration.rs +5 -4
  57. package/crates/team-agent/src/db/mod.rs +1 -0
  58. package/crates/team-agent/src/db/schema.rs +1 -0
  59. package/crates/team-agent/src/diagnose/comms.rs +2 -1
  60. package/crates/team-agent/src/diagnose/mod.rs +1 -0
  61. package/crates/team-agent/src/diagnose/orphans.rs +1 -0
  62. package/crates/team-agent/src/fake_worker.rs +21 -1
  63. package/crates/team-agent/src/layout/manager.rs +3 -7
  64. package/crates/team-agent/src/layout/mod.rs +2 -2
  65. package/crates/team-agent/src/layout/overlay.rs +6 -1
  66. package/crates/team-agent/src/layout/placement.rs +1 -0
  67. package/crates/team-agent/src/layout/recovery.rs +3 -0
  68. package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
  69. package/crates/team-agent/src/layout/sessions.rs +8 -1
  70. package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
  71. package/crates/team-agent/src/layout/worker_env.rs +3 -0
  72. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  73. package/crates/team-agent/src/leader/helpers.rs +1 -0
  74. package/crates/team-agent/src/leader/incident.rs +1 -0
  75. package/crates/team-agent/src/leader/lease.rs +27 -22
  76. package/crates/team-agent/src/leader/mod.rs +2 -3
  77. package/crates/team-agent/src/leader/owner_bind.rs +13 -3
  78. package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
  79. package/crates/team-agent/src/leader/rediscover.rs +4 -17
  80. package/crates/team-agent/src/leader/registry.rs +3 -15
  81. package/crates/team-agent/src/leader/start.rs +1072 -186
  82. package/crates/team-agent/src/leader/takeover.rs +10 -232
  83. package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
  84. package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
  85. package/crates/team-agent/src/leader/tests/identity.rs +71 -59
  86. package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
  87. package/crates/team-agent/src/leader/tests/idle.rs +0 -193
  88. package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
  89. package/crates/team-agent/src/leader/tests/mod.rs +4 -41
  90. package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
  91. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
  92. package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
  93. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
  94. package/crates/team-agent/src/leader/types.rs +1 -28
  95. package/crates/team-agent/src/lib.rs +7 -0
  96. package/crates/team-agent/src/lifecycle/display.rs +7 -1
  97. package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
  98. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
  99. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
  100. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
  102. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
  103. package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
  104. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
  105. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
  106. package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -28
  107. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
  108. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
  109. package/crates/team-agent/src/lifecycle/launch.rs +11 -12
  110. package/crates/team-agent/src/lifecycle/mod.rs +2 -1
  111. package/crates/team-agent/src/lifecycle/restart/agent.rs +4 -4
  112. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  113. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
  114. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
  115. package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
  116. package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
  117. package/crates/team-agent/src/lifecycle/restart.rs +13 -11
  118. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
  119. package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
  120. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
  121. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
  122. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
  123. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
  124. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
  125. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
  126. package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
  127. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
  128. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
  129. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
  130. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
  131. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
  132. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
  133. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
  134. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
  135. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
  136. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
  137. package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
  138. package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
  139. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
  140. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
  141. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
  142. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
  143. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
  144. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
  145. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
  146. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
  147. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
  148. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
  149. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
  150. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
  151. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
  152. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
  153. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
  154. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
  155. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
  156. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
  157. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
  158. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
  159. package/crates/team-agent/src/lifecycle/tests.rs +59 -0
  160. package/crates/team-agent/src/lifecycle/types.rs +0 -9
  161. package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
  162. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  163. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
  164. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
  165. package/crates/team-agent/src/mcp_server/mod.rs +1 -0
  166. package/crates/team-agent/src/mcp_server/normalize.rs +5 -3
  167. package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
  168. package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
  169. package/crates/team-agent/src/mcp_server/tools.rs +101 -66
  170. package/crates/team-agent/src/mcp_server/types.rs +6 -18
  171. package/crates/team-agent/src/mcp_server/wire.rs +32 -26
  172. package/crates/team-agent/src/messaging/activity.rs +1 -0
  173. package/crates/team-agent/src/messaging/address.rs +1 -0
  174. package/crates/team-agent/src/messaging/delivery.rs +109 -41
  175. package/crates/team-agent/src/messaging/helpers.rs +2 -1
  176. package/crates/team-agent/src/messaging/leader_channel.rs +34 -10
  177. package/crates/team-agent/src/messaging/leader_receiver.rs +42 -24
  178. package/crates/team-agent/src/messaging/mod.rs +10 -14
  179. package/crates/team-agent/src/messaging/peers.rs +2 -0
  180. package/crates/team-agent/src/messaging/persist.rs +3 -1
  181. package/crates/team-agent/src/messaging/presentation.rs +111 -1
  182. package/crates/team-agent/src/messaging/results.rs +166 -17
  183. package/crates/team-agent/src/messaging/scheduler.rs +1 -0
  184. package/crates/team-agent/src/messaging/selftest.rs +1 -1
  185. package/crates/team-agent/src/messaging/send.rs +135 -89
  186. package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
  187. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
  188. package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
  189. package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
  190. package/crates/team-agent/src/messaging/types.rs +7 -2
  191. package/crates/team-agent/src/messaging/watchers.rs +24 -13
  192. package/crates/team-agent/src/model/enums.rs +1 -0
  193. package/crates/team-agent/src/model/errors.rs +1 -0
  194. package/crates/team-agent/src/model/ids.rs +1 -0
  195. package/crates/team-agent/src/model/mod.rs +2 -0
  196. package/crates/team-agent/src/model/name_similarity.rs +1 -0
  197. package/crates/team-agent/src/model/pane_authority_refusal.rs +359 -0
  198. package/crates/team-agent/src/model/paths.rs +1 -0
  199. package/crates/team-agent/src/model/permissions.rs +3 -2
  200. package/crates/team-agent/src/model/routing.rs +1 -0
  201. package/crates/team-agent/src/model/spec.rs +17 -0
  202. package/crates/team-agent/src/model/task_graph.rs +1 -0
  203. package/crates/team-agent/src/model/yaml/tests.rs +1 -0
  204. package/crates/team-agent/src/model/yaml.rs +1 -0
  205. package/crates/team-agent/src/packaging/install.rs +9 -147
  206. package/crates/team-agent/src/packaging/migrate.rs +2 -0
  207. package/crates/team-agent/src/packaging/mod.rs +1 -0
  208. package/crates/team-agent/src/packaging/repair.rs +2 -0
  209. package/crates/team-agent/src/packaging/tests.rs +29 -218
  210. package/crates/team-agent/src/packaging/types.rs +8 -15
  211. package/crates/team-agent/src/platform/argv.rs +4 -0
  212. package/crates/team-agent/src/platform/errors.rs +10 -1
  213. package/crates/team-agent/src/platform/file_lock.rs +4 -157
  214. package/crates/team-agent/src/platform/mod.rs +0 -61
  215. package/crates/team-agent/src/platform/process.rs +1 -0
  216. package/crates/team-agent/src/provider/adapter.rs +1 -0
  217. package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
  218. package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
  219. package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
  220. package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
  221. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
  222. package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
  223. package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
  224. package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
  225. package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
  226. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
  227. package/crates/team-agent/src/provider/classify.rs +1 -0
  228. package/crates/team-agent/src/provider/faults.rs +1 -26
  229. package/crates/team-agent/src/provider/helpers.rs +1 -0
  230. package/crates/team-agent/src/provider/mod.rs +1 -1
  231. package/crates/team-agent/src/provider/session/capture.rs +41 -22
  232. package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
  233. package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
  234. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
  235. package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
  236. package/crates/team-agent/src/provider/session/mod.rs +2 -4
  237. package/crates/team-agent/src/provider/session/resume.rs +2 -209
  238. package/crates/team-agent/src/provider/session_scan/claude.rs +93 -3
  239. package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
  240. package/crates/team-agent/src/provider/session_scan/common.rs +24 -7
  241. package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
  242. package/crates/team-agent/src/provider/session_scan.rs +6 -42
  243. package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
  244. package/crates/team-agent/src/provider/types.rs +1 -0
  245. package/crates/team-agent/src/provider/wire.rs +1 -0
  246. package/crates/team-agent/src/state/identity.rs +16 -5
  247. package/crates/team-agent/src/state/identity_keys.rs +1 -0
  248. package/crates/team-agent/src/state/mod.rs +2 -0
  249. package/crates/team-agent/src/state/owner_gate.rs +5 -0
  250. package/crates/team-agent/src/state/ownership.rs +10 -4
  251. package/crates/team-agent/src/state/paths.rs +12 -0
  252. package/crates/team-agent/src/state/persist.rs +9 -3
  253. package/crates/team-agent/src/state/projection.rs +15 -2
  254. package/crates/team-agent/src/state/repository/intent.rs +1 -0
  255. package/crates/team-agent/src/state/repository/tests.rs +1 -0
  256. package/crates/team-agent/src/state/repository.rs +7 -0
  257. package/crates/team-agent/src/state/selector.rs +1 -0
  258. package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
  259. package/crates/team-agent/src/tmux_backend.rs +63 -0
  260. package/crates/team-agent/src/topology.rs +3 -0
  261. package/crates/team-agent/src/transport.rs +15 -0
  262. package/package.json +4 -4
  263. package/skills/team-agent/command-coverage.json +379 -0
  264. package/crates/team-agent/src/leader/inject.rs +0 -33
  265. package/crates/team-agent/src/provider/command.rs +0 -80
@@ -1,179 +1,5 @@
1
1
  use super::*;
2
2
 
3
- // ═════════════════════════════════════════════════════════════════════════
4
- // GROUP E — abnormal_track classify / dedup (abnormal_track.py) — RED
5
- // provider-neutral:断言 ZERO adapter_for 调用(MUST-NOT-13)。
6
- // ASSUMPTION:传入 `records` 是 provider reader 已产出的结构化 fault facts
7
- // ({signature, turn_id?, kind?, raw}),即 read_fault_facts 的输出形;abnormal_track
8
- // 只对它做 catch-bias + (signature, turn_id|fingerprint) 去重(error_lists 来自注入
9
- // registry,绝不经 adapter_for 触碰 provider client)。
10
- // ═════════════════════════════════════════════════════════════════════════
11
-
12
- #[test]
13
- fn abnormal_default_notify_for_structured_fault() {
14
- // abnormal_track.py:204 — C9 catch-bias: 无黑白名单命中 → notify_default.
15
- let reg = MockRegistry::new(&[], &[]);
16
- let records = vec![serde_json::json!({"raw": "session line"})];
17
- let out = process_abnormal_records(
18
- &records,
19
- &reg,
20
- Provider::Codex,
21
- &AbnormalNotificationState::default(),
22
- )
23
- .unwrap();
24
- assert_eq!(out.notifications.len(), 1);
25
- assert_eq!(
26
- out.notifications[0].decision,
27
- AbnormalDecision::NotifyDefault
28
- );
29
- // §84:绝不触碰 provider adapter client。
30
- assert_eq!(
31
- reg.adapter_calls.get(),
32
- 0,
33
- "MUST NOT call adapter_for (provider client)"
34
- );
35
- }
36
-
37
- #[test]
38
- fn abnormal_blacklist_hit_yields_notify_blacklist() {
39
- // abnormal_track.py:203 — blacklist 命中 → notify_blacklist.
40
- let reg = MockRegistry::new(&[], &["boom"]);
41
- let records = vec![serde_json::json!({"raw": "kaboom: boom happened"})];
42
- let out = process_abnormal_records(
43
- &records,
44
- &reg,
45
- Provider::Codex,
46
- &AbnormalNotificationState::default(),
47
- )
48
- .unwrap();
49
- assert_eq!(out.notifications.len(), 1);
50
- assert_eq!(
51
- out.notifications[0].decision,
52
- AbnormalDecision::NotifyBlacklist
53
- );
54
- assert_eq!(reg.adapter_calls.get(), 0);
55
- }
56
-
57
- #[test]
58
- fn abnormal_whitelist_beats_blacklist_and_skips() {
59
- // abnormal_track.py:200-201 — whitelist > blacklist > default. 同命中 → skip, 0 notify.
60
- let reg = MockRegistry::new(&["boom"], &["boom"]);
61
- let records = vec![serde_json::json!({"raw": "boom transient"})];
62
- let out = process_abnormal_records(
63
- &records,
64
- &reg,
65
- Provider::Codex,
66
- &AbnormalNotificationState::default(),
67
- )
68
- .unwrap();
69
- assert_eq!(
70
- out.notifications.len(),
71
- 0,
72
- "whitelist wins → skip (probe confirmed)"
73
- );
74
- assert_eq!(reg.adapter_calls.get(), 0);
75
- }
76
-
77
- #[test]
78
- fn abnormal_approval_kind_maps_to_blocked_on_human() {
79
- // abnormal_track.py:74 — kind=="approval" → state blocked_on_human, else abnormal.
80
- let reg = MockRegistry::new(&[], &[]);
81
- let records = vec![serde_json::json!({"raw": "approve?", "kind": "approval"})];
82
- let out = process_abnormal_records(
83
- &records,
84
- &reg,
85
- Provider::Codex,
86
- &AbnormalNotificationState::default(),
87
- )
88
- .unwrap();
89
- assert_eq!(out.notifications.len(), 1);
90
- assert_eq!(out.notifications[0].state, TurnState::BlockedOnHuman);
91
- }
92
-
93
- #[test]
94
- fn abnormal_same_turn_id_folds_to_one_notification() {
95
- // abnormal_track.py:60-68 — C8 dedup: same (signature, turn_id) → one notify.
96
- let reg = MockRegistry::new(&[], &[]);
97
- let records = vec![
98
- serde_json::json!({"signature": "sig_a", "turn_id": "t1", "raw": "boom"}),
99
- serde_json::json!({"signature": "sig_a", "turn_id": "t1", "raw": "boom"}),
100
- ];
101
- let out = process_abnormal_records(
102
- &records,
103
- &reg,
104
- Provider::Codex,
105
- &AbnormalNotificationState::default(),
106
- )
107
- .unwrap();
108
- assert_eq!(
109
- out.notifications.len(),
110
- 1,
111
- "retry-loop in SAME turn folds (probe: 1)"
112
- );
113
- // seen 状态回存,key = "sig_a\0t1"。
114
- assert!(out.notification_state.seen.contains("sig_a\u{0}t1"));
115
- }
116
-
117
- #[test]
118
- fn abnormal_missing_turn_id_distinct_faults_each_notify() {
119
- // abnormal_track.py:64 — turn_id None → per-record content fingerprint bucket:
120
- // 不同 fault 各自 notify(绝不折叠进全局桶,probe: 2)。
121
- let reg = MockRegistry::new(&[], &[]);
122
- let records = vec![
123
- serde_json::json!({"signature": "sig_b", "raw": "alpha"}),
124
- serde_json::json!({"signature": "sig_b", "raw": "beta"}),
125
- ];
126
- let out = process_abnormal_records(
127
- &records,
128
- &reg,
129
- Provider::Codex,
130
- &AbnormalNotificationState::default(),
131
- )
132
- .unwrap();
133
- assert_eq!(
134
- out.notifications.len(),
135
- 2,
136
- "distinct raw → 2 notifies (probe)"
137
- );
138
- }
139
-
140
- #[test]
141
- fn abnormal_missing_turn_id_identical_faults_fold() {
142
- // abnormal_track.py:64-68 — identical raw + missing turn_id → same fingerprint → fold (probe: 1).
143
- let reg = MockRegistry::new(&[], &[]);
144
- let records = vec![
145
- serde_json::json!({"signature": "sig_c", "raw": "same"}),
146
- serde_json::json!({"signature": "sig_c", "raw": "same"}),
147
- ];
148
- let out = process_abnormal_records(
149
- &records,
150
- &reg,
151
- Provider::Codex,
152
- &AbnormalNotificationState::default(),
153
- )
154
- .unwrap();
155
- assert_eq!(
156
- out.notifications.len(),
157
- 1,
158
- "identical raw folds even w/o turn_id (probe)"
159
- );
160
- }
161
-
162
- #[test]
163
- fn abnormal_preseeded_seen_suppresses_duplicate_across_calls() {
164
- // abnormal_track.py:67 — key already in seen → skip notification (cross-tick dedup).
165
- let reg = MockRegistry::new(&[], &[]);
166
- let mut state = AbnormalNotificationState::default();
167
- state.seen.insert("sig_a\u{0}t1".to_string());
168
- let records = vec![serde_json::json!({"signature": "sig_a", "turn_id": "t1", "raw": "boom"})];
169
- let out = process_abnormal_records(&records, &reg, Provider::Codex, &state).unwrap();
170
- assert_eq!(
171
- out.notifications.len(),
172
- 0,
173
- "pre-seeded seen suppresses (probe: 0)"
174
- );
175
- }
176
-
177
3
  #[test]
178
4
  fn abnormal_explicit_error_classifier_boundaries_stay_stable() {
179
5
  let claude_api = line(serde_json::json!({
@@ -447,7 +447,6 @@ impl Drop for RecoveryCase {
447
447
  #[derive(Clone)]
448
448
  struct CounterState {
449
449
  adapter_calls: Arc<AtomicU32>,
450
- error_list_calls: Arc<AtomicU32>,
451
450
  }
452
451
 
453
452
  struct CountingRegistry {
@@ -459,7 +458,6 @@ impl CountingRegistry {
459
458
  Self {
460
459
  counters: CounterState {
461
460
  adapter_calls: Arc::new(AtomicU32::new(0)),
462
- error_list_calls: Arc::new(AtomicU32::new(0)),
463
461
  },
464
462
  }
465
463
  }
@@ -474,13 +472,6 @@ impl ProviderRegistry for CountingRegistry {
474
472
  self.counters.adapter_calls.fetch_add(1, Ordering::SeqCst);
475
473
  crate::provider::get_adapter(provider)
476
474
  }
477
-
478
- fn error_lists(&self, _provider: Provider) -> ErrorLists {
479
- self.counters
480
- .error_list_calls
481
- .fetch_add(1, Ordering::SeqCst);
482
- ErrorLists::default()
483
- }
484
475
  }
485
476
 
486
477
  fn claude_api_error_line(uuid: &str, status: i64, error: &str) -> String {
@@ -98,18 +98,6 @@ fn status_enums_serialize_to_exact_python_strings() {
98
98
  serde_json::to_string(&MetadataSource::Start).unwrap(),
99
99
  "\"start\"",
100
100
  ),
101
- (
102
- serde_json::to_string(&AbnormalDecision::Skip).unwrap(),
103
- "\"skip\"",
104
- ),
105
- (
106
- serde_json::to_string(&AbnormalDecision::NotifyBlacklist).unwrap(),
107
- "\"notify_blacklist\"",
108
- ),
109
- (
110
- serde_json::to_string(&AbnormalDecision::NotifyDefault).unwrap(),
111
- "\"notify_default\"",
112
- ),
113
101
  (
114
102
  serde_json::to_string(&WholeTeamGoneClass::Alive).unwrap(),
115
103
  "\"alive\"",
@@ -132,178 +120,6 @@ fn status_enums_serialize_to_exact_python_strings() {
132
120
  }
133
121
  }
134
122
 
135
- #[test]
136
- fn coordinator_event_tags_are_byte_stable() {
137
- // 逐一钉 events.jsonl tag(events.py 稳定契约)。
138
- let pid = Pid(4242);
139
- let cases: Vec<(CoordinatorEvent, &str)> = vec![
140
- (
141
- CoordinatorEvent::Boot {
142
- workspace: "/w".into(),
143
- once: true,
144
- },
145
- "coordinator.boot",
146
- ),
147
- (
148
- CoordinatorEvent::Started {
149
- pid,
150
- log: "/l".into(),
151
- },
152
- "coordinator.started",
153
- ),
154
- (CoordinatorEvent::Stopped { pid }, "coordinator.stopped"),
155
- (CoordinatorEvent::Exit { stop: true }, "coordinator.exit"),
156
- (
157
- CoordinatorEvent::SessionMissing {
158
- session: "s".into(),
159
- },
160
- "coordinator.session_missing",
161
- ),
162
- (
163
- CoordinatorEvent::OrphanSelfTerminate {
164
- initial_ppid: 9,
165
- current_ppid: 1,
166
- workspace: "/w".into(),
167
- },
168
- "coordinator.orphan_self_terminate",
169
- ),
170
- (
171
- CoordinatorEvent::TickError {
172
- error: "boom".into(),
173
- exc_type: "OSError".into(),
174
- consecutive_failures: 1,
175
- next_sleep_sec: 5.0,
176
- },
177
- "coordinator.tick_error",
178
- ),
179
- (
180
- CoordinatorEvent::TickErrorSuppressed {
181
- consecutive_failures: 2,
182
- next_sleep_sec: 10.0,
183
- },
184
- "coordinator.tick_error.suppressed",
185
- ),
186
- (
187
- CoordinatorEvent::TickRecovered {
188
- consecutive_failures: 3,
189
- },
190
- "coordinator.tick_recovered",
191
- ),
192
- (
193
- CoordinatorEvent::RestartIncompatible {
194
- pid: Some(pid),
195
- expected_protocol: 2,
196
- expected_schema: 3,
197
- },
198
- "coordinator.restart_incompatible",
199
- ),
200
- (
201
- CoordinatorEvent::RestartIncompatibleStopFailed { pid: Some(pid) },
202
- "coordinator.restart_incompatible_stop_failed",
203
- ),
204
- (
205
- CoordinatorEvent::SchemaIncompatible {
206
- table: Some("messages".into()),
207
- missing_columns: vec!["owner_team_id".into()],
208
- },
209
- "coordinator.schema_incompatible",
210
- ),
211
- (
212
- CoordinatorEvent::IdleTakeoverUnknownPersistent {
213
- node_id: "w1".into(),
214
- provider: Some(Provider::Codex),
215
- auth_mode: None,
216
- consecutive_ticks: 60,
217
- rollout_path: None,
218
- },
219
- "idle_takeover.unknown_persistent",
220
- ),
221
- (
222
- CoordinatorEvent::AbnormalNotify {
223
- signature: "sig".into(),
224
- turn_id: None,
225
- decision: AbnormalDecision::NotifyDefault,
226
- },
227
- "abnormal.notify",
228
- ),
229
- (
230
- CoordinatorEvent::AbnormalWholeTeamGone {
231
- classification: WholeTeamGoneClass::UnexpectedExit,
232
- },
233
- "abnormal.whole_team_gone",
234
- ),
235
- (
236
- CoordinatorEvent::LeaderNotificationLogPruned { removed: 7 },
237
- "leader_notification.log_pruned",
238
- ),
239
- (
240
- CoordinatorEvent::LeaderNotificationPruneFailed { error: "io".into() },
241
- "leader_notification.prune_failed",
242
- ),
243
- (
244
- CoordinatorEvent::RuntimeStateSaveFailed {
245
- phase: "tick_end".into(),
246
- error: "replace".into(),
247
- exc_type: "OSError".into(),
248
- },
249
- "runtime.state.save_failed",
250
- ),
251
- ];
252
- for (evt, want_tag) in &cases {
253
- let json = serde_json::to_value(evt).unwrap();
254
- assert_eq!(json["event"], *want_tag, "tag mismatch for {want_tag}");
255
- }
256
- }
257
-
258
- #[test]
259
- fn tick_error_event_carries_exact_python_fields() {
260
- // __main__.py:69-74 — error / exc_type / consecutive_failures / next_sleep_sec.
261
- let evt = CoordinatorEvent::TickError {
262
- error: "os.replace failed".into(),
263
- exc_type: "OSError".into(),
264
- consecutive_failures: 4,
265
- next_sleep_sec: 40.0,
266
- };
267
- let json = serde_json::to_value(&evt).unwrap();
268
- assert_eq!(json["event"], "coordinator.tick_error");
269
- assert_eq!(json["error"], "os.replace failed");
270
- assert_eq!(json["exc_type"], "OSError");
271
- assert_eq!(json["consecutive_failures"], 4);
272
- assert_eq!(json["next_sleep_sec"], 40.0);
273
- }
274
-
275
- #[test]
276
- fn unknown_persistent_event_provider_none_is_null_not_missing() {
277
- // lifecycle.py:407 rollout_path=node.get(...) — None 漏穿堵死(bug-085):
278
- // provider/rollout_path 缺失序列化为 JSON null,绝不省略键。
279
- let evt = CoordinatorEvent::IdleTakeoverUnknownPersistent {
280
- node_id: "w7".into(),
281
- provider: None,
282
- auth_mode: None,
283
- consecutive_ticks: 72,
284
- rollout_path: None,
285
- };
286
- let json = serde_json::to_value(&evt).unwrap();
287
- assert_eq!(json["event"], "idle_takeover.unknown_persistent");
288
- assert_eq!(json["node_id"], "w7");
289
- assert!(
290
- json.get("provider").is_some(),
291
- "provider key MUST be present (None→null)"
292
- );
293
- assert!(json["provider"].is_null(), "provider None → JSON null");
294
- assert!(
295
- json.get("auth_mode").is_some(),
296
- "auth_mode key MUST be present"
297
- );
298
- assert!(json["auth_mode"].is_null(), "auth_mode None → JSON null");
299
- assert!(
300
- json.get("rollout_path").is_some(),
301
- "rollout_path key MUST be present"
302
- );
303
- assert!(json["rollout_path"].is_null());
304
- assert_eq!(json["consecutive_ticks"], 72);
305
- }
306
-
307
123
  #[test]
308
124
  fn coordinator_metadata_json_field_names_are_stable() {
309
125
  // metadata.py:50-57 — 稳定 coordinator.json 契约。
@@ -25,23 +25,15 @@ fn meta(pid: u32, proto: u32, schema: i64) -> CoordinatorMetadata {
25
25
  }
26
26
  }
27
27
 
28
- /// `ProviderRegistry` mock — 断言**零** provider-client 调用 (MUST-NOT-13 / §84).
29
- /// `adapter_for`/`error_lists` 各记一次调用计数;abnormal-track 只允许触碰 `error_lists`,
30
- /// 绝不触碰 `adapter_for`(那会走真实 provider client crate)。
28
+ /// `ProviderRegistry` mock — 记录 provider-client adapter 调用 (MUST-NOT-13 / §84).
31
29
  struct MockRegistry {
32
- whitelist: Vec<String>,
33
- blacklist: Vec<String>,
34
30
  adapter_calls: std::cell::Cell<u32>,
35
- error_list_calls: std::cell::Cell<u32>,
36
31
  }
37
32
 
38
33
  impl MockRegistry {
39
- fn new(whitelist: &[&str], blacklist: &[&str]) -> Self {
34
+ fn new(_whitelist: &[&str], _blacklist: &[&str]) -> Self {
40
35
  Self {
41
- whitelist: whitelist.iter().map(|s| s.to_string()).collect(),
42
- blacklist: blacklist.iter().map(|s| s.to_string()).collect(),
43
36
  adapter_calls: std::cell::Cell::new(0),
44
- error_list_calls: std::cell::Cell::new(0),
45
37
  }
46
38
  }
47
39
  }
@@ -52,13 +44,6 @@ impl ProviderRegistry for MockRegistry {
52
44
  self.adapter_calls.set(self.adapter_calls.get() + 1);
53
45
  crate::provider::get_adapter(_provider)
54
46
  }
55
- fn error_lists(&self, _provider: Provider) -> ErrorLists {
56
- self.error_list_calls.set(self.error_list_calls.get() + 1);
57
- ErrorLists {
58
- whitelist: self.whitelist.clone(),
59
- blacklist: self.blacklist.clone(),
60
- }
61
- }
62
47
  }
63
48
 
64
49
  /// in-memory `MarkerStore` —— durable marker 落盘探针。
@@ -260,45 +260,3 @@ fn p2_render_result_received_truncates_summary_to_80_chars() {
260
260
  "summary must be truncated to 80 chars"
261
261
  );
262
262
  }
263
-
264
- // P1 — idle_takeover.unknown_persistent must carry the auth_mode field (null when absent)
265
- // (lifecycle.py:401-408). NOTE: the variant currently lacks the field; the porter adds
266
- // `auth_mode` between provider and consecutive_ticks and updates this construction.
267
- #[test]
268
- fn p2_unknown_persistent_event_serializes_auth_mode_key() {
269
- let evt = CoordinatorEvent::IdleTakeoverUnknownPersistent {
270
- node_id: "w7".into(),
271
- provider: None,
272
- auth_mode: None,
273
- consecutive_ticks: 72,
274
- rollout_path: None,
275
- };
276
- let json = serde_json::to_value(&evt).unwrap();
277
- assert!(
278
- json.get("auth_mode").is_some(),
279
- "idle_takeover.unknown_persistent must serialize an auth_mode key (null when absent)"
280
- );
281
- }
282
-
283
- // P1 — process_abnormal_records matches a LOWERCASED signature too (abnormal_track.py:49,198):
284
- // raw has no needle but signature 'TimeoutError' matches blacklist 'timeout'. Current is
285
- // case-sensitive on `raw` only and ignores the signature → NotifyDefault.
286
- #[test]
287
- fn p2_abnormal_matches_lowercased_signature_too() {
288
- let reg = MockRegistry::new(&[], &["timeout"]);
289
- let records =
290
- vec![serde_json::json!({"raw":"nothing here","signature":"TimeoutError","kind":"error"})];
291
- let out = process_abnormal_records(
292
- &records,
293
- &reg,
294
- Provider::Codex,
295
- &AbnormalNotificationState::default(),
296
- )
297
- .unwrap();
298
- assert_eq!(out.notifications.len(), 1);
299
- assert_eq!(
300
- out.notifications[0].decision,
301
- AbnormalDecision::NotifyBlacklist,
302
- "blacklist 'timeout' must match the lowercased signature 'TimeoutError'"
303
- );
304
- }
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! Coordinator core:daemon lifecycle 宿主 + 单次 tick 编排(19 步固定顺序)+ health/start/stop。
2
3
 
3
4
  use std::collections::BTreeMap;
@@ -19,6 +20,7 @@ use super::health::{
19
20
  pid_is_running, read_coordinator_metadata, write_coordinator_metadata,
20
21
  };
21
22
  use super::runtime_observation::{self, CapturedRuntimeFact};
23
+ use super::steps::TickStepGroup;
22
24
  use super::types::{
23
25
  AgentId, CoordinatorHealthStatus, HealthReport, MetadataSource, Pid, ProviderRegistry,
24
26
  SchemaHealth, StartError, StartOutcome, StartReport, StopError, StopOutcome, StopReport,
@@ -225,7 +227,7 @@ impl Coordinator {
225
227
  /// `if let Some(rec) = &self.order_recorder { rec.lock()...push(STEP_NAME) }`(tick
226
228
  /// 副作用 ORDER 测试断言固定序列)。生产两者均 `None`,零开销。
227
229
  pub fn tick(&self) -> Result<TickReport, TickError> {
228
- self.record_step("load_state");
230
+ self.record_step(TickStepGroup::SessionGate, "load_state");
229
231
  let raw_state = crate::state::persist::load_runtime_state(self.workspace.as_path())?;
230
232
  // Issue 2 (Round 3b gate review §6): when the runtime carries
231
233
  // `active_team_key` AND `teams.<key>` exists, project the team-scoped
@@ -246,7 +248,7 @@ impl Coordinator {
246
248
  let event_log = EventLog::new(self.workspace.as_path());
247
249
  increment_coordinator_tick_iteration_count(&self.workspace);
248
250
 
249
- self.record_step("tmux_session_gate");
251
+ self.record_step(TickStepGroup::SessionGate, "tmux_session_gate");
250
252
  if let Some(session_name) = state
251
253
  .get("session_name")
252
254
  .and_then(Value::as_str)
@@ -268,7 +270,7 @@ impl Coordinator {
268
270
  }
269
271
  }
270
272
 
271
- self.record_step("capture_missing");
273
+ self.record_step(TickStepGroup::SessionGate, "capture_missing");
272
274
  let pending_context_fork_audits =
273
275
  match self.capture_missing_sessions(&mut state, &event_log) {
274
276
  Ok(audits) => audits,
@@ -293,10 +295,10 @@ impl Coordinator {
293
295
  .unwrap_or_default();
294
296
  let has_work_obligation = tick_has_work_obligation(&store);
295
297
 
296
- self.record_step("refresh_statuses");
298
+ self.record_step(TickStepGroup::HealthSync, "refresh_statuses");
297
299
  // TODO(spine slice 2b): split lightweight runtime status refresh from health sync.
298
300
 
299
- self.record_step("startup_prompts");
301
+ self.record_step(TickStepGroup::RuntimePrompts, "startup_prompts");
300
302
  self.handle_startup_prompts(&mut state, &event_log, &pane_snapshot, &window_snapshot);
301
303
 
302
304
  // #229 step2-retry: once an agent's `startup_prompts` flipped to `handled`
@@ -318,7 +320,7 @@ impl Coordinator {
318
320
  // 否则 deliver_pending(下行投递主干)够不到,消息卡 accepted。
319
321
  // bug-084 哲学 + A-6 同族:每步独立 try,失败写 `coordinator.tick.<step>_failed`
320
322
  // 事件后继续走下一步;tick 本身仍返 Ok。
321
- self.record_step("runtime_prompts");
323
+ self.record_step(TickStepGroup::RuntimePrompts, "runtime_prompts");
322
324
  if let Err(error) = self.handle_runtime_approval_prompts(
323
325
  &mut state,
324
326
  &event_log,
@@ -331,7 +333,7 @@ impl Coordinator {
331
333
  );
332
334
  }
333
335
 
334
- self.record_step("sync_health");
336
+ self.record_step(TickStepGroup::HealthSync, "sync_health");
335
337
  // P5 (C-P5-1, N3): ONE pane snapshot per tick, shared by sync_health and the
336
338
  // abnormal-exit pass (same-tick reuse only — the snapshot does not outlive
337
339
  // this tick; every tick re-reads).
@@ -365,7 +367,7 @@ impl Coordinator {
365
367
  );
366
368
  }
367
369
 
368
- self.record_step("deliver_pending");
370
+ self.record_step(TickStepGroup::Delivery, "deliver_pending");
369
371
  let delivered = crate::messaging::deliver_pending_messages(
370
372
  self.workspace.as_path(),
371
373
  &state,
@@ -376,7 +378,7 @@ impl Coordinator {
376
378
  .map(|message_id| DeliveredMessage { message_id })
377
379
  .collect::<Vec<_>>();
378
380
 
379
- self.record_step("fire_scheduled");
381
+ self.record_step(TickStepGroup::Delivery, "fire_scheduled");
380
382
  let scheduled = crate::messaging::fire_due_scheduled_events(
381
383
  self.workspace.as_path(),
382
384
  &store,
@@ -394,18 +396,18 @@ impl Coordinator {
394
396
  // (deliver_pending / fire_scheduled / collect_results) above and below this
395
397
  // block continue to flow unchanged. `_state` / `_store` here are intentionally
396
398
  // unused (the lookups they powered were nag inputs only).
397
- self.record_step("detect_stuck");
399
+ self.record_step(TickStepGroup::Abnormal, "detect_stuck");
398
400
  let stuck: Vec<AgentId> = Vec::new();
399
- self.record_step("record_unknown_idle");
400
- self.record_step("evaluate_takeover");
401
+ self.record_step(TickStepGroup::Abnormal, "record_unknown_idle");
402
+ self.record_step(TickStepGroup::Abnormal, "evaluate_takeover");
401
403
  let idle_alerts: Vec<IdleAlert> = Vec::new();
402
- self.record_step("detect_deadlocks");
404
+ self.record_step(TickStepGroup::Abnormal, "detect_deadlocks");
403
405
  let deadlock_alerts: Vec<DeadlockAlert> = Vec::new();
404
406
  let _ = &store;
405
407
 
406
- self.record_step("detect_compaction");
407
- self.record_step("detect_drift");
408
- self.record_step("detect_api_errors");
408
+ self.record_step(TickStepGroup::Abnormal, "detect_compaction");
409
+ self.record_step(TickStepGroup::Abnormal, "detect_drift");
410
+ self.record_step(TickStepGroup::Abnormal, "detect_api_errors");
409
411
  let leader_capture = self.capture_leader_receiver(&state);
410
412
  let observations = runtime_observation::observe(
411
413
  self.workspace.as_path(),
@@ -425,7 +427,7 @@ impl Coordinator {
425
427
  results: Vec::new(),
426
428
  };
427
429
 
428
- self.record_step("atomic_save");
430
+ self.record_step(TickStepGroup::Persist, "atomic_save");
429
431
  let saved = match &self.save_hook {
430
432
  Some(hook) => hook(&self.workspace, &state),
431
433
  None => {
@@ -474,20 +476,20 @@ impl Coordinator {
474
476
  // step, and drives the lifecycle helper. Runs OUTSIDE the pre-save
475
477
  // window so nested lifecycle saves cannot be clobbered by this
476
478
  // tick's final in-memory save (R6 boundary guard).
477
- self.record_step("attempt_api_error_recoveries");
479
+ self.record_step(TickStepGroup::Abnormal, "attempt_api_error_recoveries");
478
480
  crate::coordinator::steps::abnormal::attempt_due_recoveries(
479
481
  self.workspace.as_path(),
480
482
  &event_log,
481
483
  self.transport.as_ref(),
482
484
  );
483
485
 
484
- self.record_step("collect_results");
486
+ self.record_step(TickStepGroup::Delivery, "collect_results");
485
487
  collections.results =
486
488
  collect_results(crate::messaging::collect_results_and_notify_watchers(
487
489
  self.workspace.as_path(),
488
490
  &event_log,
489
491
  )?);
490
- self.record_step("prune_dedupe_log");
492
+ self.record_step(TickStepGroup::Persist, "prune_dedupe_log");
491
493
  Ok(base_tick_report(true, false, None, Some(true), collections))
492
494
  }
493
495
 
@@ -1430,7 +1432,8 @@ impl Coordinator {
1430
1432
  super::health::message_store_schema_health(&self.workspace)
1431
1433
  }
1432
1434
 
1433
- fn record_step(&self, step: &'static str) {
1435
+ fn record_step(&self, group: TickStepGroup, step: &'static str) {
1436
+ debug_assert!(TickStepGroup::ordered().contains(&group));
1434
1437
  if let Some(recorder) = &self.order_recorder {
1435
1438
  if let Ok(mut guard) = recorder.lock() {
1436
1439
  guard.push(step);
@@ -2757,13 +2760,6 @@ mod u1_tests {
2757
2760
  ),
2758
2761
  )
2759
2762
  }
2760
-
2761
- fn error_lists(
2762
- &self,
2763
- _provider: crate::provider::Provider,
2764
- ) -> super::super::types::ErrorLists {
2765
- super::super::types::ErrorLists::default()
2766
- }
2767
2763
  }
2768
2764
 
2769
2765
  #[test]