Fix: fail CI when package-lock.json is not a fixed point (and correct the stale adm-zip dev flag) - #345
Open
AmaadMartin wants to merge 2 commits into
Open
Conversation
added 2 commits
July 30, 2026 16:10
`node_modules/adm-zip` carried a stale `"dev": true` flag. adm-zip is a runtime `dependencies` entry of `core/package.json` and is imported by shipped source (`core/src/skills/loader.ts`); it is only a devDependency of the `dev` workspace. The lock had recorded the dev-only view, so an `--omit=dev` resolve driven by this lock would drop a package that runtime code imports. Produced with `npm install --package-lock-only`, not by hand-editing. The installed tree is unchanged: the flag only affects `--omit=dev` installs, and CI installs with dev dependencies included.
npm ci makes the install deterministic, but it only checks that the lock can satisfy the declared ranges -- it exits 0 on a lock that the resolver would still rewrite. That is exactly the drift class that went unnoticed here, so the switch alone does not close the hole. Re-resolve the lock and fail on any diff. Runs immediately after the install so a drifted lock fails in seconds rather than after the full build-and-test cycle. Gated to ubuntu-latest: lock content is OS-independent, while the platform-constrained optional nodes and the absence of .gitattributes make the other two legs pure downside. The remedy for a failure is the command the guard itself runs: `npm install --package-lock-only`, then commit the result.
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Problem: The committed
package-lock.jsonis not a fixed point of the resolver, and CI cannot tell.node_modules/adm-zipcarries"dev": true. That flag is semantically incorrect:adm-zipis declared underdependenciesincore/package.json(line 63) and is imported by shipped runtime source atcore/src/skills/loader.ts:7(import AdmZip from 'adm-zip';). It is adevDependenciesentry only of thedevworkspace (dev/package.json:48). Any resolve that honours the flag —npm ci --omit=dev, a production install driven by this lock — would omit a package that runtime code imports.npm installrecomputes the ideal tree and silently repairs the flag in the runner's working copy; nothing ever inspects the working tree afterwards, so the repair is thrown away with the runner and CI stays green. Switching the install tonpm ci(this PR's base) closes the determinism hole but does not catch this defect class:npm civalidates that the lock satisfies the declared ranges, not that the lock is a fixed point. Measured:npm ciexits 0 on the stale lock.Solution: two changes, kept in one PR because the second cannot land green without the first.
Regenerate the lock to a fixed point — produced with
npm install --package-lock-only, never hand-edited. The diff is exactly one deleted line, the"dev": true,onnode_modules/adm-zip.node_modules/@types/adm-zipcorrectly keeps its"dev": true(it really is adevDependenciesentry ofcore).Add a drift guard to
.github/workflows/validation.yaml— re-resolve the lock and fail on any diff:Design notes:
ubuntu-latest: lock content is OS-independent, so one leg is sufficient coverage.continue-on-error, no|| true, no warn-only mode. A guard that only warns reproduces the very defect being fixed.npm install --package-lock-only, then commit the result.--ignore-scripts/--no-audit/cache: 'npm'/node-versionpin was added; toolchain pinning and caching are out of scope here.Deliberately unchanged:
CONTRIBUTING.mdkeeps recommendingnpm installfor local development — contributors legitimately need it to update the lock;npm ciis a CI concern. Nopackage.jsonis modified: this fixes the lock, it does not move any dependency betweendependenciesanddevDependencies..github/workflows/cross-language-integration.ymlis not touched by this PR (the base PR already switches its install).Impact on the installed tree: none. The
devflag only influences--omit=devinstalls, and CI installs with dev dependencies included.npm ciinstalls 1089 packages both before and after this change.Collision check (open PRs on the fork,
gh pr list --state open --limit 100, thengh pr diff --name-onlyon every adjacent hit):feat/ci-npm-ci-deterministic-install— overlaps, so this PR is stacked on it. It switchesnpm install→npm ciinvalidation.yamlandcross-language-integration.yml, i.e. the exact line this change also needed. It does not fix the lock and does not add a drift guard, so it does not land this change; per the overlap rule this branches from Fix: install CI dependencies with npm ci so the committed lockfile is authoritative #338's head instead ofmainand the base is set accordingly.feat/deps-hygiene-check— adds anpm run deps:checkstep and, incidentally, drops the same"dev": trueline as a side effect of regenerating the lock after adding a dependency. Different concern (declaration hygiene, not lock fixed-pointedness); no guard, nonpm ci. Whichever lands second will need a trivial lock re-resolve.fix/ci-npm-cache-node-pin— adds npm caching tovalidation.yaml. Adjacent lines, unrelated concern (and explicitly out of scope here).package-lock.jsonfixed-pointedness or adds a lockfile check.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Coverage note: this change adds zero lines of executable product code — it is one workflow step and one resolver-generated lockfile line — so the new-line-coverage gate is not applicable and no test file was added. No existing test was modified, skipped, weakened or deleted;
vitest.config.tsis untouched. The workflow is the artifact under test, so it is verified by mutation instead: each check below was run against the unfixed state first and shown to FAIL.Unit Tests:
[x] I have added or updated unit tests for my change. — N/A, see coverage note. Targeted regression run of the
adm-zipconsumer instead:npm test -- core/test/skills/loader_test.ts→ 30 passed.[x] All unit tests pass locally.
Mutation proof 1 — the guard fails on a drifted lock (red). With the stale
"dev": truecommitted:Re-verified as a true mutation against this branch: re-injecting
"dev": trueontonode_modules/adm-zipand committing it makes the guard fail with the same one-line diff andexit=1; the scratch commit was then discarded. (Injecting it into the working tree only is not a valid mutation — the guard's ownnpm install --package-lock-onlyrepairs it andgit diffis then correctly empty.)Mutation proof 2 — the guard passes on the fixed lock (green). Same two commands on this branch print nothing and
exit=0.Mutation proof 3 —
npm ciis clean and non-mutating.Mutation proof 4 —
npm cistill fails loudly on a real range disagreement (guard-the-guard). Temporarily settingcore/package.json'sadm-ziprange to^0.6.0:Edit reverted; not committed. This proves the install step retains its validating power and was not silently degraded.
Full local gauntlet on the exact pushed commit (Linux, Node v22.22.2, npm 9.2.0):
npm cinpm install --package-lock-only && git diff --exit-code package-lock.jsonnpm run buildnpm run lintnpm run format:checknpm run docs:checknpx secretlinton both changed filesnpm test -- core/test/skills/loader_test.tsCI status: absent, validated locally instead. This is a stacked PR whose base is
feat/ci-npm-ci-deterministic-install, andvalidation.yamltriggers onpull_request: branches: [main]— so the workflow does not run for this base and no test job will ever appear on this PR. The table above is the substitute evidence, run on the exact commit pushed. The workflow change itself takes effect once the stack lands onmain.Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
To reproduce the whole chain from a clean checkout of this branch:
npm ci— must exit 0 and leavegit status --porcelain package-lock.jsonempty.npm install --package-lock-only && git diff --exit-code package-lock.json— must exit 0 (this is verbatim what the new CI step runs)."dev": true,tonode_modules/adm-zip— commit it, then repeat step 2: it exits 1 and prints the offending node.Note for local reproduction:
npm run docs:checkexits 3 withThe provided git remote "origin" was not validif the checkout has nooriginremote.actions/checkoutalways configures one, so this is a local-scratch-directory artifact, not a regression.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works. — see the coverage note and the four mutation proofs above; no product code was added, so the verification is the red/green mutation protocol rather than a new test file.
[x] New and existing unit tests pass locally with my changes.