Skip to content

fix(vault-sync): surface deleted-citation error as VaultSyncError#548

Open
boskodev790 wants to merge 1 commit into
vouchdev:testfrom
boskodev790:fix/vault-sync-catch-proposalerror
Open

fix(vault-sync): surface deleted-citation error as VaultSyncError#548
boskodev790 wants to merge 1 commit into
vouchdev:testfrom
boskodev790:fix/vault-sync-catch-proposalerror

Conversation

@boskodev790

@boskodev790 boskodev790 commented Jul 23, 2026

Copy link
Copy Markdown

What changed

sync_vault's forward-pass handler catches ProposalError alongside ArtifactNotFoundError, so a vault edit to a page whose cited claim/entity/source was deleted surfaces as the documented one-line VaultSyncError instead of an uncaught traceback.

Why

fixes #547. vault_to_kb files the edit via propose_page, which validates each cited id and converts a miss to ProposalError (proposals.py:299-300), not the raw ArtifactNotFoundError the handler caught. The two are disjoint hierarchies (ProposalError(RuntimeError) vs ArtifactNotFoundError(KeyError)), so the except never fired — the handler was dead code and the ProposalError propagated uncaught, bypassing the CLI's own except VaultSyncError renderer (cli.py:5021).

I drove vouch sync on the scenario both ways:

  • before: exit 1 with a raw traceback ending vouch.proposals.ProposalError: unknown claim id: c1
  • after: Error: vault edit references unknown artifact: unknown claim id: c1

Honest scope note: this does not keep watch_vault running — that loop catches only KeyboardInterrupt, so it ends on either exception. The fix is specifically about the surfaced error type (documented VaultSyncError vs uncaught ProposalError) and about making the dead handler live, not loop survival.

What might break

nothing on disk: no file moves, no field changes shape, no kb.* method behaves differently. sync_vault already had this except; the change only widens the exception tuple. A vault edit citing a deleted artifact that previously produced a traceback now produces a clean VaultSyncError (the documented intent). No existing passing path changes — verified the happy path still proposes normally and the other 25 vault_sync tests pass unchanged.

VEP

not a surface change — no kb.* method, object model, on-disk layout, bundle format or audit-log shape touched. vault_sync.py is non-core. no VEP.

Tests

  • make check passes locally (lint + mypy + pytest) — 1679 passed, 50 skipped; ruff clean; mypy clean across 104 source files
  • New / changed behaviour has a test
  • CHANGELOG.md updated under ## [Unreleased]

test_sync_vault_surfaces_deleted_citation_as_vault_sync_error — verified failing on test without the src change (the raw ProposalError escapes); passing with it. End-to-end drive also confirmed the fix covers entity and source citations, not just claims, and doesn't over-catch a legitimate edit.

no ui surface touched, so no screenshots.

Summary by CodeRabbit

  • Bug Fixes

    • Improved vault synchronization error handling when a cited item has been deleted.
    • Users now receive a clear, concise vault sync error instead of an unexpected traceback.
  • Tests

    • Added coverage to verify the expected error is shown during forward synchronization.

sync_vault's forward pass caught ArtifactNotFoundError to turn a
deleted-citation conflict into a clean VaultSyncError, but propose_page
validates cited ids and converts the miss to ProposalError before it
surfaces. the two are disjoint hierarchies (ProposalError(RuntimeError)
vs ArtifactNotFoundError(KeyError)), so the handler was dead and the
ProposalError escaped as an uncaught traceback — bypassing the CLI's
own `except VaultSyncError` renderer, which turns domain errors into a
one-line message.

catch ProposalError alongside ArtifactNotFoundError so a vault edit to a
page whose cited claim/entity/source was deleted lands as the documented
VaultSyncError. driving `vouch sync` on that scenario now prints
"Error: vault edit references unknown artifact: unknown claim id: ..."
instead of a python traceback.

this does not keep watch_vault running — that loop catches only
KeyboardInterrupt and still ends on either exception; the fix is about
the surfaced error type, not loop survival.
@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance sync sync, vault mirror, and diff flows tests tests and fixtures size: XS less than 50 changed non-doc lines labels Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This change updates forward vault synchronization to catch ProposalError from deleted citations and surface it as VaultSyncError. A regression test verifies the resulting "unknown artifact" message, and the fix is documented in the changelog.

Changes

Vault sync error handling

Layer / File(s) Summary
Map proposal failures to VaultSyncError
src/vouch/vault_sync.py, CHANGELOG.md
Forward synchronization catches ProposalError alongside ArtifactNotFoundError, with the correction recorded in the changelog.
Verify deleted citation handling
tests/test_vault_sync.py
A regression test deletes a cited claim after mirroring and editing a page, then asserts that forward sync raises VaultSyncError containing "unknown artifact".

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • vouchdev/vouch#50: Both changes address ProposalError behavior in the proposal and evidence path.

Suggested reviewers: plind-junior, dripsmvcp

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: surfacing deleted-citation errors as VaultSyncError.
Linked Issues check ✅ Passed The code catches ProposalError in forward sync and adds a test for the deleted-citation VaultSyncError path, matching #547.
Out of Scope Changes check ✅ Passed The changelog note, error-handling fix, and test all align with the stated vault-sync bug fix.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 3

🤖 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 `@CHANGELOG.md`:
- Around line 276-282: Move the `sync_vault`/`ProposalError` changelog bullet
from the `## [1.4.0] — 2026-07-17` section into the existing `[Unreleased]`
section, preserving its wording and formatting.

In `@src/vouch/vault_sync.py`:
- Around line 468-476: Update the inline comment block near the vault edit
conflict handler to use lowercase prose throughout, including “a vault edit,”
“that’s,” and “catch both,” while preserving exception and type names such as
ArtifactNotFoundError, ProposalError, and VaultSyncError.
- Around line 467-476: The VaultSync exception handler must not label every
ProposalError as an unknown artifact. Update the handling around propose_page()
to distinguish missing claim/entity/source errors from other proposal validation
failures, or use a neutral proposal-rejected message for ProposalError while
preserving the documented VaultSyncError conversion.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 39b3b38a-c780-4c02-8909-de7a8f38840f

📥 Commits

Reviewing files that changed from the base of the PR and between 3beed68 and 5a4493d.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/vouch/vault_sync.py
  • tests/test_vault_sync.py

Comment thread CHANGELOG.md
Comment on lines +276 to +282
- `sync_vault` catches the `ProposalError` that `propose_page` raises for a
deleted citation, not just `ArtifactNotFoundError`. a vault edit to a page
whose cited claim/entity/source was since removed hit propose_page's id
validation, which converts the miss to `ProposalError` — so the
`except ArtifactNotFoundError` handler never fired and the error escaped as
an uncaught traceback, bypassing the CLI's `except VaultSyncError` renderer.
it now surfaces as the intended one-line `VaultSyncError` (#547).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

move this entry under [Unreleased]

This is a user-visible cli error change, but it is currently recorded under ## [1.4.0] — 2026-07-17. Move the bullet to the existing [Unreleased] section.

as per coding guidelines: user-visible changes must be recorded in CHANGELOG.md under [Unreleased] in the same pr; as per path instructions: do not silently omit user-visible changes.

🤖 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 `@CHANGELOG.md` around lines 276 - 282, Move the `sync_vault`/`ProposalError`
changelog bullet from the `## [1.4.0] — 2026-07-17` section into the existing
`[Unreleased]` section, preserving its wording and formatting.

Sources: Coding guidelines, Path instructions

Comment thread src/vouch/vault_sync.py
Comment on lines +467 to +476
except (ArtifactNotFoundError, ProposalError) as e:
# A vault edit referenced a claim/entity/source that no longer
# exists in the KB. That's a *real* conflict the user has to
# resolve in Obsidian, not a vouch bug; surface it cleanly.
# propose_page validates those ids and converts the miss to a
# ProposalError ("unknown claim/entity/source id"), not the raw
# ArtifactNotFoundError this used to catch — so the handler was
# dead and the ProposalError escaped as an uncaught traceback,
# bypassing the CLI's `except VaultSyncError` renderer. Catch
# both so it surfaces as the documented VaultSyncError.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

do not classify every ProposalError as an unknown artifact

propose_page() in src/vouch/proposals.py also raises ProposalError for empty titles and page-kind validation failures. Those errors now receive the misleading unknown artifact message. Distinguish missing-artifact causes from other proposal failures, or use a neutral proposal-rejected message.

🤖 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 `@src/vouch/vault_sync.py` around lines 467 - 476, The VaultSync exception
handler must not label every ProposalError as an unknown artifact. Update the
handling around propose_page() to distinguish missing claim/entity/source errors
from other proposal validation failures, or use a neutral proposal-rejected
message for ProposalError while preserving the documented VaultSyncError
conversion.

Comment thread src/vouch/vault_sync.py
Comment on lines 468 to +476
# A vault edit referenced a claim/entity/source that no longer
# exists in the KB. That's a *real* conflict the user has to
# resolve in Obsidian, not a vouch bug; surface it cleanly.
# propose_page validates those ids and converts the miss to a
# ProposalError ("unknown claim/entity/source id"), not the raw
# ArtifactNotFoundError this used to catch — so the handler was
# dead and the ProposalError escaped as an uncaught traceback,
# bypassing the CLI's `except VaultSyncError` renderer. Catch
# both so it surfaces as the documented VaultSyncError.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

keep the new inline comment prose lowercase

The added comment block uses sentence case (A vault edit, That's, Catch both). Lowercase the prose while preserving exception and type names.

as per path instructions: use lowercase prose in comments and review notes.

🤖 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 `@src/vouch/vault_sync.py` around lines 468 - 476, Update the inline comment
block near the vault edit conflict handler to use lowercase prose throughout,
including “a vault edit,” “that’s,” and “catch both,” while preserving exception
and type names such as ArtifactNotFoundError, ProposalError, and VaultSyncError.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs documentation, specs, examples, and repo guidance size: XS less than 50 changed non-doc lines sync sync, vault mirror, and diff flows tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants