@team-agent/installer 0.5.42 → 0.5.44

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 (156) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +129 -54
  4. package/crates/team-agent/src/cli/diagnose.rs +1 -2
  5. package/crates/team-agent/src/cli/emit.rs +1 -7
  6. package/crates/team-agent/src/cli/helpers.rs +3 -1
  7. package/crates/team-agent/src/cli/leader.rs +2 -1
  8. package/crates/team-agent/src/cli/mod.rs +126 -16
  9. package/crates/team-agent/src/cli/named_address.rs +14 -5
  10. package/crates/team-agent/src/cli/profile.rs +19 -7
  11. package/crates/team-agent/src/cli/send.rs +9 -3
  12. package/crates/team-agent/src/cli/status.rs +8 -30
  13. package/crates/team-agent/src/cli/status_port.rs +1341 -1272
  14. package/crates/team-agent/src/cli/tests/base.rs +738 -660
  15. package/crates/team-agent/src/cli/tests/compile.rs +45 -18
  16. package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
  21. package/crates/team-agent/src/cli/tests/mod.rs +17 -7
  22. package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
  23. package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
  25. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
  26. package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
  27. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
  28. package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
  29. package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
  30. package/crates/team-agent/src/codex_app_server.rs +2 -5
  31. package/crates/team-agent/src/compiler/tests.rs +139 -33
  32. package/crates/team-agent/src/compiler.rs +55 -22
  33. package/crates/team-agent/src/conpty/backend.rs +23 -33
  34. package/crates/team-agent/src/coordinator/backoff.rs +2 -7
  35. package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
  36. package/crates/team-agent/src/coordinator/health.rs +46 -31
  37. package/crates/team-agent/src/coordinator/mod.rs +3 -3
  38. package/crates/team-agent/src/coordinator/orphan.rs +22 -10
  39. package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
  40. package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
  41. package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
  42. package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
  43. package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
  44. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
  45. package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
  46. package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
  47. package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
  49. package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
  50. package/crates/team-agent/src/db/message_store.rs +138 -30
  51. package/crates/team-agent/src/db/migration.rs +249 -61
  52. package/crates/team-agent/src/db/schema.rs +303 -82
  53. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  54. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  55. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  56. package/crates/team-agent/src/event_log.rs +70 -16
  57. package/crates/team-agent/src/layout/manager.rs +15 -4
  58. package/crates/team-agent/src/layout/mod.rs +4 -4
  59. package/crates/team-agent/src/layout/overlay.rs +10 -3
  60. package/crates/team-agent/src/layout/placement.rs +5 -1
  61. package/crates/team-agent/src/layout/recovery.rs +4 -2
  62. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  63. package/crates/team-agent/src/layout/sessions.rs +17 -9
  64. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  65. package/crates/team-agent/src/layout/worker_env.rs +87 -19
  66. package/crates/team-agent/src/leader/helpers.rs +7 -1
  67. package/crates/team-agent/src/leader/lease.rs +199 -89
  68. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  69. package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
  70. package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
  71. package/crates/team-agent/src/leader/rediscover.rs +74 -25
  72. package/crates/team-agent/src/leader/registry.rs +1 -1
  73. package/crates/team-agent/src/leader/start.rs +75 -54
  74. package/crates/team-agent/src/leader/takeover.rs +46 -11
  75. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  76. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  77. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  78. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  79. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  80. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  81. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  82. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  83. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  84. package/crates/team-agent/src/lib.rs +4 -4
  85. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  86. package/crates/team-agent/src/lifecycle/launch.rs +55 -15
  87. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  88. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  89. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  90. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  91. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
  92. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  93. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  94. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  95. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  96. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  97. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  98. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  99. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  100. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
  103. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  104. package/crates/team-agent/src/main.rs +4 -4
  105. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  106. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  107. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  108. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  109. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  110. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  112. package/crates/team-agent/src/messaging/mod.rs +2 -3
  113. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  114. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  115. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  116. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  117. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  118. package/crates/team-agent/src/messaging/trust.rs +25 -2
  119. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  120. package/crates/team-agent/src/model/enums.rs +65 -16
  121. package/crates/team-agent/src/model/ids.rs +12 -3
  122. package/crates/team-agent/src/model/paths.rs +28 -7
  123. package/crates/team-agent/src/model/permissions.rs +176 -33
  124. package/crates/team-agent/src/model/routing.rs +66 -20
  125. package/crates/team-agent/src/model/spec.rs +365 -69
  126. package/crates/team-agent/src/model/task_graph.rs +36 -9
  127. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  128. package/crates/team-agent/src/model/yaml.rs +7 -6
  129. package/crates/team-agent/src/packaging/install.rs +23 -9
  130. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  131. package/crates/team-agent/src/packaging/mod.rs +9 -1
  132. package/crates/team-agent/src/packaging/repair.rs +13 -6
  133. package/crates/team-agent/src/packaging/tests.rs +63 -16
  134. package/crates/team-agent/src/packaging/types.rs +22 -7
  135. package/crates/team-agent/src/platform/argv.rs +4 -1
  136. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  137. package/crates/team-agent/src/platform/process.rs +54 -24
  138. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  139. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  140. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  141. package/crates/team-agent/src/provider/classify.rs +127 -42
  142. package/crates/team-agent/src/provider/faults.rs +17 -5
  143. package/crates/team-agent/src/provider/helpers.rs +6 -5
  144. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  145. package/crates/team-agent/src/state/persist.rs +28 -0
  146. package/crates/team-agent/src/state/repository.rs +8 -3
  147. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  148. package/crates/team-agent/src/tmux_backend.rs +80 -44
  149. package/crates/team-agent/src/topology.rs +40 -20
  150. package/crates/team-agent/src/transport/test_support.rs +20 -23
  151. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  152. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  153. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  154. package/crates/team-agent/src/transport.rs +12 -17
  155. package/crates/team-agent/src/transport_factory.rs +29 -14
  156. package/package.json +4 -4
@@ -1,184 +1,337 @@
1
1
  use super::*;
2
2
 
3
- // =====================================================================
4
- // 1. 锁名 / 闭枚举 / serde 字节对齐(纯数据,不依赖 unimplemented body)
5
- // =====================================================================
6
-
7
- // LEADER_OWNERSHIP_LOCK = "send"(__init__.py:393):attach/claim/takeover/autobind 共享同一临界区。
8
- #[test]
9
- fn ownership_lock_is_send() {
10
- assert_eq!(LEADER_OWNERSHIP_LOCK, "send");
11
- }
3
+ // =====================================================================
4
+ // 1. 锁名 / 闭枚举 / serde 字节对齐(纯数据,不依赖 unimplemented body)
5
+ // =====================================================================
12
6
 
13
- // LeaseReason 闭枚举的 serde 字节必须与 _LEASE_REASON_ENUM ∪ {caller_pane_missing} 一致。
14
- // golden(probe_leader.py):enum 8 值(snake)+ binding 的 caller_pane_missing。
15
- #[test]
16
- fn lease_reason_serializes_to_exact_python_strings() {
17
- let cases = [
18
- (LeaseReason::VacantAcquired, "\"vacant_acquired\""),
19
- (LeaseReason::PreviousOwnerPaneDead, "\"previous_owner_pane_dead\""),
20
- (LeaseReason::PreviousOwnerAliveRefused, "\"previous_owner_alive_refused\""),
21
- (LeaseReason::OwnerEpochAdvanced, "\"owner_epoch_advanced\""),
22
- (LeaseReason::ForceConfirmRequired, "\"force_confirm_required\""),
23
- (LeaseReason::CallerNotLeaderShaped, "\"caller_not_leader_shaped\""),
24
- (LeaseReason::CallerCwdMismatch, "\"caller_cwd_mismatch\""),
25
- (LeaseReason::NotInTmuxPane, "\"not_in_tmux_pane\""),
26
- (LeaseReason::CallerPaneMissing, "\"caller_pane_missing\""),
27
- ];
28
- for (r, want) in cases {
29
- assert_eq!(serde_json::to_string(&r).unwrap(), want, "{r:?}");
30
- }
31
- }
7
+ // LEADER_OWNERSHIP_LOCK = "send"(__init__.py:393):attach/claim/takeover/autobind 共享同一临界区。
8
+ #[test]
9
+ fn ownership_lock_is_send() {
10
+ assert_eq!(LEADER_OWNERSHIP_LOCK, "send");
11
+ }
32
12
 
33
- // _LEASE_REBIND_REQUIRED_REASONS = {not_in_tmux_pane, caller_not_leader_shaped, caller_cwd_mismatch}
34
- // (__init__.py:384-386)→ 决定 refusal 事件名 rebind_required vs claim_refused
35
- // is_rebind_required 是 unimplemented!() → 调用即 panic = RED。
36
- #[test]
37
- fn lease_reason_rebind_required_membership_matches_python() {
38
- // 三个 rebind-required(golden probe_leader.py rebind_required 集)
39
- assert!(LeaseReason::NotInTmuxPane.is_rebind_required());
40
- assert!(LeaseReason::CallerNotLeaderShaped.is_rebind_required());
41
- assert!(LeaseReason::CallerCwdMismatch.is_rebind_required());
42
- // 其余一律 claim_refused(非 rebind-required)
43
- assert!(!LeaseReason::VacantAcquired.is_rebind_required());
44
- assert!(!LeaseReason::PreviousOwnerPaneDead.is_rebind_required());
45
- assert!(!LeaseReason::PreviousOwnerAliveRefused.is_rebind_required());
46
- assert!(!LeaseReason::OwnerEpochAdvanced.is_rebind_required());
47
- assert!(!LeaseReason::ForceConfirmRequired.is_rebind_required());
48
- assert!(!LeaseReason::CallerPaneMissing.is_rebind_required());
13
+ // LeaseReason 闭枚举的 serde 字节必须与 _LEASE_REASON_ENUM ∪ {caller_pane_missing} 一致。
14
+ // golden(probe_leader.py):enum 8 值(snake)+ binding caller_pane_missing
15
+ #[test]
16
+ fn lease_reason_serializes_to_exact_python_strings() {
17
+ let cases = [
18
+ (LeaseReason::VacantAcquired, "\"vacant_acquired\""),
19
+ (
20
+ LeaseReason::PreviousOwnerPaneDead,
21
+ "\"previous_owner_pane_dead\"",
22
+ ),
23
+ (
24
+ LeaseReason::PreviousOwnerAliveRefused,
25
+ "\"previous_owner_alive_refused\"",
26
+ ),
27
+ (LeaseReason::OwnerEpochAdvanced, "\"owner_epoch_advanced\""),
28
+ (
29
+ LeaseReason::ForceConfirmRequired,
30
+ "\"force_confirm_required\"",
31
+ ),
32
+ (
33
+ LeaseReason::CallerNotLeaderShaped,
34
+ "\"caller_not_leader_shaped\"",
35
+ ),
36
+ (LeaseReason::CallerCwdMismatch, "\"caller_cwd_mismatch\""),
37
+ (LeaseReason::NotInTmuxPane, "\"not_in_tmux_pane\""),
38
+ (LeaseReason::CallerPaneMissing, "\"caller_pane_missing\""),
39
+ ];
40
+ for (r, want) in cases {
41
+ assert_eq!(serde_json::to_string(&r).unwrap(), want, "{r:?}");
49
42
  }
43
+ }
50
44
 
51
- // LeaseStatus serde(probe_leader3.py):already_bound / claimed / refused / dry_run。
52
- #[test]
53
- fn lease_status_serializes_to_exact_python_strings() {
54
- assert_eq!(serde_json::to_string(&LeaseStatus::AlreadyBound).unwrap(), "\"already_bound\"");
55
- assert_eq!(serde_json::to_string(&LeaseStatus::Claimed).unwrap(), "\"claimed\"");
56
- assert_eq!(serde_json::to_string(&LeaseStatus::Refused).unwrap(), "\"refused\"");
57
- assert_eq!(serde_json::to_string(&LeaseStatus::DryRun).unwrap(), "\"dry_run\"");
58
- }
45
+ // _LEASE_REBIND_REQUIRED_REASONS = {not_in_tmux_pane, caller_not_leader_shaped, caller_cwd_mismatch}
46
+ // (__init__.py:384-386)→ 决定 refusal 事件名 rebind_required vs claim_refused。
47
+ // is_rebind_required 是 unimplemented!() → 调用即 panic = RED。
48
+ #[test]
49
+ fn lease_reason_rebind_required_membership_matches_python() {
50
+ // 三个 rebind-required(golden probe_leader.py rebind_required 集)
51
+ assert!(LeaseReason::NotInTmuxPane.is_rebind_required());
52
+ assert!(LeaseReason::CallerNotLeaderShaped.is_rebind_required());
53
+ assert!(LeaseReason::CallerCwdMismatch.is_rebind_required());
54
+ // 其余一律 claim_refused(非 rebind-required)。
55
+ assert!(!LeaseReason::VacantAcquired.is_rebind_required());
56
+ assert!(!LeaseReason::PreviousOwnerPaneDead.is_rebind_required());
57
+ assert!(!LeaseReason::PreviousOwnerAliveRefused.is_rebind_required());
58
+ assert!(!LeaseReason::OwnerEpochAdvanced.is_rebind_required());
59
+ assert!(!LeaseReason::ForceConfirmRequired.is_rebind_required());
60
+ assert!(!LeaseReason::CallerPaneMissing.is_rebind_required());
61
+ }
59
62
 
60
- // Discovery serde(probe_leader3.py):attach_readopt/claim_leader/env_pane/explicit_pane/current_pane
61
- #[test]
62
- fn discovery_serializes_to_exact_python_strings() {
63
- assert_eq!(serde_json::to_string(&Discovery::AttachReadopt).unwrap(), "\"attach_readopt\"");
64
- assert_eq!(serde_json::to_string(&Discovery::ClaimLeader).unwrap(), "\"claim_leader\"");
65
- assert_eq!(serde_json::to_string(&Discovery::EnvPane).unwrap(), "\"env_pane\"");
66
- assert_eq!(serde_json::to_string(&Discovery::ExplicitPane).unwrap(), "\"explicit_pane\"");
67
- assert_eq!(serde_json::to_string(&Discovery::CurrentPane).unwrap(), "\"current_pane\"");
68
- }
63
+ // LeaseStatus serde(probe_leader3.py):already_bound / claimed / refused / dry_run
64
+ #[test]
65
+ fn lease_status_serializes_to_exact_python_strings() {
66
+ assert_eq!(
67
+ serde_json::to_string(&LeaseStatus::AlreadyBound).unwrap(),
68
+ "\"already_bound\""
69
+ );
70
+ assert_eq!(
71
+ serde_json::to_string(&LeaseStatus::Claimed).unwrap(),
72
+ "\"claimed\""
73
+ );
74
+ assert_eq!(
75
+ serde_json::to_string(&LeaseStatus::Refused).unwrap(),
76
+ "\"refused\""
77
+ );
78
+ assert_eq!(
79
+ serde_json::to_string(&LeaseStatus::DryRun).unwrap(),
80
+ "\"dry_run\""
81
+ );
82
+ }
69
83
 
70
- // ClaimedVia 是 kebab-case(__init__.py:545/693/876 等写 "attach-leader"/"claim-leader")
71
- #[test]
72
- fn claimed_via_serializes_kebab_case() {
73
- assert_eq!(serde_json::to_string(&ClaimedVia::ClaimLeader).unwrap(), "\"claim-leader\"");
74
- assert_eq!(serde_json::to_string(&ClaimedVia::AttachLeader).unwrap(), "\"attach-leader\"");
75
- }
84
+ // Discovery serde(probe_leader3.py):attach_readopt/claim_leader/env_pane/explicit_pane/current_pane
85
+ #[test]
86
+ fn discovery_serializes_to_exact_python_strings() {
87
+ assert_eq!(
88
+ serde_json::to_string(&Discovery::AttachReadopt).unwrap(),
89
+ "\"attach_readopt\""
90
+ );
91
+ assert_eq!(
92
+ serde_json::to_string(&Discovery::ClaimLeader).unwrap(),
93
+ "\"claim_leader\""
94
+ );
95
+ assert_eq!(
96
+ serde_json::to_string(&Discovery::EnvPane).unwrap(),
97
+ "\"env_pane\""
98
+ );
99
+ assert_eq!(
100
+ serde_json::to_string(&Discovery::ExplicitPane).unwrap(),
101
+ "\"explicit_pane\""
102
+ );
103
+ assert_eq!(
104
+ serde_json::to_string(&Discovery::CurrentPane).unwrap(),
105
+ "\"current_pane\""
106
+ );
107
+ }
76
108
 
77
- // LeaseSource serde:launch/quick_start/restart/manual(__init__.py source 取值)。
78
- #[test]
79
- fn lease_source_serializes_snake_case() {
80
- assert_eq!(serde_json::to_string(&LeaseSource::Launch).unwrap(), "\"launch\"");
81
- assert_eq!(serde_json::to_string(&LeaseSource::QuickStart).unwrap(), "\"quick_start\"");
82
- assert_eq!(serde_json::to_string(&LeaseSource::Restart).unwrap(), "\"restart\"");
83
- assert_eq!(serde_json::to_string(&LeaseSource::Manual).unwrap(), "\"manual\"");
84
- }
109
+ // ClaimedVia 是 kebab-case(__init__.py:545/693/876 等写 "attach-leader"/"claim-leader")。
110
+ #[test]
111
+ fn claimed_via_serializes_kebab_case() {
112
+ assert_eq!(
113
+ serde_json::to_string(&ClaimedVia::ClaimLeader).unwrap(),
114
+ "\"claim-leader\""
115
+ );
116
+ assert_eq!(
117
+ serde_json::to_string(&ClaimedVia::AttachLeader).unwrap(),
118
+ "\"attach-leader\""
119
+ );
120
+ }
85
121
 
86
- // ReceiverMode/Status:direct_tmux / attached(__init__.py:283-284)。
87
- #[test]
88
- fn receiver_mode_and_status_serialize_to_python_strings() {
89
- assert_eq!(serde_json::to_string(&ReceiverMode::DirectTmux).unwrap(), "\"direct_tmux\"");
90
- assert_eq!(serde_json::to_string(&ReceiverStatus::Attached).unwrap(), "\"attached\"");
91
- }
122
+ // LeaseSource serde:launch/quick_start/restart/manual(__init__.py source 取值)。
123
+ #[test]
124
+ fn lease_source_serializes_snake_case() {
125
+ assert_eq!(
126
+ serde_json::to_string(&LeaseSource::Launch).unwrap(),
127
+ "\"launch\""
128
+ );
129
+ assert_eq!(
130
+ serde_json::to_string(&LeaseSource::QuickStart).unwrap(),
131
+ "\"quick_start\""
132
+ );
133
+ assert_eq!(
134
+ serde_json::to_string(&LeaseSource::Restart).unwrap(),
135
+ "\"restart\""
136
+ );
137
+ assert_eq!(
138
+ serde_json::to_string(&LeaseSource::Manual).unwrap(),
139
+ "\"manual\""
140
+ );
141
+ }
92
142
 
93
- // LeaderStartMode:exec_provider/new_tmux_session/attach_existing(leader_start_plan)。
94
- #[test]
95
- fn leader_start_mode_serializes_to_python_strings() {
96
- assert_eq!(serde_json::to_string(&LeaderStartMode::ExecProvider).unwrap(), "\"exec_provider\"");
97
- assert_eq!(serde_json::to_string(&LeaderStartMode::ManagedTmuxClient).unwrap(), "\"managed_tmux_client\"");
98
- assert_eq!(serde_json::to_string(&LeaderStartMode::NewTmuxSession).unwrap(), "\"new_tmux_session\"");
99
- assert_eq!(serde_json::to_string(&LeaderStartMode::AttachExisting).unwrap(), "\"attach_existing\"");
100
- }
143
+ // ReceiverMode/Status:direct_tmux / attached(__init__.py:283-284)。
144
+ #[test]
145
+ fn receiver_mode_and_status_serialize_to_python_strings() {
146
+ assert_eq!(
147
+ serde_json::to_string(&ReceiverMode::DirectTmux).unwrap(),
148
+ "\"direct_tmux\""
149
+ );
150
+ assert_eq!(
151
+ serde_json::to_string(&ReceiverStatus::Attached).unwrap(),
152
+ "\"attached\""
153
+ );
154
+ }
101
155
 
102
- // NodeRole:worker/leader(build_idle_nodes role 字段)。
103
- #[test]
104
- fn node_role_serializes_to_python_strings() {
105
- assert_eq!(serde_json::to_string(&NodeRole::Worker).unwrap(), "\"worker\"");
106
- assert_eq!(serde_json::to_string(&NodeRole::Leader).unwrap(), "\"leader\"");
107
- }
156
+ // LeaderStartMode:exec_provider/new_tmux_session/attach_existing(leader_start_plan)。
157
+ #[test]
158
+ fn leader_start_mode_serializes_to_python_strings() {
159
+ assert_eq!(
160
+ serde_json::to_string(&LeaderStartMode::ExecProvider).unwrap(),
161
+ "\"exec_provider\""
162
+ );
163
+ assert_eq!(
164
+ serde_json::to_string(&LeaderStartMode::ManagedTmuxClient).unwrap(),
165
+ "\"managed_tmux_client\""
166
+ );
167
+ assert_eq!(
168
+ serde_json::to_string(&LeaderStartMode::NewTmuxSession).unwrap(),
169
+ "\"new_tmux_session\""
170
+ );
171
+ assert_eq!(
172
+ serde_json::to_string(&LeaderStartMode::AttachExisting).unwrap(),
173
+ "\"attach_existing\""
174
+ );
175
+ }
108
176
 
109
- // RereadReason:wake.py 五值(probe_leader.py)。
110
- #[test]
111
- fn reread_reason_serializes_to_python_strings() {
112
- assert_eq!(serde_json::to_string(&RereadReason::NoFile).unwrap(), "\"no_file\"");
113
- assert_eq!(serde_json::to_string(&RereadReason::NeverClassified).unwrap(), "\"never_classified\"");
114
- assert_eq!(serde_json::to_string(&RereadReason::FileChanged).unwrap(), "\"file_changed\"");
115
- assert_eq!(
116
- serde_json::to_string(&RereadReason::QuiescentAlreadyClassified).unwrap(),
117
- "\"quiescent_already_classified\""
118
- );
119
- assert_eq!(serde_json::to_string(&RereadReason::Unchanged).unwrap(), "\"unchanged\"");
120
- }
177
+ // NodeRole:worker/leader(build_idle_nodes role 字段)。
178
+ #[test]
179
+ fn node_role_serializes_to_python_strings() {
180
+ assert_eq!(
181
+ serde_json::to_string(&NodeRole::Worker).unwrap(),
182
+ "\"worker\""
183
+ );
184
+ assert_eq!(
185
+ serde_json::to_string(&NodeRole::Leader).unwrap(),
186
+ "\"leader\""
187
+ );
188
+ }
121
189
 
122
- // =====================================================================
123
- // 2. LeaderSessionUuidSource — 漂移 NOTE(card §168-179)
124
- // =====================================================================
125
-
126
- // leader plan 侧 _leader_identity_context(__init__.py:206)只产 "override"/"derived";
127
- // identity lane(state.py:332)用 "explicit-override"/"env"/"derived"。
128
- // 此 enum 的 serde 字节当对齐 LEADER 侧:Override→"override"。Env→"env" 也在闭集。
129
- #[test]
130
- fn leader_session_uuid_source_serializes_leader_plan_strings() {
131
- assert_eq!(serde_json::to_string(&LeaderSessionUuidSource::Derived).unwrap(), "\"derived\"");
132
- // NOTE(drift):leader plan 写裸 "override"(非 identity lane 的 "explicit-override")。
133
- assert_eq!(serde_json::to_string(&LeaderSessionUuidSource::Override).unwrap(), "\"override\"");
134
- assert_eq!(serde_json::to_string(&LeaderSessionUuidSource::Env).unwrap(), "\"env\"");
135
- }
190
+ // RereadReason:wake.py 五值(probe_leader.py)。
191
+ #[test]
192
+ fn reread_reason_serializes_to_python_strings() {
193
+ assert_eq!(
194
+ serde_json::to_string(&RereadReason::NoFile).unwrap(),
195
+ "\"no_file\""
196
+ );
197
+ assert_eq!(
198
+ serde_json::to_string(&RereadReason::NeverClassified).unwrap(),
199
+ "\"never_classified\""
200
+ );
201
+ assert_eq!(
202
+ serde_json::to_string(&RereadReason::FileChanged).unwrap(),
203
+ "\"file_changed\""
204
+ );
205
+ assert_eq!(
206
+ serde_json::to_string(&RereadReason::QuiescentAlreadyClassified).unwrap(),
207
+ "\"quiescent_already_classified\""
208
+ );
209
+ assert_eq!(
210
+ serde_json::to_string(&RereadReason::Unchanged).unwrap(),
211
+ "\"unchanged\""
212
+ );
213
+ }
136
214
 
137
- // 漂移可观测:identity lane 的 caller dict 用 "explicit-override",二者不可互换。
138
- // 这是 leader 集成时必须 reconcile NOTE —— 锁死 leader-plan 侧字节,防被误统一成
139
- // identity 串。serde override≠"explicit-override"。
140
- #[test]
141
- fn leader_plan_override_is_not_identity_explicit_override_string() {
142
- let leader_plan = serde_json::to_string(&LeaderSessionUuidSource::Override).unwrap();
143
- assert_eq!(leader_plan, "\"override\"");
144
- assert_ne!(leader_plan, "\"explicit-override\"", "drift NOTE: leader plan 用 'override',不可写成 identity 的 'explicit-override'");
145
- }
215
+ // =====================================================================
216
+ // 2. LeaderSessionUuidSource 漂移 NOTE(card §168-179)
217
+ // =====================================================================
218
+
219
+ // leader plan 侧 _leader_identity_context(__init__.py:206)只产 "override"/"derived";
220
+ // identity lane(state.py:332)用 "explicit-override"/"env"/"derived"。
221
+ // 此 enum 的 serde 字节当对齐 LEADER 侧:Override→"override"。Env→"env" 也在闭集。
222
+ #[test]
223
+ fn leader_session_uuid_source_serializes_leader_plan_strings() {
224
+ assert_eq!(
225
+ serde_json::to_string(&LeaderSessionUuidSource::Derived).unwrap(),
226
+ "\"derived\""
227
+ );
228
+ // NOTE(drift):leader plan 写裸 "override"(非 identity lane 的 "explicit-override")。
229
+ assert_eq!(
230
+ serde_json::to_string(&LeaderSessionUuidSource::Override).unwrap(),
231
+ "\"override\""
232
+ );
233
+ assert_eq!(
234
+ serde_json::to_string(&LeaderSessionUuidSource::Env).unwrap(),
235
+ "\"env\""
236
+ );
237
+ }
238
+
239
+ // 漂移可观测:identity lane 的 caller dict 用 "explicit-override",二者不可互换。
240
+ // 这是 leader 集成时必须 reconcile 的 NOTE —— 锁死 leader-plan 侧字节,防被误统一成
241
+ // identity 串。serde override≠"explicit-override"。
242
+ #[test]
243
+ fn leader_plan_override_is_not_identity_explicit_override_string() {
244
+ let leader_plan = serde_json::to_string(&LeaderSessionUuidSource::Override).unwrap();
245
+ assert_eq!(leader_plan, "\"override\"");
246
+ assert_ne!(
247
+ leader_plan, "\"explicit-override\"",
248
+ "drift NOTE: leader plan 用 'override',不可写成 identity 的 'explicit-override'"
249
+ );
250
+ }
251
+
252
+ // =====================================================================
253
+ // 3. LeaderEvent::name() — §40 字节级一致(unimplemented → RED)
254
+ // =====================================================================
146
255
 
147
- // =====================================================================
148
- // 3. LeaderEvent::name() §40 字节级一致(unimplemented RED)
149
- // =====================================================================
150
-
151
- // 全部 leader 审计事件名(probe_leader2.py FOUND 验证;ping 来自 idle_predicate 跨 lane,亦 golden)。
152
- // 注:ReceiverClaimLeaderNotification v0.2.11 任何 source 都查无 "leader_receiver.claim_leader_notification"
153
- // 字面 → 见 deferred,此处不钉该串(只钉可验证的 23 个)
154
- #[test]
155
- fn leader_event_names_match_python_byte_for_byte() {
156
- let cases = [
157
- (LeaderEvent::ReceiverAttached, "leader_receiver.attached"),
158
- (LeaderEvent::ReceiverRebindApplied, "leader_receiver.rebind_applied"),
159
- (LeaderEvent::ReceiverClaimApplied, "leader_receiver.claim_applied"),
160
- (LeaderEvent::ReceiverClaimRefused, "leader_receiver.claim_refused"),
161
- (LeaderEvent::ReceiverRebindRequired, "leader_receiver.rebind_required"),
162
- (LeaderEvent::ReceiverAttachFailed, "leader_receiver.attach_failed"),
163
- (LeaderEvent::ReceiverStateDivergenceRepaired, "leader_receiver.state_divergence_repaired"),
164
- (LeaderEvent::ReceiverFirstTimeEnvSeeded, "leader_receiver.first_time_env_seeded"),
165
- (LeaderEvent::ReceiverAutobindSkipped, "leader_receiver.autobind_skipped"),
166
- (LeaderEvent::ReceiverRequeuedExhaustedWatchers, "leader_receiver.requeued_exhausted_watchers"),
167
- (LeaderEvent::ReceiverAmbiguousCandidates, "leader_receiver.ambiguous_candidates"),
168
- (LeaderEvent::ReceiverClaimRequeue, "leader_receiver.claim_requeue"),
169
- (LeaderEvent::OwnerAdoptedOnRestart, "owner.adopted_on_restart"),
170
- (LeaderEvent::OwnerBoundFromCallerPane, "owner.bound_from_caller_pane"),
171
- (LeaderEvent::OwnerBindRefused, "owner.bind_refused"),
172
- (LeaderEvent::OwnerEpochAdvanced, "owner_epoch_advanced"),
173
- (LeaderEvent::LeaderSessionUuidOverride, "leader_session_uuid.override"),
174
- (LeaderEvent::LeaderStart, "leader.start"),
175
- (LeaderEvent::ResultWatcherRequeued, "result_watcher.requeued"),
176
- (LeaderEvent::IdleTakeoverClassify, "idle_takeover.classify"),
177
- (LeaderEvent::IdleTakeoverPing, "idle_takeover.ping"),
178
- (LeaderEvent::IdleTakeoverReminder, "idle_takeover.reminder"),
179
- (LeaderEvent::IdleTakeoverPushFailed, "idle_takeover.push_failed"),
180
- ];
181
- for (ev, want) in cases {
182
- assert_eq!(ev.name(), want, "{ev:?}");
183
- }
256
+ // 全部 leader 审计事件名(probe_leader2.py FOUND 验证;ping 来自 idle_predicate 跨 lane,亦 golden)。
257
+ // 注:ReceiverClaimLeaderNotification v0.2.11 任何 source 都查无 "leader_receiver.claim_leader_notification"
258
+ // 字面 → 见 deferred,此处不钉该串(只钉可验证的 23 个)。
259
+ #[test]
260
+ fn leader_event_names_match_python_byte_for_byte() {
261
+ let cases = [
262
+ (LeaderEvent::ReceiverAttached, "leader_receiver.attached"),
263
+ (
264
+ LeaderEvent::ReceiverRebindApplied,
265
+ "leader_receiver.rebind_applied",
266
+ ),
267
+ (
268
+ LeaderEvent::ReceiverClaimApplied,
269
+ "leader_receiver.claim_applied",
270
+ ),
271
+ (
272
+ LeaderEvent::ReceiverClaimRefused,
273
+ "leader_receiver.claim_refused",
274
+ ),
275
+ (
276
+ LeaderEvent::ReceiverRebindRequired,
277
+ "leader_receiver.rebind_required",
278
+ ),
279
+ (
280
+ LeaderEvent::ReceiverAttachFailed,
281
+ "leader_receiver.attach_failed",
282
+ ),
283
+ (
284
+ LeaderEvent::ReceiverStateDivergenceRepaired,
285
+ "leader_receiver.state_divergence_repaired",
286
+ ),
287
+ (
288
+ LeaderEvent::ReceiverFirstTimeEnvSeeded,
289
+ "leader_receiver.first_time_env_seeded",
290
+ ),
291
+ (
292
+ LeaderEvent::ReceiverAutobindSkipped,
293
+ "leader_receiver.autobind_skipped",
294
+ ),
295
+ (
296
+ LeaderEvent::ReceiverRequeuedExhaustedWatchers,
297
+ "leader_receiver.requeued_exhausted_watchers",
298
+ ),
299
+ (
300
+ LeaderEvent::ReceiverAmbiguousCandidates,
301
+ "leader_receiver.ambiguous_candidates",
302
+ ),
303
+ (
304
+ LeaderEvent::ReceiverClaimRequeue,
305
+ "leader_receiver.claim_requeue",
306
+ ),
307
+ (
308
+ LeaderEvent::OwnerAdoptedOnRestart,
309
+ "owner.adopted_on_restart",
310
+ ),
311
+ (
312
+ LeaderEvent::OwnerBoundFromCallerPane,
313
+ "owner.bound_from_caller_pane",
314
+ ),
315
+ (LeaderEvent::OwnerBindRefused, "owner.bind_refused"),
316
+ (LeaderEvent::OwnerEpochAdvanced, "owner_epoch_advanced"),
317
+ (
318
+ LeaderEvent::LeaderSessionUuidOverride,
319
+ "leader_session_uuid.override",
320
+ ),
321
+ (LeaderEvent::LeaderStart, "leader.start"),
322
+ (
323
+ LeaderEvent::ResultWatcherRequeued,
324
+ "result_watcher.requeued",
325
+ ),
326
+ (LeaderEvent::IdleTakeoverClassify, "idle_takeover.classify"),
327
+ (LeaderEvent::IdleTakeoverPing, "idle_takeover.ping"),
328
+ (LeaderEvent::IdleTakeoverReminder, "idle_takeover.reminder"),
329
+ (
330
+ LeaderEvent::IdleTakeoverPushFailed,
331
+ "idle_takeover.push_failed",
332
+ ),
333
+ ];
334
+ for (ev, want) in cases {
335
+ assert_eq!(ev.name(), want, "{ev:?}");
184
336
  }
337
+ }