Bind the update to two files and one decision

Start with the exact manifest and lockfile diff. npm documents that npm install uses the lockfile when its resolved versions satisfy the declared ranges and updates the lockfile when they do not. npm ci is the clean-install check: it expects an existing lockfile and fails when the manifest and lockfile do not match.

Record:

  • package name and old/new version or range;
  • direct versus transitive change;
  • resolved URL or registry identity;
  • lockfile diff and integrity data;
  • engine, peer, native, and platform assumptions; and
  • the named human who decides whether residual risk is acceptable.

Run the clean behavior gate

Use a clean dependency tree rather than the agent's already-warm node_modules:

npm ci --ignore-scripts --no-audit --no-fund
npm run build
npm test

The flags do not make the dependency safe. --ignore-scripts prevents install- time lifecycle scripts while you inspect them; it does not prove that the application works when a package's required build step is withheld. If the package needs a lifecycle script, review and allow that exact package and version in a controlled environment instead of silently running every script.

The local P78 fixture upgraded a package from 1.0.0 to 1.1.0 on Node 22.18.0, npm 10.9.3, and Darwin arm64. It bound the direct manifest specification and target lockfile package, rejected an injected unrelated node, ran a clean dependency-tree install, and passed build and behavior tests. The candidate package advertised postinstall; the fixture blocked its execution and left the merge decision blocked for human review.

npm ci removes the existing dependency tree, but it does not isolate npm configuration, cache, credentials, environment variables, operating system, architecture, or registry behavior. Use a fresh checkout or controlled worker when those boundaries matter.

Treat lifecycle scripts as code

npm's lifecycle documentation lists preinstall, install, postinstall, prepare, and related events. A dependency's script can run during installation even when the application code looks unchanged. Inspect the candidate manifest and the resolved package contents. Record each script, why it exists, what files or network access it can touch, and whether a reviewer allows it.

An allowlist is narrower than “scripts are fine.” npm 11 documents a version-pinned install-script approval mechanism, but this fixture ran on npm 10.9.3 and did not test that npm 11 workflow. Its observed claim is limited to --ignore-scripts blocking the synthetic postinstall.

Treat audit output as one evidence channel

npm audit uses the dependency description and asks the configured registry for known-vulnerability information. Its exit code alone does not say whether evidence is usable: a valid adverse report can exit nonzero, while malformed or missing output is not a clean report. Parse the JSON, validate the report structure, record severity counts, and apply an explicit threshold. A zero exit code is not a guarantee that the package has no vulnerability or supply-chain risk.

The fixture exercises three deterministic negative cases. It blocks malformed or unavailable audit evidence, a report with a high-severity finding, and a missing human reviewer. Its actual local audit reported no high or critical finding, but the merge decision remained blocked because lifecycle approval and a named reviewer were absent.

OpenSSF Scorecard checks can add repository and dependency signals. They are signals with scope and assumptions, not a maintainer identity proof or a substitute for reading the change.

Define recovery before merge

Restore package.json and the lockfile together to the reviewed prior revision. Run the clean dependency-tree install, build, behavior tests, and runtime verification again. A simple downgrade may be insufficient when the update changed migrations, generated artifacts, native components, persisted data, or external formats; name the additional recovery owner and procedure before merge.

Merge decision

Merge only when the reviewer can point to the exact diff, clean-install result, build and behavior result, lifecycle-script decision, vulnerability evidence, and rollback or downgrade path. Stop when any of these is missing.

Download the public dependency-update gate and run:

node dependency-update-gate.js

An exit code of zero means the fixture assertions passed. It does not approve a real pull request; the example deliberately leaves reviewer and lifecycle approval absent, so its merge decision is blocked. Compare the boundary with human ownership of a pull request, API integration testing, and the Terraform plan gate.