Skip to main content

release-artifact-pipeline

This skill should be used when setting up, standardizing, auditing, or modifying a release artifact pipeline on a PulseEngine project — including "standardize release artifacts", "set up release workflow", "fix the release pipeline", "add cosign signing", "add SLSA attestation", "add SBOM", "switch to signed SHA256SUMS", "sign the wasm with sigil", "add a witness/scry gate to the release", "publish to crates.io / npm", "add a Pages verification dashboard", "audit release artifacts", "migrate off per-file .sha256 sidecars", or any GitHub Actions release.yml setup/refactor. ALWAYS use this skill when proposing or reviewing changes to a release.yml workflow, when adopting the PulseEngine release-artifact standard for a new repo, or before claiming a release pipeline is "compliant" or "signed". Covers all five tracks — native binaries, distribution channels (crates.io + npm), wasm signing (sigil + cosign) and wasm verification gates (witness MC/DC + scry), the Pages verification dashboard, and rivet verification

Ir para a instalação

Informações da origem

Repositório
pulseengine/pulseengine.eu
Última atividade na origem
26 de agosto de 2026 às 18:16
Idioma detectado do SKILL.md
inglês
Estrelas
0
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
release-artifact-pipeline
description
This skill should be used when setting up, standardizing, auditing, or modifying a release artifact pipeline on a PulseEngine project — including "standardize release artifacts", "set up release workflow", "fix the release pipeline", "add cosign signing", "add SLSA attestation", "add SBOM", "switch to signed SHA256SUMS", "sign the wasm with sigil", "add a witness/scry gate to the release", "publish to crates.io / npm", "add a Pages verification dashboard", "audit release artifacts", "migrate off per-file .sha256 sidecars", or any GitHub Actions release.yml setup/refactor. ALWAYS use this skill when proposing or reviewing changes to a release.yml workflow, when adopting the PulseEngine release-artifact standard for a new repo, or before claiming a release pipeline is "compliant" or "signed". Covers all five tracks — native binaries, distribution channels (crates.io + npm), wasm signing (sigil + cosign) and wasm verification gates (witness MC/DC + scry), the Pages verification dashboard, and rivet verification extraction.
metadata
{"author":"pulseengine.eu","version":"0.4.0"}
# Release artifact pipeline ## When this fires Anytime you touch the *release workflow itself* on a PulseEngine project — setting it up for the first time, standardizing assets, adding cosign signing, adding SLSA attestation, adding SBOMs, migrating off per-file `.sha256` sidecars, or auditing whether the pipeline meets the project's verifiability bar. This is distinct from `release-execution`, which fires when you're *cutting* a release on top of an already-working pipeline. Pipeline setup is the rarer, deeper task. ## The standard — match it for *every artifact type the repo ships* A repo's release is compliant only when **each kind of artifact it produces** meets its track below. The recurring org-wide defect (2026-06 sweep): the *native binary* gets the full supply chain everywhere, while the *wasm* it ships gets weaker signing and no verification gate. Hold wasm to the **same bar** as the binary. - **Track A — native binaries** → the cosign+SBOM+SLSA bundle (canonical: synth). - **Track B — distribution channels** → crates.io for everything Rust; npm for CLIs/tools; **varve layers** for the toolchain as a pinned, signed set. - **Track C — wasm artifacts** → sigil + cosign signature, **and** a witness MC/DC gate **and** a scry abstract-interpretation gate. No wasm ships unverified. - **Track D — Pages verification dashboard** → witness-viz / scry-viz (canonical: witness, scry). A repo skips a track only if it produces none of that artifact type — and "we emit wasm but only Track A is wired" is the exact drift this skill exists to close. ## Track A — native binaries The canonical implementation lives at **`pulseengine/synth/.github/workflows/release.yml`** — Phase 6 onward. **Copy that block verbatim** into the target repo's release workflow, then adapt the SBOM step's manifest path to point at the target repo's main crate. ### Required release assets (and no others for checksums/attestation) ``` <tool>-vX.Y.Z-<triple>.{tar.gz|zip} # binary archives <tool>-X.Y.Z.cdx.json # CycloneDX SBOM SHA256SUMS.txt # checksums over the archives + SBOM SHA256SUMS.txt.sig # cosign signature (keyless OIDC) SHA256SUMS.txt.pem # cosign certificate SHA256SUMS.txt.cosign.bundle # cosign bundle for verify-blob build-env.txt # rustc / cargo / cosign / runner versions ``` ### Required workflow steps, in this order 1. **Build binary archives** → `release-assets/`. 2. **Generate SBOM** — `cargo cyclonedx --manifest-path <main-crate>/Cargo.toml --format json --spec-version 1.5`, then copy result to `release-assets/<tool>-${BARE}.cdx.json`. **Must run before step 3** so its digest enters the sums. 3. **Generate checksums** — `cd release-assets && sha256sum ./* > SHA256SUMS.txt`. 4. **SLSA provenance** — `actions/attest-build-provenance@v2` with `subject-path: "release-assets/*.tar.gz"` (SLSA v1 provenance, GitHub-native). 5. **Install cosign** — `sigstore/cosign-installer@v3`. 6. **Sign the sums file** — `cosign sign-blob --yes --bundle SHA256SUMS.txt.cosign.bundle --output-signature SHA256SUMS.txt.sig --output-certificate SHA256SUMS.txt.pem SHA256SUMS.txt` (keyless OIDC). 7. **Record build environment** — write `build-env.txt` (rustc/cargo/cosign/runner versions). 8. **Upload everything** — `gh release upload` over everything in `release-assets/`. ### Required: the CLI baseline, asserted at release Every binary in the layer must meet [`pulseengine-cli-conventions`] — `--version`/`-V` print `<binary-name> <semver>` and exit 0, `--help` exits 0, an unknown flag exits 2, structured output is `--format json`. **Assert it in the release job rather than trusting it**, because a version string is exactly the thing that slips silently: - a **version-guard** step that fails the build when the git tag ≠ the workspace version, *before* anything compiles; and - the artifact-level twin — run the **freshly built** binary and assert `--version` equals the tag (native targets). Both come from varve's own slip: v0.14.0 shipped a binary reporting `0.13.1`, so `self-update` advertised the same upgrade forever (pulseengine/varve#38). Guarding the workspace version alone would not have caught it — the binary is the artifact, so the binary is what gets asked. A tool that cannot report its own version cannot appear in release evidence; see [`oracle-gate-a-change`] step 4b. ### Required workflow permissions ```yaml permissions: contents: write id-token: write attestations: write ``` ### What to delete from any existing release flow - **All per-file `<asset>.sha256` sidecars.** The single signed `SHA256SUMS.txt` replaces them. - **Exception**: witness keeps its per-asset `.cert` / `.sig` files because they're consumed as certification evidence. For witness, the signed sums file is *added*, not replacing. ## Track B — distribution channels Distribution is currently incoherent (rivet npm-only, sigil/synth/scry crates.io-only, mcp a stale unsigned manual script). The rule: - **crates.io for everything written in Rust** — canonical, published from CI via OIDC trusted publishing, never a hand-run `cargo publish` from a laptop. Keep it in a dedicated `publish-to-crates-io.yml` on the `v*` tag (canonical: sigil, synth, scry) so the artifact-release and the registry-publish don't race. **mcp's manual `scripts/publish.sh` is the anti-pattern — it ships unsigned, out of CI, with stale pinned versions; move it into CI.** - **npm for CLIs and tools** — the platform-package wrapper pattern (canonical: rivet's `release-npm.yml`: per-target `@pulseengine/<tool>-<platform>` packages wired to a root launcher via `optionalDependencies`), triggered `workflow_run` after the GitHub Release so the binaries exist. This is **not** rivet-only — every user-facing CLI (rivet, spar, …) should ship it. - **varve layers — the canonical org-internal channel, and now shipping.** A tool's signed release becomes an entry in a dated OCI layer via `varve deposit` (`ghcr.io/pulseengine/varve/layers`); consumers pin *one layer* rather than N independently-drifting tool versions. crates.io and npm remain the public channels — the layer is how the toolchain is consumed as a set. `varve export-bazel` compiles a Bazel checksum registry from the verified layer, so every hash Bazel enforces is a transcription from the signed manifest **instead of TOFU**. - **Editor marketplaces are in scope later** — rivet/spar's VS Code Marketplace is the precedent; not required now. A Rust tool is compliant on Track B only when it is on crates.io **and** (if it's a CLI) on npm. crates.io-but-no-npm and npm-but-no-crates.io are both drift. ### The deposit workflow is an org release-standard enforcer This is the part worth internalising: `deposit-layer.yml` verifies each tool's release against **that tool's own repo cosign identity**, and a tool whose release lacks cosign-signed `SHA256SUMS.txt` is **excluded from the layer** with a notice rather than deposited unverified. Exclusion is visible and dated — the workflow's own comments record `ordeal is EXCLUDED until its releases carry cosign-signed sums`, then `ordeal rejoined at v0.18.0 — its first cosign-signed release`. So Track A compliance stopped being advisory the day layers shipped: an unsigned release no longer merely *fails an audit*, it **drops the tool out of the toolchain everyone installs**. Treat a tool's absence from the current layer as a release-pipeline defect in that tool's repo, and file it there per [`report-tool-friction`]. ## Track C — wasm artifacts (same bar as the binary) Any repo that **ships or emits wasm** (component or module) must sign and verify it to the binary's standard. The sweep found this is where every repo cuts corners. ### Signing — sigil + cosign - **sigil signature** (dogfood the attestation tool) **and** cosign over the sums. Canonical signer: sigil's own `wsc sign --keyless` (see sigil `release.yml` / `wasm-signing.yml`). - **Prerequisite — fix sigil first.** sigil cannot yet parse its own `wasm32-wasip2` output, so it ships unsigned-on-failure. **Do not mandate the sigil step on a repo until that parser blocker is fixed** (tracked upstream in `pulseengine/sigil`); add cosign now, add the sigil signature as the blocker clears. Mandating a broken step just reintroduces `continue-on-error` theatre. - SLSA `subject-path` must cover the **`.wasm`**, not only the `.o`/`.tar.gz` (gale's provenance currently covers the `.o` objects but not the wasm — fix). ### Verification gates — witness AND scry (both required) Every wasm-emitting repo runs, as a **CI/release gate** (not a manual side-script): - **witness** — MC/DC truth-table on the wasm; the gate asserts zero unresolved gap rows for new decisions (canonical: witness `ci.yml` dogfood + `verdict-suite`; scry's `mcdc-gate.sh` is the consume-as-library exemplar). Where the bundled witness can't parse component-format exports (relay #145), that's a witness bug to file via [`report-tool-friction`] — not a reason to leave the gate manual. - **scry** — sound abstract interpretation over the fused Wasm core (consume scry as a crates.io library, v1.15+; canonical: scry's self-analysis dogfood). "We emit wasm and run neither" is the headline gap (loom — a wasm *optimizer* — runs no wasm verification; meld, gale, spar likewise). Manual witness runs (relay, wohl) count as **not a gate** until they're in CI. ## Track D — Pages verification dashboard Publish the verification evidence as a browsable dashboard on every repo that runs witness/scry. Canonical: witness's and scry's `publish-pages` job (witness-viz / scry-viz MC/DC truth-table + self-analysis, `actions/upload-pages-artifact` + `deploy-pages`). **One-time setup gotcha to document in the PR** (it bit both repos): Pages *Source* must be "GitHub Actions", and the `github-pages` environment needs a `v*` **tag** deployment-branch policy or tag deploys are rejected — `gh api -X POST repos/<org>/<repo>/environments/github-pages/deployment-branch-policies -f name='v*' -f type=tag`. (gale's `pages.yml` deploys only the gust demo, not a verification dashboard — that's the gap, not coverage.) ## Track E — the verification IS extracted into rivet (gate, not prose) A signed, dashboarded wasm is still non-compliant if the requirement→test mapping isn't in the rivet graph. The right side of the V must be *driven*, not narrated. The in-house exemplars already exist — **copy them, don't reinvent**: - **relay** — test/target-level `verifies` links (e.g. a specific bazel coverage target → `SWREQ-…`), all 174 verification artifacts linked, + a `verification-gate.yml` that *executes* the steps (`run-falcon-verification.py`). The model for "name the actual test, not the crate." - **gale** (642 links, ~complete coverage) and **synth** (141) for volume. - The rivet-driven PR gate (`tools/run_verification.py` over `type: test-case` artifacts) is canonical in witness/loom/spar — adopt it where missing. Laggards as of the sweep: scry (0 links despite 111 tests + 12 Rocq proofs + a live MC/DC gate), witness (2/55), loom, meld, mcp. The procedure for closing this lives in [`traceability-audit`]; this track just makes it a release-pipeline requirement. ## Verification — the oracle for this skill Per [`oracle-gate-a-change`], the release pipeline is itself a mechanical oracle. The diff that flips it red→green is one where the verification one-liner below runs cleanly against the published release. **Paste this verification block into the release notes of every release that uses this pipeline**, so consumers can re-run the check: ```sh cosign verify-blob \ --certificate-identity-regexp 'https://github.com/pulseengine/<tool>/.github/workflows/release.yml@.*' \ --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ --bundle SHA256SUMS.txt.cosign.bundle SHA256SUMS.txt gh attestation verify <tool>-vX.Y.Z-<triple>.tar.gz --repo pulseengine/<tool> ``` If either of these fails on the published release, the pipeline did not actually do what was claimed. That's a refute under [`clean-room-verification`]. ## How to apply to a target repo 1. **Read the canonical**: open `pulseengine/synth/.github/workflows/release.yml`. Find Phase 6 onward (the artifact-generation block). 2. **Identify the target repo's deltas** — Track A is broadly done org-wide; the live gaps from the 2026-06 sweep are Tracks B–E: - **Track A (binaries):** mostly compliant. Exceptions: sigil lacks the CycloneDX SBOM; kiln/mcp have no real release at all. - **Track B (distribution):** rivet is npm-only (no crates.io); sigil/synth/scry crates.io-only (no npm CLI wrapper); **mcp publishes via a stale unsigned manual script — move it into CI.** - **Track C (wasm):** the big one. loom/meld/gale/spar emit/handle wasm and run **neither** witness nor scry; relay/wohl run witness **manually** (not a gate); gale has an open `TODO(sigil)` and SLSA that misses the `.wasm`. (sigil-sign step is blocked on the wasip2-parser fix — add cosign now.) - **Track D (Pages):** only witness + scry deploy a verification dashboard — roll it to every repo running witness/scry. - **Track E (rivet extraction):** scry (0 links), witness (2/55), loom, meld, mcp are the laggards; copy relay's test-level pattern. 3. **Copy the Phase 6+ block from synth verbatim**, adapt only: - The main crate manifest path for the SBOM step. - The `<tool>` name in the verification one-liner. - The asset name pattern. 4. **Run [`oracle-gate-a-change`]** to verify the new workflow flips a check that didn't previously exist. The check is "the verification one-liner runs green against a test release." 5. **Run [`clean-room-verification`]** on the claim "the pipeline produces signed, attestable, SBOM-bearing artifacts." Have the verifier actually re-run `cosign verify-blob` and `gh attestation verify` against a real published release, not infer from the workflow YAML. ## Anti-patterns - **Trusting the workflow YAML as proof.** A green Actions run doesn't prove the artifacts are signed correctly — the verification one-liner against the *published* release does. The YAML is intent; the verifier is evidence. - **Pinning specific tool versions in this skill body.** `actions/attest-build-provenance@v2`, `sigstore/cosign-installer@v3`, `cosign v2.4.1`, `--spec-version 1.5` — these all decay. The canonical version is whatever synth's `release.yml` currently ships. If versions drift, sync to synth, don't update this skill. - **Keeping per-file `.sha256` sidecars "for backwards compat".** They're not in the standard. Drop them. (Witness's `.cert`/`.sig` are a different artifact serving certification evidence, not checksums — keep those.) - **Adding the cosign step without `id-token: write` permission.** Cosign keyless OIDC requires it. Missing this is the most common pipeline-broken-silently failure mode. - **Generating the SBOM after the sums file.** Order matters: SBOM must enter the sums. ## Cross-links - [`oracle-gate-a-change`] — pipeline setup is itself an oracle-flip; this skill names the oracle. - [`release-execution`] — what cuts a release atop a green pipeline. - [`clean-room-verification`] — verifies the pipeline actually does what it claims (re-run the verification one-liner against the published release). - [`pulseengine-feature-loop`] — sigil is the attestation step of the feature loop; the pipeline standard makes that step actually verifiable downstream. ## Notes on the canonical (synth) synth's `release.yml` is the reference. If patterns there look synth-specific (e.g. transcoder-output naming, Rocq-proof handling), generalize them when adapting. Generalization is fine — divergence from the 8-step skeleton is not. If you find yourself wanting to skip a step, that's a [`oracle-gate-a-change`] decision, not a casual one.
Ver no GitHub