ci: add release.yml so the npm trusted-publisher binding becomes live - #81
Conversation
The trusted publisher for this package is already configured on npm and names `--file release.yml --env npm-release --allow-publish`. Measured 2026-08-03, that file returned 404 in this repository, so the binding named a workflow that never ran and therefore never matched. Publishing has stayed on hand-run workstation tokens. This adds the missing workflow. The filename and the `npm-release` environment are fixed by the npm-side configuration and cannot be renamed without redoing the trust setup. Authentication is OIDC only: the job requests `id-token: write` and the workflow carries no npm token of any kind. npm exchanges the Actions id-token for a short-lived publish credential, which is the migration path off the 2FA-bypass tokens npm removes in January 2027. Provenance is generated automatically by npm under trusted publishing. That holds only because this repository is public: npm does not generate provenance for private repositories even when the package is public. Two gates fail closed before any work is done: the tag must agree with package.json, and the version must not already exist on the registry, since npm versions are immutable. `workflow_dispatch` with a `dry_run` input defaulting to true runs every gate and the OIDC mint without publishing, so the wiring can be proven before a real release depends on it. Agent: Vespasian
|
[REVIEW] NO_GO — #81 @ 27f21c5 — lens: correctness+security+gates, reviewer unresolved-account002 (1 of 1) Reviewed the full diff from fetched Commands and measured results (all unpiped):
Blocking P0/P1 findings:
Non-blocking follow-up:
No secrets were found in the changed workflow. Action dependencies are commit-pinned, checkout credentials are not persisted, the runner is GitHub-hosted, and job permissions are otherwise least-privilege. |
Agent: unresolved-account002
|
[REVIEW] GO — #81 @ fb621f9 — lens: correctness+security+gates, reviewer unresolved-account002 (1 of 1) Focused re-review of the two named Remediation verified:
Verification on the remediated candidate:
Blocking P0/P1 findings: none remain. The prior P2 direct-vs-declared test invocation was fixed in the same narrow remediation. GitHub's required |
What this closes
npm trusted publishing is already configured for
@hasna/conversations. Thebinding names
--file release.yml --env npm-release --allow-publish.That file did not exist in this repository. Measured 2026-08-03:
.github/workflows/release.ymlreturned 404, verified with a positive controlin the same call so the probe is known to work. The binding was therefore
inert — it named a workflow that never ran and so never matched — and every
release has continued to be hand-published from a workstation.
The filename
release.ymland the environmentnpm-releaseare not freechoices. Both strings are already recorded on npm's side as part of the live
trusted-publisher configuration. Renaming either one silently de-authorises
publishing: the binding stops matching and the failure surfaces as an auth
error that never mentions the rename. Change them only together with
npm trust.Why a minimal OIDC workflow rather than a port of the accounts harness
hasna/accounts/.github/workflows/release.ymlis the only release pipeline onthis fleet that has demonstrably reached the publish step, and it was the
reference. It was deliberately not copied. Three reasons, the first two
being the strongest:
NPM_DIST_TAG_TOKEN,RELEASE_APP_IDandRELEASE_APP_PRIVATE_KEYtoexist in the repository's
npm-releaseenvironment, and gates on theirpresence before doing anything else. None of those secrets exists in this
repository. Porting it produces a workflow that is red on arrival: the npm
binding stays inert and CI goes red.
promotestep authenticates with
NPM_DIST_TAG_TOKEN— a granular npm token, whichis precisely the credential class npm removes from direct publishing around
January 2027. The whole point of moving to CI is to stop depending on that.
automatically under trusted publishing, so the harness is not what buys
provenance here. It also depends on
scripts/release-provenance.ts, whichdoes not exist in this repository.
The condition that makes point 3 true, stated because the next person will
not know it: npm does not generate provenance for private
repositories, even when the package itself is public.
hasna/conversationsispublic (
private=false, measured, with a control read against a known-privaterepo). If this repository is ever made private, provenance silently stops
being produced and nothing in this workflow will tell you.
What this gives up, stated rather than glossed: no App-minted admin token,
no live ruleset preflight, no staged-then-promoted publish, and no
re-verification of controls between stages. Those are real properties of the
accounts pipeline that this workflow does not have. They are a follow-up gated
on someone provisioning those three environment secrets — a release-policy
decision, not something to smuggle into a CI plumbing PR.
What it does
npm/conversations/v*tags. This convention already exists in this repositoryand matches what
hasna/accountsuses.permissions: id-token: writeon the job. The workflow contains no npmtoken of any kind — that is the entire point. npm exchanges the Actions
OIDC id-token for a short-lived, publish-scoped credential.
npm publish, notbun publish: bun has no OIDC trustedpublishing support and cannot authenticate here at all.
--provenanceis passed explicitly. npm documents provenance as automaticunder trusted publishing; that has been reported not to hold in practice, and
the flag is a no-op when it already is, so passing it strictly dominates.
package.json, and the version must not already exist on the registry, sincenpm versions are immutable.
prefix is rejected by name.
below 11.5.1, the minimum for trusted publishing.
workflow_dispatchwithdry_rundefaulting to true runs every gate andthe OIDC mint without publishing. This workflow has never executed, so
there is a way to prove the wiring before a real release depends on it.
The
npm-releaseenvironment is deliberately unprotectedIt was created with no protection rules, and that is a considered choice rather
than an omission.
The npm trust binding is itself the gate. It binds the triple
(repository, workflow file, environment), so npm rejects any publish whose
OIDC claim names a different workflow file. An unprotected environment
therefore does not let some other workflow in this repository publish the
package.
Meanwhile a misconfigured deployment-branch policy silently blocks the
tag-triggered release — which is the exact failure mode this PR exists to
remove. Tag-restriction policies are worth evaluating as defence-in-depth, and
that is being handled separately rather than guessed at here.
Verification performed
environment: npm-releaseandid-token: writeconfirmed byreading the parsed tree, not by eyeballing the file.
11.5.1 accepted — it can both pass and fail, at the correct boundary.
accepted, mismatched version rejected, unrecognised prefix rejected.
npm-releaseGitHub environment was created and read back from theforge by an independent call, with a 404 negative control proving the read
discriminates.
vacuous. The workflow contains no credential by construction.
hasna/accountsalready uses, sono new unvetted action version enters the fleet.
Gate results on this branch
bun run typecheck— exit 0.bun testwas not run on this branch. The station was at loadavg 29 on 20 cores and running eight full suites was disproportionate for a change that adds one YAML file under.github/workflows/which no test imports. The equivalent comparison was done once, onhasna/todos: its integration failures reproduce identically on unmodifiedmainat the same base commit, confirming they are pre-existing. CI on this PR exercises the repository's own suite.Task: A3-00308
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.