@team-agent/installer 0.5.75 → 0.5.78
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/README.md +9 -0
- package/README.zh.md +8 -0
- package/crates/team-agent/src/cli/adapters.rs +184 -125
- package/crates/team-agent/src/cli/diagnose.rs +333 -68
- package/crates/team-agent/src/cli/emit.rs +59 -15
- package/crates/team-agent/src/cli/mod.rs +600 -134
- package/crates/team-agent/src/cli/models.rs +485 -44
- package/crates/team-agent/src/cli/named_address.rs +19 -5
- package/crates/team-agent/src/cli/profile.rs +35 -4
- package/crates/team-agent/src/cli/spec.rs +7 -2
- package/crates/team-agent/src/cli/status_port/brief.rs +1137 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +12 -8
- package/crates/team-agent/src/cli/status_port/snapshot.rs +15 -12
- package/crates/team-agent/src/cli/status_port/store.rs +1 -1
- package/crates/team-agent/src/cli/status_port.rs +8 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +62 -8
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/named_address.rs +70 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +29 -24
- package/crates/team-agent/src/cli/tests/status_brief.rs +691 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +8 -2
- package/crates/team-agent/src/cli/tests/verb_profile.rs +81 -6
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +538 -41
- package/crates/team-agent/src/leader/owner_bind.rs +40 -12
- package/crates/team-agent/src/leader/rediscover/tests.rs +65 -0
- package/crates/team-agent/src/leader/rediscover.rs +36 -6
- package/crates/team-agent/src/leader/start.rs +151 -14
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +15 -9
- package/crates/team-agent/src/leader/types.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +28 -227
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -18
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +94 -227
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +59 -10
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +138 -14
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1449 -282
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +349 -4
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +11 -11
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +35 -3
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +16 -0
- package/crates/team-agent/src/lifecycle/launch.rs +30 -7
- package/crates/team-agent/src/lifecycle/profile_launch.rs +37 -3
- package/crates/team-agent/src/lifecycle/restart/agent.rs +112 -100
- package/crates/team-agent/src/lifecycle/restart/common.rs +82 -52
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +1002 -65
- package/crates/team-agent/src/lifecycle/restart.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +45 -14
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +15 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +5 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +11 -13
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +119 -3
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +22 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +265 -198
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +14 -30
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +2 -5
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +58 -35
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +9 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +4 -3
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -168
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +145 -212
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +16 -2
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +69 -28
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +78 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +51 -10
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +22 -10
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +50 -7
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +137 -0
- package/crates/team-agent/src/lifecycle/types.rs +12 -0
- package/crates/team-agent/src/mcp_server/wire.rs +140 -11
- package/crates/team-agent/src/messaging/delivery.rs +67 -8
- package/crates/team-agent/src/messaging/leader_channel.rs +56 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +10 -2
- package/crates/team-agent/src/messaging/watchers.rs +48 -4
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +2 -5
- package/crates/team-agent/src/state/paths.rs +17 -2
- package/crates/team-agent/src/state/persist.rs +97 -1
- package/crates/team-agent/src/state/projection.rs +18 -2
- package/crates/team-agent/src/state/repository.rs +29 -0
- package/crates/team-agent/src/state/selector.rs +23 -3
- package/crates/team-agent/src/test_support/models_timeout_fixture.rs +144 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +121 -4
- package/crates/team-agent/src/tmux_backend.rs +208 -118
- package/crates/team-agent/src/transport.rs +72 -0
- package/crates/team-agent/src/transport_factory.rs +53 -0
- package/package.json +4 -5
- package/skills/team-agent/SKILL.md +45 -17
- package/skills/team-agent/command-coverage.json +4 -29
- package/npm/check-skill-links.mjs +0 -88
- package/skills/team-agent/references/team-agent-operator.md +0 -672
|
@@ -60,19 +60,23 @@ pub(super) fn compact_not_ready(full: &Value) -> Value {
|
|
|
60
60
|
})
|
|
61
61
|
.unwrap_or_default();
|
|
62
62
|
let mut obj = Map::new();
|
|
63
|
-
let channel_unbound = reasons
|
|
64
|
-
.iter()
|
|
65
|
-
.any(|reason| reason == "leader_receiver_unbound");
|
|
66
63
|
obj.insert(
|
|
67
64
|
"reasons".to_string(),
|
|
68
65
|
Value::Array(reasons.into_iter().map(Value::String).collect()),
|
|
69
66
|
);
|
|
70
67
|
obj.insert("agents".to_string(), Value::Array(agents));
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
68
|
+
if let Some(next_action) = full
|
|
69
|
+
.pointer("/readiness/next_action")
|
|
70
|
+
.and_then(Value::as_str)
|
|
71
|
+
{
|
|
72
|
+
let truly_unbound = full.pointer("/readiness/state").and_then(Value::as_str)
|
|
73
|
+
== Some("leader_receiver_unbound");
|
|
74
|
+
if !next_action.contains("claim-leader") || truly_unbound {
|
|
75
|
+
obj.insert(
|
|
76
|
+
"next_action".to_string(),
|
|
77
|
+
Value::String(next_action.to_string()),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
Value::Object(obj)
|
|
78
82
|
}
|
|
@@ -95,18 +95,21 @@ impl RuntimeSnapshot {
|
|
|
95
95
|
.filter(|team| !team.is_empty())
|
|
96
96
|
.map(str::to_string)
|
|
97
97
|
.unwrap_or_else(|| crate::state::projection::team_state_key(state));
|
|
98
|
-
if !team_key.is_empty()
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
if !team_key.is_empty() {
|
|
99
|
+
let class = crate::lifecycle::launch::classify_leader_binding(workspace, &team_key);
|
|
100
|
+
if class != crate::lifecycle::launch::LeaderBindingClass::Attached {
|
|
101
|
+
if let Some(object) = readiness.as_object_mut() {
|
|
102
|
+
object.insert("all_attached_receiver".to_string(), json!(false));
|
|
103
|
+
object.insert("ready".to_string(), json!(false));
|
|
104
|
+
object.insert("state".to_string(), json!(class.issue_id()));
|
|
105
|
+
object.insert("reason".to_string(), json!(class.issue_id()));
|
|
106
|
+
if class == crate::lifecycle::launch::LeaderBindingClass::Unbound {
|
|
107
|
+
object.insert(
|
|
108
|
+
"next_action".to_string(),
|
|
109
|
+
json!(class.repair()),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
let grok_slot = crate::cli::grok_slot_report(workspace, state).to_json();
|
|
@@ -230,7 +230,7 @@ pub(super) fn pending_leader_notifications(
|
|
|
230
230
|
"created_at": row.get::<_, Option<String>>(4)?,
|
|
231
231
|
"delivery_attempts": row.get::<_, i64>(5)?,
|
|
232
232
|
"channel": channel,
|
|
233
|
-
"action": "
|
|
233
|
+
"action": "historical leader mailbox debt; not proof of current unbound owner",
|
|
234
234
|
}))
|
|
235
235
|
};
|
|
236
236
|
let rows = match owner_team_id {
|
|
@@ -34,6 +34,14 @@ use store::{
|
|
|
34
34
|
mod compact;
|
|
35
35
|
pub(super) use compact::*;
|
|
36
36
|
|
|
37
|
+
mod brief;
|
|
38
|
+
pub(crate) use brief::{format_status_brief, registered_agent_exists, status_brief_scoped};
|
|
39
|
+
#[cfg(test)]
|
|
40
|
+
pub(crate) use brief::{
|
|
41
|
+
with_test_nodeprobe, with_test_nodeprobe_candidates, with_test_nodeprobe_resolver_delay,
|
|
42
|
+
with_test_nodeprobe_unbound, ResolverDelaySignal,
|
|
43
|
+
};
|
|
44
|
+
|
|
37
45
|
mod runtime;
|
|
38
46
|
pub(super) use runtime::*;
|
|
39
47
|
|
|
@@ -90,6 +90,44 @@ fn seed_self_signed_attached_leader(ws: &std::path::Path) {
|
|
|
90
90
|
.unwrap();
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
fn seed_canonical_healthy_leader(ws: &std::path::Path, endpoint: &str) {
|
|
94
|
+
crate::state::persist::save_runtime_state(
|
|
95
|
+
ws,
|
|
96
|
+
&json!({
|
|
97
|
+
"team_key": "current",
|
|
98
|
+
"workspace": ws,
|
|
99
|
+
"teams": {
|
|
100
|
+
"current": {
|
|
101
|
+
"team_owner": {
|
|
102
|
+
"pane_id": "%1",
|
|
103
|
+
"owner_epoch": 1,
|
|
104
|
+
"provider": "codex"
|
|
105
|
+
},
|
|
106
|
+
"leader_receiver": {
|
|
107
|
+
"mode": "direct_tmux",
|
|
108
|
+
"status": "attached",
|
|
109
|
+
"pane_id": "%1",
|
|
110
|
+
"owner_epoch": 1,
|
|
111
|
+
"provider": "codex",
|
|
112
|
+
"tmux_socket": endpoint
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
)
|
|
118
|
+
.unwrap();
|
|
119
|
+
assert_eq!(
|
|
120
|
+
crate::leader::registry::register_binding_from_state_best_effort(
|
|
121
|
+
ws,
|
|
122
|
+
Some("current"),
|
|
123
|
+
"diagnose-healthy-fixture",
|
|
124
|
+
)
|
|
125
|
+
.as_ref()
|
|
126
|
+
.map(|outcome| outcome.status),
|
|
127
|
+
Some("registered")
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
93
131
|
fn seed_leader_registry_entry(ws: &std::path::Path) {
|
|
94
132
|
let entry = crate::leader::registry::build_entry(
|
|
95
133
|
ws,
|
|
@@ -345,17 +383,33 @@ fn dispatch_routes_diagnose_healthy_leader() {
|
|
|
345
383
|
]);
|
|
346
384
|
let _home = IsolatedHome::enter("diagnose-healthy");
|
|
347
385
|
let ws = tmp_workspace();
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
let
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
386
|
+
let endpoint = "/tmp/ta-diagnose-healthy.sock";
|
|
387
|
+
seed_canonical_healthy_leader(&ws, endpoint);
|
|
388
|
+
let transport = crate::transport::test_support::OfflineTransport::default()
|
|
389
|
+
.with_tmux_endpoint(endpoint)
|
|
390
|
+
.with_targets(vec![crate::transport::PaneInfo {
|
|
391
|
+
pane_id: crate::transport::PaneId::new("%1"),
|
|
392
|
+
session: crate::transport::SessionName::new("s"),
|
|
393
|
+
window_index: None,
|
|
394
|
+
window_name: None,
|
|
395
|
+
pane_index: None,
|
|
396
|
+
tty: None,
|
|
397
|
+
current_command: Some("codex".to_string()),
|
|
398
|
+
current_path: Some(ws.clone()),
|
|
399
|
+
active: true,
|
|
400
|
+
pane_pid: None,
|
|
401
|
+
leader_env: Default::default(),
|
|
402
|
+
}]);
|
|
403
|
+
let code = crate::transport_factory::with_leader_endpoint_transport(endpoint, transport, || {
|
|
404
|
+
run(
|
|
405
|
+
&cli_argv(&["diagnose", "--workspace", &ws.to_string_lossy(), "--json"]),
|
|
406
|
+
&ws,
|
|
407
|
+
)
|
|
408
|
+
});
|
|
354
409
|
assert_eq!(
|
|
355
410
|
code,
|
|
356
411
|
ExitCode::Ok,
|
|
357
|
-
"`diagnose` must be Ok when
|
|
358
|
-
has a matching attached row; got {code:?}"
|
|
412
|
+
"`diagnose` must be Ok when canonical owner/receiver, production registry, and live align; got {code:?}"
|
|
359
413
|
);
|
|
360
414
|
let _ = std::fs::remove_dir_all(&ws);
|
|
361
415
|
}
|
|
@@ -234,6 +234,76 @@ fn resolve_name_team_disambiguated() {
|
|
|
234
234
|
let _ = std::fs::remove_dir_all(&ws);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
#[test]
|
|
238
|
+
fn resolve_qualified_name_prefers_unique_state_pane_over_same_window_idle_pane() {
|
|
239
|
+
let ws = named_ws("qualified-state-pane-disambiguation");
|
|
240
|
+
seed_state(
|
|
241
|
+
&ws,
|
|
242
|
+
state_with_teams(json!({
|
|
243
|
+
"team-a": worker_team("team-a", "qa", "%worker", "qa")
|
|
244
|
+
})),
|
|
245
|
+
);
|
|
246
|
+
let transport = OfflineTransport::new().with_targets(vec![
|
|
247
|
+
pane("team-a", "qa", "%worker"),
|
|
248
|
+
pane("team-a", "qa", "%idle"),
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
let resolved = resolve_name_with_transport(
|
|
252
|
+
&ws,
|
|
253
|
+
&format!("{}::team-a/qa", ws.display()),
|
|
254
|
+
&transport,
|
|
255
|
+
)
|
|
256
|
+
.expect("a qualified worker address should use its unique state pane");
|
|
257
|
+
assert_eq!(resolved.pane_id, "%worker");
|
|
258
|
+
assert_eq!(resolved.state_pane_id.as_deref(), Some("%worker"));
|
|
259
|
+
assert!(!resolved.state_pane_stale);
|
|
260
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#[test]
|
|
264
|
+
fn resolve_qualified_name_state_pane_must_uniquely_match_scoped_candidates() {
|
|
265
|
+
for scenario in ["missing", "stale", "cross-session", "duplicate"] {
|
|
266
|
+
let ws = named_ws(&format!("qualified-state-pane-{scenario}"));
|
|
267
|
+
let mut team = worker_team("team-a", "qa", "%worker", "qa");
|
|
268
|
+
let targets = match scenario {
|
|
269
|
+
"missing" => {
|
|
270
|
+
team["agents"]["qa"]
|
|
271
|
+
.as_object_mut()
|
|
272
|
+
.unwrap()
|
|
273
|
+
.remove("pane_id");
|
|
274
|
+
vec![pane("team-a", "qa", "%worker"), pane("team-a", "qa", "%idle")]
|
|
275
|
+
}
|
|
276
|
+
"stale" => {
|
|
277
|
+
team["agents"]["qa"]["pane_id"] = json!("%stale");
|
|
278
|
+
vec![pane("team-a", "qa", "%worker"), pane("team-a", "qa", "%idle")]
|
|
279
|
+
}
|
|
280
|
+
"cross-session" => vec![
|
|
281
|
+
pane("old-team-a", "qa", "%worker"),
|
|
282
|
+
pane("team-a", "qa", "%current-one"),
|
|
283
|
+
pane("team-a", "qa", "%current-two"),
|
|
284
|
+
],
|
|
285
|
+
"duplicate" => {
|
|
286
|
+
team["agents"]["qa"]["pane_id"] = json!("%duplicate");
|
|
287
|
+
vec![
|
|
288
|
+
pane("team-a", "qa", "%duplicate"),
|
|
289
|
+
pane("team-a", "qa", "%duplicate"),
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
_ => unreachable!(),
|
|
293
|
+
};
|
|
294
|
+
seed_state(&ws, state_with_teams(json!({"team-a": team})));
|
|
295
|
+
let transport = OfflineTransport::new().with_targets(targets);
|
|
296
|
+
let err = resolve_name_with_transport(
|
|
297
|
+
&ws,
|
|
298
|
+
&format!("{}::team-a/qa", ws.display()),
|
|
299
|
+
&transport,
|
|
300
|
+
)
|
|
301
|
+
.expect_err("non-unique state pane evidence must remain fail-closed");
|
|
302
|
+
assert_eq!(err.kind, NamedAddressErrorKind::NameAmbiguous, "{scenario}");
|
|
303
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
237
307
|
#[test]
|
|
238
308
|
fn resolve_qualified_name_canonicalizes_legacy_session_alias() {
|
|
239
309
|
let ws = named_ws("legacy-session-alias");
|
|
@@ -335,13 +335,12 @@ fn cli_add_agent_duplicate_id_surfaces_real_error() {
|
|
|
335
335
|
);
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
// 5 [P1] — cmd_status --json
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
// counts. OS-safe (all reads/seeds, no spawn).
|
|
338
|
+
// 5 [P1] — cmd_status --json emits the concise seven-field node projection.
|
|
339
|
+
// Status must not leak coordinator/db/history diagnostics; missing exact nodeprobe evidence remains
|
|
340
|
+
// an explicit unknown projection. The fixture still seeds a real store to ensure the status path
|
|
341
|
+
// does not accidentally depend on coordinator or mailbox reads.
|
|
343
342
|
#[test]
|
|
344
|
-
fn
|
|
343
|
+
fn cli_status_emits_seven_field_brief_without_diagnostics() {
|
|
345
344
|
let ws = seed_status_workspace(); // writes .team/runtime/state.json
|
|
346
345
|
let store = crate::message_store::MessageStore::open(&ws).unwrap();
|
|
347
346
|
let _ = store
|
|
@@ -386,25 +385,31 @@ fn cli_status_reflects_real_coordinator_health_and_db_counts() {
|
|
|
386
385
|
CmdOutput::Json(v) => v,
|
|
387
386
|
other => panic!("status --json must yield a Json CmdOutput; got {other:?}"),
|
|
388
387
|
};
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
"status
|
|
393
|
-
|
|
394
|
-
|
|
388
|
+
let nodes = value
|
|
389
|
+
.get("nodes")
|
|
390
|
+
.and_then(serde_json::Value::as_array)
|
|
391
|
+
.expect("status brief must expose a nodes array");
|
|
392
|
+
assert_eq!(nodes.len(), 1);
|
|
393
|
+
let fields: std::collections::BTreeSet<&str> = nodes[0]
|
|
394
|
+
.as_object()
|
|
395
|
+
.expect("status brief node must be an object")
|
|
396
|
+
.keys()
|
|
397
|
+
.map(String::as_str)
|
|
398
|
+
.collect();
|
|
399
|
+
assert_eq!(
|
|
400
|
+
fields,
|
|
401
|
+
[
|
|
402
|
+
"name",
|
|
403
|
+
"provider",
|
|
404
|
+
"runtime_status",
|
|
405
|
+
"activity",
|
|
406
|
+
"health",
|
|
407
|
+
"session_name",
|
|
408
|
+
"tmux_command",
|
|
409
|
+
]
|
|
410
|
+
.into_iter()
|
|
411
|
+
.collect()
|
|
395
412
|
);
|
|
396
|
-
assert_ne!(
|
|
397
|
-
value["messages"],
|
|
398
|
-
json!({"count": 0}),
|
|
399
|
-
"status `messages` must reflect the REAL DB rows (2 seeded), not the placeholder's {{count:0}}; got {}",
|
|
400
|
-
value["messages"]
|
|
401
|
-
);
|
|
402
|
-
assert_ne!(
|
|
403
|
-
value["results"],
|
|
404
|
-
json!({"count": 0}),
|
|
405
|
-
"status `results` must reflect the REAL DB result row, not the placeholder's {{count:0}}; got {}",
|
|
406
|
-
value["results"]
|
|
407
|
-
);
|
|
408
413
|
}
|
|
409
414
|
|
|
410
415
|
// 6 [P1] — cmd_shutdown delegates to the REAL crate::coordinator::stop_coordinator (+ tmux
|