chore(release): retire the FFI publishing guard and activate its parked changesets - #910
Conversation
…ed changesets npm trusted publishing for all seven `@cipherstash/protect-ffi` packages now names `cipherstash/stack` and `release.yml`, so the window the guard existed to protect is closed. `scripts/lint-no-ffi-changeset.mjs` failed CI on any changeset naming an FFI package, because such a changeset bumps all seven through their fixed group and the next release would then attempt a publish npm still attributed to `cipherstash/protectjs-ffi`. Delete it, its self-test, the `lint:ffi-changeset` script and the `tests.yml` step that ran it. There were no fixtures to delete — the self-test wrote its own to a tmpdir, so the plan's "its fixtures" was always wrong. The other half is the point of the guard: rename the two parked changesets back, so the behaviour they describe ships with a changelog entry instead of being reconstructed from the git log months later. - `protect-ffi-lazy-load.md` (minor) — the native binding loads lazily, plus `assertNativeBindingAvailable()` - `protect-ffi-repository-url.md` (patch) — published metadata names `cipherstash/stack` `changeset status` now reports all seven FFI packages at minor, as CIP-3719 step 2 expects. A third changeset is parked on the branch of #905, the `jsonwebtoken` CVE bump. Once this lands nothing reads `.md.deferred` and nothing warns about it, so #905 must rename its own file before merging or the fix ships with an empty changelog. CIP-3719
|
| Name | Type |
|---|---|
| @cipherstash/protect-ffi | Minor |
| @cipherstash/stack-drizzle | Patch |
| @cipherstash/stack-supabase | Patch |
| @cipherstash/stack | Patch |
| @cipherstash/bench | Patch |
| stash | Patch |
| @cipherstash/stack-prisma | Patch |
| @cipherstash/test-kit | Patch |
| @cipherstash/basic-example | Patch |
| @cipherstash/prisma-example | Patch |
| @cipherstash/e2e | Patch |
| @cipherstash/wizard | Patch |
| @cipherstash/protect-ffi-darwin-x64 | Minor |
| @cipherstash/protect-ffi-darwin-arm64 | Minor |
| @cipherstash/protect-ffi-win32-x64-msvc | Minor |
| @cipherstash/protect-ffi-linux-x64-gnu | Minor |
| @cipherstash/protect-ffi-linux-arm64-gnu | Minor |
| @cipherstash/protect-ffi-linux-x64-musl | Minor |
Click here to learn what changesets are, and how to add one.
Click here if you're a maintainer who wants to add a changeset to this PR
…radicted Review findings on the cutover PR, all four in its own additions. **Six orphaned fixtures.** CIP-3719 said to delete "its self-test, fixtures"; the first pass deleted the self-test and left `scripts/__tests__/fixtures/lint-no-ffi-changeset/` behind, then recorded in the plan that no fixtures existed. That conclusion came from checking `scripts/fixtures/` — the wrong directory. The self-test used a tmpdir for two cases only; the rest resolved these six files. Nothing referenced them once the test was gone, and `test:scripts` passes either way, which is why the claim needed checking rather than inferring. Deleted, and the plan's correction reversed rather than left to mislead the next reader. **Prose contradicted its own checklist, twice.** AGENTS.md and the protect-ffi README asserted publishing had moved while the same commit left the plan's "Repoint npm trusted publishing" box unticked; and the plan's summary claimed "Phases 3 and 5 are built" over four unticked Phase 5 boxes. Both reconciled in the direction of what is true: the repoint is done, and all four Phase 5 items verified against the tree — the `./diagnostics` subpath with `import` and `require` conditions, the doctor probe that keeps the separate auth probe, the missing-binary e2e, and both changesets. The repoint box carries what is genuinely still open: npm accepts a publisher scoped to `npm stage publish` alone, which reads as enabled and fails every `npm publish`, and that setting is unverified on all seven. **Step 2's verification criterion was stale.** It told a reviewer to expect a patch bump of the six Stack packages. `.changeset/prisma-next-0-17.md` takes them to major through the fixed group, on `origin/main` already and with no FFI changeset in play. Left as-is it invites reading an unrelated major as evidence the FFI bump misbehaved. AGENTS.md also now names #905 as carrying a parked changeset, since AGENTS.md is the file agents actually read and a `.md.deferred` is inert rather than loud now that the guard is gone. CIP-3719
Round-2 review findings, all in this PR's own prose. Both axes reached the same place from different directions: the docs were written as though the cutover had completed, when what has completed is the first of seven steps. - AGENTS.md and the protect-ffi README asserted publishing works here in unqualified present tense. Nothing has published from this repository yet — 0.31.0 came from the old one, and the release, the verification and the archive are all still ahead. Both now say the path is configured rather than proven, and point at the plan for what is open. - AGENTS.md named #905 as carrying a parked changeset. That is transient state in durable doctrine: the line goes false the day #905 merges, and the fact is already recorded in the plan where such things belong. The general rule stays, the PR number goes. - The Phase 4 step-4 criterion had lost its "only after the versioned pre-flight is green" sentence when the box was ticked, so a reader skimming boxes no longer saw the ordering rule at all. Restored; the note below it still records that the repoint in fact ran ahead of the pre-flight, and why that was judged acceptable. - The changesets-`ignore` rationale still described the guard in the present tense, in a section the earlier reconciliation walked past. Left open deliberately: three independent reviews now suggest replacing the deleted guard with its inverse — fail CI on any `.changeset/*.md.deferred`, since such a file is inert to changesets and, as of this PR, inert to CI too. That is a design decision against CIP-3719's explicit instruction to delete the script, not a defect fix, so it is not taken here. CIP-3719
freshtonic
left a comment
There was a problem hiding this comment.
Approve. The deletion is complete and internally consistent, the two activated changesets are the real payload (written prose, not reconstruction), and the PR body's load-bearing claims all check out under independent verification. CI green.
What I verified:
- No dangling references. The only mention of
lint-no-ffi-changeset/lint:ffi-changesetoutside the plan's historical record is AGENTS.md's own past-tense explanation of what a found.md.deferredfile means — which is the intended one. Script, self-test, six fixtures,package.jsonentry, and thetests.ymlstep all go together; nothing else in the tree loads the fixtures directory. - No
.md.deferredsurvives on this branch, and both renamed changesets carry substantive content: the lazy-load minor (with theassertNativeBindingAvailable()rationale) and the repository-url patch (including the trusted-publishing exact-match requirement and the quietly-404ingrepository.directorydetail). Both name only the wrapper; the fixed group carries the other six. - The Stack-major warning is correct and correctly attributed:
origin/main's.changeset/prisma-next-0-17.mddeclares'@cipherstash/stack-prisma': major, so the six Stack packages go major with or without this PR. Flagging that in the Phase 4 criterion — so nobody reads a 2.0.0 as the FFI bump misbehaving — is exactly the right place for it. - The "finite and named" residue claim holds. I scanned every open PR branch: exactly two carry a distinct parked file —
protect-ffi-auth-error-code.md.deferredon #894's branch andprotect-ffi-jsonwebtoken-cve.md.deferredon #905's — matching the PR body's table. Every other branch just inherits the two files this PR renames, and git's rename detection resolves those at merge with no re-add. That scan is also why I agree with declining the inverted guard: the residue really is two known files with named owners, and a permanent CI check would outlive the practice it polices. The AGENTS.md rule ("nothing detects one for you —git mvit back") is the right durable form. - The docs now match reality rather than aspiration: "configured rather than proven" for a path that has published nothing yet, the Allowed-actions trap called out with the
npm trust listcheck (and honestly left unticked), and therelease.yml-filename-is-load-bearing constraint recorded where a future rename would find it.
One non-blocking note for the follow-through: since the Allowed-actions verification is the known remaining trap and publish-ffi publishes the six platform packages first, running the seven npm trust list checks before dispatching ffi-preflight.yml against the versioned ref would keep a stage-only publisher from surfacing halfway through the first real release — the failure mode the plan itself describes.
Closes the publishing-cutover window for the seven
@cipherstash/protect-ffipackages. CIP-3719 step 1.Trusted publishing now names
cipherstash/stack+release.ymlfor all seven, so the guard that kept an FFI changeset from landing early has nothing left to protect.What this removes
scripts/lint-no-ffi-changeset.mjsfailed CI on any changeset naming an FFI package: such a changeset bumps all seven through their fixed group, and the next release would attempt a publish npm still attributed tocipherstash/protectjs-ffi. Gone, with its self-test, thelint:ffi-changesetscript, and thetests.ymlstep.Six fixtures under
scripts/__tests__/fixtures/lint-no-ffi-changeset/go with the self-test. An earlier revision of this PR claimed there were none and edited the plan to say so — that came from checkingscripts/fixtures/, the wrong directory, and was caught in review. The self-test used a tmpdir for two cases only; the rest resolved these six files.test:scriptspasses either way, which is why the claim needed checking rather than inferring.What this activates
The point of the guard was that parked prose is written once, not reconstructed from the git log later:
protect-ffi-lazy-load.mdassertNativeBindingAvailable()protect-ffi-repository-url.mdcipherstash/stackchangeset statuson this branch reports all seven FFI packages at minor — 0.31.0 → 0.32.0, as CIP-3719 step 2 expects.Two things to know before merging Version Packages
1. The Stack line goes to 2.0.0, and this PR is not why.
changeset statusreports the six Stack packages at major, not the patch the parent issue's pinning analysis predicts. The cause is pre-existing onmain:.changeset/prisma-next-0-17.mddeclares@cipherstash/stack-prisma: major, which propagates through the Stack fixed group. Verified by runningchangeset statusagainstorigin/mainwith this branch's changes reverted — same result. So "an FFI patch never disturbs Stack's version line" is true and irrelevant here; something else already moved it.2. #905 needs its changeset renamed. The
jsonwebtokenCVE bump parksprotect-ffi-jsonwebtoken-cve.md.deferred. Once this merges, nothing reads that extension and nothing warns about it — the guard was the only thing that did. Rename it on that branch before merging, or a CVE fix ships with an empty changelog.Still to do (CIP-3719 steps 3–7)
Repoint trusted publishing— done; the plan's box is tickednpm publishunder "Allowed actions" —npm trust list <pkg> --json. Required for configurations created after 2026-05-20; npm accepts a stage-only publisher, which reads as configured and fails everynpm publish. Not done. (An earlier revision of this description ticked this box in error.)ffi-preflight.ymlagainst the versioned Version Packages ref. The workflow has run four times, all on 2026-08-12: two failures, one success onfix/neon-dist-crate-name(8105da2b, since merged), and a cancelled run onmain. None was against a versioned ref — no such ref has existed, because the guard kept the FFI bumps out of Version Packages #859 until this PR. It is the only dry run available, sincechangeset publishhas no--dry-run.ffi=true.protect-ffi-v0.32.0release. Smoke-test a fresh install.cipherstash/protectjs-ffi.Docs corrected
AGENTS.md(the parking convention is gone; added the trusted-publishing constraints that now matter —release.ymlis load-bearing as a filename, and the Allowed-actions trap),packages/protect-ffi/README.md, and the plan's Phase 4 checkbox.The plan's status header also claimed "Phase 5 is blocked until phase 4 publishes." It wasn't —
stash doctorlanded in #883 ahead of this. Corrected.Test plan
pnpm run test:scripts— 414 passing across 31 files, with the guard's self-test removedpnpm exec changeset status— seven FFI packages at minorlint-no-ffi-changesetorlint:ffi-changesetsurvives outside the plan's historical record.md.deferredfiles remain in this treepackage.jsonor.github/**(both ignored by config), so this PR is outside its scopeCIP-3719
Review round 1 — applied in 24f4117
Two-axis review (Standards / Spec) returned four findings, all in this PR's own additions. All four fixed:
npm publish)../diagnosticssubpath with bothimportandrequireconditions, the doctor probe that keeps the separate auth probe, the missing-binary e2e, and both changesets..md.deferredis silently inert once the guard is gone.Also corrected: Phase 4 step 2 told a reviewer to expect a patch bump of the six Stack packages. It will be a major — see the note in the PR body above.
Both reviewers independently confirmed: no dangling assertion anywhere for the deleted script,
skills/stash-supply-chain-security/SKILL.mdwas already written for the post-cutover world and correctly untouched, no changeset required for the guard removal itself,release.ymluntouched, and noNPM_TOKENintroduced.Review round 2 — applied in 4d6dca9
A second two-axis review confirmed all four round-1 fixes correct, and found four more, all in this PR's own prose. Both axes reached the same underlying point from different directions: the docs were written as though the cutover had finished, when what has finished is step 1 of 7.
AGENTS.mdand the protect-ffi README asserted publishing works here in unqualified present tense. Nothing has published from this repository yet — 0.31.0 came from the old one. Both now say the path is configured rather than proven.AGENTS.mdnamed fix(deps): bump stack-auth to 0.42.2 #905 as carrying a parked changeset. Transient state in durable doctrine; false the day fix(deps): bump stack-auth to 0.42.2 #905 merges. The rule stays, the PR number goes — the fact lives in the plan.ignorerationale still described the guard in the present tense.Not done: the inverted guard
Three independent reviews suggested replacing the deleted guard with its inverse — fail CI on any
.changeset/*.md.deferred, since such a file is inert to changesets and, after this PR, inert to CI too. Deliberately not taken. CIP-3719 says to delete the script, and the reason to park a changeset disappears once the cutover completes, so a permanent check would guard a practice that should never recur. The residue is finite and named below.Parked changesets still outstanding — two, not one
Scanning every remote branch, two open PRs each carry a distinct parked file. Each needs a
git mvon its own branch before merging, or the change ships with no changelog entry:protect-ffi-auth-error-code.md.deferredauthCode,help,getAuthErrorCode(),ProtectAuthErrorCodeprotect-ffi-jsonwebtoken-cve.md.deferredjsonwebtokenCVE fix#894 is not an FFI PR by title, but 10 of its 36 files are in that package, including both Rust sources — which is why it acquired a parked changeset at all.