Fail closed on malformed lockfile patch metadata - #1365
Conversation
|
🔗 Paperclip issue: BLO-27241 |
1 similar comment
|
🔗 Paperclip issue: BLO-27241 |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
f3c93f2 to
f38193f
Compare
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
…O-27241) The patch-content verification added by #1338 ran only under `if (repoRoot && lockedEntry.hash)`. An absent, empty, or unparsed hash skipped the whole block, pushed nothing to `mismatches`, and fell through to the next key — so the guard standing between master and ERR_PNPM_LOCKFILE_CONFIG_MISMATCH reported green on precisely the edit class #1338 was written to catch. The missing-patch-file check lived inside the same block, so a falsy hash also disabled detection of a patch file that does not exist at all. A falsy hash was reachable from parsing, not just hand-editing: `parsePatchedDependenciesBlock` populated `hash` only from a line containing the literal `": "`, and `continue`d past anything else. Any change in how pnpm serialises that line would have disabled content verification fleet-wide while the guard still reported green — the thing that would catch the regression being the thing that regressed. The same shape sat one layer up, raised on #1340's post-hoc review: both parsers `break` out of a block on an unrecognised line and return a *partial* Map, which is structurally indistinguishable from a complete parse. An entry that was never parsed can never mismatch, so a truncated or oddly-indented block compared green. Green meant "everything I managed to parse matched", not "everything matched". So: a missing hash is now itself a mismatch; patch-file existence is checked independently of the hash; and every unrecognised shape throws rather than truncating, surfaced by `main()` as a distinct guard failure ("could not parse") rather than a drift report, since the fix is different — the parser needs to learn the shape pnpm now emits. Each of the six new tests fails against the pre-fix script and passes after; the seven existing cases, including the #1338 content-swap coverage, are untouched and pass against both. Co-Authored-By: Claude <noreply@anthropic.com>
f38193f to
a1e7ea4
Compare
End-to-end verification against the repo's real lockfileUnit fixtures prove the branch logic; this drives the actual CLI against a copy of this repo's real A — patch content edited, B — declared patch file deleted from disk, entry has no hash (proves the ENOENT check is no longer gated behind C — Note C reports as a guard failure with a line number, not as drift — the two have different fixes, and telling a reader to run Untampered, the same script exits 0 on this repo's real tree, so the stricter parser does not false-positive on what pnpm emits today. |
Thinking Path
Linked Issues or Issue Description
Refs: BLO-27241
Related PRs (both merged, neither duplicates this work):
No open PR overlaps: searched
repo:Blockcast/paperclip lockfile(157 results reviewed) andlockfile consistency guard in:title. The only other open policy-guard PR, #1183, covers workflow block scalars — unrelated.What Changed
if (repoRoot && lockedEntry.hash); an absent, empty, or unparsed hash skipped the block, pushed nothing, and fell through to the next key.breakout of a block returns a partial Map, structurally indistinguishable from a complete parse.main()reports a parse failure distinctly from drift, because the fix differs: the parser needs to learn the shape pnpm now emits, rather than someone re-runningpnpm install --lockfile-only.hashkey,hash: "", missing patch file with no hash, ahash:line not matching the": "shape, a 3-space-indentedoverridesblock, and a package key with no trailing:.Verification
node --test scripts/check-lockfile-overrides-consistency.test.mjs→ 13/13 pass, run locally.node ./scripts/check-lockfile-overrides-consistency.mjs→ green (exit 0) against this repo's realpackage.json/pnpm-lock.yaml, so the stricter parser does not false-positive on the shape pnpm emits today.node ./scripts/check-commit-author-attribution.mjs --base origin/master --head HEAD→ clean.Risks
The tradeoff is deliberate and worth stating plainly: the guard now throws on lockfile shapes it does not recognise, and it runs on every PR plus hourly on master. If pnpm changes its serialisation, this turns fleet-wide red instead of silently green. That is the intended direction for a guard — a control that cannot verify must not report success — and the failure message names the file to update. Valid pnpm shapes are unchanged; the repo's own lockfile parses green.
No UI surface. No migration. No documentation outside the script's own comments, which are updated in place.
Model Used
Claude Opus 4.5 (
claude-opus-4-5), extended thinking, with tool use / code execution — tests and the negative control were executed in a real checkout, not reasoned about.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue templateNote on the force-push. The prior revision of this branch was rebuilt, for two reasons:
allyblockcast[bot]App identity (the MCP write path stamps it), failingpolicyon the commit-attribution gate — that, not the guard tests, was the actual CI failure. The guard's own tests passed 14/14 inside that same red job, andverify/build/typecheck/general_testswereskippedas a knock-on rather than independently broken.computePnpmPatchHashreproduces@pnpm/crypto.base32-hashand was verified against this repo's own committed hashes, and the BLO-24169 reproduction context on the tests. On a guard whose entire failure mode is subtlety, that context is load-bearing, and BLO-27241 asks for an additive change rather than a rewrite. The rebuilt branch is 229 insertions / 18 deletions where the prior one was 95 / 179; the seven pre-existing tests keep their original names and assertions.