Skip to content

Commit 92be6c0

Browse files
authored
Merge pull request #1167 from cipherstash/ci/golang-release-workflow
ci(golang): tag Go module releases with their WASI guests
2 parents 1e9fba3 + 556f239 commit 92be6c0

14 files changed

Lines changed: 1175 additions & 19 deletions

‎.github/workflows/release-golang.yml‎

Lines changed: 415 additions & 0 deletions
Large diffs are not rendered by default.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Every npm package except EQL lives under `languages/typescript/`: packages in `l
9292
links are provenance only.
9393
- `packages/stack-auth`, `packages/stack-profile`, `packages/stack-kms`, `packages/stack-encrypt`, `packages/stack-encrypt-derive`, `packages/stack-guest-abi`: The Rust crates imported from `cipherstash/cipherstash-suite` with their history — `stack-auth` and `stack-profile` (published to crates.io, one version group), `stack-kms` (published to crates.io from 0.1.0, its own version group, re-exported by `stack-encrypt` as `stack_encrypt::kms`), `stack-encrypt` and `stack-encrypt-derive` (published to crates.io from 0.1.0, one version group; `eql-bindings`' `stack-encrypt` feature depends on them from the registry), and `stack-guest-abi` (`publish = false`). They are the members of the **root Cargo workspace**, with the three node binding crates below. See "Working on the Rust crates".
9494
- `languages/typescript/packages/auth`, `languages/typescript/packages/profile`, `languages/typescript/packages/stack-auth-wasm`: The node bindings of those crates. `@cipherstash/auth` (napi-rs v2) and its six `platforms/*` packages are published to npm from this repository by `release.yml` (`auth-artifacts`, `publish-auth`); a change to what it ships, the `stack-auth` crate included, needs an `@cipherstash/auth` changeset (`require-auth-npm-changeset.yml`). `@cipherstash/profile` and its platforms are private and never published; `@cipherstash/stack-auth-wasm` is private and builds the wasm that `@cipherstash/auth` ships. Their `build` and `test` scripts never invoke cargo; `build:native`, `build:debug` and `test:cargo` do.
95-
- `languages/golang`: The Go module — the SDK `encrypt` with its generated-code support `encrypt/gensupport` and the policy packages `encrypt/policy` and `encrypt/policy/protosource`; the credential package `auth`; the generator `stashgen` and its command `cmd/stashgen`; and `internal` (the shared guest plumbing and the `record` wire model). A wazero host with no cgo. Its two WASI guests (`encrypt/guest`, `auth/guest`) are detached Cargo workspaces built by `mise run wasm:guest:build` and `mise run wasm:auth-guest:build`; `mise run wasm:guest:build:deterministic` builds the seeded TEST build the hermetic Go tests use; the `.wasm` files they embed are gitignored. The stack-encrypt guest has a second build with the EQL types (`mise run wasm:guest:build:eql`, cargo feature `eql`, linking `eql-bindings` by path; `wasm:guest:build:eql:deterministic` is its test build), embedded by `encrypt/eql` and registered on import, so a program whose generated code names an EQL type runs it (ADR-0007, amended). `encrypt/eql`'s types and `Types` table are generated from the EQL catalog by `eql-codegen` (`eql_gen.go`, written by `mise run types:generate` in `packages/eql` and drift-gated by `types:check` and `cargo test -p eql-codegen`). Generated `*_stash.go` files are committed and CI fails when `go generate ./...` changes one. There is no Go release process yet.
95+
- `languages/golang`: The Go module — the SDK `encrypt` with its generated-code support `encrypt/gensupport` and the policy packages `encrypt/policy` and `encrypt/policy/protosource`; the credential package `auth`; the generator `stashgen` and its command `cmd/stashgen`; and `internal` (the shared guest plumbing and the `record` wire model). A wazero host with no cgo. Its two WASI guests (`encrypt/guest`, `auth/guest`) are detached Cargo workspaces built by `mise run wasm:guest:build` and `mise run wasm:auth-guest:build`; `mise run wasm:guest:build:deterministic` builds the seeded TEST build the hermetic Go tests use; the `.wasm` files they embed are gitignored. The stack-encrypt guest has a second build with the EQL types (`mise run wasm:guest:build:eql`, cargo feature `eql`, linking `eql-bindings` by path; `wasm:guest:build:eql:deterministic` is its test build), embedded by `encrypt/eql` and registered on import, so a program whose generated code names an EQL type runs it (ADR-0007, amended). `encrypt/eql`'s types and `Types` table are generated from the EQL catalog by `eql-codegen` (`eql_gen.go`, written by `mise run types:generate` in `packages/eql` and drift-gated by `types:check` and `cargo test -p eql-codegen`). Generated `*_stash.go` files are committed and CI fails when `go generate ./...` changes one. Releases are cut by hand with `.github/workflows/release-golang.yml` (`gh workflow run release-golang.yml -f version=X.Y.Z[-pre] --ref <branch>`): because the guests are gitignored on main and Go fetches a version as the files committed at its tag, the workflow builds them, commits them on a release-only commit no branch points at, tags it `languages/golang/vX.Y.Z` and pushes the tag alone, then attaches the EQL v4 SQL bundle to the GitHub release. The ref must carry the EQL v4 bundle, or the build stops at "Build the EQL SQL bundles". `scripts/go-release-version.mjs` refuses, before anything is built, every version Go would refuse (non-canonical semver, `+build` metadata, a v2+ major on a module path with no `/vN` suffix, a version shaped like a pseudo-version), and a final version dispatched from anywhere but `main`; a pre-release may come from a branch. The build job also refuses a version the Go proxy already lists in `@v/list`, which catches a tag deleted after the proxy fetched it; it never asks the proxy for the version itself before the tag exists, because the proxy would cache "not found" for up to 30 minutes and `verify` would fail after the push. The build checks the `Cargo.lock` of the EQL workspace and both guest workspaces with `--locked` first, since none of those builds passes it. It runs in a job with no write token and hands the guests to a `publish` job that holds `contents: write` and runs only git and gh. `publish` fetches the commit with plain unauthenticated git rather than `actions/checkout`, whose `token` input defaults to the job's writable token, and refuses a release commit whose tree is not the one the build tested. `publish` takes the paths it commits from the workflow's fixed `GUESTS` list, never from the artifact, and refuses a `guests.sha256` that is not exactly one SHA-256 line for each of those three guests: the build job runs third-party code after writing that manifest, so a path read from it could put any file on the tag (`scripts/__tests__/go-release-artifact.test.mjs`). A version is immutable once the Go proxy has fetched it, so a tag is never moved or deleted: if `publish` or `verify` fails after the tag is pushed, use "Re-run failed jobs" on the same run, which keeps a tag that run pushed and creates or completes the release; a new dispatch of the same version is refused. Never tag a main commit by hand: `go get` would fetch a module with no guests.
9696
- `e2e/*`: Cross-package end-to-end tests (package managers, supply chain, Prisma example README)
9797
- `languages/typescript/examples/*`: Working apps (basic, prisma, supabase-worker)
9898
- `docs/plans/*`: Internal design plans. User-facing documentation lives at https://cipherstash.com/docs (not in this repo).

‎SECURITY.md‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ It also carries the source of five Rust crates published to crates.io,
3434
**`stack-encrypt-derive`** (`packages/stack-kms`, `packages/stack-encrypt`,
3535
`packages/stack-encrypt-derive`), and of the **Go module** at
3636
`languages/golang` (`encrypt` and `auth`, over WASI guests built
37-
from the stack-* crates), which has no release yet. All of these are in scope
37+
from the stack-* crates), released by `.github/workflows/release-golang.yml`
38+
as `languages/golang/vX.Y.Z` tags. All of these are in scope
3839
for security reports on the same terms as the npm packages above.
3940

4041
The five stack-* crates are licensed under the PolyForm Internal Use License
@@ -57,7 +58,10 @@ production service.
5758
> `stack-profile`, `stack-kms`, `stack-encrypt` and `stack-encrypt-derive`
5859
> crates are published from here by `.github/workflows/release-plz.yml` (the
5960
> first version of each of the last three was published by hand, because
60-
> crates.io configures Trusted Publishing on an existing crate). EQL moved here at the Phase 5 cutover in
61+
> crates.io configures Trusted Publishing on an existing crate). The Go module
62+
> is released from here by `.github/workflows/release-golang.yml`, as
63+
> `languages/golang/vX.Y.Z` tags that `go get` fetches through the Go module
64+
> proxy. EQL moved here at the Phase 5 cutover in
6165
> `docs/plans/2026-08-13-eql-monorepo-absorption.md`, and the auth packages and
6266
> the two crates at the arming PR of the stack-* crates import. Releases made
6367
> before those moves were built elsewhere: `@cipherstash/eql@3.0.5` and earlier
@@ -222,13 +226,13 @@ We mitigate this by:
222226
223227
- Explicitly disabling all caching in every workflow that publishes an artefact
224228
— `release.yml`, the reusables it calls (`_build-ffi-artifacts.yml`,
225-
`_build-eql-sql.yml`, `_build-eql-docs.yml`), `release-plz.yml` and
226-
`release-postgres-eql-image.yml`
229+
`_build-eql-sql.yml`, `_build-eql-docs.yml`), `release-plz.yml`,
230+
`release-postgres-eql-image.yml` and `release-golang.yml`
227231
- Automated checks for disabled caching on high-risk workflows
228232
(`scripts/lint-no-workflow-caching.mjs`), working from an allowlist of audited
229233
actions rather than a denylist of cache actions — so an action it has never
230234
seen is a finding by default. The cost is accepted: four EQL release jobs
231-
compile Rust with no cache restore.
235+
and the Go release's build job compile Rust with no cache restore.
232236
233237
[1]: https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/
234238

‎languages/golang/LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 CipherStash
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎languages/golang/auth/README.md‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,21 @@ mise run wasm:auth-guest:build # the guest, with its import-surface gate
117117
mise run go:test # the whole Go module, both packages
118118
```
119119

120-
The guest module is embedded from `wasm/` and not committed; without it,
121-
`Open` returns `ErrGuestNotBuilt` and the tests skip.
120+
The guest module is embedded from `wasm/`. A tagged version of the module,
121+
as `go get` resolves it, carries it: the release workflow commits it to the
122+
commit the tag names, so nothing needs building. A pseudo-version
123+
(`go get ...@main`, or any commit rather than a tag) does not, and neither
124+
does a checkout of this repository, where the guest is not committed and the
125+
commands above build it. Without it, `Open` returns `ErrGuestNotBuilt` and
126+
the tests skip.
127+
128+
## Licence
129+
130+
The Go code in this module is MIT-licensed ([`LICENSE`](../LICENSE)). The
131+
credential guest it embeds is compiled from Rust crates that are not:
132+
`stack-auth`, `stack-profile` and `stack-guest-abi` are licensed under the
133+
[PolyForm Internal Use License 1.0.0](https://polyformproject.org/licenses/internal-use/1.0.0).
134+
Those terms allow use for your internal business operations only. Each
135+
crate's `LICENSE` in the
136+
[CipherStash Stack repository](https://github.com/cipherstash/stack/tree/main/packages)
137+
has the full text.

‎languages/golang/encrypt/README.md‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ The package reference is on [pkg.go.dev]; the generator's reference is
2020
go get -tool github.com/cipherstash/stack/languages/golang/cmd/stashgen
2121
```
2222

23+
This resolves to the latest tagged version, which carries the engine
24+
builds the module embeds; see [Building](#building).
25+
2326
2. Put a `stash` tag on every exported field of the struct, and a `go:generate`
2427
comment beside it.
2528

@@ -287,9 +290,31 @@ column.
287290
## Building
288291

289292
The package embeds `wasm/stack_encrypt_guest.wasm`, a build artefact of the
290-
Rust crate in [`guest/`](guest/). It is not committed: run
293+
Rust crate in [`guest/`](guest/), and package `encrypt/eql` embeds the EQL
294+
build beside it. A tagged version of the module (such as `v0.1.0-alpha.1`, as
295+
`go get` resolves it) carries both builds and the credential guest: the
296+
release workflow commits them to the commit the tag names, so a module that
297+
requires a tagged version needs no Rust toolchain and no build step. A
298+
pseudo-version does not. `go get ...@main`, or any commit rather than a
299+
tag, fetches a tree without the builds; it compiles, and `NewClient` returns
300+
`ErrGuestNotBuilt`.
301+
302+
In a checkout of this repository the builds are not committed: run
291303
`mise run wasm:guest:build` (and `mise run wasm:auth-guest:build` for the
292304
credential guest) before `go test`, and
293305
`mise run wasm:guest:build:deterministic` for the test build the hermetic
294306
round-trip and fixture tests use. Without the builds `NewClient` returns
295307
`ErrGuestNotBuilt` and the tests skip.
308+
309+
## Licence
310+
311+
The Go code in this module is MIT-licensed ([`LICENSE`](../LICENSE)). The
312+
WASI guests it embeds are compiled from Rust crates that are not: the
313+
stack-encrypt guest and its EQL build are compiled from `stack-encrypt`,
314+
`stack-kms`, `stack-auth`, `stack-profile` (a dependency of `stack-auth`) and
315+
`stack-guest-abi`, which are licensed under the
316+
[PolyForm Internal Use License 1.0.0](https://polyformproject.org/licenses/internal-use/1.0.0).
317+
Those terms allow use for your internal business operations only. Each
318+
crate's `LICENSE` in the
319+
[CipherStash Stack repository](https://github.com/cipherstash/stack/tree/main/packages)
320+
has the full text.

‎scripts/__tests__/eql-suite-ci.test.mjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,6 +1770,7 @@ describe('every job that compiles Rust restores the shared cache', () => {
17701770
).toEqual([
17711771
'.github/workflows/_build-eql-docs.yml (publish-docs)',
17721772
'.github/workflows/_build-eql-sql.yml (build)',
1773+
'.github/workflows/release-golang.yml (build)',
17731774
'.github/workflows/release-postgres-eql-image.yml (build-sql)',
17741775
'.github/workflows/release.yml (prerelease-eql-npm)',
17751776
])

0 commit comments

Comments
 (0)