Publish by OIDC trusted publishing instead of the expired npm token
The publish credential is dead, and nothing said so
pm-ops last reached npm on 2026.8.17. main reads 2026.8.26, with no matching tag and nothing on the registry. Sixteen of the eighteen published fleet packages are in the same state, all stopping on 2026-08-16 or 2026-08-17.
The release job fails at Publish npm package:
npm error code E404
npm error 404 Not Found - PUT https://registry.npmjs.org/pm-ops - Not found
A 404 on PUT is a rejected write credential, not a missing package. The registry answers 404 rather than 401 so it does not disclose package existence to an unauthorised caller — which is why this reads like a normal first publish rather than an outage. The repository secret was already rotated on 2026-08-22 and the failures continued unchanged; npm whoami against the maintainer host's stored auth returns 401. The credential is dead, not mis-stored.
Nothing else noticed, because the version bump and the release commit both land before the publish step — deliberately, so an interrupted release resumes the same version. So main advances whether or not the artifact ships and every other job stays green.
The change
Trusted publishing removes the credential that can expire: the registry mints a short-lived one from the workflow's own OIDC identity. Two changes had to go together, and the second is the easy one to miss:
NODE_AUTH_TOKEN/secrets.NPM_TOKENare gone from the publish step.id-token: writeandregistry-urlstay — they are what make OIDC reachable.- npm is raised to
>=11.5.1before the publish step. node 22 ships npm 10.x, which has no trusted-publishing support and would silently fall back to token auth — producing exactly the same E404 and looking like the migration simply failed.
Verification
Two guards fail closed, and both were mutation-checked rather than assumed:
| Mutation | Result |
|---|---|
Reintroduce NODE_AUTH_TOKEN | OIDC test fails |
| Delete the npm upgrade step | npm-version test fails |
| Unmodified | both pass |
Gates: build, check, test, docstring, changelog:full + changelog:check, and pm health --strict-exit against the pinned binary — all green.
Requires a one-time npmjs.com setup binding pm-ops to unbraind/pm-ops and release.yml before the next release can publish. Until that exists this workflow fails closed, which is correct but is not a release.
pm item
Summary by Sourcery
Migrate npm releases to fail-closed OIDC trusted publishing with preflight validation and strengthened release integrity checks.
New Features:
- Enable npm releases to authenticate through OIDC trusted publishing instead of a long-lived npm token.
- Add a preflight identity check that verifies registry acceptance before release files are mutated.
- Add forward-looking audits for commit identities and absolute home paths.
Bug Fixes:
- Prevent releases from advancing versions and commits when npm cannot accept the workflow identity.
- Ensure the effective npm version supports trusted publishing at installation and publication time.
- Ensure release changelog dates are derived from version inputs and validate all release-time generator invocations.
- Report HTTP 503 responses from web status as degraded rather than unreachable.
Enhancements:
- Harden release workflow execution with main-branch job gating, credential scrubbing, bounded diagnostics, and safer environment handling.
CI:
- Run changelog-date verification in CI and release checks.
- Add mutation-tested workflow, identity-audit, and changelog-verifier coverage.
Tests:
- Add comprehensive fixture and workflow tests covering trusted publishing guards, changelog-date validation, identity controls, credential removal, and release preflight behavior.
Chores:
- Ignore SDK workspace transaction journals.
Summary by cubic
Switches npm publishing from the expired stored token to OIDC trusted publishing so releases no longer fail at publish, and fails the release before any version bump when npm refuses the workflow's identity. Adds a forward-looking identity gate that freezes the privacy issue in reachable history and stops new unapproved identities or absolute home paths from appearing.
- The stored token was rejected from 2026-08-17 UTC, leaving sixteen of the eighteen published fleet packages unreleased while version bumps and release commits still landed.
- Removes cached credentials structurally in registry-scoped, unscoped, and global-config spellings —
_authToken,_auth,username/_password, mTLS pairs,npm login, space-separated options, andNPM_CONFIG_*overrides — scrubbing both the userconfig and npm's global config, with each scrub expression asserted on its own; the command guard joins backslash continuations before splitting and reduces every npm config command to the key it sets, refusing the credential names regardless of flags or separate-value options. - Pins npm to exactly 11.19.0 and re-verifies the effective version right before publish with the window closed through the end of the step, because node 22's bundled npm 10.x cannot exchange an OIDC token; publish also strips every legacy credential line
setup-nodewrites, since a configured-but-empty token blocks the exchange. - The preflight asks the registry for a credential before anything is bumped or committed, distinguishes a registry outage and rate limiting from an identity refusal, encodes scoped package names the way npm expects, only runs on
refs/heads/main, and passes workflow context to run scripts via env rather than shell interpolation, scanning every block-scalar spelling of a run step in both YAML indicator orders. Its success message also warns it does not prove which actions the binding permits, since a staging-scoped binding still fails at publish after mutating main. - Guards are verified by property: every enumerated mutation must fail the suite, so the count is a measurement rather than a promise that goes stale; the permission parser now normalizes flow mappings to one entry per line so a correctly declared inline
id-token: writeno longer false-fails, and the release-job boundary also accepts job ids that lead with an underscore. - The identity audit rejects any commit after the recorded baseline with an unapproved identity or a newly added absolute home path; the baseline must be an ancestor of the base ref, and the path scan covers commit messages in addition to diff lines, since rewriting the frozen history would orphan release tags and break published npm provenance.
- Audit control files can now be updated by a PR: a proposed change only takes effect once it lands on the base ref, and only a base ref that lacks the control falls through to the working tree — every other read failure is rethrown so the audit cannot fail open, and the interpolation scan is scoped to the release job so a later job cannot trip it.
- The changelog-date verifier now runs as
verify:release-changelog-dateinrelease:checkand CI — previously it shipped but was never executed — and is rewritten in TypeScript to find release-time invocations the shell scan missed: it joins line continuations, expands shared options arrays, understands all three version-input spellings, splits logical commands on shell separators with or without spacing while keeping bare pipes inside arguments, strips unquoted trailing comments so a comment cannot satisfy the flag check, and fails any command that carries a version but no--date-from-version, with the rules executed against twenty-one fixtures. It asserts the flag changes the heading rather than pinning the generator's current default. pm web statusnow separates reachability from health: a server answering 503 reports as degraded rather than down..gitignorenow excludes SDK workspace-transaction journals so committing one no longer fails the health gate.
Migration
- Requires a one-time npmjs.com setup binding
pm-opstounbraind/pm-opsandrelease.yml.