Skip to content

ci: add the release pipelines for the stack-* crates and @cipherstash/auth, inert - #1002

Merged
auxesis merged 3 commits into
mainfrom
ci/stack-crates-release-pipelines
Oct 2, 2026
Merged

auxesis merged 3 commits into
mainfrom
ci/stack-crates-release-pipelines

Conversation

@auxesis

@auxesis auxesis commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

This PR adds the jobs that will publish stack-auth and stack-profile to crates.io, and @cipherstash/auth with its six platform packages to npm. Each platform package holds the native binary for one platform, such as darwin-arm64. Every new job stays switched off, which the title calls "inert". PR E switches them on.

This is PR D of 6 in the stack crates import. The import moves six Rust crates, their bindings and the Go module here from the private cipherstash/cipherstash-suite repository, with their history. The stack crates import plan in Linear lists every step, and Linear issue CIP-4274 tracks the work.

This PR is stacked on PR C, #1003. Its base branch is PR C's branch, so the diff shows only this PR's own changes. It stays a draft until the freeze on Friday 2 October 2026, Pacific time. The freeze is the window in which the six PRs merge in order: #1000, #1001, #1003, #1002, #1009, then #1010. PR D is #1002, so it merges after PR C, #1003.

Read the three commits in order, starting with the release gate

Read each commit in full. Start with commit 1, because the jobs in commit 3 run on the auth output that it adds. Then compare the table under "Nothing in this PR can publish" with the job conditions in the diff.

Each commit makes one change

  1. d9af16df makes the release gate report a separate auth output. The release gate, scripts/release-gate.mjs, runs on every push to main before anything publishes to npm. It writes flags such as ffi=true and js=true that tell release.yml which publish jobs to run. Without an auth flag, a new auth version would count as js. The npm publish step, changeset publish, would then pack the platform packages with no binary in them.
  2. 4c6a49c0 adds a release-crates job to release-plz.yml. release-plz is the tool that publishes Rust crates to crates.io. The job runs only when a new crates-armed job reports that the crates may publish. A root release-plz.toml gives stack-auth and stack-profile one shared version, and stops the other seven workspace members publishing.
  3. 2a38fb4e adds the jobs that build and publish @cipherstash/auth, and the settings they need:
    • _build-auth-artifacts.yml builds the native binding for six platforms with napi, plus the WebAssembly wrapper. napi is the tool that builds Rust code into a Node.js native module.
    • release.yml gains an auth-artifacts job and a publish-auth job. They publish the six platform packages first and the wrapper last.
    • auth-preflight.yml is a dry run that someone starts by hand. It builds all seven packages and test-installs the wrapper with its linux-x64-gnu package, but it cannot publish.
    • .changeset/config.json gains a fixed group, a setting that keeps the seven auth packages on one shared version.
    • All seven auth manifests gain repository.directory, the package.json field that names the package's folder. npm needs it for a provenance publish, which records the workflow that built the package. A new test checks it.

Nothing in this PR can publish

Each publishing job waits on a condition that stays false until PR E, and the dry run has no credentials. In the table, a frozen package is one that the release gate fails on if its version changes.

Job What keeps it switched off
release-crates in release-plz.yml It runs only when crates-armed reports true. That needs the release gate's freeze on @cipherstash/auth removed, which PR E does.
auth-artifacts and publish-auth in release.yml They run only when the gate reports auth=true. The auth packages are still frozen, so a version bump of one makes the gate fail first.
auth-preflight.yml It has no publish credentials.

crates-armed reads the gate's @cipherstash/auth entry, because no crate has an entry of its own there. PR E removes the auth entry, which switches on the npm jobs and the crates.io job together.

The release workflows run only on pushes to main or when someone starts them by hand. Pushing this branch starts no release.

The checks pass locally and in CI

  • The napi build leaves the frozen index.js unchanged. napi writes its own loader over that file unless the build passes --js false.
  • The packed wrapper's six peer dependencies equal its own version. pnpm replaces each workspace:* range with a real version when it packs.
  • A mutation is a deliberate break in the new code, made to prove that a test notices. 27 of 28 mutations make a test fail.
  • The one surviving mutation removes the EXPECTED_DISPATCHABLE entry. That registration was already known to check nothing, so the survivor confirms it.
  • CI: 17 checks pass and 11 are skipped.

Merge this PR with a merge commit after PR C

Merge it with a merge commit, which keeps every commit, and never with a squash or a rebase. PR E is built on these exact commits. A squash or a rebase would leave PR E based on commits that are not in main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a

@changeset-bot

changeset-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2a38fb4

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 no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

@auxesis
auxesis force-pushed the ci/stack-crates-release-pipelines branch 2 times, most recently from b4efd51 to d669946 Compare October 2, 2026 07:01
@auxesis
auxesis force-pushed the build/import-stack-crates branch from bceb0bc to 1ad1055 Compare October 2, 2026 08:07
@auxesis
auxesis force-pushed the ci/stack-crates-release-pipelines branch from d669946 to db6b3b9 Compare October 2, 2026 08:07
@auxesis
auxesis changed the base branch from build/import-stack-crates to ci/port-stack-crates October 2, 2026 08:07
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Mutation testing (cargo-mutants, --in-diff, stack-auth + stack-encrypt)

No mutants were generated for the changed lines.

auxesis and others added 3 commits October 2, 2026 18:16
classify() gains an `auth` output keyed on the `@cipherstash/auth`
prefix, and `js` no longer counts the seven auth packages. Without the
branch an unpublished auth version reads as `js`, and `changeset
publish` would pack the platform workspaces with no binary in them. The
gate prints and writes `auth=` beside `ffi=` and `js=`.

A process test pins the three flags in GITHUB_OUTPUT, because a flag the
gate never writes reads as "not in scope" in the workflow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
release-plz.yml gains a `crates-armed` switch and a `release-crates` job
that runs `release-plz release` on the root workspace, with
`manifest_path: Cargo.toml` and `config: release-plz.toml`. It is its
own job because a step in `release` would inherit the EQL gate. A
crates.io preflight decides whether the GPG key is imported, as the EQL
job does. The push filter adds the two crates and the root release-plz
inputs.

scripts/eql-pipeline-armed.mjs learns a `crates` line, keyed on the
`@cipherstash/auth` entry of FROZEN_PUBLISHERS: no crate is in that npm
map, and PR E deletes the auth entries when it moves both registries.
While the entry is there the switch prints armed=false and the job is
skipped.

The root release-plz.toml puts stack-auth and stack-profile in one
version group and sets `release = false` on the seven other members.
cliff.toml is cipherstash-suite main's, unchanged. A new test holds the
config to the workspace members and to the job's inputs.

Registered in OIDC_JOBS and PERMISSIVE_NEEDS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
- _build-auth-artifacts.yml: a reusable six-target napi matrix and a
  wasm job. Each target runs `napi build --platform --release --target
  ... --strip --dts native.d.ts --js false` (without `--js false` napi
  writes its own loader over the frozen index.js), then `git diff
  --exit-code` on the package, and `npm pack`s its platform package.
  linux-arm64-gnu runs on GitHub's ubuntu-24.04-arm. The wrapper is
  packed with pnpm, which rewrites its `workspace:*` platform peers, and
  the job checks the six peers equal the wrapper's version. Rust is the
  root mise 1.94.1; nothing restores a cache.
- auth-preflight.yml: a workflow_dispatch dry run that builds the seven
  tarballs, checks each non-host binary with `file` and `readelf`, and
  smoke-installs the linux-x64-gnu pair. No id-token, no secret, no
  registry-url.
- release.yml: `auth-artifacts` and `publish-auth` between `gate` and
  `release`, on the gate's new `auth` output. They publish platforms
  first and the wrapper last with --provenance, then tag and make a
  GitHub release; `release` waits for publish-auth to succeed whenever
  auth was in scope.
- .changeset/config.json: a fixed group of the seven auth packages.
- The seven auth manifests get `repository` with `directory`, which npm
  requires for a provenance publish. The gate hashes the 15 listed
  files, not package.json, so the freeze still passes.

Inert: the auth packages are in FROZEN_PUBLISHERS, so any unpublished
auth version fails the gate before `auth-artifacts` can run.

Guards: auth-repository-urls and auth-build-artifacts tests; a derived
lint-release-scope check that every reusable workflow release.yml calls,
and every other caller of one, is linted. Registered in
lint-no-workflow-caching TARGETS, tests-supply-chain paths, OIDC_JOBS,
PERMISSIVE_NEEDS and EXPECTED_DISPATCHABLE. The frozen reason and the
lint-no-auth-changeset message no longer say release.yml cannot build
the binaries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a

@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.

I read the three commits in order. Each publishing job stays switched off until the FROZEN_PUBLISHERS entries for @cipherstash/auth are removed in PR E:

  • classify() now sends an unpublished auth version to auth, not to js. So changeset publish can no longer pack the platform workspaces with no binary in them.
  • release keeps the publish-ffi rule for auth: if auth=true, publish-auth must have succeeded. A skipped auth-artifacts cannot let changeset publish continue.
  • crates-armed uses the same FROZEN_PUBLISHERS map through eql-pipeline-armed.mjs crates. Thus one deletion in PR E arms both the npm jobs and the crates.io job. The EQL readers stay unchanged.
  • The auth fixed group, repository.directory, the caching-lint targets and the actionlint scope all agree with the new files.

I have one non-blocking point, in an inline comment. The six-platform build has not run yet, because auth-preflight.yml is dispatch-only. Its musl leg also differs from the proven FFI recipe. Please run the preflight before PR E arms the pipeline.

The missing changeset is correct: this PR changes only CI and repository tooling, and changesets for the auth packages stay blocked until PR E.

working-directory: languages/typescript/packages/auth
env:
TARGET: ${{ matrix.target }}
run: mise x -- pnpm exec napi build --platform --release --target "$TARGET" --strip --dts native.d.ts --js false

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.

Non-blocking. The linux-x64-musl leg installs musl-tools, but it does not set a musl linker or RUSTFLAGS="-C target-feature=-crt-static". _build-ffi-artifacts.yml sets both for its musl leg. The musl target defaults to crt-static, and rustc drops a cdylib under crt-static. With -crt-static and no CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER, the final link may use the host glibc cc. That can give a .node file that needs libc.so.6.

napi CLI v2 may add -crt-static for musl targets itself, so this could be fine. No CI run has built this matrix yet, though. The readelf check in auth-preflight.yml will catch a glibc-linked binary. But release.yml does not run that check, so the only guard is a person who dispatches the preflight. Please run auth-preflight.yml once before PR E, or use the FFI leg's linker setup here. Both are acceptable.

Base automatically changed from ci/port-stack-crates to main October 2, 2026 18:31
@auxesis
auxesis merged commit 4f11f41 into main Oct 2, 2026
29 checks passed
@auxesis
auxesis deleted the ci/stack-crates-release-pipelines branch October 2, 2026 18:41
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.

3 participants