* test(#4145): regression rows for hash-matching prefix-less pristine baselines
RED skeleton: src/pristine-baseline.cts exports findPristineByHash as a
null-returning stub so the new rows fail behaviorally, not at require time.
Failing-first rows: verifier resolution (no_baseline must drop to 0 when an
exact-hash orphan exists), findPristineByHash unit row, and the two
saveLocalPatches relocation rows. Negative-space rows pin today's behavior:
missing baselines still report ok_no_baseline, mismatching orphans are never
adopted or deleted, canonical precedence and the #3657 drift posture are
untouched.
* fix(#4145): resolve gsd-pristine/ baselines by recorded hash, relocate orphans
Both pristine readers joined the manifest-keyed path strictly, so a snapshot
stored without the gsd-core/ prefix (an earlier release's writer) was reported
as ok_no_baseline by the verifier and pushed into regeneration by
saveLocalPatches — where incoming-release candidates can never satisfy the
recorded outgoing hash, leaving the correct baseline permanently unconsumed.
- src/pristine-baseline.cts (new, ADR-457): shared findPristineByHash —
deterministic sorted scan of gsd-pristine/, exact sha-256 equality with the
recorded pristine_hashes entry (the same authority the #3657 drift guard
trusts), symlink-skipping, canonical path excluded via skipRel.
- verify-reapply-patches.cjs verifyFile(): on canonical miss with a recorded
hash, adopt byte-identical content found anywhere under gsd-pristine/ before
reporting OK_NO_BASELINE. Drift posture (#3657), canonical precedence, and
the frozen REASON/report shapes are untouched; the verifier stays read-only.
- install.js saveLocalPatches(): preserve-check rescue — relocate a
hash-matching orphan to the canonical path (copy, hash-verify, then remove
the orphan) so the state self-heals on the next update instead of repeating
forever. Honest accounting: new non-overlapping rescued counter.
- Workflow doc: one-sentence note on hash-based snapshot resolution.
- Derived ripples: INVENTORY-MANIFEST.json regen, eslint ignore + .gitignore
entries for the compiled artifact, seedFixture mkdir fix in the new rows.
Emitted-Drift-Ack-Growth: reapply-patches.md — one-sentence note on hash-based pristine snapshot resolution (#4145)
* fix(#4145): review follow-up — orphan scan never consumes a canonical path
Adversarial review finding: with two modified files sharing byte-identical
outgoing content, recoverOrphanedPristine could adopt the OTHER file's
canonical pristine as its rescue source — relocating it (copy + delete at
its home path) and ping-ponging the single baseline between the two files
across updates. findPristineByHash's skip parameter now accepts a Set, and
saveLocalPatches passes the normalized manifest keys so every canonical
path is excluded; only genuine non-canonical orphans are eligible for
removal (no strict-join reader ever consults those). Adds the
canonical-theft regression row, a Set-skip unit assertion, and tightens the
workflow doc sentence the same pass flagged as overstated.
* fix(#4145): INVENTORY roster row + symlink-fixture correction
Two leftovers from the ab17b7a1e5 bench run, both root-caused:
- docs/INVENTORY.md roster row for cli_modules/pristine-baseline.cjs
(#3762 gate: every manifest entry carries a row).
- The findPristineByHash symlink unit fixture placed its symlink target
INSIDE the scanned root, so the walk legitimately matched the real target
file. The implementation skips the symlink itself; the fixture now keeps
the target outside the scanned tree so the assertion tests what it claims.
* changeset(#4145): fixed fragment for pristine baseline hash resolution
---------
Co-authored-by: gsd-agent <agent@gsd.local>