Skip to content

chore(release): retire the FFI publishing guard and activate its parked changesets - #910

Merged
tobyhede merged 3 commits into
mainfrom
toby/cip-3719-protect-ffi-npm-trusted-publishing-cutover-irreversible
Aug 19, 2026
Merged

chore(release): retire the FFI publishing guard and activate its parked changesets#910
tobyhede merged 3 commits into
mainfrom
toby/cip-3719-protect-ffi-npm-trusted-publishing-cutover-irreversible

Conversation

@tobyhede

@tobyhede tobyhede commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Closes the publishing-cutover window for the seven @cipherstash/protect-ffi packages. CIP-3719 step 1.

Trusted publishing now names cipherstash/stack + release.yml for 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.mjs failed 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 to cipherstash/protectjs-ffi. Gone, with its self-test, the lint:ffi-changeset script, and the tests.yml step.

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 checking scripts/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:scripts passes 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:

Changeset Bump What
protect-ffi-lazy-load.md minor Native binding loads lazily; adds assertNativeBindingAvailable()
protect-ffi-repository-url.md patch Published metadata names cipherstash/stack

changeset status on 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 status reports the six Stack packages at major, not the patch the parent issue's pinning analysis predicts. The cause is pre-existing on main: .changeset/prisma-next-0-17.md declares @cipherstash/stack-prisma: major, which propagates through the Stack fixed group. Verified by running changeset status against origin/main with 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 jsonwebtoken CVE bump parks protect-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 ticked
  • Verify all seven publishers list npm publish under "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 every npm publish. Not done. (An earlier revision of this description ticked this box in error.)
  • Run ffi-preflight.yml against the versioned Version Packages ref. The workflow has run four times, all on 2026-08-12: two failures, one success on fix/neon-dist-crate-name (8105da2b, since merged), and a cancelled run on main. 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, since changeset publish has no --dry-run.
  • Merge Version Packages (Version Packages #859). Gate flips to ffi=true.
  • Verify provenance on all seven, the seven git tags, the protect-ffi-v0.32.0 release. Smoke-test a fresh install.
  • Archive cipherstash/protectjs-ffi.

Docs corrected

AGENTS.md (the parking convention is gone; added the trusted-publishing constraints that now matter — release.yml is 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 doctor landed in #883 ahead of this. Corrected.

Test plan

  • pnpm run test:scripts — 414 passing across 31 files, with the guard's self-test removed
  • pnpm exec changeset status — seven FFI packages at minor
  • No live reference to lint-no-ffi-changeset or lint:ffi-changeset survives outside the plan's historical record
  • No .md.deferred files remain in this tree
  • Biome does not process package.json or .github/** (both ignored by config), so this PR is outside its scope

CIP-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:

  1. Six orphaned fixtures — deleted, and the false "there were no fixtures" claim reversed in the plan (see above).
  2. AGENTS.md/README asserted publishing had moved while the plan's repoint box was unticked — reconciled; the box is ticked, and it now carries the part that is genuinely still open (the Allowed-actions setting is unverified, and npm accepts a stage-only publisher that reads as enabled and fails every npm publish).
  3. Plan summary said "Phases 3 and 5 are built" over four unticked Phase 5 boxes — all four verified against the tree and ticked: the ./diagnostics subpath with both import and require conditions, the doctor probe that keeps the separate auth probe, the missing-binary e2e, and both changesets.
  4. AGENTS.md did not name fix(deps): bump stack-auth to 0.42.2 #905 as carrying a parked changeset — now does, since a .md.deferred is 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.md was already written for the post-cutover world and correctly untouched, no changeset required for the guard removal itself, release.yml untouched, and no NPM_TOKEN introduced.

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.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. Both now say the path is configured rather than proven.
  • AGENTS.md named 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.
  • 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. Restored; the note below it still records that the repoint ran ahead of the pre-flight, and why.
  • The changesets-ignore rationale 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 mv on its own branch before merging, or the change ships with no changelog entry:

PR File Bump What is lost if forgotten
#894 protect-ffi-auth-error-code.md.deferred minor A new public API — authCode, help, getAuthErrorCode(), ProtectAuthErrorCode
#905 protect-ffi-jsonwebtoken-cve.md.deferred patch The jsonwebtoken CVE 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.

…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
@tobyhede
tobyhede requested a review from a team as a code owner August 18, 2026 22:40
@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4d6dca9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 18 packages
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 freshtonic 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.

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-changeset outside the plan's historical record is AGENTS.md's own past-tense explanation of what a found .md.deferred file means — which is the intended one. Script, self-test, six fixtures, package.json entry, and the tests.yml step all go together; nothing else in the tree loads the fixtures directory.
  • No .md.deferred survives on this branch, and both renamed changesets carry substantive content: the lazy-load minor (with the assertNativeBindingAvailable() rationale) and the repository-url patch (including the trusted-publishing exact-match requirement and the quietly-404ing repository.directory detail). 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.md declares '@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.deferred on #894's branch and protect-ffi-jsonwebtoken-cve.md.deferred on #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 mv it 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 list check (and honestly left unticked), and the release.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.

@tobyhede
tobyhede merged commit 989ba10 into main Aug 19, 2026
9 checks passed
@tobyhede
tobyhede deleted the toby/cip-3719-protect-ffi-npm-trusted-publishing-cutover-irreversible branch August 19, 2026 00:48
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