chore(deps): enrol installs by package.json, and fail on a missing lockfile

From Copilot's fifteenth pass of #1962. `installDirs` filtered on the presence
of a lockfile, so a missing one silently removed that install from the
comparison rather than failing. For the root — the install every shared source
resolves from — that meant the guard could report success from client locks
alone, which is the deny-by-default contract inverted.

Enrolment is now by `package.json` (an install we are meant to compare), with
the root always enrolled, and a missing lockfile is a named, loud failure. A
`clients/` directory with no `package.json` is not an install and is skipped,
so a stray scratch dir isn't asked for a lockfile it should never have.

Three regression cases: a missing client lockfile in a fixture that IS skewed
(so failing open would show as a pass), a missing ROOT lockfile, and a stray
`clients/` dir that must not be enrolled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
This commit is contained in:
cliffhall 2026-08-10 22:31:05 -04:00
commit 288adbcfd6

View file

@ -377,9 +377,18 @@ function installDirs() {
.map((e) => `clients/${e.name}`)
.sort()
: [];
return ["."]
.concat(clients)
.filter((dir) => existsSync(path.join(repoRoot, dir, "package-lock.json")));
// Enrolment is by `package.json` — an install we are meant to compare —
// NOT by the presence of a lockfile (Copilot, #1962). Filtering on the
// lockfile made a missing one silently drop that install from the
// comparison; for the root, the install every shared source resolves from,
// that meant the guard could report success from client locks alone. A
// missing lockfile is now a loud failure in `main`, not an absent row. The
// root is always enrolled: it is this repo, so its manifest is a given.
return ["."].concat(
clients.filter((dir) =>
existsSync(path.join(repoRoot, dir, "package.json")),
),
);
}
/**
@ -427,6 +436,25 @@ export function main() {
}
const dirs = installDirs();
// A missing lockfile is a failure, not a skipped install: dropping one would
// remove its versions from the comparison and could report a real skew as
// aligned.
const missing = dirs.filter(
(dir) => !existsSync(path.join(repoRoot, dir, "package-lock.json")),
);
if (missing.length > 0) {
console.error(
`verify:dep-lockstep — ${missing.length} install(s) have a package.json but no lockfile:\n`,
);
for (const dir of missing) console.error(` ${dir}/package-lock.json`);
console.error(
"\nEvery install must be compared; skipping one could report a real skew as aligned." +
"\nRun `npm install` there, or remove the install if it is no longer part of the repo.",
);
process.exit(1);
}
const locks = dirs.map((dir) => {
const file = path.join(repoRoot, dir, "package-lock.json");
let lock;