Skip to content

fix(vault): stop destroying a second edit while the first is pending#542

Open
michiot05 wants to merge 2 commits into
vouchdev:testfrom
michiot05:fix/vault-sync-body-aware-dedup
Open

fix(vault): stop destroying a second edit while the first is pending#542
michiot05 wants to merge 2 commits into
vouchdev:testfrom
michiot05:fix/vault-sync-body-aware-dedup

Conversation

@michiot05

@michiot05 michiot05 commented Jul 22, 2026

Copy link
Copy Markdown

what changed

vault_to_kb's pending-proposal dedup guard now passes the edited body to _has_pending_page_proposal. the helper has carried a body-aware mode since the #219 fixes — its docstring promises "allowing a second different vault edit to file a new proposal even while the first is still pending" — but no call site ever passed body, so the helper always fell back to page-id-only matching and that documented behavior was dead code.

why

the id-only match silently destroys user data in bidirectional sync:

  1. edit a mirrored page in the vault → sync → proposal A filed (correct)
  2. edit the same page again, differently, while proposal A is still pending
  3. forward pass: the dedup sees a pending proposal for the page id, skips the new edit, and misreports it in pages_skipped_unchanged (the cli prints it under "unchanged")
  4. backward pass: kb_to_vault rewrites the mirror file with kb-canonical content and records the new hash in the state file

after step 4 the second edit exists nowhere — no proposal, no warning, and the vault file is gone too. that breaks sync_vault's own documented ordering contract ("forward runs first so user edits land as proposals before the backward mirror overwrites them"), and it's exactly the case the helper's body parameter was written for.

with the body passed through, both behaviors compose correctly: re-running sync on the same pending edit still coalesces (the existing dedup test stays green), while a genuinely different edit files its own proposal for the reviewer to pick from.

what would break for an existing .vouch/

nothing — no on-disk shape, object model, or method surface changes. the only behavior change is that a second distinct vault edit now files a proposal where it was previously dropped. kbs relying on the buggy skip would just see one more pending proposal in the review queue, which is the review gate working as designed.

test plan

two regression tests in tests/test_vault_sync.py, both failing on the previous code:

  • test_vault_to_kb_second_different_edit_files_new_proposal — edit A proposed, edit B while A pending → B files its own proposal (was: misclassified as unchanged)
  • test_sync_vault_preserves_second_edit_while_first_pending — full bidirectional flow → edit B must end up in a proposal (was: destroyed by the backward mirror pass)

the existing test_vault_to_kb_deduplicates_pending_proposals (same-edit re-run coalesces) still passes, so the anti-duplicate guard is intact.

validation run locally:

python -m pytest tests/ -q --ignore=tests/embeddings   # all passed
python -m mypy src                                     # no issues in 102 files
python -m ruff check src tests                         # all checks passed

Summary by CodeRabbit

  • Bug Fixes

    • Fixed vault sync deduplication when multiple edits occur while a prior proposal is still pending, so distinct edits (including frontmatter-only changes) aren’t incorrectly treated as unchanged.
    • Duplicate re-runs are still coalesced, but only when the pending content matches the earlier edit.
  • Tests

    • Added new test coverage for forward vault→vouch sync behavior with successive edits and for end-to-end sync_vault preserving the second edit.

the pending-proposal dedup in vault_to_kb matched on page id alone —
_has_pending_page_proposal grew a body-aware mode for exactly this case
("allowing a second different vault edit to file a new proposal even
while the first is still pending"), but no call site ever passed body,
so that mode was dead code.

the consequence is silent data loss in bidirectional sync: edit a
mirrored page (proposal filed), edit it again while the first proposal
is pending, and the forward pass skips the new edit as "unchanged";
the backward pass then rewrites the mirror with kb-canonical content.
the second edit is gone — no proposal, no warning, no file — despite
sync_vault's own contract that the forward pass captures user edits
before the backward mirror overwrites them.

pass the edited body through, so duplicate re-runs still coalesce on
the pending proposal while a genuinely different edit files its own.
@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance sync sync, vault mirror, and diff flows tests tests and fixtures labels Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b754cf9d-f392-4339-84eb-417ff6e78dd1

📥 Commits

Reviewing files that changed from the base of the PR and between 5e07a23 and ab62869.

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

Walkthrough

Vault sync deduplication now matches pending page proposals by page ID and vault-edit content hash. Distinct sequential edits create separate proposals, including frontmatter-only edits, and end-to-end tests verify later edits survive synchronization.

Changes

Vault sync proposal deduplication

Layer / File(s) Summary
Match pending proposals by edit source
src/vouch/vault_sync.py
Pending proposals are skipped only when the same page and vault-edit content fingerprint are already pending; distinct edits create new proposals.
Validate sequential edit preservation
tests/test_vault_sync.py, CHANGELOG.md
Tests cover distinct body edits, frontmatter-only edits, and preservation through sync_vault; the changelog documents the fix.

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

Suggested reviewers: plind-junior

🚥 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 reflects the main change: preventing a second vault edit from being lost while an earlier proposal is pending.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@github-actions github-actions Bot added the size: S 50-199 changed non-doc lines label Jul 22, 2026

@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 `@src/vouch/vault_sync.py`:
- Line 365: Update the deduplication check in the page-sync flow around
_has_pending_page_proposal to compare the complete editable payload recorded by
propose_page—title, type, body, claims, entities, sources, and tags—or a
canonical fingerprint containing those fields. Ensure frontmatter-only edits are
not treated as duplicates, and add a regression test covering an unchanged body
with changed editable metadata.
- Around line 357-364: Convert the prose in the comment describing the duplicate
pending-proposal guard to lowercase, including sentence starts and the quoted
error text where applicable, while preserving the existing wording and code
references.
🪄 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: d27fda8a-1825-4bfd-80ee-5e8bcc8740b4

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9ca7c and 5e07a23.

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

Comment thread src/vouch/vault_sync.py Outdated
Comment thread src/vouch/vault_sync.py Outdated
address the review round: comparing only the body still swallowed a
frontmatter-only edit (title, tags, claims — body untouched) while a
matching-body proposal was pending, leaving it to the same backward-pass
destruction.

rather than comparing every editable payload field, match on the
vault-edit source id the proposal already cites: put_source is
content-addressed, so the id is the sha256 of the whole mirror file —
the canonical fingerprint of the complete edit, body and frontmatter
alike. current_hash in the sync loop is that same digest, so the guard
needs no extra hashing. lowercase the touched comment prose while here.
@michiot05

Copy link
Copy Markdown
Author

both findings addressed in the new commit:

  • the dedup now matches on the vault-edit source id instead of the body. put_source is content-addressed, so the source id is the sha256 of the whole mirror file — the canonical fingerprint of the complete edit, covering title, type, tags, claims, entities, sources and body in one comparison (it's also current_hash, which the sync loop already computes, so no extra hashing). a frontmatter-only edit now files its own proposal; the same-edit re-run still coalesces on the pending one. new regression test_vault_to_kb_frontmatter_only_edit_files_new_proposal fails on the body-only comparison and passes now.
  • touched comment prose lowercased.

validation rerun: pytest tests/ -q --ignore=tests/embeddings, mypy src, ruff check src tests — all green.

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: S 50-199 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.

1 participant