Import unification PR 6b: unify and extract the stamping loop (spec decision 11) - #1445
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ding, prefix count, anchors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lan as-built erratum Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sion 11) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…2/D3) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ec D1/D5-D7)
Both stamping loops restructured to one shared shape (local's branch
order), stamps gated on verified_counted_for_copies in both (a no-op
locally where it is always True; identical to the params.verify_by_hash
gate remotely), imported_photo_ids.add outside the gate on every accept
path, _rehash_dest_or_none hoisted to module level taking
(path, stop_requested), and the unified five-message reason table
applied via a per-function dest_noun local ("mount"/"archive").
Textual-identity verification (comments/blanks stripped, dest_noun
literal normalized):
extract both regions (raw_companion_invalidations decl through the
end of the for-loop body), replace the dest_noun literal with "NOUN",
drop blank/comment-only lines, then:
diff /tmp/remote_stripped.txt /tmp/local_stripped.txt
-> (empty)
The full-text diff including comments is also empty modulo the noun
literal (one trailing blank line after the local loop).
One test tail updated as part of the remote verify/no-verify wording
collapse: the no-verify paired-JPEG mismatch test now expects "hash
this import recorded" instead of "source hash". All 4 pinned reason
prefixes (9 assertion sites) unchanged.
Full file: 231 passed, 1 skipped (zero flips).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… remain) Move-only: _stamp_landed_and_validate_catalog(state, batch_st, db, params, rel, *, attests_bytes, dest_noun, stop_requested) returns raw_companion_invalidations; both import functions call it (dest_noun="mount"/"archive", attests_bytes=verified_counted_for_copies, stop_requested=_stop_requested) and pass the result into _invalidate_changed_and_sweep unchanged. Both loop bodies and the "deliberately not yet extracted" NOTE comments are deleted. Move-only proof: the pre-extraction loop body equals the function body modulo an 8-space dedent and the parameter renames verified_counted_for_copies -> attests_bytes, _stop_requested -> stop_requested (verified by normalized diff: empty). The docstring carries the three invariants: stamps gated on attests_bytes; imported_photo_ids.add on every accept path OUTSIDE the gate; the decision-11 backfill excludes EMPTY_FILE_SHA256. Full file: 231 passed, 1 skipped. Parity net (-k "behavior or agree_on_plain or adoption or renamed_twin or zero_byte or dest_read_cancel or mount_detach or pairs"): 29 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 17 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
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. Comment |
What this is
The stamping alignment + extraction PR carved out of PR 6 (#1444): the post-scan stamping loop was the last piece of shared logic hand-mirrored between
run_import_joband_run_remote_import_job, held back because it hid genuine behavioral divergences. This PR resolves them one reviewable commit at a time, proves the two loops textually identical, and extracts the result as_stamp_landed_and_validate_catalog.docs/superpowers/specs/2026-08-06-import-path-unification-design.md(decision 11 now RESOLVED; PR-sequence 6b entry updated)docs/superpowers/plans/2026-08-09-import-unify-pr6b-stamping.mdCommits
_rollback_on_mount_losstakes noreland readsbatch_st.rel;_batch_preflightreturnsdest_folderand takesmissing_root_check; merged code is truth).file_hashbackfill (decision 11, THE vetoable commit). When scan leaves a landed file's row hash NULL and the destination re-read agrees with the hash this import recorded, remote now backfillsphotos.file_hash, matching local. The zero-byte exclusion (the backfill trap): the formula isfile_hash=(hash if hash != EMPTY_FILE_SHA256 else None)— backfillingEMPTY_FILE_SHA256would recreate the exact collision the scanner's zero-byte NULL convention exists to prevent (every empty file an exact duplicate of every other;empty_hash_needs_repairchurning repairs).file_hash=Nonehitsupdate_photo_hash_check's status-only arm, so the row keeps scan's NULL. The scan-hash-agrees path needs no backfill (the row already holds the hash; local doesn't backfill there either). Two guard tests pinfile_hash IS NULLafter zero-byte accepts on both paths, forever.verified_hashat_LandedFileconstruction. Both remote construction sites now produceEMPTY_FILE_SHA256instead of None for zero-byte files;_LandedFile.verified_hashtightened tostr. Thesrc_hashcomputation is deliberately untouched (the intra-batch dedup maps, adopt gate, and_record_checkerrely on the None convention). The one behavior flip: remote zero-byte re-import over a pre-existing row now fires the import's defense-in-depth derived-cache diff loop (NULL != EMPTY), aligning with local. This is NOT a user-visible cache change — scanner's own zero-byte content-change arm already invalidated that row during the batch scan; the pin test therefore uses a timing-gated spy that counts only post-scan invalidations (counting scanner's in-scan call would have made the red test green pre-change).verified_counted_for_copiesin both loops (a no-op locally, where it is always True; identical to remote's oldparams.verify_by_hashgate);imported_photo_ids.addoutside the gate on every accept path;_rehash_dest_or_nonehoisted to module level; unified five-message reason table via adest_nounlocal. Textual-identity proof: both loop regions, comments stripped and the noun literal normalized, diff EMPTY (the full-text diff including comments is also empty modulo the literal). 231 passed at this commit — zero test flips._stamp_landed_and_validate_catalog(state, batch_st, db, params, rel, *, attests_bytes, dest_noun, stop_requested)returnsraw_companion_invalidations; both functions call it and feed_invalidate_changed_and_sweepunchanged. Proof: the pre-extraction loop body equals the function body modulo an 8-space dedent and theattests_bytes/stop_requestedparameter renames (normalized diff: empty). The docstring carries the three invariants (attests_bytes gate; imported_photo_ids on every accept path outside the gate; backfill excludes EMPTY).Reason wordings, old → new
The "recorded, not verified" choice is deliberate UI transparency: a no-verify remote run computed the card-side hash but verified nothing destination-side, so the wording must not overclaim.
Local (all unpinned by tests):
Remote (verify/no-verify tail collapse at three sites):
All 4 pinned reason prefixes at all 9 assertion sites are unchanged. One test conjunct was updated as part of the tail collapse:
test_remote_import_paired_jpeg_no_verify_fails_on_mount_mismatchnow expects "hash this import recorded" instead of "source hash".Edge note: legacy EMPTY rows
Adopting local's raw-compare shape drops remote's defensive
scan_h EMPTY→Nonenormalization: a convention-violating legacy row whosefile_hashcolumn holdsEMPTY_FILE_SHA256under a non-empty landed file now reclassifies as failed (hash mismatch) instead of re-read-accepting. Unreachable by any test or by scanner-written rows, and the tightened behavior is fail-safe.Preflight-mirror check
No duplicate-walk changes in this PR — the spec's preflight-mirror checklist item is n/a.
What this means
The LAST piece of shared logic is unified: both import functions are now transport cores plus calls into shared phase functions. PR 7 is the transport merge.
Test results
vireo/tests/test_import_job.py: 231 passed, 1 skipped at every production commit boundary (baseline 225 + 4 D4 tests + 2 D2/D3 tests); red-green patterns exactly as planned (1 red for D4, 1 red for D2/D3).-k "behavior or agree_on_plain or adoption or renamed_twin or zero_byte or dest_read_cancel or mount_detach or pairs"): 29 passed.test_api_exiftool_status_reports_missing.🤖 Generated with Claude Code