Skip to content

ci(publish): pin the trusted-publishing npm and add a token fallback - #732

Merged
drewstone merged 1 commit into
mainfrom
ci/publish-trusted-publishing
Sep 2, 2026
Merged

ci(publish): pin the trusted-publishing npm and add a token fallback#732
drewstone merged 1 commit into
mainfrom
ci/publish-trusted-publishing

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

What this changes

The npm publish is already tokenless. There is no NPM_TOKEN secret on this repository or on the tangle-network organization, and @tangle-network/agent-eval@0.173.0 carries an SLSA provenance attestation with _npmUser: "GitHub Actions" — the signature of an OIDC publish. So the trusted publisher for @tangle-network/agent-eval is already registered and working.

This makes that path deterministic and adds a break-glass fallback, matching agent-sdk #284 and agent-dev-container/.github/workflows/publish-sandbox-sdk.yml.

Change Why
npm install -g npm@11.18.0 with an exact-version assertion, replacing npm@11 A floating npm@11 accepts any future 11.x. npm 12.0.0 ships a provenance publish that fails with MODULE_NOT_FOUND for sigstore. The OIDC floor is 11.5.1. Same pin agent-sdk release.yml carries.
NPM_CONFIG_PROVENANCE: 'true' on the publish-npm job Requests an attestation on both the OIDC path and the fallback path, and covers a publish an npm lifecycle script starts. --provenance stays on the command.
A Configure npm auth step that writes an _authToken only when NPM_TOKEN is set Break-glass for repairing a publish by hand. The secret is unset, so today's behavior does not change. A token set here takes precedence over OIDC, which is the agent-sdk #284 failure, so the step prints which path it took.

permissions: contents: read + id-token: write were already present on publish-npm and are unchanged.

Release semantics: unchanged

  • Trigger is still push on a v* tag or workflow_dispatch.
  • The verify job still locks tag to package.json version and to the Python client version.
  • publish-npm is still idempotent, and publish-pypi still runs after it.

Note: this repository has no tip-of-main rule for a release tag. It checks tag-to-version equality only, unlike agent-runtime, which also verifies that the tag sits on the tip of main or of its release/<major>.<minor>.x line. Out of scope here; worth adding separately.

PyPI: already trusted publishing, no change

publish-pypi uses pypa/gh-action-pypi-publish@release/v1 with id-token: write and no password: input, and no PyPI token secret exists on the repository or the organization. That is PyPI trusted publishing, and agent-eval-rpc==0.173.0 published on 2026-09-02. Left as is.

Checks

  • actionlint .github/workflows/publish.yml — clean (shellcheck available).
  • bash -n on every run block in the file — clean.

Before merge

The npm trusted publisher already exists for @tangle-network/agent-eval. Nothing to register.

The npm publish already runs tokenless: no NPM_TOKEN secret exists on the
repository or the tangle-network organization, and 0.173.0 carries an SLSA
provenance attestation published by "GitHub Actions". This makes the path
deterministic and gives it a break-glass fallback.

Pin npm to the exact 11.18.0 that agent-sdk release.yml uses. A floating
`npm@11` accepts any future 11.x, and npm 12.0.0 ships a provenance publish
that fails with MODULE_NOT_FOUND for sigstore. The OIDC floor is 11.5.1.

Set NPM_CONFIG_PROVENANCE so an attestation is requested on both the OIDC
path and the fallback path.

Read NPM_TOKEN as an explicit fallback. It is unset today, so the OIDC path
is unchanged. A token set later takes precedence over OIDC, which is the
agent-sdk #284 failure, so the step says that in the log.

Release semantics do not change: the v* tag trigger, the tag/version lock in
the verify job, and the PyPI job that runs after npm all stay as they were.

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — f0b10af8

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

This approval is provisional. It rests on the audit running. If the audit cannot run — for example the CLI bridge rejects it — this approval is dismissed rather than left standing, so an unrun check never reads as a passing one.

tangletools · auto-approval · reason: drewstone_author · 2026-09-02T03:24:07Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Value Audit — sound

Verdict sound
Coverage 1 of 2 lenses (value)
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 161.7s (2 bridge agents)
Total 161.7s

⚠️ Partial audit — the verdict covers only value. usefulness: agent returned no usable verdict. Treat the missing lens as unexamined, not as clear.

💰 Value — sound

Makes the npm trusted-publishing path deterministic (exact npm pin + assertion, provenance env) and adds an unset-by-default NPM_TOKEN break-glass fallback — a coherent, well-documented hardening of an already-working OIDC publish.

  • What it does: Three changes to the publish-npm job in .github/workflows/publish.yml: (1) pins the npm CLI to exact version 11.18.0 with a test "$(npm --version)" = '11.18.0' assertion, replacing the floating npm install -g npm@11 that was previously inlined in the publish step (publish.yml:180-183, previously 182); (2) adds job-level NPM_CONFIG_PROVENANCE: 'true' so every publish — including one a lifecyc
  • Goals it achieves: Determinism and a safe repair path for an already-functioning tokenless publish. The floating npm@11 could silently pull a future 11.x regression (or the broken npm 12 provenance line); the exact pin plus assertion fails fast instead. The break-glass fallback lets a human repair a publish by hand via NPM_TOKEN without disturbing today's OIDC behavior, and the env-level provenance flag guarantees
  • Assessment: Good on its merits and fully in the grain of the file. The prior version already documented OIDC, the npm 12 sigstore break, and the >=11.5.1 floor — this change consolidates that scattered reasoning into a dedicated, named, asserted step rather than a bash comment inside the publish command. Comments are precise (explain precedence, the empty-_authToken gotcha, and the provenance-coverage rationa
  • Better / existing approach: none — this is the right approach. I grepped .github/ for NPM_CONFIG_PROVENANCE|_authToken|npm install -g npm@|trusted publishing|OIDC; the only matches are in this file. The repo has only ci.yml and publish.yml (no shared release action/reusable workflow to extend), and the sibling patterns the PR cites (agent-sdk #284, agent-dev-container) live in other repositories, not here. The change is
  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge warning: opencode/kimi-for-coding/k2p7: opencode: opencode error event without a message: {"type":"error","timestamp":1788319595710,"sessionID":"ses_f9fd88668ffeQSoy579BG16OtT","error":{"name":"UnknownError","data":{"message":"Unexpected server error. Check server logs for details.","ref":"err_9c92adbf"}}}; opencode/zai-coding-plan/glm-5.2: opencode: opencode error event without a message: {"type":"error",

🎯 Usefulness — error

usefulness agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/zai-coding-plan/glm-5.2: opencode: opencode error event without a message: {"type":"error","timestamp":1788319669858,"sessionID":"ses_f9fd88676ffe0TNSo4YdqKJVVQ","error":{"name":"APIError","data":{"message":"Usage limit reached for 5 hour. Your limit will reset at 2026-09-02 12:05:20","statusCode":429,"isRetryable":true,"responseHeaders":{"alt-svc":"h3=":443"; ma=3600","connection":"kee

No concerns from the lens that ran (value). The missing lens examined nothing, so this is not a full clean bill of health.


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260902T032913Z

@tangletools

Copy link
Copy Markdown
Contributor

✅ No Blockers — f0b10af8

Review health 100/100 · Reviewer score 83/100 · Confidence 65/100 · 6 findings (6 low)

opencode DeepSeek v4 Pro opencode DeepSeek v4 Flash aggregate
Readiness 89 83 83
Confidence 65 65 65
Correctness 89 83 83
Security 89 83 83
Testing 89 83 83
Architecture 89 83 83

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 1/1 planned shots over 1 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 1/1 planned shots over 1 changed files. Global verifier still owns final merge decision.

🟡 LOW Break-glass token fallback branch is the one untested code path — .github/workflows/publish.yml

The NPM_TOKEN branch (npm config set --location=user //registry.npmjs.org/:_authToken) is never exercised in CI because the secret is unset by default — yet it is the path a maintainer relies on to repair a publish by hand. Its success depends on the untested combination of npm 11.18.0 provenance signing with a legacy token (npm had a history of provenance+token failures). Since NPM_CONFIG_PROVENANCE=true is job-wide and id-token: write is always granted, provenance should still work, but this should be validated once with a scoped test token before it is needed in an outage.

🟡 LOW Exact npm pin is a hard single point of failure for every release — .github/workflows/publish.yml

npm install -g npm@11.18.0 hard-pins one patch release. If that version is ever yanked from the registry, or carries a later-discovered regression, every tag/dispatch release hard-fails. The test "$(npm --version)" = '11.18.0' guard fails loud (good), but nothing prompts a bump when npm 11.x security fixes land. The previous npm@11 floated to the newest 11.x. Recommend documenting a bump cadence in the step comment, or a reusable workflow input, so the pin does not silently rot. Verified 11.18.0 exists today (npm view exit 0).

🟡 LOW NPM_CONFIG_PROVENANCE is redundant with the --provenance flag — .github/workflows/publish.yml

The job-level env sets NPM_CONFIG_PROVENANCE: 'true' while the publish command already passes npm publish --provenance. Both request provenance, so the env var is redundant for the actual publish; as a job-level env it also blankets the npm install -g npm@11.18.0 and pnpm install/build steps, where the key is inert. The comment claims the variable additionally covers 'a publish an npm lifecycle script starts', which is accurate only if a lifecycle script invoked npm publish — it does not here (prepublishOnly is pnpm build). Harmless, no functional bug. Fix (optional): keep the flag only, or keep the env var and drop the flag, and tighten the comment to avoid implying an npm lifecycle publish that does not exist.

🟡 LOW Publish step name no longer matches its behavior — .github/workflows/publish.yml

The step is named 'Publish to npm (OIDC trusted publishing)', but after this change it publishes via OIDC only when NPM_TOKEN is unset and falls back to a legacy token when NPM_TOKEN is set. The name is now misleading for the fallback path (a reader skimming the log or this step name would not learn a token publish happened). Impact: confusion only, no functional effect. Fix: rename to 'Publish to npm' or 'Publish to npm (OIDC, token fallback)'.

🟡 LOW Token precedence is a silent footgun if NPM_TOKEN is ever set — .github/workflows/publish.yml

The design is 'leave NPM_TOKEN unset so OIDC wins', but the code does not enforce it: if NPM_TOKEN becomes available (repo or org-level secret inheritance), every publish silently switches from short-lived OIDC credentials to the long-lived token, with only an echo line announcing it. The agent-sdk #284 incident is cited as why precedence is intentional, but a short-lived-credential policy is weaker the moment the secret exists. Suggest emitting a ::warning:: annotation (and consider failing) when the token path is taken, so the switch is visible in the run log.

🟡 LOW sed _authToken cleanup swallows failure that would break OIDC — .github/workflows/publish.yml

sed -i '/_authToken/d' "$HOME/.npmrc" 2>/dev/null || true discards errors and missing-file status. If $HOME/.npmrc exists but is not writable, or a future setup-node step adds registry-url, a stale //registry.npmjs.org/:_authToken line survives and npm silently disables the OIDC exchange, producing a confusing 404 on PUT (the exact agent-sdk #284 failure mode the comment cites). Since a token takes precedence over OIDC, the step should fail loud when it intended OIDC but finds a leftover token line it could not remove. Today the publish job writes no .npmrc, so this is latent, not live.


tangletools · 2026-09-02T03:30:02Z · trace

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved — 6 non-blocking findings — f0b10af8

Full multi-shot audit completed 1/1 planned shots over 1 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 1/1 planned shots over 1 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-09-02T03:30:02Z · immutable trace

@drewstone
drewstone merged commit 3c11a59 into main Sep 2, 2026
2 checks passed
@drewstone
drewstone deleted the ci/publish-trusted-publishing branch September 2, 2026 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants