This lands the long-running "doctor v2" effort (tracked in docs/planning/DOCTOR_V2_RELEASE_CHECKLIST.md). It reshapes `cass doctor` from a flat "check + auto-fix" command into a typed surface of bounded operations whose recovery model is **archive-first**: cass-owned evidence (archives, raw-session mirrors, backups, receipts, source ledgers) is preserved before any repair runs, and every mutating action goes through a plan-fingerprint candidate flow that the operator must inspect and approve. ==== New / changed doctor surfaces (`src/doctor.rs`) ==== DoctorCommandSurface now enumerates: Diagnose, Check, Repair, Cleanup, **ArchiveScan**, **ArchiveNormalize**, Backups, Reconstruct, Restore, SafeAutoFix, Init. DoctorExecutionMode adds the corresponding `*DryRun` / `*Apply` pairs for ArchiveNormalize and tightens the existing Cleanup/Repair modes around plan-fingerprint matching. DoctorCommandRequest grows `archive_scan` and `archive_normalize` flags and the plumbing to keep the typed surface exhaustive (compile-time match coverage on every variant). Repair, restore, archive-normalize, and cleanup all emit `operation_outcome` envelopes and machine-readable receipts; the SafeAutoFix path is preserved as the legacy escape hatch for low-risk derived-only repairs (Tantivy index rebuild, stale lock removal) and now fails closed if it detects archive- or source-affecting risk. ==== Source / indexer / storage hardening (`src/sources/*`, `src/indexer/*`, `src/storage/sqlite.rs`) ==== - `src/sources/sync.rs` — `remote_spec_for_scp`, `parse_remote_home_stdout`, and friends so the SSH/SCP source-mirror path quotes remote operands consistently and rejects ambiguous remote-home outputs (more than one candidate path → refuse instead of guessing). Closes the #raw-mirror scan-root capture-before-parse hole that5c1dba9e/5be8a6e9first surfaced. - `src/sources/config.rs`, `index.rs`, `install.rs`, `probe.rs`, `setup.rs` — typed source-provider lifecycle: discovery, probe, install, setup, sync now share a single error vocabulary the doctor surface understands so its candidate plans can describe both what they will do and which source the action is rooted at. - `src/indexer/mod.rs`, `src/indexer/refresh_ledger.rs` — small fixes to bind refresh ledgers to source-authority decisions. - `src/storage/sqlite.rs` — extra integrity probes called by `doctor check` before any cleanup or archive operation runs. ==== UI / HUD / CLI surface ==== - `src/ui/app.rs` — new `DoctorHudSummary` (parsed from the unified doctor runtime summary) drives a HUD lane that surfaces archive coverage, source-mirror state, fallback mode, sole-copy warnings, active repair and index-maintenance, and the recommended next action. Operators no longer have to leave the TUI to know whether running a repair is safe. - `src/lib.rs` — large surface change: every CLI subcommand that touched doctor surfaces is rewired through the bounded request/response types, the new archive-scan / archive-normalize subcommands are exposed, the receipts and plan-fingerprint payloads are emitted on stdout (json/jsonl) with stderr reserved for fatal envelopes, and the `--robot` mode learns the new doctor JSON shape (see golden updates). - `src/update_check.rs` — version-check now feeds into the doctor HUD as one of the recommended-action sources (an outdated cass that knows about archive issues should still nudge the operator to upgrade first). ==== Tests + golden snapshots ==== - `tests/cli_doctor.rs` (+2086 lines) — behavioral coverage for every new subcommand, plan-fingerprint mismatch handling, dry-run/apply parity, archive-coverage gating of mutating ops, and the legacy `cass doctor` alias mapping into the v2 surface. - `tests/doctor_e2e_runner.rs`, `tests/util/doctor_e2e_runner.rs`, `tests/util/doctor_fixture.rs`, `tests/doctor_fixture_factory.rs` — end-to-end doctor harness updates: synthesizes archive trees, raw-session mirrors, partial backups, and corrupt SQLite headers; asserts that archive-normalize and repair never escape into source / archive paths. - `tests/doctor_release_checklist.rs` (new) — release-gate test that cross-checks the runbook against the implemented surfaces (every entry in DOCTOR_V2_RELEASE_CHECKLIST.md must map to a known DoctorCommandSurface variant; new variants must add a checklist entry). - `tests/cli_robot.rs`, `tests/cli_status.rs`, `tests/cli_dispatch_coverage.rs`, `tests/e2e_health.rs` — smaller dispatch-coverage / status / robot surface-area follow-ups required by the new doctor shape. - `tests/golden_robot_docs.rs`, `tests/golden_robot_json.rs` — assertion helpers for the regenerated robot JSON / docs goldens. - `tests/golden/robot/*.json.golden` and `tests/golden/robot_docs/*.txt.golden` — regenerated for the new shapes; `introspect.json.golden` and `introspect_shape.json.golden` carry the bulk of the diff because they enumerate the full doctor surface + argument schema. ==== Docs + scripts ==== - `docs/planning/RECOVERY_RUNBOOK.md` — operator runbook (referenced from the README) describing the archive-first model, plan-fingerprint flow, source-pruning warnings, sole-copy prompts, and the support-bundle handoff. - `docs/planning/DOCTOR_V2_RELEASE_CHECKLIST.md` (new) — release gate; the new checklist test consumes this file. - `README.md` — replaces the old single-doctor table with the v2 surface table and updated examples (`cass doctor check --json`, `cass doctor repair --plan-fingerprint`, etc.). - `CHANGELOG.md` — Unreleased entry covering the archive-first documentation and the doctor migration guidance for existing data dirs. - `scripts/e2e/doctor_v2.sh`, `scripts/bakeoff/cass_validation_e2e.sh` — scripted runners for the doctor v2 surface; consumed by the e2e harness and the cass bakeoff campaign. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
Archive-First Doctor Recovery Runbook
This runbook documents the operator workflow for cass doctor after the
archive-first doctor v2 work. The core rule is preservation before repair:
cass treats the SQLite archive, raw mirror metadata, raw mirror blobs, backup
bundles, source ledgers, receipts, failure contexts, and support-bundle manifests
as evidence. Derived lexical, semantic, and report assets may be rebuilt through
fingerprinted plans, but recovery must not discard the only remaining copy of a
coding-agent session.
Use robot-safe commands for automation. Never run bare cass from an agent or
script because it launches the interactive TUI.
Mental Model
- SQLite is the archive of record once a conversation is indexed.
- Provider session logs under harness-owned locations such as
~/.codex,~/.claude, Cursor, Gemini, OpenCode, and remote sources are upstream inputs, not guaranteed long-term backups. Those harnesses may prune old session logs. - The raw-session mirror is cass-owned evidence captured before parsing. It is content-addressed, path-safe, privacy-aware, and verified by hashes.
- Lexical and semantic indexes are derived assets. Rebuild them when the doctor plan says so; do not treat an index rebuild as proof that archived sessions are recoverable from upstream logs.
- Repairs build candidates first, compare coverage, verify integrity, and only promote a candidate when the approved plan proves equal-or-better archive coverage.
What Doctor Must Never Delete
Doctor workflows must preserve:
- canonical archive databases such as
agent_search.dband required sidecars - raw mirror blobs, manifests, provenance ledgers, and source coverage ledgers
- backup bundles, restore rehearsal receipts, promotion receipts, and rollback references
- bookmarks, TUI state, source configuration, remote mirror metadata, and user configs
- provider source logs and private raw sessions unless an operator explicitly opts into a sensitive support artifact
Do not hand-remove cass data directories, index directories, raw mirrors, backup bundles, WAL/SHM sidecars, or provider session trees as a repair step. If disk pressure is the problem, run the storage-pressure and archive export flows below and inspect their fingerprints before any mutation.
First Response Checklist
-
Capture the read-only truth surface:
cass doctor check --json -
Branch on JSON fields, not prose:
statusrisk_levelrecommended_actionoperation_outcome.kindoperation_outcome.exit_code_kindcoverage_risk.statussource_authority.authority_levelraw_mirror.statusremote_source_sync.statusstorage_pressure.statusrepair_failure_marker.status
-
If the command reports active locks, wait or retry later. Do not remove lock files by hand.
-
If the command reports sole-copy or source-pruned risk, preserve the cass archive first. Do not run source-only rebuild recipes.
-
If support is needed, collect the support handoff bundle near the end of this runbook.
Source Pruning and Sole-Copy Warnings
Run the archive scan when the question is "does cass still have enough evidence if upstream logs disappeared?":
cass doctor archive-scan --json
Important fields:
source_inventoryinventories current provider paths and FAD-backed sources.coverage_summarycompares archived rows, current sources, raw mirror links, and legacy DB-only rows.sole_copy_warningsidentifies conversations where cass may be the only remaining archival copy.source_authorityexplains whether the live source, raw mirror, verified backup, or current DB is the authority for a repair.remote_source_syncclassifies remote gaps such as unavailable hosts, pruned upstream paths, local archive ahead of remote, and verified remote copies.
Treat missing upstream files as a preservation warning, not proof that the cass
archive is bad. If sole_copy_warnings is non-empty, take an archive export or
backup verification path before any repair that could replace canonical state.
Low-Risk Auto-Run
The legacy command remains available for low-risk derived repairs:
cass doctor --fix --json
For a derived refresh request:
cass doctor --fix --force-rebuild --json
Safe auto-run is intentionally narrow. It can apply only predeclared safe actions and must emit receipts for every mutation. It must fail closed when archive coverage, source authority, prior repair failure markers, or storage-pressure evidence make the action unsafe.
Fingerprinted Repair Flow
Use explicit repair for candidate-based archive work.
-
Generate a read-only plan:
cass doctor repair --dry-run --json -
Inspect:
repair_plan.plan_fingerprintrepair_plan.apply_commandcandidate_stagingcoverage_summarysource_authoritysafety_gatesforensic_bundle.artifact_manifest_path
-
Apply only the exact inspected fingerprint:
cass doctor repair --yes --plan-fingerprint <plan_fingerprint> --json -
Confirm:
operation_outcome.kindis an applied or no-op outcome, not blockedpost_repair_probesshow successful read/write verificationreceipt.pathorreceipts[]existscoverage_summarydid not shrink- rollback or restore guidance is present if promotion failed
If repair_failure_marker.status shows a previous failed repair, do not loop
blindly. Re-run the dry-run and use --allow-repeated-repair only when the
reported failure marker is part of the plan you inspected.
Reconstruct Candidates
Reconstruction builds an isolated candidate from verified authority, such as the raw mirror or a verified backup. It must not mutate the live archive while the candidate is being built.
Current operator entry point:
cass doctor repair --dry-run --json
Inspect candidate_staging and source_authority. When a completed candidate is
eligible for promotion, the repair dry-run emits the fingerprinted apply command.
Promotion must still pass non-decreasing coverage, integrity checks, and
post-repair probes.
The doctor-reconstruct-dry-run schema in cass introspect --json documents the
candidate contract for automation. Treat any example path in documentation as
illustrative unless it comes from an actual scripts/e2e/doctor_v2.sh run
artifact.
Backups, Restore Rehearsal, and Restore Apply
List backups before trusting a restore target:
cass doctor backups list --json
Verify a specific backup:
cass doctor backups verify <backup_id> --json
Run the restore rehearsal first. This is the default no-mutation restore mode:
cass doctor backups restore <backup_id> --json
Inspect restore_plan.plan_fingerprint, restore_rehearsal.status, manifest
hashes, sidecar completeness, and the rehearsal receipt. Apply only the matching
fingerprint:
cass doctor backups restore <backup_id> --yes --plan-fingerprint <plan_fingerprint> --json
Restore apply must capture a pre-restore backup, build a candidate, verify the candidate, promote atomically, and emit a restore receipt. If any verification fails, stop and keep all artifacts for inspection.
Cleanup and Archive Normalize
Cleanup is for derived or explicitly reclaimable assets, not archive evidence.
cass doctor cleanup --json
cass doctor cleanup --yes --plan-fingerprint <plan_fingerprint> --json
Archive normalize may add metadata annotations for hygiene findings. It must not rewrite raw session blobs or canonical archive rows.
cass doctor archive-normalize --dry-run --json
cass doctor archive-normalize --yes --plan-fingerprint <plan_fingerprint> --json
If either command routes a finding to repair, reconstruct, or restore, leave it out of cleanup and follow the higher-authority workflow.
Storage Pressure and Archive Export
Use archive export or relocation planning when the archive is precious and the current filesystem is under pressure.
Plan export:
cass doctor archive export /absolute/target/cass-archive-export --json
Apply export:
cass doctor archive export /absolute/target/cass-archive-export --yes --plan-fingerprint <plan_fingerprint> --json
Verify a copied bundle:
cass doctor archive export verify /absolute/target/cass-archive-export --json
For relocation planning, use:
cass doctor archive relocate /absolute/target/cass-archive --json
Important fields:
archive_export_plan.plan_fingerprintrequired_bytescopied_bytesverified_asset_classesskipped_asset_classesprivacy_modecompressionencryptionconfig_update_statusold_archive_retainedwill_delete_old_archivereceiptsevent_log_pathverify_status
The old archive is retained. If will_delete_old_archive is ever true, stop and
audit the implementation before proceeding.
Diagnostic Baselines
Save a baseline before risky investigation or after a known-good state:
cass doctor baseline save --json
Diff a later state against it:
cass doctor baseline diff <baseline_id> --json
Update a baseline only when the new state is intentionally the new known-good reference:
cass doctor baseline update <baseline_id> --json
Baseline outputs are diagnostic-only. They should include artifact manifests and redacted paths; they should not mutate archive evidence.
Support Bundle Handoff
Create a scrubbed support bundle:
cass doctor support-bundle --json
Verify the manifest before sending or attaching it:
cass doctor support-bundle verify <bundle_or_manifest_path> --json
Default bundles are redacted diagnostic handoffs, not backups. They exclude raw session content, raw mirror blobs, the full SQLite archive, encrypted payloads, environment secrets, private source snippets, and full home-directory paths.
Support checklist:
cass doctor check --jsonoutput- latest
failure_context.json, if present - support bundle
manifest.json artifact_manifest_pathvalues referenced by doctor outputsevent_log_pathvalues referenced by doctor outputs- baseline diff JSON, if a baseline exists
- backup verification JSON, if restore is being discussed
- exact command line and exit code for the failing command
- no raw session logs, no full SQLite archive copy, and no private source files unless the user explicitly opts into sensitive evidence attachment
Troubleshooting Recipes
Lock Contention
Symptom: err.kind is lock-busy, or operation_state reports an active
doctor, index, or watch owner.
Action: wait for the owner, retry cass doctor check --json, and attach lock
diagnostics if it stays busy. Do not remove lock files by hand.
Storage Pressure
Symptom: storage_pressure.status is degraded or
recommended_action mentions archive export, relocation, or cleanup.
Action: run archive export planning first, verify the target has enough space, then run cleanup only for derived/reclaimable assets with a matching fingerprint.
Missing Semantic Models
Symptom: fallback_mode is lexical, semantic model fields report absent
models, or search remains lexical-only.
Action: this is usually not an archive repair issue. Run:
cass models status --json
Install models only when the operator consents:
cass models install --json
Remote Sync Gaps
Symptom: remote_source_sync.status or source_inventory reports unavailable
hosts, pruned paths, or local archive ahead of remote.
Action: preserve local cass evidence, then run:
cass sources sync --all --json
If the remote harness pruned logs, do not assume a local archive row is invalid.
Use source_authority and coverage_summary.
Failed Post-Repair Probes
Symptom: repair apply wrote a receipt but post_repair_probes reports failed
read/write checks.
Action: stop. Collect the receipt, failure context, candidate manifest, pre-mutation backup manifest, and event log. Use backup verify/restore rehearsal before any further apply.
Repeated Repair Markers
Symptom: repair_failure_marker.status reports a previous failed repair.
Action: do not re-run apply with the old fingerprint. Re-run the dry-run, inspect
the marker, and use --allow-repeated-repair only when the new plan explicitly
accounts for the marker.
Support Bundle Verification Fails
Symptom: support bundle verify reports missing_artifact,
checksum_mismatch, extra_file, or unsafe_path.
Action: regenerate the bundle from the same cass data directory and verify the new manifest. Do not edit manifests by hand.
E2E Evidence
The doctor v2 scripted runner is the source for reproducible journey artifacts:
scripts/e2e/doctor_v2.sh list --json
scripts/e2e/doctor_v2.sh describe <scenario_id> --json
scripts/e2e/doctor_v2.sh run <scenario_id> --json --artifact-dir /tmp/cass-doctor-e2e
Each run writes an artifact directory with run-summary.json, command stdout and
stderr, JSON snapshots, file-tree diffs, checksums, receipts, failure contexts,
and rerun commands. Any artifact path shown in docs should either come from one
of those fixture runs or be clearly marked illustrative.
Stop Conditions
Stop and inspect manually when:
- a plan would reduce coverage
source_authorityis ambiguous- sole-copy warnings are present and no verified export or backup exists
- a target path is inside a source path, symlinked unexpectedly, or not absolute
- a plan fingerprint does not match the inspected dry-run
- restore rehearsal fails
- post-repair probes fail
- support-bundle verification fails
- a command suggests deleting, hand-removing, or overwriting archive evidence
The safe default is to keep every artifact and gather a support bundle.