@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +126 -16
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +87 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +199 -89
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/registry.rs +1 -1
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +55 -15
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +54 -24
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/persist.rs +28 -0
- package/crates/team-agent/src/state/repository.rs +8 -3
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +40 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- package/package.json +4 -4
|
@@ -1,71 +1,71 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
// =========================================================================
|
|
4
|
+
// WAVE-2 LANE C — diagnose/status byte-parity DIVERGENCES + target-scan wiring.
|
|
5
|
+
// The status/diagnose handlers are IMPLEMENTED (status) or STUBBED (approvals/inbox/doctor/
|
|
6
|
+
// comms_selftest/orphan_gate/cleanup_orphans/fix_schema) — these REDs lock the EXACT golden JSON
|
|
7
|
+
// shapes (keys/values/insertion-order) each one DIVERGES from. Golden bytes captured via
|
|
8
|
+
// PYTHONPATH=.../src python3 /tmp/lanec_probe*.py on a fresh workspace.
|
|
9
|
+
// =========================================================================
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
/// Seed `.team/runtime/state.json` with `state` so `status_port::status` reads a real fixture.
|
|
12
|
+
fn seed_status_state(state: serde_json::Value) -> std::path::PathBuf {
|
|
13
|
+
let ws = tmp_workspace();
|
|
14
|
+
std::fs::write(
|
|
15
|
+
ws.join(".team").join("runtime").join("state.json"),
|
|
16
|
+
serde_json::to_vec_pretty(&state).unwrap(),
|
|
17
|
+
)
|
|
18
|
+
.unwrap();
|
|
19
|
+
ws
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
// ── status #1: `team` = state["leader"]["id"] (default "leader"), NOT state["team"] ──────────────
|
|
23
|
+
// GOLDEN queries.py:66 `state.get("leader", {}).get("id", "leader")`. RUST mod.rs:94 reads
|
|
24
|
+
// `state.get("team")`. Seed BOTH distinct -> golden binds leader.id, Rust binds the team key. RED.
|
|
25
|
+
#[test]
|
|
26
|
+
fn status_team_is_leader_id_not_team_key() {
|
|
27
|
+
let ws = seed_status_state(json!({"team": "TEAMKEY", "leader": {"id": "LEADERID"}}));
|
|
28
|
+
let v = status_port::status(&ws, /*compact=*/ false, /*detail=*/ true).expect("status");
|
|
29
|
+
assert_eq!(
|
|
30
|
+
v["team"],
|
|
31
|
+
json!("LEADERID"),
|
|
32
|
+
"golden status.team = state['leader']['id'] (queries.py:66), NOT state['team']; got {:?}",
|
|
33
|
+
v["team"]
|
|
34
|
+
);
|
|
35
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
36
|
+
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
// ── status #2: messages = store.message_counts() ({} when empty), results = store.result_counts()
|
|
39
|
+
// ({total,uncollected,collected,invalid,by_status}) — NOT the Rust {"count": N} placeholders. ──────
|
|
40
|
+
// GOLDEN queries.py:73,75. RUST mod.rs:101,103 emit `{"count": N}`. RED.
|
|
41
|
+
#[test]
|
|
42
|
+
fn status_counts_are_store_count_maps_not_count_int() {
|
|
43
|
+
let ws = tmp_workspace(); // empty store
|
|
44
|
+
let v = status_port::status(&ws, false, true).expect("status");
|
|
45
|
+
assert_eq!(
|
|
46
|
+
v["messages"],
|
|
47
|
+
json!({}),
|
|
48
|
+
"golden empty store.message_counts() == {{}} (queries.py:73), NOT {{count:0}}; got {:?}",
|
|
49
|
+
v["messages"]
|
|
50
|
+
);
|
|
51
|
+
assert_eq!(
|
|
52
|
+
v["results"],
|
|
53
|
+
json!({"total": 0, "uncollected": 0, "collected": 0, "invalid": 0, "by_status": {}}),
|
|
54
|
+
"golden store.result_counts() full shape (queries.py:75), NOT {{count:0}}; got {:?}",
|
|
55
|
+
v["results"]
|
|
56
|
+
);
|
|
57
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
58
|
+
}
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
// ── status #3: coordinator = coordinator_health(ws) FULL shape in golden order ──────────────────
|
|
61
|
+
// 0.5.18 adds binary identity diagnostics to expose stale coordinator rotation reasons.
|
|
62
|
+
#[test]
|
|
63
|
+
fn status_coordinator_is_full_health_identity_shape() {
|
|
64
|
+
let ws = tmp_workspace();
|
|
65
|
+
let v = status_port::status(&ws, false, true).expect("status");
|
|
66
|
+
let coord = v["coordinator"].as_object().expect("coordinator dict");
|
|
67
|
+
let order: Vec<&str> = coord.keys().map(String::as_str).collect();
|
|
68
|
+
assert_eq!(
|
|
69
69
|
order,
|
|
70
70
|
vec![
|
|
71
71
|
"ok",
|
|
@@ -89,197 +89,220 @@ use super::*;
|
|
|
89
89
|
],
|
|
90
90
|
"golden coordinator_health insertion order with 0.5.41 fault-invisibility fields; got {order:?}"
|
|
91
91
|
);
|
|
92
|
-
|
|
92
|
+
assert!(
|
|
93
93
|
coord.get("schema_version").is_none(),
|
|
94
94
|
"golden coordinator has NO flat `schema_version` (it nests schema:{{message_store_schema_version}}); \
|
|
95
95
|
Rust emits the flat key. got {coord:?}"
|
|
96
96
|
);
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
98
|
+
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
// ── status #4: last_events = EventLog.tail(10) ([] when no events), NOT the literal placeholder ──
|
|
101
|
+
// GOLDEN queries.py:78 `EventLog(workspace).tail(10)`. RUST mod.rs:113 (non-compact) emits the
|
|
102
|
+
// hardcoded placeholder `[{"event":"status.full"}]`. On a fresh log golden == []. RED. ────────────
|
|
103
|
+
#[test]
|
|
104
|
+
fn status_last_events_is_event_tail_not_status_full_placeholder() {
|
|
105
|
+
let ws = tmp_workspace(); // no events.jsonl
|
|
106
|
+
let v = status_port::status(&ws, false, true).expect("status");
|
|
107
|
+
assert_ne!(
|
|
108
108
|
v["last_events"],
|
|
109
109
|
json!([{"event": "status.full"}]),
|
|
110
110
|
"golden last_events = EventLog.tail(10), NOT the literal [{{event:status.full}}] placeholder"
|
|
111
111
|
);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
assert_eq!(
|
|
113
|
+
v["last_events"],
|
|
114
|
+
json!([]),
|
|
115
|
+
"golden last_events on a fresh event log is [] (tail of empty); got {:?}",
|
|
116
|
+
v["last_events"]
|
|
117
|
+
);
|
|
118
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
119
|
+
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
// ── status #5: the full (non-compact) status dict has NO `detail` key ────────────────────────────
|
|
122
|
+
// GOLDEN queries.py:65-79 builds exactly 13 keys; there is no `detail`. RUST mod.rs:115-119 injects
|
|
123
|
+
// `detail:true` whenever `!compact || detail`. The full --detail call MUST NOT carry detail. RED. ──
|
|
124
|
+
#[test]
|
|
125
|
+
fn status_noncompact_has_no_detail_key() {
|
|
126
|
+
let ws = tmp_workspace();
|
|
127
|
+
let v = status_port::status(&ws, /*compact=*/ false, /*detail=*/ true).expect("status");
|
|
128
|
+
assert!(
|
|
129
129
|
v.as_object().unwrap().get("detail").is_none(),
|
|
130
130
|
"golden status dict has NO `detail` key (queries.py:65-79); Rust injects detail:true. got {v:?}"
|
|
131
131
|
);
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
133
|
+
}
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
135
|
+
// ── status #6: each agent entry is enriched with an `interacted` marker (C3, queries.py:53-64) ────
|
|
136
|
+
// GOLDEN: valid ISO first_send_at passes through; any other shape (invalid str / missing) -> the
|
|
137
|
+
// literal "never" (_interacted_marker, queries.py:15-30). RUST mod.rs:81-84 passes agents through
|
|
138
|
+
// RAW with no enrichment -> no `interacted` key. RED. ────────────────────────────────────────────
|
|
139
|
+
#[test]
|
|
140
|
+
fn status_agents_enriched_with_interacted_marker() {
|
|
141
|
+
let ws = seed_status_state(json!({
|
|
142
|
+
"leader": {"id": "leader"},
|
|
143
|
+
"agents": {
|
|
144
|
+
"alpha": {"provider": "codex", "first_send_at": "2026-05-27T10:00:00+00:00"},
|
|
145
|
+
"beta": {"provider": "claude", "first_send_at": "not-a-date"},
|
|
146
|
+
"gamma": {"provider": "claude"},
|
|
147
|
+
}
|
|
148
|
+
}));
|
|
149
|
+
let v = status_port::status(&ws, false, true).expect("status");
|
|
150
|
+
let agents = &v["agents"];
|
|
151
|
+
assert_eq!(
|
|
152
|
+
agents["alpha"]["interacted"],
|
|
153
|
+
json!("2026-05-27T10:00:00+00:00"),
|
|
154
|
+
"valid ISO first_send_at must pass through as the interacted marker (queries.py:24-29)"
|
|
155
|
+
);
|
|
156
|
+
assert_eq!(
|
|
157
|
+
agents["beta"]["interacted"],
|
|
158
|
+
json!("never"),
|
|
159
|
+
"an unparseable first_send_at must render as the literal 'never' (queries.py:27-28)"
|
|
160
|
+
);
|
|
161
|
+
assert_eq!(
|
|
162
|
+
agents["gamma"]["interacted"],
|
|
163
|
+
json!("never"),
|
|
164
|
+
"a missing first_send_at must render as 'never' (queries.py:30)"
|
|
165
|
+
);
|
|
166
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
167
|
+
}
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
// ── status #7 (TARGET-SCAN WIRING, in-process): tmux_session_present uses a LIVE tmux probe ───────
|
|
170
|
+
// GOLDEN queries.py:52 `_tmux_session_exists(session_name)` (real `tmux has-session`). RUST mod.rs:96
|
|
171
|
+
// shortcuts to `session_name.is_some()`. Seed a present-but-nonexistent session -> golden False,
|
|
172
|
+
// Rust True. This is the status-level pane-discovery wiring the dangling list_targets feeds. RED. ──
|
|
173
|
+
#[test]
|
|
174
|
+
fn status_tmux_session_present_uses_live_tmux_probe_not_is_some() {
|
|
175
|
+
let absent = format!("ta-lanec-absent-{}", std::process::id());
|
|
176
|
+
let ws = seed_status_state(json!({"leader": {"id": "leader"}, "session_name": absent}));
|
|
177
|
+
let v = status_port::status(&ws, false, true).expect("status");
|
|
178
|
+
assert_eq!(
|
|
179
179
|
v["tmux_session_present"],
|
|
180
180
|
json!(false),
|
|
181
181
|
"golden tmux_session_present = _tmux_session_exists(name) (a live probe; queries.py:52); a \
|
|
182
182
|
present-but-nonexistent session name must be False, not is_some()==true. got {:?}",
|
|
183
183
|
v["tmux_session_present"]
|
|
184
184
|
);
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
186
|
+
}
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
188
|
+
// ── approvals: golden 5-key {ok,waiting,waiting_count,approvals,scan} (status/approvals.py:30-36) ──
|
|
189
|
+
// RUST mod.rs:131-133 stub {ok,agent,approvals}. RED. ───────────────────────────────────────────
|
|
190
|
+
#[test]
|
|
191
|
+
fn approvals_golden_shape_has_waiting_count_and_scan_not_agent() {
|
|
192
|
+
let ws = tmp_workspace();
|
|
193
|
+
let v = status_port::approvals(&ws, None, true).expect("approvals");
|
|
194
|
+
let obj = v.as_object().expect("approvals dict");
|
|
195
|
+
let order: Vec<&str> = obj.keys().map(String::as_str).collect();
|
|
196
|
+
assert_eq!(
|
|
197
197
|
order,
|
|
198
198
|
vec!["ok", "waiting", "waiting_count", "approvals", "scan"],
|
|
199
199
|
"golden approvals 5-key order (approvals.py:30-36); Rust stub emits {{ok,agent,approvals}}. got {order:?}"
|
|
200
200
|
);
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
assert!(
|
|
202
|
+
!obj.contains_key("agent"),
|
|
203
|
+
"golden approvals has NO `agent` key"
|
|
204
|
+
);
|
|
205
|
+
assert_eq!(
|
|
203
206
|
v["scan"],
|
|
204
207
|
json!({"mode": "tail", "lines": 120, "raw_output": false}),
|
|
205
208
|
"golden scan == {{mode:tail,lines:120,raw_output:false}} (APPROVAL_SCAN_LINES=120); got {:?}",
|
|
206
209
|
v["scan"]
|
|
207
210
|
);
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
212
|
+
}
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
214
|
+
// ── inbox: golden {ok,agent_id,messages,since} (status/inbox.py:35-38) — key is `agent_id`, carries
|
|
215
|
+
// `since` (not `agent`/`limit`). RUST mod.rs:136-144 stub {ok,agent,limit,messages}. RED. ──────────
|
|
216
|
+
#[test]
|
|
217
|
+
fn inbox_golden_shape_is_agent_id_and_since_not_agent_limit() {
|
|
218
|
+
let ws = tmp_workspace();
|
|
219
|
+
let v = status_port::inbox(&ws, "alpha", 20, None, true, None).expect("inbox");
|
|
220
|
+
let obj = v.as_object().expect("inbox dict");
|
|
221
|
+
let order: Vec<&str> = obj.keys().map(String::as_str).collect();
|
|
222
|
+
assert_eq!(
|
|
220
223
|
order,
|
|
221
224
|
vec!["ok", "agent_id", "messages", "since"],
|
|
222
225
|
"golden inbox 4-key order {{ok,agent_id,messages,since}} (inbox.py:38); Rust stub uses agent/limit. got {order:?}"
|
|
223
226
|
);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
assert_eq!(
|
|
228
|
+
v["agent_id"],
|
|
229
|
+
json!("alpha"),
|
|
230
|
+
"golden key is `agent_id` (not `agent`)"
|
|
231
|
+
);
|
|
232
|
+
assert!(
|
|
233
|
+
obj.contains_key("since"),
|
|
234
|
+
"golden inbox carries `since` (echoed, null here)"
|
|
235
|
+
);
|
|
236
|
+
assert!(
|
|
237
|
+
!obj.contains_key("agent"),
|
|
238
|
+
"golden inbox has NO bare `agent` key"
|
|
239
|
+
);
|
|
240
|
+
assert!(
|
|
241
|
+
!obj.contains_key("limit"),
|
|
242
|
+
"golden inbox has NO `limit` key in the result dict"
|
|
243
|
+
);
|
|
244
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
245
|
+
}
|
|
230
246
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
// ── comms_selftest: golden {ok,status,run_id,scope,boundary,checks} (diagnose/comms.py:40-47) ─────
|
|
248
|
+
// RUST mod.rs:525-527 stub {ok,team,gate,provider_sdk_calls:int}. RED. Locks COMMS_BOUNDARY_TEXT +
|
|
249
|
+
// the deterministic check sub-shapes (run_id is a random uuid; not value-locked). ────────────────
|
|
250
|
+
#[test]
|
|
251
|
+
fn comms_selftest_golden_boundary_scope_and_check_shapes() {
|
|
252
|
+
let boundary = "validates live pane binding consistency and zero-token comms contracts. \
|
|
237
253
|
Does NOT perform live runtime message round-trip. (zero token, zero pollution)";
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
254
|
+
let ws = tmp_workspace();
|
|
255
|
+
let v = diagnose_port::comms_selftest(&ws, None, None).expect("comms_selftest");
|
|
256
|
+
let obj = v.as_object().expect("comms dict");
|
|
257
|
+
assert_eq!(
|
|
258
|
+
v["boundary"],
|
|
259
|
+
json!(boundary),
|
|
260
|
+
"golden COMMS_BOUNDARY_TEXT prefix (comms.py:11-14)"
|
|
261
|
+
);
|
|
262
|
+
assert_eq!(v["scope"], json!("binding_consistency"), "golden scope");
|
|
263
|
+
assert_eq!(
|
|
264
|
+
v["status"],
|
|
265
|
+
json!("fail"),
|
|
266
|
+
"empty workspace has no runtime receiver binding, so comms gate must not pass"
|
|
267
|
+
);
|
|
268
|
+
assert!(
|
|
269
|
+
obj.contains_key("run_id"),
|
|
270
|
+
"golden carries a run_id (uuid hex[:12])"
|
|
271
|
+
);
|
|
272
|
+
assert!(!obj.contains_key("team"), "golden has NO `team` key");
|
|
273
|
+
assert!(!obj.contains_key("gate"), "golden has NO `gate` key");
|
|
274
|
+
assert_eq!(v["checks"]["contract_suite"]["status"], json!("pass"));
|
|
275
|
+
assert_eq!(v["checks"]["contract_suite"]["failed"], json!([]));
|
|
276
|
+
let suite_names: Vec<&str> = v["checks"]["contract_suite"]["checks"]
|
|
277
|
+
.as_array()
|
|
278
|
+
.expect("contract suite checks")
|
|
279
|
+
.iter()
|
|
280
|
+
.filter_map(|check| check.get("name").and_then(Value::as_str))
|
|
281
|
+
.collect();
|
|
282
|
+
assert!(
|
|
283
|
+
suite_names.contains(&"message_store_schema")
|
|
284
|
+
&& suite_names.contains(&"message_token_shape")
|
|
285
|
+
&& suite_names.contains(&"result_notification_render")
|
|
286
|
+
&& suite_names.contains(&"leader_projection_owner_team"),
|
|
287
|
+
"contract suite must be executable, not deferred: {suite_names:?}"
|
|
288
|
+
);
|
|
289
|
+
assert_eq!(
|
|
290
|
+
v["checks"]["provider_sdk_calls"]["calls"],
|
|
291
|
+
json!({"anthropic": 0, "openai": 0, "httpx": 0}),
|
|
292
|
+
"golden provider_sdk_calls.calls (comms.py:142-151); Rust stub is a bare int 0"
|
|
293
|
+
);
|
|
294
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
295
|
+
}
|
|
273
296
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
297
|
+
// ── orphan_gate(fix=true,confirm=false): golden REFUSED envelope (orphan_cleanup.py:304-311) ──────
|
|
298
|
+
// {ok:false,gate:"orphans",status:"refused",reason:"fix_requires_confirm",action:...}. RUST mod.rs:
|
|
299
|
+
// 530-531 stub {ok:true,fix,confirm,orphans:[]}. Fully deterministic (no subprocess). RED. ─────────
|
|
300
|
+
#[test]
|
|
301
|
+
fn orphan_gate_fix_without_confirm_is_refused_envelope() {
|
|
302
|
+
let ws = tmp_workspace();
|
|
303
|
+
let v =
|
|
304
|
+
diagnose_port::orphan_gate(&ws, /*fix=*/ true, /*confirm=*/ false).expect("orphan_gate");
|
|
305
|
+
assert_eq!(
|
|
283
306
|
v,
|
|
284
307
|
json!({
|
|
285
308
|
"ok": false,
|
|
@@ -290,74 +313,134 @@ use super::*;
|
|
|
290
313
|
}),
|
|
291
314
|
"golden orphan_gate fix-without-confirm refused envelope (orphan_cleanup.py:304-311); got {v:?}"
|
|
292
315
|
);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
316
|
+
let order: Vec<&str> = v.as_object().unwrap().keys().map(String::as_str).collect();
|
|
317
|
+
assert_eq!(
|
|
318
|
+
order,
|
|
319
|
+
vec!["ok", "gate", "status", "reason", "action"],
|
|
320
|
+
"golden refused key order"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
296
323
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
324
|
+
// ── cleanup_orphans(confirm=false): golden DRY-RUN envelope (orphan_cleanup.py cleanup_…) ─────────
|
|
325
|
+
// {ok,scanned,orphans,dry_run:true,scanned_at,action_required}. RUST mod.rs:534-535 stub
|
|
326
|
+
// {ok,confirm,cleaned}. scanned/scanned_at are machine/clock-derived; lock the deterministic ones. RED.
|
|
327
|
+
#[test]
|
|
328
|
+
#[ignore = "real-machine: cleanup_orphans scans machine-wide ta-* tmux/process residue"]
|
|
329
|
+
#[serial_test::file_serial(tmux)]
|
|
330
|
+
fn cleanup_orphans_dryrun_golden_envelope() {
|
|
331
|
+
let ws = tmp_workspace();
|
|
332
|
+
let v = diagnose_port::cleanup_orphans(&ws, /*confirm=*/ false).expect("cleanup_orphans");
|
|
333
|
+
let obj = v.as_object().expect("cleanup dict");
|
|
334
|
+
assert_eq!(
|
|
335
|
+
v["dry_run"],
|
|
336
|
+
json!(true),
|
|
337
|
+
"no --confirm => dry_run:true (cleanup_orphan_coordinators)"
|
|
338
|
+
);
|
|
339
|
+
assert_eq!(
|
|
340
|
+
v["orphans"],
|
|
341
|
+
json!([]),
|
|
342
|
+
"golden lists orphans (empty when none)"
|
|
343
|
+
);
|
|
344
|
+
assert_eq!(
|
|
345
|
+
v["action_required"],
|
|
346
|
+
json!("re-run with --confirm to send SIGTERM"),
|
|
347
|
+
"golden dry-run action_required text"
|
|
348
|
+
);
|
|
349
|
+
assert!(
|
|
350
|
+
obj.contains_key("scanned_at"),
|
|
351
|
+
"golden carries scanned_at timestamp"
|
|
352
|
+
);
|
|
353
|
+
assert!(obj.contains_key("scanned"), "golden carries scanned count");
|
|
354
|
+
assert!(!obj.contains_key("confirm"), "golden has NO `confirm` key");
|
|
355
|
+
assert!(
|
|
356
|
+
!obj.contains_key("cleaned"),
|
|
357
|
+
"golden has NO `cleaned` key (uses `orphans`)"
|
|
358
|
+
);
|
|
359
|
+
}
|
|
319
360
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
361
|
+
// ── fix_schema: golden fix_schema_layout diagnosis envelope (schema_migration.py:258) ─────────────
|
|
362
|
+
// {ok,status,db_path,schema_version,user_version,layout_diffs,recommended_action,would_backup_path,
|
|
363
|
+
// fixed,rebuilds}. RUST mod.rs:538-540 stub {ok,fixed:false}. db_path/would_backup_path are
|
|
364
|
+
// path/clock-derived; lock the deterministic fields. SCHEMA_VERSION==3. RED. ─────────────────────
|
|
365
|
+
#[test]
|
|
366
|
+
fn fix_schema_golden_layout_diagnosis_envelope() {
|
|
367
|
+
let ws = tmp_workspace();
|
|
368
|
+
let v = diagnose_port::fix_schema(&ws).expect("fix_schema");
|
|
369
|
+
let obj = v.as_object().expect("fix_schema dict");
|
|
370
|
+
assert_eq!(v["status"], json!("missing"), "missing db: status missing");
|
|
371
|
+
assert_eq!(
|
|
372
|
+
v["schema_version"],
|
|
373
|
+
json!(3),
|
|
374
|
+
"golden schema_version == SCHEMA_VERSION (3)"
|
|
375
|
+
);
|
|
376
|
+
assert_eq!(v["user_version"], json!(0), "missing db user_version == 0");
|
|
377
|
+
assert_eq!(
|
|
378
|
+
v["layout_diffs"],
|
|
379
|
+
json!([]),
|
|
380
|
+
"missing db => empty layout_diffs list"
|
|
381
|
+
);
|
|
382
|
+
assert_eq!(
|
|
383
|
+
v["recommended_action"],
|
|
384
|
+
json!("none"),
|
|
385
|
+
"no drift => recommended_action 'none'"
|
|
386
|
+
);
|
|
387
|
+
assert_eq!(
|
|
388
|
+
v["fixed"],
|
|
389
|
+
json!(false),
|
|
390
|
+
"missing db is diagnostic-only and does not create a db"
|
|
391
|
+
);
|
|
392
|
+
assert_eq!(v["rebuilds"], json!([]), "no rebuilds on a fresh db");
|
|
393
|
+
assert!(obj.contains_key("db_path"), "golden carries db_path");
|
|
394
|
+
assert!(
|
|
395
|
+
obj.contains_key("would_backup_path"),
|
|
396
|
+
"golden carries would_backup_path"
|
|
397
|
+
);
|
|
398
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
399
|
+
}
|
|
340
400
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
401
|
+
// ── doctor: golden checks dict (diagnose/health.py:179-241) — tmux/workspace/workspace_is_git_repo/
|
|
402
|
+
// providers/mcp/coordinator/ok. RUST mod.rs:520-522 stub {ok,spec,blockers}. Lock the golden
|
|
403
|
+
// top-level keys (values are machine-derived) + absence of the stub's spec/blockers. RED. ─────────
|
|
404
|
+
#[test]
|
|
405
|
+
fn doctor_golden_checks_shape_not_spec_blockers_stub() {
|
|
406
|
+
let ws = tmp_workspace();
|
|
407
|
+
let v = diagnose_port::doctor(&ws, None).expect("doctor");
|
|
408
|
+
let obj = v.as_object().expect("doctor dict");
|
|
409
|
+
let tmux = v["tmux"]
|
|
410
|
+
.as_object()
|
|
411
|
+
.expect("golden doctor.tmux is a dict {installed,path}");
|
|
412
|
+
assert!(
|
|
413
|
+
tmux.contains_key("installed") && tmux.contains_key("path"),
|
|
414
|
+
"tmux check shape"
|
|
415
|
+
);
|
|
416
|
+
assert!(
|
|
417
|
+
v["workspace"].is_string(),
|
|
418
|
+
"golden doctor.workspace is a string path"
|
|
419
|
+
);
|
|
420
|
+
assert!(
|
|
421
|
+
v["workspace_is_git_repo"].is_boolean(),
|
|
422
|
+
"golden doctor.workspace_is_git_repo bool"
|
|
423
|
+
);
|
|
424
|
+
assert!(
|
|
425
|
+
v["providers"].is_object(),
|
|
426
|
+
"golden doctor.providers is a dict"
|
|
427
|
+
);
|
|
428
|
+
assert_eq!(
|
|
429
|
+
v["mcp"]["local_module"],
|
|
430
|
+
json!(true),
|
|
431
|
+
"golden doctor.mcp.local_module == true (health.py:197-200)"
|
|
432
|
+
);
|
|
433
|
+
assert!(
|
|
434
|
+
v["coordinator"].is_object(),
|
|
435
|
+
"golden doctor.coordinator is the coordinator_health dict"
|
|
436
|
+
);
|
|
437
|
+
assert!(
|
|
438
|
+
!obj.contains_key("spec"),
|
|
439
|
+
"golden doctor has NO `spec` key (Rust stub adds it)"
|
|
440
|
+
);
|
|
441
|
+
assert!(
|
|
442
|
+
!obj.contains_key("blockers"),
|
|
443
|
+
"golden doctor has NO `blockers` key (Rust stub adds it)"
|
|
444
|
+
);
|
|
445
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
446
|
+
}
|