@monumental-archive/lab-wasm 0.24.4 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +196 -0
  2. package/package.json +7 -1
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # release-lab
2
+
3
+ <!-- badges:begin -->
4
+ [![ci](https://github.com/monumental-archive/release-lab/actions/workflows/gate.yml/badge.svg)](https://github.com/monumental-archive/release-lab/actions/workflows/gate.yml)
5
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/monumental-archive/release-lab/badge)](https://scorecard.dev/viewer/?uri=github.com/monumental-archive/release-lab)
6
+ [![SLSA Build L3](https://img.shields.io/badge/SLSA-Build%20L3-2ea44f)](https://github.com/monumental-archive/.github/blob/main/docs/runbook.md#verifying-as-a-consumer-would)
7
+ [![SLSA Source L3](https://img.shields.io/badge/SLSA-Source%20L3-2ea44f)](https://github.com/monumental-archive/.github/blob/main/docs/source-track.md)
8
+ [![SLSA Dependencies L2](https://img.shields.io/badge/SLSA-Dependencies%20L2-2ea44f)](https://github.com/monumental-archive/.github/blob/main/docs/dependency-track.md)
9
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14067/badge)](https://www.bestpractices.dev/projects/14067)
10
+ [![OpenSSF Baseline](https://www.bestpractices.dev/projects/14067/baseline)](https://www.bestpractices.dev/projects/14067)
11
+ [![REUSE status](https://api.reuse.software/badge/github.com/monumental-archive/release-lab)](https://api.reuse.software/info/github.com/monumental-archive/release-lab)
12
+ [![coverage](https://codecov.io/gh/monumental-archive/release-lab/branch/main/graph/badge.svg)](https://codecov.io/gh/monumental-archive/release-lab)
13
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21914272.svg)](https://doi.org/10.5281/zenodo.21914272)
14
+ [![npm](https://img.shields.io/npm/v/%40monumental-archive%2Flab-wasm.svg)](https://www.npmjs.com/package/@monumental-archive/lab-wasm)
15
+ [![ghcr release-lab](https://img.shields.io/badge/ghcr.io-monumental--archive%2Frelease--lab-blue)](https://github.com/orgs/monumental-archive/packages/container/package/release-lab)
16
+ [![ghcr lab-pg](https://img.shields.io/badge/ghcr.io-monumental--archive%2Flab--pg-blue)](https://github.com/orgs/monumental-archive/packages/container/package/lab-pg)
17
+ [![fair-software](https://img.shields.io/badge/fair--software.eu-%E2%97%8F%20%E2%97%8F%20%E2%97%8F%20%E2%97%8F%20%E2%97%8F-green)](https://fair-software.eu)
18
+ <!-- badges:end -->
19
+
20
+ The org's release lab: where risky release machinery is proven before any
21
+ production repository runs it. Fixture crates, **real GitHub APIs, real
22
+ credentials, real rulesets, real registries**.
23
+
24
+ The publishes are real, and that is the point — a rehearsal that ships
25
+ nowhere proves nothing. `@monumental-archive/lab-wasm` is on npm with
26
+ provenance, the images are on GHCR, the releases are immutable and carry
27
+ their full evidence bundles. What is disposable is the *meaning*: the
28
+ crates have no consumers and the version numbers are spent freely.
29
+
30
+ One line the lab never crosses: **crates.io**. Uploads there are
31
+ yank-only and a fixture has no business holding a name, so `rust-crate`
32
+ is the single class rehearsed dry. DOIs are deliberately NOT grouped
33
+ with that line (.github#316): a crates.io name is a scarce global
34
+ namespace a fixture would squat; a DOI is a citation record that
35
+ squats nothing. Every lab release mints a **real, permanent** version
36
+ DOI under the lab's one concept record — that pile-up at rehearsal
37
+ cadence is the design, because a rehearsal against a mirrored sandbox
38
+ API never proves the path the permanent record takes, and proving that
39
+ path is this repository's entire job.
40
+
41
+ The canon it proves lives in
42
+ [monumental-archive/.github](https://github.com/monumental-archive/.github)
43
+ — `docs/release.md`. Repositories conform to that canon; this is where the
44
+ canon is exercised first.
45
+
46
+ ## Why a separate repository
47
+
48
+ A reusable workflow cannot run on its own — it needs a caller, and the
49
+ caller has to be a repository. Letting `.github` call its own workflows is
50
+ not enough, and that is not a theory: the shared gate carried a
51
+ `github.workflow_sha` bug precisely because its only exerciser lived in the
52
+ same repository, where the caller's SHA and the reusable workflow's SHA
53
+ happen to be identical. It broke the moment a different repository called
54
+ it.
55
+
56
+ So the lab is three things at once:
57
+
58
+ - **A foreign caller.** The only way to exercise the cross-repository path
59
+ — `job_workflow_sha`, permission downgrades, secrets forwarding, App
60
+ tokens crossing a repository boundary.
61
+ - **Fixtures.** The shared workflows are generic, but they still need real
62
+ inputs to execute their real code paths.
63
+ - **A cheap blast radius.** Release failures are mostly irreversible:
64
+ crates.io is yank-only, a published GHCR digest exists forever, an
65
+ immutable release cannot be repaired. Here they cost nothing.
66
+
67
+ The standing job is the last one. `uses:` accepts no expressions, so a
68
+ caller's SHA pin is frozen into whatever ref it ran from — **a bug in a
69
+ shared workflow is permanent for every tag already pinned to it.** Every
70
+ change to a shared workflow needs a foreign caller to prove it before any
71
+ production repository bumps its pin.
72
+
73
+ ## What is exercised here
74
+
75
+ The canon's release machinery is complete (`.github#28`, closed): both
76
+ archetypes, every artifact class, repro gate, evidence bundles, signed
77
+ verdicts, DOIs. All of it ran here first. Four fixture crates carry it —
78
+ minimal, but structurally honest enough to make the shared workflows
79
+ execute their real code paths and reach their real endpoints:
80
+
81
+ | Fixture | Class it feeds |
82
+ | --- | --- |
83
+ | `lab-core` | rust-crate (the library, and the internal path+version dependency shape) |
84
+ | `lab-cli` | rust-binary, and the binary that lands in the image |
85
+ | `lab-wasm` | wasm-npm |
86
+ | `lab-pg` | pgrx-extension, and its per-major extension images |
87
+
88
+ The caller stubs are the entire surface a conforming repository owns; the
89
+ logic they invoke lives in the canon, SHA-pinned:
90
+
91
+ | Stub | What it proves |
92
+ | --- | --- |
93
+ | `gate.yml` | the shared CI gate, called across a repository boundary |
94
+ | `release.yml` | phase 1 — version decision, changelog, Release PR, and on merge the App-minted `v*` tag and draft release |
95
+ | `publish.yml` | phase 2 at full width — `rust-binary`, `oci-image`, `wasm-npm` and `pgrx-extension` built for real across every supported Postgres major, repro-gated, signed through the org signer, pushed to GHCR and npm, with a real version DOI and an evidence bundle |
96
+ | `continuous.yml` | the continuous archetype — digest publish on merge, weekly rebuild, no tags, no versions |
97
+ | `exercise-sign.yml` | the org signer across a repository boundary: bytes built here, signed there without the signer ever seeing them, verified the way a stranger would |
98
+ | `source-attest.yml` | this repository's reserved source-signing identity (below) |
99
+ | `audit.yml`, `audit-repro.yml`, `scorecard.yml` | the Monday advisory audit, the Thursday cold rebuild of the latest published release, and the copied Scorecard stub |
100
+
101
+ Two classes are deliberately not run here for real. **rust-crate** is
102
+ rehearsed dry — the lab never uploads to crates.io — and its coexistence
103
+ and dry-run shape were proven on v0.15.3. **source-archive** is the
104
+ canon's own phase 2, exercised by `.github` on itself.
105
+
106
+ Both archetypes coexisting in one repository is intentional, not
107
+ leftover: `continuous.yml` refuses tags and `publish.yml` refuses
108
+ branches, and that pair of inverse guards is only tested where both live.
109
+
110
+ ## What proving here has caught
111
+
112
+ Each of these was found on a real run, before any production repository
113
+ was touched, and none of them was visible to a linter:
114
+
115
+ | Found | How it presented | Resolution |
116
+ | --- | --- | --- |
117
+ | Canon checkout used `github.workflow_sha` | `upload-pack: not our ref` — the entry-point workflow's SHA is the *caller's* commit | `github.job_workflow_sha`, and ultimately the `$/.github/actions/canon` self-reference (`.github#165`) |
118
+ | The same bug, latent in the shared CI gate | never failed: its only caller lived in the same repository, where both SHAs coincide | fixed together |
119
+ | `git-cliff` absent from the toolbelt | `git: 'cliff' is not a git command` | belt standup |
120
+ | Tag-mint App lacked `pull_requests: write` | `The permissions requested are not granted to this installation` | App permission + install approval |
121
+ | An undecided advisory in a release SBOM | the dependency gate refused to publish v0.19.1 | dependency-keyed VEX; RUSTSEC-2021-0127 decided on v0.20.1 |
122
+ | The containerised `cargo build` was not reproducible | the repro gate went red on all seven image digests at v0.21.0, and nothing published | `.github#295` — five of seven fixed by `--provenance=false --sbom=false` + `rewrite-timestamp`, the last two by moving the compile out of the Dockerfile; bit-for-bit on v0.22.1 |
123
+
124
+ The last row is the shape worth keeping in mind. The gate blocked the
125
+ registry uploads, the tags, the append-only Sigstore entries and the
126
+ release itself; the entire cost of the finding was a disposable lab
127
+ version number.
128
+
129
+ ## The `v*` tag-creation lock
130
+
131
+ Canon: humans never push release tags; only the pipeline's App mints them.
132
+ The ruleset restricts `v*` **creation** with the tag-mint App as sole bypass
133
+ actor.
134
+
135
+ Both halves are proven here under `enforcement: active` — note that
136
+ `evaluate` proves *neither*, since it enforces nothing and bypass actors
137
+ record no evaluation, so an empty rule-suite list looks identical whether
138
+ the lock works perfectly or does not exist:
139
+
140
+ | Half | Repro | Expected |
141
+ | --- | --- | --- |
142
+ | Negative | `git tag -a v9.9.9-x -m x && git push origin v9.9.9-x` as a human | `GH013 ... Cannot create ref due to creations being restricted.` |
143
+ | Positive | merge a release PR; the tag job mints the tag via the App | annotated tag + draft release appear |
144
+
145
+ `current_user_can_bypass: never` is the check that the org owner is bound
146
+ too.
147
+
148
+ ## Source attestation
149
+
150
+ This repository is one of the org's three source-track emitters
151
+ (`.github#207`) and was the first: it proved the emitter end to end before
152
+ `signer` and the canon founded their own chains, and the org's Source L3
153
+ claim moved only once all three were stranger-verified.
154
+
155
+ The chain was founded at `ea49b2f0` (2026-08-12) by
156
+ `.github/workflows/source-attest.yml`, the reserved signing identity. That
157
+ file's path at `@refs/heads/main` **is** the identity — a keyless
158
+ certificate names the workflow path plus ref — so moving or renaming it is
159
+ a breaking change to the root-of-trust contract, and its contents change
160
+ freely while its path never does.
161
+
162
+ Every revision on `main` since carries signed source provenance and a
163
+ source VSA in `refs/notes/commits`. Verify any of them with nothing but
164
+ the published root of trust (`docs/source-assessment.md` in the canon):
165
+
166
+ ```bash
167
+ san="https://github.com/monumental-archive/release-lab"
168
+ san="${san}/.github/workflows/source-attest.yml@refs/heads/main"
169
+ cosign verify-blob --bundle <bundle> --certificate-identity "${san}" \
170
+ --certificate-oidc-issuer \
171
+ https://token.actions.githubusercontent.com <statement>
172
+ ```
173
+
174
+ The first five links claim `SLSA_SOURCE_LEVEL_2` and stay that way: they
175
+ were emitted before the claims job held a token that could read org-level
176
+ tag-ruleset details, so the VSA under-claimed rather than assert a control
177
+ it could not see. They are not backfilled — honest degradation is a real
178
+ behaviour here, not a design intention.
179
+
180
+ An earlier pilot (2026-08-10) drove `slsa-framework/source-tool` and
181
+ parked on four upstream defects (watch `.github#199`); the org built its
182
+ own emitter instead of waiting. Those genesis attestations remain in this
183
+ repository's notes as the historical record — a different dialect, not a
184
+ link in the current chain.
185
+
186
+ Unlike releases, the notes chain is **not** disposable: each link verifies
187
+ its predecessor, so deleting notes breaks verification for everything after.
188
+
189
+ ## Housekeeping
190
+
191
+ Immutable releases are ON (the draft-then-publish shape depends on it). Old
192
+ lab releases and tags are disposable; delete them freely between runs —
193
+ this repository has no consumers. One exception: the **latest** release is
194
+ load-bearing, because the Thursday `audit-repro` job rebuilds it and
195
+ compares against its published `checksums.txt`. Delete that one and the
196
+ audit reddens until the next release is cut.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@monumental-archive/lab-wasm",
3
3
  "type": "module",
4
4
  "description": "Fixture wasm crate for the org release pipeline",
5
- "version": "0.24.4",
5
+ "version": "0.25.1",
6
6
  "license": "MIT OR Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
@@ -19,5 +19,11 @@
19
19
  "sideEffects": [
20
20
  "./lab_wasm.js",
21
21
  "./snippets/*"
22
+ ],
23
+ "keywords": [
24
+ "fixture",
25
+ "release",
26
+ "provenance",
27
+ "slsa"
22
28
  ]
23
29
  }