Files
Dicklesworthstone 5f05938484 feat(pages,doctor,status,indexer,sources,gate): pages key CLI, WAL hygiene, segment pressure, setup sync, install fall-through, docs validator, and end-to-end proofs for #439/#440/#441 (coding_agent_session_search-ctigq, -2w1sc, -hr9rn, -q2vn9, -z2uon, -wpe6s, -vg2e1, -tcrxi, -c2xxq, -xwuon, -jwox6, -vdyxd, -apriu)
Correction to 1f642a24's message: the golden leak was not caused by the golden
harness's own stale-on-read refresh — verify7 reproduced it with that opt-out
in place. The writer is tests/cli_robot.rs: 52 call sites use
tests/fixtures/search_demo_data IN PLACE through a base_cmd() that never
disabled auto-refresh, and on a fleet worker the checkout is not under the OS
temp dir, so a robot-CLI search spawned `cass index --background` into the
committed fixture and ingested that worker's real sessions before the golden
tests copied it. base_cmd() now sets CASS_AUTO_REFRESH=0. Proof: verify9 ran
the in-place robot tests before golden regeneration in one job and
search_robot/stats_full_payload came back byte-identical to HEAD.

pages key (ctigq): `cass pages key list|add-password|add-recovery|revoke|
rotate --archive <bundle>` over the complete key_management engine (3,915
lines, zero callers until now). Passwords via prompt or --password-stdin
(current on line 1, new on line 2), never argv; --json; exit 1 engine refused,
2 usage, 3 not a bundle, 6 password required. Four unit tests on a real
encrypted bundle plus a CLI suite (tests/pages_key_cli.rs). docs/RECOVERY.md
rewritten to the real surface; README row.

WAL hygiene (z2uon): forget --apply, dedup --apply and analytics validate
--fix close through the index run's checkpointing close; doctor `archive_wal`
(pass ≤ 64 MiB; warn with size and remedy; warn-and-defer under an index run;
--fix checkpoints and reports pass / blocked / failed truthfully) with a
sparse-WAL e2e in tests/cli_doctor.rs. status/health e2e for db_bytes /
wal_bytes / shm_present, now on the `status` surface too (q2vn9).

Segment pressure (wpe6s, GH #441): quill_bridge::segment_file_count
(metadata-only upper bound), status/health `index.segment_files`, doctor
`index_segments` (warn above 8× the merge threshold, `cass index --full`
remedy), CASS_TEST_SKIP_POST_RUN_LEXICAL_MAINTENANCE hook and an e2e that
builds a 40-segment generation, sees the warning, and proves a plain
`cass index` consolidates it.

GH #439 (vg2e1) and GH #440 (tcrxi) end-to-end proofs: FTS-repair hooks
(sleep per page after the heartbeat; park before the first page) — a slow
repair survives a 20 s abort window, a parked one exits 70 with the
`index-stalled` envelope; a hook parks a force-rebuild between the staged
engine commit and the checkpoint write, records the gap, and a plain
`cass index` resumes with exit 0 and no duplicate identities.

sources (vdyxd, G.1): setup no longer marks the final sync complete without
running it — interactive setup runs `cass sources sync` afterwards and records
it only on success; --json setup defers it and reports sync.status=pending.
Remote install walks candidate_methods() (binstall → verified prebuilt → cargo
install → bootstrap) and falls through on failure instead of stopping at the
first choice. `cass sources sync --all` exists (jwox6): doctor, robot-docs and
the fleet rehearsal recommended it while clap rejected it.

installer (apriu, G.2a): install.sh probes host glibc and falls back to
build-from-source below 2.38; proven on ubuntu:22.04 (2.35 → source route)
and ubuntu:24.04 (2.39 → prebuilt).

docs validator (xwuon, A.9): scripts/validate_docs.sh --keys/--flags/--env/
--truth check README key bindings against the TUI key map (123/123, planted
fake key fails), every `cass … --flag` usage against `cass … --help`, and
every README env var against the code; found and fixed `sources sync --all`,
`timeline --days`, the `<mark>` highlight claim and a dead CASS_UI_METRICS
row. gate.sh gains an opt-in docs-truth stage.

gate (c2xxq): scripts/gate.sh had a false green — verify8 was cut by the
fleet's 30-minute SSH ceiling after fmt+clippy and the script still said
GREEN. Now a non-zero rch exit is RED, every expected stage (plus a terminal
job-complete marker) must report, the receipt captures both streams and
survives a RED run, --lib-only/--no-goldens split runs under the ceiling, the
lib stage runs under `timeout`, and the fleet target dir no longer inherits
the shell's CARGO_TARGET_DIR.

contract (hr9rn): introspect/capabilities has_json_output looks through the
subcommand tree (11 commands flip to true and gain the robot-format arg).
TUI: the unreachable `Alt+W => swarm cockpit` arm removed. CHANGELOG
[Unreleased], AGENTS swarm/issue rules, plan §7b progress log.

Verification (fleet, scripts/gate.sh STAGE receipts, RUST_MIN_STACK=16777216):
- verify9 (2026-09-02 00:35-00:58): fmt 0, clippy 0, goldens-regen 0,
  goldens 0 with search_robot/stats_full_payload byte-identical to HEAD
  after the in-place cli_robot tests ran first (leak fix proven);
  cli_index/cli_robot/cli_status 101 (Codex fixture names, thresholds,
  health nesting) fixed in tree and re-run below.
- verify10 (01:04-01:19): `cargo test --lib` to completion, 6,847 passed /
  6 failed / 38 ignored, no wedge; the six (bookmarks missing from
  CANONICAL_TOP_LEVEL_COMMANDS, four sqlite3-CLI tests on a worker without
  sqlite3, daemon socket path over sun_path on a long TMPDIR) fixed in
  tree and re-run below.
- verify11 (05:08-05:23): fmt 0, clippy 0, lib-tests 0 (canonical
  commands, daemon, sqlite, sources install/setup, pages key,
  quill_bridge, stall diagnostics, fingerprint cache), pages_key_cli 0,
  cli_robot 0, cli_doctor 0, tui_flows 0, bookmarks_cli 0, goldens-regen
  0, goldens 0; cli_index 101 (gh439 reads the last stdout JSON line now)
  and cli_status 101 (health nests state.database) fixed in tree.
- verify12 (05:41-05:47, full lib suite on this tree): fmt 0, clippy 0,
  lib-tests 0 — 6,855 passed / 0 failed / 38 ignored in 144 s.
- verify13 (05:47-05:55, --regen-goldens): fmt 0, clippy 0, lib-tests 0
  (quill_bridge, stall diagnostics), cli_status 0, cli_doctor 0,
  pages_key_cli 0, goldens-regen 0, goldens 0; cli_index 101 because the
  gh439 slow-repair test parsed the last LINE of a pretty-printed summary
  (harness bug) - it now stream-parses the last JSON document.
- verify14 (05:57-06:00, cli_index gh439 after that fix): fmt 0, clippy 0,
  test-cli_index 0 (gate: GREEN).
Goldens: 12 reviewed diffs (segment_files / db_bytes / wal_bytes /
shm_present fields, has_json_output flips, pages key and sources sync
--all in the command trees, doctor archive_wal / index_segments checks);
the four that differ only by the regeneration writer's dropped final
newline were left at HEAD.

Measurement (owner's archive: 10.3 GB db, 200 MB WAL, 267 Quill segment
files; `search "borrow checker lifetimes" --robot --robot-meta --limit 5`,
five warm runs, CASS_AUTO_REFRESH=0): v0.7.1 median wall 15.3 s (search_ms
6.3-7.5 s, other_ms 1.0-1.9 s); this tree (release build) median wall
1.9 s (search_ms 337-406 ms, other_ms 754-810 ms after a first-run 2.6 s
sidecar miss). hits=1 on both. Not yet isolated: the engine-time drop
happened with the segment count unchanged, so the per-open seal in the old
Quill config is the likelier cause than fragmentation on this archive.
Consolidation of the owner's 267 segments was NOT achieved: the documented
path (`cass index`, and `cass doctor --fix` for the WAL) never leaves the
frankensqlite writable open on that archive - one core, 6.7 GB RSS,
~30k 24-byte header reads/s on the 200 MB WAL, no progress in 7 min -
while read-only opens take 1.5 s. On a COPY whose WAL C SQLite checkpointed
losslessly (1,012 conversations / 538,807 messages before and after), the
same `cass index` gets through the open and indexes, so the WAL is the
trigger; C SQLite's quick_check on that copy also reports a doubly-
referenced freelist page and free-space corruption on ~35 pages that
`cass doctor` calls healthy. The archive's last completed index is
2026-08-14. Recorded as a new P0 in the plan (section 7b); the live
archive was not modified.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 06:18:55 -04:00

1329 lines
47 KiB
Rust

use assert_cmd::Command;
use coding_agent_search::search::tantivy::{SCHEMA_HASH, expected_index_dir};
use coding_agent_search::sources::config::{SourceDefinition, SourcesConfig, SyncSchedule};
use coding_agent_search::storage::sqlite::CURRENT_SCHEMA_VERSION;
use fs2::FileExt;
use serde_json::json;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::time::Duration;
fn write_quarantined_manifest(generation_dir: &Path) {
fs::create_dir_all(generation_dir).expect("create generation dir");
fs::write(
generation_dir.join("lexical-generation-manifest.json"),
serde_json::to_vec_pretty(&json!({
"manifest_version": 1,
"generation_id": "gen-quarantined",
"attempt_id": "attempt-1",
"created_at_ms": 1_733_000_000_000_i64,
"updated_at_ms": 1_733_000_000_321_i64,
"source_db_fingerprint": "fp-test",
"conversation_count": 3,
"message_count": 9,
"indexed_doc_count": 9,
"equivalence_manifest_fingerprint": null,
"shard_plan": null,
"build_budget": null,
"shards": [{
"shard_id": "shard-a",
"shard_ordinal": 0,
"state": "quarantined",
"updated_at_ms": 1_733_000_000_222_i64,
"indexed_doc_count": 9,
"message_count": 9,
"artifact_bytes": 512,
"stable_hash": "stable-hash-a",
"reclaimable": false,
"pinned": false,
"recovery_reason": null,
"quarantine_reason": "validation_failed"
}],
"merge_debt": {
"state": "none",
"updated_at_ms": null,
"pending_shard_count": 0,
"pending_artifact_bytes": 0,
"reason": null,
"controller_reason": null
},
"build_state": "failed",
"publish_state": "quarantined",
"failure_history": []
}))
.expect("serialize manifest"),
)
.expect("write manifest");
}
fn write_generation_manifest(
generation_dir: &Path,
generation_id: &str,
build_state: &str,
publish_state: &str,
shard_state: &str,
pinned: bool,
reclaimable: bool,
) {
fs::create_dir_all(generation_dir).expect("create generation dir");
fs::write(
generation_dir.join("lexical-generation-manifest.json"),
serde_json::to_vec_pretty(&json!({
"manifest_version": 3,
"generation_id": generation_id,
"attempt_id": format!("{generation_id}-attempt"),
"created_at_ms": 1_733_000_010_000_i64,
"updated_at_ms": 1_733_000_010_321_i64,
"source_db_fingerprint": "fp-lifecycle-test",
"conversation_count": 4,
"message_count": 12,
"indexed_doc_count": 12,
"equivalence_manifest_fingerprint": "eq-lifecycle-test",
"shard_plan": null,
"build_budget": null,
"shards": [{
"shard_id": format!("{generation_id}-shard"),
"shard_ordinal": 0,
"state": shard_state,
"updated_at_ms": 1_733_000_010_222_i64,
"indexed_doc_count": 12,
"message_count": 12,
"artifact_bytes": 256,
"stable_hash": format!("{generation_id}-stable"),
"reclaimable": reclaimable,
"pinned": pinned,
"recovery_reason": null,
"quarantine_reason": if shard_state == "quarantined" { Some("validation_failed") } else { None::<&str> }
}],
"merge_debt": {
"state": "none",
"updated_at_ms": null,
"pending_shard_count": 0,
"pending_artifact_bytes": 0,
"reason": null,
"controller_reason": null
},
"build_state": build_state,
"publish_state": publish_state,
"failure_history": []
}))
.expect("serialize manifest"),
)
.expect("write manifest");
fs::write(
generation_dir.join("segment"),
format!("{generation_id}-artifact-bytes"),
)
.expect("write generation artifact");
}
fn write_remote_source_config(config_home: &Path) {
let mut source = SourceDefinition::ssh("status-remote", "user@status-remote");
source.paths = vec!["~/.codex/sessions".to_string()];
source.sync_schedule = SyncSchedule::Hourly;
SourcesConfig {
sources: vec![source],
disabled_agents: Vec::new(),
}
.save_to(&config_home.join("cass/sources.toml"))
.expect("write sources config");
}
fn isolated_cass_cmd(temp_home: &Path) -> Command {
let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("cass"));
cmd.env("CODING_AGENT_SEARCH_NO_UPDATE_PROMPT", "1");
cmd.env("CASS_IGNORE_SOURCES_CONFIG", "1");
cmd.env("HOME", temp_home);
cmd.env("XDG_DATA_HOME", temp_home.join(".local/share"));
cmd.env("XDG_CONFIG_HOME", temp_home.join(".config"));
cmd.env("CODEX_HOME", temp_home.join(".codex"));
cmd
}
fn write_ingest_quarantine_record(data_dir: &Path) {
write_ingest_quarantine_records(data_dir, 1);
}
fn write_ingest_quarantine_records(data_dir: &Path, count: usize) {
let quarantine_dir = data_dir.join("quarantine");
fs::create_dir_all(&quarantine_dir).expect("create ingest quarantine dir");
let now_ms = chrono::Utc::now().timestamp_millis();
let mut lines = String::new();
for idx in 0..count {
let started_at = i64::try_from(idx).expect("idx fits i64") + 1;
let ended_at = started_at + 1;
let record = json!({
"schema_version": 1,
"conversation_id": format!("tester|/logs/demo-{idx}.jsonl|/workspace/demo|poison-{idx}|{started_at}|{ended_at}|1"),
"schema_version_at_quarantine": CURRENT_SCHEMA_VERSION,
"first_quarantined_at_ms": now_ms,
"last_attempt_at_ms": now_ms,
"attempt_count": 1,
"reason": "index-ingest-out-of-memory",
"error_kind": "out-of-memory",
"last_error": "out of memory",
"agent_slug": "tester",
"external_id": format!("poison-{idx}"),
"source_path": format!("/logs/demo-{idx}.jsonl"),
"workspace": "/workspace/demo",
"started_at": started_at,
"ended_at": ended_at,
"message_count": 1
});
lines.push_str(&format!("{record}\n"));
}
fs::write(quarantine_dir.join("index_ingest_poison.jsonl"), lines)
.expect("write ingest quarantine record");
}
fn seed_active_rebuild_runtime(data_dir: &Path) -> std::fs::File {
let db_path = data_dir.join("agent_search.db");
let index_path = expected_index_dir(data_dir);
fs::create_dir_all(&index_path).expect("create index dir");
fs::write(
index_path.join(".lexical-rebuild-state.json"),
serde_json::to_vec_pretty(&json!({
"version": 2,
"schema_hash": SCHEMA_HASH,
"db": {
"db_path": db_path.display().to_string(),
"total_conversations": 10,
"total_messages": 20,
"storage_fingerprint": "seed:10"
},
"page_size": 1024,
"committed_offset": 4,
"committed_conversation_id": 4,
"processed_conversations": 4,
"indexed_docs": 20,
"committed_meta_fingerprint": null,
"pending": null,
"completed": false,
"updated_at_ms": 1_733_000_123_000_i64,
"runtime": {
"queue_depth": 3,
"inflight_message_bytes": 65_536,
"max_message_bytes_in_flight": 131_072,
"pending_batch_conversations": 9,
"pending_batch_message_bytes": 131_072,
"page_prep_workers": 6,
"active_page_prep_jobs": 2,
"ordered_buffered_pages": 4,
"budget_generation": 1,
"producer_budget_wait_count": 2,
"producer_budget_wait_ms": 17,
"producer_handoff_wait_count": 1,
"producer_handoff_wait_ms": 9,
"host_loadavg_1m_milli": 7_250,
"controller_mode": "pressure_limited",
"controller_reason": "queue_depth_3_reached_pipeline_capacity_3",
"staged_merge_workers_max": 3,
"staged_merge_allowed_jobs": 1,
"staged_merge_active_jobs": 1,
"staged_merge_ready_artifacts": 5,
"staged_merge_ready_groups": 1,
"staged_merge_controller_reason": "page_prep_workers_saturated_6_of_6",
"staged_shard_build_workers_max": 6,
"staged_shard_build_allowed_jobs": 5,
"staged_shard_build_active_jobs": 4,
"staged_shard_build_pending_jobs": 2,
"staged_shard_build_controller_reason": "reserving_1_slots_for_staged_merge_active_jobs_1_ready_groups_1",
"updated_at_ms": 1_733_000_124_000_i64
}
}))
.expect("serialize rebuild state"),
)
.expect("write rebuild state");
let lock_path = data_dir.join("index-run.lock");
let mut lock_file = OpenOptions::new()
.create(true)
.truncate(true)
.read(true)
.write(true)
.open(&lock_path)
.expect("open lock file");
lock_file.lock_exclusive().expect("hold index lock");
writeln!(
lock_file,
"pid={}\nstarted_at_ms={}\ndb_path={}\nmode=index",
std::process::id(),
1_733_000_111_000_i64,
db_path.display()
)
.expect("write lock metadata");
lock_file.flush().expect("flush lock metadata");
lock_file
}
#[test]
fn status_and_health_json_surface_ingest_quarantine_as_nonfatal_degraded() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
let index_out = isolated_cass_cmd(test_home.path())
.args([
"index",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
"--no-progress-events",
])
.output()
.expect("run cass index --json");
assert!(
index_out.status.success(),
"cass index --json failed: stdout={} stderr={}",
String::from_utf8_lossy(&index_out.stdout),
String::from_utf8_lossy(&index_out.stderr)
);
write_ingest_quarantine_record(&data_dir);
let status_out = isolated_cass_cmd(test_home.path())
.args([
"status",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass status --json");
assert!(
status_out.status.success(),
"cass status --json failed: stdout={} stderr={}",
String::from_utf8_lossy(&status_out.stdout),
String::from_utf8_lossy(&status_out.stderr)
);
let status_payload: serde_json::Value =
serde_json::from_slice(&status_out.stdout).expect("status JSON");
assert_eq!(status_payload["status"].as_str(), Some("healthy"));
assert_eq!(status_payload["healthy"].as_bool(), Some(true));
assert_eq!(status_payload["health_level"].as_str(), Some("degraded"));
assert_eq!(
status_payload["index"]["quarantined_conversations"].as_u64(),
Some(1)
);
assert_eq!(
status_payload["ingest_quarantine"]["quarantined_conversations"].as_u64(),
Some(1)
);
assert!(
status_payload["warnings"]
.as_array()
.is_some_and(|warnings| !warnings.is_empty()),
"status should expose a nonfatal ingest-quarantine warning: {status_payload}"
);
assert!(
status_payload["recommended_action"]
.as_str()
.is_some_and(|action| action.contains("quarantine")),
"status should recommend inspecting quarantine state: {status_payload}"
);
let health_out = isolated_cass_cmd(test_home.path())
.args([
"health",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass health --json");
assert!(
health_out.status.success(),
"cass health --json should remain exit 0 for nonfatal ingest quarantine: stdout={} stderr={}",
String::from_utf8_lossy(&health_out.stdout),
String::from_utf8_lossy(&health_out.stderr)
);
let health_payload: serde_json::Value =
serde_json::from_slice(&health_out.stdout).expect("health JSON");
assert_eq!(health_payload["status"].as_str(), Some("healthy"));
assert_eq!(health_payload["healthy"].as_bool(), Some(true));
assert_eq!(health_payload["health_level"].as_str(), Some("degraded"));
assert_eq!(
health_payload["ingest_quarantine"]["quarantined_conversations"].as_u64(),
Some(1)
);
assert!(
health_payload["warnings"]
.as_array()
.is_some_and(|warnings| !warnings.is_empty()),
"health should expose a nonfatal ingest-quarantine warning: {health_payload}"
);
}
/// uojcg.3.3: health/status/triage and search --robot-meta must carry a
/// structured `search_completeness` verdict so agents can tell a *stale* index
/// (still complete) from one that *excludes known conversations* (quarantined).
#[test]
fn readiness_surfaces_report_degraded_search_completeness_when_quarantined() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
let index_out = isolated_cass_cmd(test_home.path())
.args([
"index",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
"--no-progress-events",
])
.output()
.expect("run cass index --json");
assert!(
index_out.status.success(),
"cass index --json failed: stdout={} stderr={}",
String::from_utf8_lossy(&index_out.stdout),
String::from_utf8_lossy(&index_out.stderr)
);
write_ingest_quarantine_record(&data_dir);
// The degraded (but searchable) verdict every readiness surface must agree on.
let assert_degraded = |payload: &serde_json::Value, surface: &str| {
let sc = &payload["search_completeness"];
assert_eq!(
sc["quarantine_status"].as_str(),
Some("degraded"),
"{surface} search_completeness should be degraded: {payload}"
);
assert_eq!(
sc["quarantined_conversations"].as_u64(),
Some(1),
"{surface} should count the quarantined conversation: {payload}"
);
assert_eq!(
sc["complete"].as_bool(),
Some(false),
"{surface} coverage is incomplete: {payload}"
);
assert_eq!(
sc["can_search"].as_bool(),
Some(true),
"{surface} ordinary search still runs: {payload}"
);
assert_eq!(
sc["coverage_suspect"].as_bool(),
Some(false),
"{surface} a single non-burst quarantine is not suspect: {payload}"
);
assert!(
sc["next_command"]
.as_str()
.is_some_and(|cmd| cmd.contains("quarantine")),
"{surface} should point at the quarantine inspect command: {payload}"
);
};
for surface in ["status", "triage"] {
let out = isolated_cass_cmd(test_home.path())
.args([
surface,
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass readiness surface --json");
assert!(
out.status.success(),
"cass {surface} --json failed: stdout={} stderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let payload: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("parse readiness surface JSON");
assert_degraded(&payload, surface);
}
// triage must also surface the quarantine facts inside its readiness block.
let triage_out = isolated_cass_cmd(test_home.path())
.args([
"triage",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass triage --json");
let triage_payload: serde_json::Value =
serde_json::from_slice(&triage_out.stdout).expect("triage JSON");
assert_eq!(
triage_payload["readiness"]["ingest_quarantine"]["quarantined_conversations"].as_u64(),
Some(1),
"triage readiness should now include ingest_quarantine: {triage_payload}"
);
// health (exit 0 for a single nonfatal quarantine) carries the same verdict.
let health_out = isolated_cass_cmd(test_home.path())
.args([
"health",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass health --json");
assert!(
health_out.status.success(),
"cass health --json should stay exit 0 for a single nonfatal quarantine: stdout={} stderr={}",
String::from_utf8_lossy(&health_out.stdout),
String::from_utf8_lossy(&health_out.stderr)
);
let health_payload: serde_json::Value =
serde_json::from_slice(&health_out.stdout).expect("health JSON");
assert_degraded(&health_payload, "health");
// search --robot-meta carries the compact verdict in its _meta header.
let search_out = isolated_cass_cmd(test_home.path())
.args([
"search",
"anything",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--robot",
"--robot-meta",
])
.output()
.expect("run cass search --robot --robot-meta");
let search_payload: serde_json::Value =
serde_json::from_slice(&search_out.stdout).expect("search JSON");
let search_meta = &search_payload["_meta"]["search_completeness"];
assert_eq!(
search_meta["quarantine_status"].as_str(),
Some("degraded"),
"search --robot-meta should carry the degraded completeness verdict: {search_payload}"
);
assert_eq!(search_meta["quarantined_conversations"].as_u64(), Some(1));
}
/// uojcg.3.3: with no quarantine, every readiness surface reports complete
/// coverage (`ok`), and search --robot-meta omits the verdict entirely so the
/// common payload is unchanged.
#[test]
fn readiness_surfaces_report_complete_search_coverage_without_quarantine() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
let index_out = isolated_cass_cmd(test_home.path())
.args([
"index",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
"--no-progress-events",
])
.output()
.expect("run cass index --json");
assert!(index_out.status.success());
for surface in ["status", "health", "triage"] {
let out = isolated_cass_cmd(test_home.path())
.args([
surface,
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass readiness surface --json");
let payload: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("parse readiness surface JSON");
let sc = &payload["search_completeness"];
assert_eq!(
sc["quarantine_status"].as_str(),
Some("ok"),
"{surface} should report complete coverage: {payload}"
);
assert_eq!(sc["complete"].as_bool(), Some(true), "{surface}: {payload}");
assert_eq!(
sc["quarantined_conversations"].as_u64(),
Some(0),
"{surface}: {payload}"
);
}
// No quarantine => search --robot-meta omits the verdict (no new bytes).
let search_out = isolated_cass_cmd(test_home.path())
.args([
"search",
"anything",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--robot",
"--robot-meta",
])
.output()
.expect("run cass search --robot --robot-meta");
let search_payload: serde_json::Value =
serde_json::from_slice(&search_out.stdout).expect("search JSON");
assert!(
search_payload["_meta"].get("search_completeness").is_none(),
"search --robot-meta should omit search_completeness when nothing is quarantined: {search_payload}"
);
}
/// uojcg.9.2/9.5: status/triage/doctor --json carry a root_cause attribution
/// (family/locus/confidence/evidence_refs/summary). On a clean, freshly-indexed
/// dir no signal implicates a family, so the attribution is the explicit
/// unknown/unknown record — the field is always present and well-shaped.
#[test]
fn status_triage_doctor_emit_root_cause_attribution() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
let index_out = isolated_cass_cmd(test_home.path())
.args([
"index",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
"--no-progress-events",
])
.output()
.expect("run cass index --json");
assert!(index_out.status.success());
let assert_root_cause = |rc: &serde_json::Value, surface: &str| {
assert!(
rc.get("schema_version")
.and_then(serde_json::Value::as_u64)
.is_some(),
"{surface} root_cause needs schema_version: {rc}"
);
assert_eq!(
rc["family"].as_str(),
Some("unknown"),
"{surface} clean dir => unknown family: {rc}"
);
assert_eq!(
rc["confidence"].as_str(),
Some("unknown"),
"{surface} clean dir => unknown confidence: {rc}"
);
assert!(
rc.get("locus")
.and_then(serde_json::Value::as_str)
.is_some()
);
assert!(
rc.get("evidence_refs")
.map(serde_json::Value::is_array)
.unwrap_or(false)
);
assert!(
rc.get("summary")
.and_then(serde_json::Value::as_str)
.is_some()
);
};
for surface in ["status", "triage"] {
let out = isolated_cass_cmd(test_home.path())
.args([
surface,
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass readiness surface --json");
let payload: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("parse readiness surface JSON");
assert_root_cause(&payload["root_cause"], surface);
}
let doctor_out = isolated_cass_cmd(test_home.path())
.args([
"doctor",
"check",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass doctor check --json");
let doctor_payload: serde_json::Value =
serde_json::from_slice(&doctor_out.stdout).expect("parse doctor JSON");
assert_root_cause(&doctor_payload["root_cause"], "doctor");
}
#[test]
fn status_and_health_json_escalate_recent_ingest_quarantine_bursts() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
let index_out = isolated_cass_cmd(test_home.path())
.args([
"index",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
"--no-progress-events",
])
.output()
.expect("run cass index --json");
assert!(
index_out.status.success(),
"cass index --json failed: stdout={} stderr={}",
String::from_utf8_lossy(&index_out.stdout),
String::from_utf8_lossy(&index_out.stderr)
);
write_ingest_quarantine_records(&data_dir, 3);
let status_out = isolated_cass_cmd(test_home.path())
.args([
"status",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.env("CASS_INGEST_QUARANTINE_CIRCUIT_LIMIT", "3")
.output()
.expect("run cass status --json");
assert!(
status_out.status.success(),
"cass status --json failed: stdout={} stderr={}",
String::from_utf8_lossy(&status_out.stdout),
String::from_utf8_lossy(&status_out.stderr)
);
let status_payload: serde_json::Value =
serde_json::from_slice(&status_out.stdout).expect("status JSON");
assert_eq!(status_payload["status"].as_str(), Some("unhealthy"));
assert_eq!(status_payload["healthy"].as_bool(), Some(false));
assert_eq!(status_payload["health_level"].as_str(), Some("critical"));
assert_eq!(
status_payload["ingest_quarantine"]["circuit_breaker_active"].as_bool(),
Some(true)
);
let health_out = isolated_cass_cmd(test_home.path())
.args([
"health",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.env("CASS_INGEST_QUARANTINE_CIRCUIT_LIMIT", "3")
.output()
.expect("run cass health --json");
assert!(
!health_out.status.success(),
"cass health --json should exit nonzero for critical ingest quarantine readiness: stdout={} stderr={}",
String::from_utf8_lossy(&health_out.stdout),
String::from_utf8_lossy(&health_out.stderr)
);
let health_payload: serde_json::Value =
serde_json::from_slice(&health_out.stdout).expect("health JSON");
assert_eq!(health_payload["status"].as_str(), Some("unhealthy"));
assert_eq!(health_payload["healthy"].as_bool(), Some(false));
assert_eq!(health_payload["health_level"].as_str(), Some("critical"));
assert!(
health_payload["errors"]
.as_array()
.is_some_and(|errors| errors.iter().any(|err| err
.as_str()
.is_some_and(|msg| msg.contains("quarantine circuit breaker")))),
"health should expose the circuit breaker as an error: {health_payload}"
);
}
#[test]
fn status_and_health_json_surface_remote_source_sync_summary_without_live_probe() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
write_remote_source_config(test_home.path());
let status_out = Command::new(assert_cmd::cargo::cargo_bin!("cass"))
.args([
"status",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.env("CODING_AGENT_SEARCH_NO_UPDATE_PROMPT", "1")
.env("XDG_DATA_HOME", test_home.path())
.env("XDG_CONFIG_HOME", test_home.path())
.env("HOME", test_home.path())
.output()
.expect("run cass status --json");
assert!(
status_out.status.success(),
"cass status --json failed: {}",
String::from_utf8_lossy(&status_out.stderr)
);
let status_payload: serde_json::Value =
serde_json::from_slice(&status_out.stdout).expect("status JSON");
let status_remote = &status_payload["remote_source_sync"];
assert_eq!(status_remote["checked"].as_bool(), Some(true));
assert_eq!(status_remote["archive_checked"].as_bool(), Some(false));
assert_eq!(
status_remote["live_remote_probe_attempted"].as_bool(),
Some(false)
);
assert_eq!(
status_remote["remote_source_state"].as_str(),
Some("local_mirror_gap")
);
assert_eq!(
status_remote["sync_staleness"].as_str(),
Some("never_synced")
);
assert_eq!(
status_remote["local_mirror_state"].as_str(),
Some("missing")
);
assert_eq!(
status_remote["configured_remote_source_count"].as_u64(),
Some(1)
);
assert_eq!(
status_remote["recommended_action"].as_str(),
Some("cass sources sync --all --json")
);
assert_eq!(
status_payload["doctor_summary"]["remote_source_sync"]["remote_source_state"].as_str(),
Some("local_mirror_gap")
);
let health_out = Command::new(assert_cmd::cargo::cargo_bin!("cass"))
.args([
"health",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.env("CODING_AGENT_SEARCH_NO_UPDATE_PROMPT", "1")
.env("XDG_DATA_HOME", test_home.path())
.env("XDG_CONFIG_HOME", test_home.path())
.env("HOME", test_home.path())
.output()
.expect("run cass health --json");
let health_payload: serde_json::Value =
serde_json::from_slice(&health_out.stdout).expect("health JSON");
assert_eq!(
health_payload["remote_source_sync"]["remote_source_state"].as_str(),
Some("local_mirror_gap")
);
assert_eq!(
health_payload["remote_source_sync"]["source"].as_str(),
Some("health-fast-local-config")
);
assert_eq!(
health_payload["remote_source_sync"]["live_remote_probe_attempted"].as_bool(),
Some(false)
);
assert_eq!(
health_payload["doctor_summary"]["remote_source_sync"]["recommended_action"].as_str(),
Some("cass sources sync --all --json")
);
}
#[test]
fn status_json_surfaces_runtime_queue_and_byte_budget_headroom() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
let _lock = seed_active_rebuild_runtime(&data_dir);
let out = Command::new(assert_cmd::cargo::cargo_bin!("cass"))
.args([
"status",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.env("CODING_AGENT_SEARCH_NO_UPDATE_PROMPT", "1")
.env("CASS_IGNORE_SOURCES_CONFIG", "1")
.env("CASS_TANTIVY_REBUILD_PIPELINE_CHANNEL_SIZE", "5")
.env("XDG_DATA_HOME", test_home.path())
.env("HOME", test_home.path())
.output()
.expect("run cass status --json");
assert!(
out.status.success(),
"cass status --json failed: {:?}",
out.status
);
let stdout = String::from_utf8(out.stdout).expect("utf8");
let payload: serde_json::Value = serde_json::from_str(&stdout).expect("valid JSON");
let runtime = &payload["rebuild"]["pipeline"]["runtime"];
let rebuild_progress = &payload["rebuild_progress"];
assert_eq!(runtime["queue_depth"].as_u64(), Some(3));
assert_eq!(runtime["queue_capacity"].as_u64(), Some(5));
assert_eq!(runtime["queue_headroom"].as_u64(), Some(2));
assert_eq!(runtime["inflight_message_bytes"].as_u64(), Some(65_536));
assert_eq!(
runtime["max_message_bytes_in_flight"].as_u64(),
Some(131_072)
);
assert_eq!(
runtime["inflight_message_bytes_headroom"].as_u64(),
Some(65_536)
);
assert_eq!(rebuild_progress["active"].as_bool(), Some(true));
assert_eq!(
rebuild_progress["processed_conversations"].as_u64(),
Some(4)
);
assert_eq!(rebuild_progress["total_conversations"].as_u64(), Some(10));
assert_eq!(
rebuild_progress["remaining_conversations"].as_u64(),
Some(6)
);
assert_eq!(rebuild_progress["completion_ratio"].as_f64(), Some(0.4));
assert_eq!(rebuild_progress["queue_depth"].as_u64(), Some(3));
assert_eq!(rebuild_progress["queue_capacity"].as_u64(), Some(5));
assert_eq!(rebuild_progress["queue_headroom"].as_u64(), Some(2));
assert_eq!(
rebuild_progress["inflight_message_bytes"].as_u64(),
Some(65_536)
);
assert_eq!(
rebuild_progress["inflight_message_bytes_headroom"].as_u64(),
Some(65_536)
);
assert_eq!(
rebuild_progress["controller_mode"].as_str(),
Some("pressure_limited")
);
assert_eq!(
rebuild_progress["controller_reason"].as_str(),
Some("queue_depth_3_reached_pipeline_capacity_3")
);
}
#[test]
fn status_json_surfaces_quarantine_gc_summary() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
let backups_dir = data_dir.join("backups");
fs::create_dir_all(&backups_dir).expect("create backups dir");
let failed_seed_root =
backups_dir.join("agent_search.db.20260423T120000.12345.deadbeef.failed-baseline-seed.bak");
fs::write(&failed_seed_root, b"seed-backup").expect("write failed seed bundle");
fs::write(
failed_seed_root.with_file_name(format!(
"{}-wal",
failed_seed_root
.file_name()
.and_then(|name| name.to_str())
.expect("file name")
)),
b"seed-wal",
)
.expect("write failed seed wal");
let index_path = expected_index_dir(&data_dir);
fs::create_dir_all(&index_path).expect("create expected index dir");
let retained_publish_dir = index_path
.parent()
.expect("index parent")
.join(".lexical-publish-backups");
fs::create_dir_all(&retained_publish_dir).expect("create retained publish dir");
let older_backup = retained_publish_dir.join("prior-live-older");
fs::create_dir_all(&older_backup).expect("create older retained backup");
fs::write(older_backup.join("segment-a"), b"retained-live-segment-old")
.expect("write older retained publish backup");
std::thread::sleep(Duration::from_millis(20));
let newer_backup = retained_publish_dir.join("prior-live-newer");
fs::create_dir_all(&newer_backup).expect("create newer retained backup");
fs::write(newer_backup.join("segment-b"), b"retained-live-segment-new")
.expect("write newer retained publish backup");
let generation_dir = index_path
.parent()
.expect("index parent")
.join("generation-quarantined");
write_quarantined_manifest(&generation_dir);
fs::write(
generation_dir.join("segment-a"),
b"quarantined-generation-bytes",
)
.expect("write quarantined generation artifact");
let out = Command::new(assert_cmd::cargo::cargo_bin!("cass"))
.args([
"status",
"--json",
"--data-dir",
data_dir.to_str().expect("utf8"),
])
.env("CODING_AGENT_SEARCH_NO_UPDATE_PROMPT", "1")
.env("CASS_IGNORE_SOURCES_CONFIG", "1")
.env("CASS_LEXICAL_PUBLISH_BACKUP_RETENTION", "1")
.env("XDG_DATA_HOME", test_home.path())
.env("XDG_CONFIG_HOME", test_home.path())
.env("HOME", test_home.path())
.output()
.expect("run cass status --json");
assert!(
out.status.success(),
"cass status --json failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let payload: serde_json::Value = serde_json::from_slice(&out.stdout).expect("valid JSON");
let quarantine = &payload["quarantine"];
let summary = &quarantine["summary"];
assert_eq!(summary["gc_eligible_asset_count"].as_u64(), Some(1));
assert!(
summary["gc_eligible_bytes"].as_u64().unwrap_or(0) > 0,
"one retained publish backup should fall outside the retention cap"
);
assert_eq!(summary["inspection_required_asset_count"].as_u64(), Some(3));
assert!(
summary["inspection_required_bytes"].as_u64().unwrap_or(0) > 0,
"failed seed bundles and quarantined lexical generation should remain inspection-only"
);
assert_eq!(
summary["retained_publish_backup_retention_limit"].as_u64(),
Some(1)
);
let failed_seed_entries = quarantine["failed_seed_bundle_files"]
.as_array()
.expect("status.quarantine must expose failed seed bundle entries");
assert_eq!(
failed_seed_entries.len(),
2,
"status.quarantine must preserve full failed seed bundle inventory, not only summary"
);
assert!(failed_seed_entries.iter().all(|entry| {
entry["path"]
.as_str()
.is_some_and(|path| path.contains(".failed-baseline-seed.bak"))
|| entry["path"].as_str().is_some_and(|path| {
path.contains(".failed-baseline-seed.bak-wal")
|| path.ends_with(".failed-baseline-seed.bak-wal")
})
}));
let retained_backups = quarantine["retained_publish_backups"]
.as_array()
.expect("status.quarantine must expose retained publish backups");
assert_eq!(
retained_backups.len(),
2,
"status.quarantine must preserve retained publish backup details"
);
assert!(
retained_backups
.iter()
.any(|entry| entry["safe_to_gc"].as_bool() == Some(true)),
"one retained publish backup should be GC-eligible outside the cap"
);
assert!(
retained_backups
.iter()
.any(|entry| entry["safe_to_gc"].as_bool() == Some(false)),
"one retained publish backup should remain protected by the cap"
);
let lexical_generations = quarantine["lexical_generations"]
.as_array()
.expect("status.quarantine must expose lexical generation inventory");
assert_eq!(lexical_generations.len(), 1);
assert_eq!(
lexical_generations[0]["generation_id"].as_str(),
Some("gen-quarantined")
);
assert_eq!(
lexical_generations[0]["publish_state"].as_str(),
Some("quarantined")
);
let inspection_artifacts = quarantine["quarantined_artifacts"]
.as_array()
.expect("status.quarantine must expose flattened quarantined artifacts");
assert!(
inspection_artifacts.iter().any(|entry| {
entry["artifact_kind"].as_str() == Some("lexical_generation")
&& entry["generation_id"].as_str() == Some("gen-quarantined")
}),
"status.quarantine must include the quarantined lexical generation artifact"
);
assert!(
inspection_artifacts.iter().any(|entry| {
entry["artifact_kind"].as_str() == Some("lexical_shard")
&& entry["generation_id"].as_str() == Some("gen-quarantined")
&& entry["shard_id"].as_str() == Some("shard-a")
}),
"status.quarantine must include the quarantined shard artifact"
);
}
#[test]
fn status_json_surfaces_lexical_generation_lifecycle_inventory() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
let index_path = expected_index_dir(&data_dir);
fs::create_dir_all(&index_path).expect("create expected index dir");
let generation_root = index_path.parent().expect("index parent");
write_generation_manifest(
&generation_root.join("generation-current"),
"gen-current",
"validated",
"published",
"published",
true,
false,
);
write_generation_manifest(
&generation_root.join("generation-staged"),
"gen-staged",
"built",
"staged",
"staged",
false,
true,
);
write_generation_manifest(
&generation_root.join("generation-failed"),
"gen-failed",
"failed",
"staged",
"abandoned",
false,
true,
);
write_generation_manifest(
&generation_root.join("generation-superseded"),
"gen-superseded",
"validated",
"superseded",
"published",
false,
true,
);
write_generation_manifest(
&generation_root.join("generation-quarantined"),
"gen-quarantined",
"failed",
"quarantined",
"quarantined",
false,
false,
);
write_generation_manifest(
&data_dir
.join("synced-sessions")
.join("remote-a")
.join("generation-decoy-outside-index"),
"gen-decoy-outside-index",
"validated",
"published",
"published",
false,
false,
);
let out = Command::new(assert_cmd::cargo::cargo_bin!("cass"))
.args([
"status",
"--json",
"--data-dir",
data_dir.to_str().expect("utf8"),
])
.env("CODING_AGENT_SEARCH_NO_UPDATE_PROMPT", "1")
.env("CASS_IGNORE_SOURCES_CONFIG", "1")
.env("XDG_DATA_HOME", test_home.path())
.env("XDG_CONFIG_HOME", test_home.path())
.env("HOME", test_home.path())
.output()
.expect("run cass status --json");
assert!(
out.status.success(),
"cass status --json failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let payload: serde_json::Value = serde_json::from_slice(&out.stdout).expect("valid JSON");
let summary = &payload["quarantine"]["summary"];
assert_eq!(summary["lexical_generation_count"].as_u64(), Some(5));
assert_eq!(
summary["lexical_generation_publish_state_counts"]["published"].as_u64(),
Some(1)
);
assert_eq!(
summary["lexical_generation_publish_state_counts"]["staged"].as_u64(),
Some(2)
);
assert_eq!(
summary["lexical_generation_publish_state_counts"]["superseded"].as_u64(),
Some(1)
);
assert_eq!(
summary["lexical_generation_publish_state_counts"]["quarantined"].as_u64(),
Some(1)
);
assert_eq!(
summary["lexical_generation_build_state_counts"]["validated"].as_u64(),
Some(2)
);
assert_eq!(
summary["lexical_generation_build_state_counts"]["built"].as_u64(),
Some(1)
);
assert_eq!(
summary["lexical_generation_build_state_counts"]["failed"].as_u64(),
Some(2)
);
assert_eq!(
summary["lexical_quarantined_generation_count"].as_u64(),
Some(1)
);
}
/// WS-B.4a (q2vn9): `status --json` and `health --json` must expose the
/// archive's physical footprint from metadata alone, so an untruncated WAL is
/// visible without a shell. Positive observable: `database.wal_bytes` equals
/// the sidecar's actual length, including after the sidecar is grown (sparse,
/// so the test costs no disk). Planted negative: a data dir with no archive
/// reports `db_bytes` 0, `wal_bytes` 0 and `shm_present` false instead of
/// omitting the keys. No-claim: nothing here proves the values are cheap to
/// compute on a large archive; the observation surfaces read metadata only
/// by construction.
#[test]
fn status_and_health_report_archive_footprint_from_metadata() {
let test_home = tempfile::tempdir().expect("tempdir");
let data_dir = test_home.path().join("cass-data");
fs::create_dir_all(&data_dir).expect("create data dir");
let index_out = isolated_cass_cmd(test_home.path())
.args([
"index",
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
"--no-progress-events",
])
.output()
.expect("run cass index --json");
assert!(
index_out.status.success(),
"cass index --json failed: stdout={} stderr={}",
String::from_utf8_lossy(&index_out.stdout),
String::from_utf8_lossy(&index_out.stderr)
);
let db_path = data_dir.join("agent_search.db");
let wal_path = data_dir.join("agent_search.db-wal");
let footprint = |surface: &str| -> serde_json::Value {
let out = isolated_cass_cmd(test_home.path())
.args([
surface,
"--data-dir",
data_dir.to_str().expect("utf8"),
"--json",
])
.output()
.unwrap_or_else(|err| panic!("run cass {surface} --json: {err}"));
let payload: serde_json::Value =
serde_json::from_slice(&out.stdout).unwrap_or_else(|err| {
panic!(
"{surface} json: {err}\nstdout={}\nstderr={}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
)
});
// `status` carries the database block at the top level; `health`
// nests the same state meta under `state`.
if surface == "health" {
payload["state"]["database"].clone()
} else {
payload["database"].clone()
}
};
let db_len = fs::metadata(&db_path).expect("db metadata").len();
let wal_len = fs::metadata(&wal_path).map_or(0, |meta| meta.len());
for surface in ["status", "health"] {
let database = footprint(surface);
assert_eq!(
database["db_bytes"].as_u64(),
Some(db_len),
"{surface}: {database}"
);
assert_eq!(
database["wal_bytes"].as_u64(),
Some(wal_len),
"{surface}: {database}"
);
assert!(
database["shm_present"].is_boolean(),
"{surface}: {database}"
);
}
// Grow the sidecar past anything the engine would leave behind; a sparse
// tail of zeros is invalid WAL content the engine ignores, but it is the
// size an operator would see with `ls -l`.
let grown: u64 = 48 * 1024 * 1024;
let wal = fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&wal_path)
.expect("open wal sidecar");
wal.set_len(grown).expect("grow wal sidecar");
drop(wal);
let database = footprint("status");
assert_eq!(database["wal_bytes"].as_u64(), Some(grown), "{database}");
assert_eq!(
fs::metadata(&wal_path).expect("wal metadata").len(),
grown,
"status must not checkpoint or truncate the sidecar"
);
// Planted negative: no archive at all still reports the keys, as zeros.
let empty_dir = test_home.path().join("empty-data");
fs::create_dir_all(&empty_dir).expect("create empty data dir");
let out = isolated_cass_cmd(test_home.path())
.args([
"status",
"--data-dir",
empty_dir.to_str().expect("utf8"),
"--json",
])
.output()
.expect("run cass status --json on an empty data dir");
let payload: serde_json::Value =
serde_json::from_slice(&out.stdout).expect("status json on empty data dir");
assert_eq!(
payload["database"]["exists"].as_bool(),
Some(false),
"{payload}"
);
assert_eq!(
payload["database"]["db_bytes"].as_u64(),
Some(0),
"{payload}"
);
assert_eq!(
payload["database"]["wal_bytes"].as_u64(),
Some(0),
"{payload}"
);
assert_eq!(
payload["database"]["shm_present"].as_bool(),
Some(false),
"{payload}"
);
}