Skip to content

feat(health): vouch fsck — deep consistency checks beyond doctor#112

Merged
plind-junior merged 8 commits into
vouchdev:testfrom
Khaostica:feat/vouch-fsck
Jun 2, 2026
Merged

feat(health): vouch fsck — deep consistency checks beyond doctor#112
plind-junior merged 8 commits into
vouchdev:testfrom
Khaostica:feat/vouch-fsck

Conversation

@Khaostica

@Khaostica Khaostica commented May 26, 2026

Copy link
Copy Markdown
Contributor

What changed

Adds vouch fsck, a deep consistency check that goes beyond vouch doctor. Each finding is reported with the affected object ids; the command exits non-zero on any error-severity finding.

The new checks are:

  • Orphaned embeddings — rows in embeddings (legacy) or embedding_index for kind/id pairs whose underlying artifact file no longer exists on disk.
  • Dangling supersede / contradict chainsclaim.supersedes, claim.superseded_by, and claim.contradicts references pointing at missing claims; asymmetric contradicts edges (A says it contradicts B but B does not contradict A).
  • Decided proposal ↔ artifact mismatches — approved proposals in decided/ whose target artifact (claim / page / entity / relation) is no longer on disk.
  • Index-vs-file drift — FTS5 rows for missing artifacts (index_orphan_*), durable artifacts with no FTS5 row (index_missing_row), and claims_fts.status drift against the on-disk claim.status (the failure shape from bug: kb.context returns archived/superseded claims; lifecycle mutations never re-index FTS5 status #78).

When state.db is absent, fsck emits an info-severity index_missing finding (same convention as doctor) and the report stays ok=True.

Closes #96

Why

doctor / lint are intentionally surface-level — they catch broken citations, stale claims, and dangling relations. Recent bugs (#78 archived-claim leakage; #80 missing bundle members) were each symptoms of a missing systematic integrity pass. fsck consolidates the deeper checks into one command that an operator can run to ask the KB "are your derived caches and lifecycle chains internally consistent?"

What might break

Additive, CLI + library only. No .vouch/ files move, no persisted shapes change, no kb.* methods change, no bundle format change, no audit-log shape change. Existing .vouch/ directories work unchanged.

VEP

Not required. No change to the object model, kb.* method surface, on-disk layout, bundle format, or audit-log shape — this only adds a read-only CLI command and a health.fsck() library function.

Tests

  • python -m ruff check src tests passes
  • python -m pytest tests/test_health.py tests/test_cli.py — 27 passed
  • Full python -m pytest — only the pre-existing Windows-only O_NOFOLLOW failures remain (unrelated to this change)
  • New behaviour has tests in tests/test_health.py (10 cases, one per finding code + clean-KB + no-state.db) and a CLI smoke test in tests/test_cli.py
  • CHANGELOG.md updated under ## [Unreleased]
  • README.md cheatsheet updated

Summary by CodeRabbit

  • New Features

    • Added a new vouch fsck CLI command for deep, read-only consistency checks; prints severity-marked findings (with affected object IDs) or "clean", and returns non-zero when issues are found.
  • Documentation

    • Updated README and changelog to document the new fsck command.
    • Added a comprehensive PROJECT-INDEX documenting architecture, storage layout, CLI reference, and workflows.
  • Tests

    • Added regression tests covering clean KBs, missing index DB, dangling relations, missing decided artifacts, index-vs-disk drift, and orphan embeddings.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8612a68a-565b-4c9a-8238-f1a4ed36c129

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new vouch fsck command performs deep read-only consistency checks on KB artifacts: lifecycle-chain validation, decided-proposal artifact verification, FTS index-vs-disk drift detection, and orphaned embeddings. CLI, docs, and tests were added for all check categories and the absent-index scenario.

Changes

vouch fsck feature

Layer / File(s) Summary
Documentation, README, and CLI wiring
CHANGELOG.md, README.md, docs/PROJECT-INDEX.md, src/vouch/cli.py, tests/test_cli.py
Documents the fsck command and adds the CLI handler that calls health.fsck(store), prints per-finding lines or clean, and exits 0 when report.ok is true (1 otherwise). CLI tests verify index-missing info and dangling-chain failure.
Health checks implementation
src/vouch/health.py
Implements fsck(store) and helpers: _check_lifecycle_chains() (dangling/asymmetric supersedes/contradicts), _check_decided_proposals() (approved proposals reference existing artifacts), _check_index_drift() (FTS orphan/missing rows and claim status drift), and _check_orphan_embeddings() (embeddings pointing to missing artifacts). Expands model imports used by these checks.
Health unit tests and index helper
tests/test_health.py
Adds index_db helper to write FTS rows and a suite of tests covering clean KB, dangling supersedes/superseded_by/contradicts, asymmetric contradicts, decided-missing-artifact, index orphan/missing/status-drift, orphan embeddings, and absent state.db info reporting.

Sequence Diagram

sequenceDiagram
  participant CLI as vouch CLI
  participant Health as health.fsck
  participant IndexDB as state.db (FTS)
  participant FS as on-disk artifacts
  participant Report as HealthReport
  CLI->>Health: invoke fsck(store)
  Health->>FS: scan claims/pages/entities on disk
  Health->>IndexDB: query FTS tables and embedding tables
  Health->>Health: run lifecycle, decided, index-drift, embedding checks
  Health->>Report: emit findings and ok flag
  Health-->>CLI: return report
  CLI->>Report: print findings / "clean" and exit(code)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Poem

🐰 I hop through claims and index rows,
I sniff out ghosts where error grows.
No fixes here — I only show,
The paths where artifacts don't go.
A tidy KB when I say so.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: a new vouch fsck command for deeper consistency checks beyond the existing doctor command.
Linked Issues check ✅ Passed All acceptance criteria from #96 are met: orphaned embeddings, dangling chains, decided-proposal mismatches, and index-vs-file drift detection with object IDs are implemented; --fix is correctly kept out of scope.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing vouch fsck: CLI command, health.fsck() function, tests, and documentation updates. No unrelated changes to object models, APIs, or storage layouts.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 146: The README added the new CLI command "vouch fsck" but the "What
ships today" CLI inventory list still omits it; update that second CLI list to
include "vouch fsck" (with the same short description "deep consistency:
indexes, lifecycle, decided" used on Line 146) so both lists match—edit the
README section titled "What ships today" and add the "vouch fsck" entry to the
CLI inventory.

In `@src/vouch/cli.py`:
- Around line 195-197: The CLI output loop over report.findings currently prints
only the severity marker, f.code and f.message; update it to also include the
finding's affected object IDs (Finding.object_ids) so users see which objects
are impacted. In the block iterating report.findings (where f.severity, f.code
and f.message are used), append a formatted representation of f.object_ids (e.g.
join the list or fallback to a compact repr when empty) to the click.echo string
only when object_ids is non-empty, ensuring the printed line becomes something
like "<marker> [code] message (objects: id1, id2)".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f009561f-c95e-4661-a74c-4f5ff4e8272e

📥 Commits

Reviewing files that changed from the base of the PR and between 3beb821 and 5f3045b.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • README.md
  • src/vouch/cli.py
  • src/vouch/health.py
  • tests/test_cli.py
  • tests/test_health.py

Comment thread README.md
Comment thread src/vouch/cli.py Outdated
Khaostica pushed a commit to Khaostica/vouch that referenced this pull request May 26, 2026
Addresses review comments on vouchdev#112:
- CLI fsck output now appends `(objects: id1, id2)` when a finding has
  structured object_ids, so users can grep / pipe straight to inspection.
- The 'What ships today' CLI inventory in README.md now lists `fsck`
  alongside `doctor`, matching the cheatsheet earlier in the file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/test_health.py (2)

91-99: ⚡ Quick win

Missing report.ok is False assertion for consistency.

Unlike test_fsck_flags_dangling_supersedes (line 88), this test doesn't verify report.ok is False. Since dangling_superseded_by is an error-severity finding, the assertion would improve test coverage consistency.

Suggested fix
 def test_fsck_flags_dangling_superseded_by(store: KBStore) -> None:
     """`claim.superseded_by` pointing at a missing claim is an error."""
     src = store.put_source(b"e")
     store.put_claim(Claim(id="c1", text="t", evidence=[src.id],
                           superseded_by="ghost"))
     report = health.fsck(store)
     codes = {f.code for f in report.findings}
     assert "dangling_superseded_by" in codes
+    assert report.ok is False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_health.py` around lines 91 - 99, Add an assertion in
test_fsck_flags_dangling_superseded_by to check that report.ok is False
(mirroring test_fsck_flags_dangling_supersedes); locate the test function
test_fsck_flags_dangling_superseded_by and after computing report =
health.fsck(store) add assert report.ok is False so error-severity finding
"dangling_superseded_by" fails the overall report status.

101-109: ⚡ Quick win

Missing report.ok is False assertion.

Same as above — dangling_contradicts is error-severity so the test should verify the report fails.

Suggested fix
 def test_fsck_flags_dangling_contradicts(store: KBStore) -> None:
     """`claim.contradicts` pointing at a missing claim is an error."""
     src = store.put_source(b"e")
     store.put_claim(Claim(id="c1", text="t", evidence=[src.id],
                           contradicts=["ghost"]))
     report = health.fsck(store)
     codes = {f.code for f in report.findings}
     assert "dangling_contradicts" in codes
+    assert report.ok is False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_health.py` around lines 101 - 109, The test
test_fsck_flags_dangling_contradicts is missing an assertion that the
health.fsck report is failing; add an assertion that report.ok is False (e.g.,
assert report.ok is False) in that test after calling report =
health.fsck(store) so the test verifies that the dangling_contradicts finding is
treated as an error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_health.py`:
- Around line 91-99: Add an assertion in test_fsck_flags_dangling_superseded_by
to check that report.ok is False (mirroring
test_fsck_flags_dangling_supersedes); locate the test function
test_fsck_flags_dangling_superseded_by and after computing report =
health.fsck(store) add assert report.ok is False so error-severity finding
"dangling_superseded_by" fails the overall report status.
- Around line 101-109: The test test_fsck_flags_dangling_contradicts is missing
an assertion that the health.fsck report is failing; add an assertion that
report.ok is False (e.g., assert report.ok is False) in that test after calling
report = health.fsck(store) so the test verifies that the dangling_contradicts
finding is treated as an error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0badeed7-079e-4d2e-ae07-41bf6ed65f59

📥 Commits

Reviewing files that changed from the base of the PR and between 3d400e7 and 3093023.

📒 Files selected for processing (3)
  • src/vouch/health.py
  • tests/test_cli.py
  • tests/test_health.py

@plind-junior

Copy link
Copy Markdown
Collaborator

PR should target test branch

Johnb1109 and others added 4 commits May 28, 2026 09:07
Adds a `vouch fsck` command that runs deeper integrity checks than
`vouch doctor`:

- orphaned embeddings (rows in `embeddings` / `embedding_index` for
  artifacts that no longer exist on disk)
- dangling supersede / contradict chains (claim.supersedes,
  superseded_by, contradicts pointing at missing claims; asymmetric
  contradicts edges)
- decided proposals whose approved artifact is missing on disk
- FTS5 index drift: orphan rows, missing rows, and the
  `claims_fts.status` drift shape from vouchdev#78

Read-only; reports findings with object ids. `--fix` is intentionally
out of scope for v1 per the acceptance criteria.

Closes vouchdev#96

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses review comments on vouchdev#112:
- CLI fsck output now appends `(objects: id1, id2)` when a finding has
  structured object_ids, so users can grep / pipe straight to inspection.
- The 'What ships today' CLI inventory in README.md now lists `fsck`
  alongside `doctor`, matching the cheatsheet earlier in the file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CodeRabbit's pre-merge docstring-coverage check was at 23% for this
change set. Add concise docstrings to:

- the four `_check_*` helpers in `health.py` — each one names *why* the
  check exists (the failure mode it catches), not just what it does
- the `_index_claim` test helper and the eleven new `test_fsck_*` tests
  — one-line summaries of what each test asserts

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…radicts}

Both tests checked the finding code was emitted but didn't verify the
error-severity finding actually flipped the report status. Mirrors the
sibling `test_fsck_flags_dangling_supersedes`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Khaostica Khaostica changed the base branch from main to test May 28, 2026 13:08
@plind-junior

Copy link
Copy Markdown
Collaborator

One thing worth tightening: fsck opens with store.list_claims(), which is strict — a single invalid YAML (say, a legacy uncited claim from before #82) will raise and the whole check aborts before reporting anything. That's the exact case a deep checker should surface. lint() already has _load_claims_for_lint for this — could fsck reuse it so a bad YAML becomes an invalid_claim finding instead of a traceback?

Non-blocking: most of the +422 is the same shape repeated across claims/pages/entities. A little helper that takes (kind, indexed_ids, on_disk_ids) and emits the orphan + missing-row findings would collapse the three near-identical blocks in _check_index_drift, drop ~30 lines, and make adding a kind a one-liner.

Khaostica added 2 commits June 1, 2026 10:44
Address review on vouchdev#112.

- fsck() now loads claims via _load_claims_for_lint() like lint(), so a
  single invalid YAML (e.g. a legacy uncited claim from before vouchdev#81)
  becomes an `invalid_claim` finding instead of aborting the whole check
  with a traceback — the exact inconsistency a deep checker should
  surface. Counts are built from the safely-loaded claims via a shared
  _safe_counts() helper so the report can't re-trip the strict loader.

- Add _drift_findings(kind, indexed_ids, on_disk_ids, findings) to emit
  the orphan + missing-row findings, collapsing the three near-identical
  blocks in _check_index_drift. Codes and messages are unchanged; adding
  a kind is now a one-liner.

Add test_fsck_surfaces_invalid_claim_yaml_without_crashing.
@plind-junior plind-junior merged commit e31155e into vouchdev:test Jun 2, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 3, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: vouch fsck — deeper consistency checks than doctor

3 participants