Skip to content

fix(verify): catch ArtifactNotFoundError on missing stored content#32

Merged
plind-junior merged 3 commits into
vouchdev:mainfrom
dripsmvcp:fix/30-verify-source-exception
May 20, 2026
Merged

fix(verify): catch ArtifactNotFoundError on missing stored content#32
plind-junior merged 3 commits into
vouchdev:mainfrom
dripsmvcp:fix/30-verify-source-exception

Conversation

@dripsmvcp

@dripsmvcp dripsmvcp commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • verify_source() now catches the exception that KBStore.read_source_content() actually raises, so vouch source verify and vouch doctor no longer crash on the first source with a missing content blob.

Root Cause

src/vouch/verify.py:30 catches FileNotFoundError, but src/vouch/storage.py:217-221 raises ArtifactNotFoundError — defined as class ArtifactNotFoundError(KeyError), which is not a subclass of FileNotFoundError. The except therefore never matched, and the unhandled exception propagated out of verify_source() and aborted the entire verify_all() sweep.

Fix

Replace the caught type with ArtifactNotFoundError and add it to the storage import. No other behavior change — the intended graceful path (VerificationResult(stored_ok=False, external_status="n/a", note="stored content missing")) was already written; it just was never reached.

Test Plan

  • Regression test added: tests/test_verify.py::test_verify_handles_missing_stored_content — registers a source, deletes the on-disk content blob, runs verify_all, asserts a graceful VerificationResult (not a traceback). Verified to fail on the unfixed code with ArtifactNotFoundError, and to pass with the fix.
  • Existing test_verify_detects_external_drift still passes.
  • Adjacent tests still green: pytest tests/test_verify.py tests/test_storage.py tests/test_health.py -q → 37 passed.
  • ruff check src/vouch/verify.py tests/test_verify.py → All checks passed.

Fixes #30

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling when stored source content is missing, returning clear status and user-facing note.
    • Improved handling for unreadable/unavailable stored content (access errors), with informative notes.
    • Clearer error reporting for attempts to create items with duplicate IDs.
  • Tests

    • Added regression tests for missing and unreadable stored content.
    • Added end-to-end test ensuring verification continues after a single-source failure.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d1ea9bc3-3d6a-4a24-9bba-638e2c64fb75

📥 Commits

Reviewing files that changed from the base of the PR and between 1cb329f and df508a5.

📒 Files selected for processing (3)
  • src/vouch/context.py
  • src/vouch/sessions.py
  • src/vouch/storage.py

📝 Walkthrough

Walkthrough

Catch ArtifactNotFoundError in verify_source; add tests for missing and unreadable stored content and for verify_all continuing after a single failure. Also add ContextItemKind typing, change session persistence call to update_session, and chain FileExistsError when put_* operations detect duplicates.

Changes

Maintenance and correctness updates

Layer / File(s) Summary
Exception type fix in verify_source
src/vouch/verify.py
Import ArtifactNotFoundError and update verify_source to catch it, returning stored_ok=False, external_status="n/a", and note="stored content missing".
Regression tests for verify_all
tests/test_verify.py
Add test_verify_handles_missing_stored_content, test_verify_handles_unreadable_stored_content, and test_verify_all_continues_after_one_failure to validate missing/unreadable stored content behavior and that verify_all continues after a single-source failure.
ContextItem kind typing
src/vouch/context.py
Add ContextItemKind = Literal["claim","page","entity","relation","source"] and cast retrieved kind to this type in build_context_pack.
Session persistence update
src/vouch/sessions.py
After backfilling sess.proposal_ids, persist with store.update_session(sess) instead of store.put_session(sess).
KBStore put_ exception chaining*
src/vouch/storage.py
Capture FileExistsError in put_claim, put_page, put_entity, put_relation, put_evidence, put_session, and put_proposal, and re-raise ValueError(... ) from e; ensure put_session returns the session after exclusive-create write.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • vouchdev/vouch#26: Related changes to KBStore put_* exclusive-create behavior and error propagation.

"I nibble at a missing byte,
A gentle fix to set things right.
Tests hop in, two then three,
Now sweeps complete, content free.
Hooray — no crash, just quiet night." 🐰

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes out-of-scope changes: updates to context.py (ContextItemKind type alias), sessions.py (put_session to update_session), and storage.py (exception chaining for FileExistsError) are unrelated to the issue #30 fix. Separate the unrelated changes in context.py, sessions.py, and storage.py into separate PRs focused on their specific purposes.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing verify_source to catch ArtifactNotFoundError instead of FileNotFoundError when stored content is missing.
Linked Issues check ✅ Passed The PR implementation directly addresses all requirements from issue #30: catching ArtifactNotFoundError, importing it, and returning graceful VerificationResult with stored_ok=False and appropriate note.

✏️ 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.

verify_source() caught FileNotFoundError, but store.read_source_content()
raises ArtifactNotFoundError (a KeyError subclass) when the content blob
is missing -- so the "stored content missing" graceful path never ran
and a single broken source crashed the entire verify_all() sweep,
breaking `vouch source verify` and `vouch doctor`.

The same call can also raise OSError (permission denied, TOCTOU race
between exists() and read_bytes(), underlying I/O error) which was
likewise unhandled. Catch both: the existing "missing" path keeps its
note, and OSError surfaces as "stored content unreadable: <reason>".

Adds three regression tests:
- missing-content blob -> graceful per-source failure
- unreadable stored content (monkeypatched PermissionError) -> graceful
  per-source failure with the underlying reason in the note
- mixed sweep with one good + one broken source -> verify_all returns
  both results instead of aborting at the first failure

Fixes vouchdev#30
@dripsmvcp
dripsmvcp force-pushed the fix/30-verify-source-exception branch from 52839b3 to 1cb329f Compare May 19, 2026 23:20
dripsmvcp added 2 commits May 20, 2026 10:39
ruff B904 requires `raise ... from err` inside an except block so the
original exception is preserved on the chained __cause__. Without it,
the CI lint step rejects the file. The seven sites are pre-existing
(put_claim, put_page, put_entity, put_relation, put_evidence,
put_session, put_proposal); this PR inherited the failure from main
rather than introducing it, but the path traversal fix cannot land
until lint is green, so the cleanup happens here.

No behavior change: the chained exception carries .__cause__ but the
public ValueError message and type are unchanged.
The B904 lint failures were already addressed in 2a439a5, but the CI
matrix still fails on two pre-existing main-branch issues:

* mypy: context.py:64 passed `kind: str` into ContextItem.type, which
  is Literal["claim","page","entity","relation","source"]. Narrow it
  with a typing.cast so strict mode accepts the search-backend output.
* pytest: session_end() mutates a session that session_start() already
  wrote, but put_session() switched to exclusive create and rejects
  the second write. Add KBStore.update_session() mirroring update_claim
  and have session_end call it; test_sessions coverage passes again.

No behavior change for the path-traversal fix itself.
@plind-junior

Copy link
Copy Markdown
Member

Nice — the fix itself is exactly what #30 asked for, and the bonus OSError catch with a separate note is the kind of follow-through I'd hoped for. The three tests (missing, unreadable, end-to-end sweep continuation) cover the bug and its siblings cleanly.

Same scope-creep concern I had on #31, though: this PR also lands update_session in storage.py:393-401 (and the session_end switch at sessions.py:47), the seven from e chains across the put_* methods, and the cast(ContextItemKind, kind) in context.py:68. All correct, but none of them belong under "fix(verify): catch ArtifactNotFoundError…" — they were CI-unblocking work for #28/#31 that came along for the ride. Either split them out into a chore(ci): PR or at minimum call them out in the description. And while you're at it, please add a direct unit test for update_session — right now it has no dedicated coverage.

One small thing on verify.py:36note=f"stored content unreadable: {e}" will splat the full system path of the source content blob into the note via OSError's default repr ([Errno 13] Permission denied: '/path/to/blob'). Probably fine for a local CLI but worth a thought if you ever want these notes safe to surface in a multi-tenant context.

Otherwise this is good to merge once the scope is sorted.

@plind-junior
plind-junior merged commit a6f1c3c into vouchdev:main May 20, 2026
5 checks passed
plind-junior added a commit that referenced this pull request May 22, 2026
fix(verify): catch ArtifactNotFoundError on missing stored content
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.

fix(verify): wrong exception type caught — verification crashes on missing content

2 participants