feat(quality): ask npm once whether the lockfile still matches the manifest - #767
Merged
Conversation
…nifest The main to beta to development sync merge can carry main's package.json across while keeping development's package-lock.json. Neither file is individually wrong, the two are separate paths so git reports no conflict, and npm then refuses to install anything. Measured on 2026-09-12 (#766): one broken install read as sixteen failing jobs on decidiq and twenty-three on shillinq, every one of them npm ci dying at step one, and it blocked every open PR in both repos because Actions builds pull_request runs on the merge of head into base. This job asks that one question with npm ci --dry-run, in seconds, before anything is installed or built. It WARNS, it does not block. All 21 core apps resolve this workflow at @main, so a gate merged as blocking fails every affected repo the same minute. Measured today on the toolchain pins (Node 24.11.1, npm 11.19.1) with each repo's own .npmrc: 21 of 21 pass on development and 21 of 21 on beta, so it would fail nothing. lockfile-sync-blocking flips it per repo, the same shape as reuse-blocking. Only npm's EUSAGE desync answer counts as a finding, and only when the message names the two files: npm answers EUSAGE for a missing lockfile as well. Anything else is reported as no verdict rather than as a fact about the repo. Verified against three real trees: decidiq at 69971ec (pre-repair) classifies as a desync, decidiq development classifies as agreeing, and a tree with no lockfile classifies as no verdict rather than as a finding.
Contributor
Author
|
One red check on this PR, and it is inherited rather than this diff's.
Evidence it is not mine, rather than an assertion that it is not:
Reported and not fixed, per the inherited-debt rule. It belongs to whoever owns the runner's push-event scoping, not to a workflow PR. Everything else on this PR is green: 22 successes, zero pending. |
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.
Closes half of #766.
The defect
The
maintobetatodevelopmentsync merge takesmain'spackage.jsonacross and keeps
development'spackage-lock.json. Neither file isindividually wrong. The merge has no conflict to report, because the two are
separate paths. Git has no reason to complain and npm has every reason to.
Measured 2026-09-12 in #766: one broken install read as sixteen failing jobs
on decidiq and twenty-three on shillinq. Eslint, stylelint, build, l10n, unit
tests, npm audit, licence, gates: every one of them was
npm cidying at stepone. It also blocked every open PR in both repos, because Actions checks out
the merge of head into base for
pull_requestevents, so each PR inherited thebroken base through no fault of its own.
What lands
A
Lockfile Syncjob that runsnpm ci --dry-run --ignore-scriptsand nothingelse. It answers in seconds the question those thirty-nine jobs were each
discovering separately.
npm installsucceeding proves nothing here: it REWRITES the lockfile tomatch, which is the repair rather than the test.
npm lsreads an installedtree. Only
npm cicompares the two files as committed.It warns, it does not block
All 21 core apps resolve this workflow at
@main, so a gate merged as blockingfails every affected repo the same minute. That has happened before on this
fleet and it is why the default is off.
Measured today, with
npm ciitself rather than a proxy, on the toolchainpins (Node 24.11.1, npm 11.19.1) and with each repo's own
.npmrc:development, all 21 core appsbeta, all 21 core appsSo the would-fail count is zero. decidiq and shillinq, the two apps #766
found broken this morning, were repaired by decidiq#1300 and shillinq#1591 and
are clean at their current tips.
lockfile-sync-blocking: trueflips it perrepo, the same shape as
reuse-blockingandcheck-code-blocking. Re-take themeasurement before flipping it anywhere, rather than trusting this table later.
Only one npm answer counts as a finding
npm reports
EUSAGEfor the desync AND for a missing lockfile, with the samecode and a different sentence. A code on its own is not a diagnosis, so the
check requires both the code and the message that names the two files. Every
other non-zero exit is a registry hiccup, a proxy or a missing optional
platform binary, and is recorded as no verdict rather than as a fact about
the repo. A gate that reds on those is a gate that gets switched off.
Verification
The step logic was run against three real trees on the CI pins:
69971ec, the commit before the repairdevelopmenttodaypackage.jsonand no lockfileThe first one is the positive control: this check demonstrably fails on the
real defect, so its green elsewhere means something.
Repo assertions, all passing on the patched workflow:
assert-quality-report-gates-every-leg.py(the new job is inQuality Report'sneeds:, so it can gate once a repo turns it on),assert-no-producer-deletes-a-verdict.py,assert-run-steps-resolvable.py,and
yaml.safe_load.What this PR does not do
#766 asks for two things. This is the first. The second, making the sync
workflow regenerate the lock when it carries a
package.jsonacross (or refuseand open an issue), is a change to
sync-to-beta.ymlandfleet-back-merge.ymland is not in this diff.
🤖 Generated with Claude Code