fix(vault-sync): key pending-proposal dedup on edit content, not page id - #707
Conversation
_has_pending_page_proposal only checked whether *any* pending proposal targeted a page id, so a second, distinct edit made to a vault page while the first edit's proposal was still pending got silently skipped instead of filing its own proposal - vault_to_kb never passed the existing body kwarg, so the guard degraded to id-only. the second edit was never captured anywhere: not in a proposal, not in the kb, and the next backward sync pass then overwrote the vault mirror with the kb's still-just-the-first-edit content, erasing the second edit with no trace. key the dedup check on source_id - the content-address (sha256) of the whole mirror file, the same fingerprint put_source assigns - instead of comparing raw body text. a second, different edit now correctly files its own proposal alongside the first instead of being coalesced into it. Fixes vouchdev#706
|
the diagnosis is right and the content-address key is the correct fix — the thing i would want answered before merge is what a reviewer is supposed to do with two pending proposals for one page. so the fix trades a silent clobber for an order-dependent one. that is still a clear improvement — the second edit is at least captured now, and a reviewer draining the queue in order gets the right answer — but it is worth either a note in the docstring that these must be approved oldest-first, or having the second proposal reference the one it supersedes. a test that approves out of order and asserts the resulting body would pin whichever behaviour you decide is correct. smaller: the rewritten docstring ends on "Without this guard, running vault_to_kb twice before the first proposal is approved files duplicate proposals for the same edit" — that sentence describes the absence of the page-id check (the original #219 fix), not the absence of the |
…nd-edit-clobbered # Conflicts: # CHANGELOG.md
changelog union only; no source conflict.
changelog union only; no source conflict.
|
diff coverage: n/a — this PR changes no python under |
What changed
_has_pending_page_proposal's dedup check for pending page proposals nowkeys on
source_id— the content-address (sha256) of the whole vault-editmirror file — instead of comparing raw
bodytext (which the call site invault_to_kbnever actually passed, so the guard degraded to id-only).Why
The dedup guard only checked whether any pending proposal targeted a
page id, regardless of what edit it contained. So a second, distinct edit
made to a vault page while the first edit's proposal was still pending
was silently skipped instead of filing its own proposal. The second edit
was never captured anywhere — not in a proposal, not in the KB — and the
next backward sync pass then overwrote the vault mirror with the KB's
still-just-the-first-edit content, erasing the second edit with no trace
and no error.
Confirmed with a repro: edit a mirrored page (edit A), run
vault_to_kb(proposal correctly filed), edit the same page again withunrelated content (edit B) before approving edit A's proposal, run
vault_to_kbagain — edit B never gets a proposal(
pages_proposed: [],pages_skipped_unchanged: [...]) and is gone.Fixes #706
What might break
Nothing for users with an existing
.vouch/directory — no on-diskshape,
kb.*method, or object model change. Behaviorally: a second,distinct edit to an already-pending page now files its own proposal
instead of being silently dropped. The original dedup guarantee this
function exists for (#219 — re-running sync on an unchanged edit must
not file a duplicate) still holds, since an unchanged edit has the same
source_idas the already-pending proposal.VEP
Not applicable — no object model,
kb.*method, on-disk layout, bundleformat, or audit-log shape change. A dedup-key correctness fix inside
the vault sync internals.
Tests
make check-equivalent: ruff clean (src+tests); mypyclean on
vault_sync.py; alltests/test_vault_sync.pycases pass(32 pre-existing + 1 new)
test_vault_to_kb_files_new_proposal_for_second_distinct_editCHANGELOG.mdupdated under## [Unreleased]