Skip to content

feat(release): crates.io trusted publishing + toolchain SBOM (Phases 4+6)#136

Merged
avrabe merged 1 commit into
mainfrom
feat/release-phase-4-6
May 24, 2026
Merged

feat(release): crates.io trusted publishing + toolchain SBOM (Phases 4+6)#136
avrabe merged 1 commit into
mainfrom
feat/release-phase-4-6

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 24, 2026

Summary

Closes Phases 4 (crates.io trusted publishing) and 6 (toolchain SBOM auto-emit) of synth's release roadmap, defined in docs/release-process.md. Together with PR #135 (Phase 5, sigil ELF signing) this completes the supply-chain evidence layers for synth — Phases 1-6 all implemented.

Phase 4 — crates.io trusted publishing

Publish set (11 crates)synth-cli, synth-core, synth-frontend, synth-synthesis, synth-backend, synth-backend-{awsm,wasker,riscv}, synth-cfg, synth-opt, synth-verify.

publish = false (6 crates, kept private)synth-analysis, synth-abi, synth-memory, synth-qemu, synth-test, synth-wit. None are on synth-cli's transitive face; promotion later is a one-line change.

Metadata — every publishable Cargo.toml has description, license, repository (already present), plus newly-added homepage.workspace, keywords.workspace, categories.workspace. Internal path = "..." deps on publishable crates now carry version = "0.6.0" so cargo publish rewrites to crates.io coordinates.

Versioning — workspace version bumped 0.1.00.6.0 (also MODULE.bazel). Convention going forward: bump pre-tag in lockstep with the release tag; the new workflow refuses to run if they disagree.

Workflow.github/workflows/publish-to-crates-io.yml triggers on v* tag (parallel to release.yml); if: github.repository == 'pulseengine/synth'; permissions: id-token: write; uses rust-lang/crates-io-auth-action@v1 for OIDC. Dependency-order walk lives in scripts/publish.rs (rustc-compiled at workflow start, mirrors sigil's pattern) with a 10-attempt × 40s retry loop for index propagation. Injection-safe: inputs.tag bound via env: INPUT_TAG, dereferenced as $INPUT_TAG in shell.

Phase 6 — toolchain SBOM in release pipeline

Added two steps to the create-release job in .github/workflows/release.yml:

  • cargo install --locked cargo-cyclonedx
  • cargo cyclonedx --format json --spec-version 1.5 -p synth-cli → renamed to synth-v<VERSION>.cdx.json in release assets

Emitted before SHA256SUMS.txt, so its digest is in the manifest and the existing cosign keyless signature over SHA256SUMS.txt transitively covers it — no new signing step.

This is the complement to the per-compilation SBOM (synth compile --sbom, PR #129): that one documents what synth compiles; this one documents what synth is. Comparison table in docs/sbom.md.

Honest blocker — needs the maintainer (verified clean-room)

Per-crate trusted-publisher registration on crates.io. For each of the 11 publishable crates, the repo owner must visit https://crates.io/crates/<NAME>/settings → Trusted Publishing and register pulseengine/synth with workflow filename publish-to-crates-io.yml. For brand-new crates not yet on crates.io, a manual first publish is needed before the registration page exists. Documented in docs/release-process.md § "Phase 4 — user-side setup".

cargo publish --dry-run was blocked in the agent's sandbox; the workflow's ./publish verify step runs the full dry-run gate before any real publish.

Clean-room verification

A clean-room subagent independently verified the report: 15 of 16 claims CONFIRMED with file:line citations (including the publish set, the publish = false set, the workspace version bump, MODULE.bazel diff, per-crate metadata, path-dep versions, workflow injection-safety, scripts/publish.rs shape, SBOM-before-SHA256SUMS ordering, docs updates, both YAMLs parse, blocker documented). 1 CANNOT-VERIFY (historical CI run — structurally unverifiable).

Test plan

  • CI green
  • First v* tag push after this lands: watch the new Publish to crates.io workflow — will likely need workflow_dispatch re-runs as each crate is registered
  • First release SBOM appears as synth-v0.6.0.cdx.json in the release assets

🤖 Generated with Claude Code

…BOM)

Phase 4 — crates.io OIDC trusted publishing
  - new .github/workflows/publish-to-crates-io.yml, triggered in
    parallel with release.yml on every v* tag push
  - rust-lang/crates-io-auth-action@v1 exchanges the workflow OIDC
    token for a short-lived crates.io token; no CRATES_IO_TOKEN
    secret stored
  - scripts/publish.rs walks the curated dependency order with retry
    on index-propagation lag (mirrors pulseengine/sigil's helper)
  - workspace version bumped 0.1.0 -> 0.6.0 (trusted publishing
    needs real semvers; bumped in lockstep with the next release tag)
  - publishable crates carry homepage/keywords/categories via
    workspace inheritance; internal path deps add explicit version
    so cargo publish rewrites them to crates.io coordinates
  - publish set (11 crates): synth-cli, synth-core, synth-frontend,
    synth-synthesis, synth-backend, synth-backend-{awsm,wasker,riscv},
    synth-cfg, synth-opt, synth-verify
  - publish = false for synth-analysis, synth-abi, synth-memory,
    synth-qemu, synth-test, synth-wit (not on the synth-cli public
    face; promotion later only needs removing the line)
  - MODULE.bazel version bumped 0.1.0 -> 0.6.0 to match
  - blocker: per-crate trusted-publisher registration on crates.io
    is a manual one-time step the maintainer must do; documented in
    docs/release-process.md "Phase 4 user-side setup"

Phase 6 — toolchain SBOM auto-emit in release.yml
  - cargo cyclonedx generates a CycloneDX 1.5 JSON SBOM rooted at
    synth-cli and writes release-assets/synth-v<VERSION>.cdx.json
  - SBOM is produced before SHA256SUMS so its digest is captured
    in the signed checksum manifest; the existing cosign signature
    over SHA256SUMS.txt transitively covers the SBOM
  - distinct from the per-compilation SBOM `synth compile --sbom`
    added in #129; docs/sbom.md now opens with the comparison table

Validation: cargo test --workspace --exclude synth-verify clean,
cargo clippy --workspace --exclude synth-verify --all-targets
-- -D warnings clean, cargo fmt --check clean, both workflow YAMLs
parse with python yaml.safe_load. cargo publish --dry-run is
blocked in this environment; the dry-run gate runs first in the
workflow itself before any real publish.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe avrabe merged commit a3db9c9 into main May 24, 2026
11 of 13 checks passed
@avrabe avrabe deleted the feat/release-phase-4-6 branch May 24, 2026 13:13
avrabe added a commit that referenced this pull request May 24, 2026
v0.6.0 — supply-chain close-out (rivet #107):
- #135 Phase 5: sigil ELF signing of compiler output (synth compile --sign-output)
- #136 Phases 4+6: crates.io trusted publishing + toolchain SBOM auto-emit
avrabe added a commit that referenced this pull request May 24, 2026
v0.6.0 — supply-chain close-out (rivet #107):
- #135 Phase 5: sigil ELF signing of compiler output (synth compile --sign-output)
- #136 Phases 4+6: crates.io trusted publishing + toolchain SBOM auto-emit
avrabe added a commit that referenced this pull request May 25, 2026
The Cargo.toml workspace-package version was bumped to 0.7.0 in
PR #142, but the per-crate `version = "0.6.0"` pins on path
dependencies (introduced in #136 to give `cargo publish` real
crates.io coordinates to rewrite to) were not swept. Cargo
refuses to resolve `synth-backend = "^0.6.0"` against the local
0.7.0 path-dep, breaking both release.yml and
publish-to-crates-io.yml on the v0.7.0 tag.

This commit sweeps all 23 intra-workspace `version = "0.6.0"`
pins (across 8 crate manifests) to `"0.7.0"`, and bumps the
MODULE.bazel `module(version = ...)` declaration to match. The
v0.7.0 tag was deleted from origin (no GitHub Release was
produced — release.yml's build matrix failed before the
'Create GitHub Release' job, which was skipped) and will be
re-pushed against this commit.

Local validation:
  cargo metadata --no-deps  # resolves at 0.7.0
  cargo build --release -p synth-cli  # green

Follow-up tracked separately: docs/release-process.md should
gain an explicit 'sweep intra-workspace pins' step (or a CI
gate that fails when workspace version diverges from path-dep
pins) so v0.8.0 doesn't repeat this.
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.

1 participant