fix: install-time store verification, staging cleanup on failure, mode-aware status#48
Conversation
…led reconcile Copy-mode installs now re-hash the tree materialized from the content store before it reaches the vendor directory: a corrupted store entry fails with exit 4, is removed so the next run re-fetches it, and is never installed silently (REQ-STORE-INSTALL-VERIFY). A failed reconcile no longer leaves an empty <vendor>/.graft-tmp (or an otherwise-empty vendor root) behind (REQ-APPLY-STAGING-CLEANUP). Spec, translations, and testing manuals updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR Summary by QodoVerify copy-mode store installs and always clean vendor staging on reconcile failure
AI Description
Diagram
High-Level Assessment
Files changed (7)
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Code Review by Qodo
Context used✅ Compliance rules (platform):
15 rules 1.
|
status previously classified a dest by its on-disk form alone: a symlinked dest was always checked as a link target, a real tree always by hash — regardless of GRAFT_LINK_MODE. Both mode-drift quadrants therefore reported ok while apply would rewrite the dest, breaking the "status ok ⇒ apply is a no-op" contract the CI-guard use case relies on (a link-mode leftover in copy mode passed status but was a broken symlink for every other machine). status now reports modified for a dest materialized in the other mode, matching apply's drift judgment (REQ-STATUS-MODE-DRIFT). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A parallel install of the same hash can find the entry corrupted and remove it while another worker is still materializing from it; surface that as the exit-4 corrupted-store error instead of a raw I/O error. Also note in the testing manuals that status validates GRAFT_LINK_MODE (exit 2 on an unsupported mode), since it now judges dests by mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
Three fixes found by black-box testing of
bin/graft:1. Copy-mode installs re-hash what they materialize from the content store (REQ-STORE-INSTALL-VERIFY).
Previously a corrupted (deliberately tampered, read-only bit bypassed) store entry was installed into the vendor directory silently with exit 0 — the corruption was only caught by the next
apply/status. The spec (§5.4/§7) promises copy mode behaves exactly like graft without a cache, which would have failed with exit 4 at checkout time. Nowinstallre-hashes the staged tree before the rename into<dest>; a mismatch installs nothing, fails with exit 4 (cached content for … is corrupted), and removes the corrupted entry so a plain re-run re-fetches and heals.2. A failed reconcile no longer leaves
<vendor>/.graft-tmpbehind (REQ-APPLY-STAGING-CLEANUP).Error paths (integrity failure, lost commit, …) returned before the staging cleanup, leaving an empty
.graft-tmp(and an otherwise-empty vendor root) in the project. Cleanup is now deferred so it runs on failure too; the success path still reports its removal error.3.
statusjudges each dest by the current link mode (REQ-STATUS-MODE-DRIFT).statusclassified a dest by its on-disk form alone (symlink → link-target check, real tree → hash check), ignoringGRAFT_LINK_MODE. Both mode-drift quadrants reportedokwhileapplywould rewrite the dest — breaking the "status ok ⇒ apply is a no-op" contract the CI-guard use case relies on (a link-mode leftover passed a copy-mode status but is a broken symlink on every other machine). A dest materialized in the other mode now reportsmodified, matchingapply's drift judgment.Docs kept in sync
docs/design.zh-TW.md+docs/design.md: §4.5 status semantics (new mode-drift bullet), §5.1 flow diagram & staging paragraph, §5.4 content-store/copy-mode wording ("store hit needs no network" — the no-re-hash claim no longer holds at install), §7 shared-cache paragraphdocs/requirements.md: three new REQ rows, each bound to a covering test (// spec: REQ-…)docs/testing.md(+ local zh manual): new black-box rows in §3.7 and §4.2README.md+README.zh-TW.md: status section mentions mode-drift reportingTesting
TestReconcile_corruptedStoreEntryFailsAndHeals,TestReconcile_failedRunRemovesStaging,TestStatus_modeDriftReportsModifiedapplyexits 4, nothing installed, entry removed, secondapplyself-heals; failedapplyleaves no.graft-tmp; both mode-drift directions reportmodified(exit 1) and re-applying in the current mode returns status to all-ok🤖 Generated with Claude Code