Skip to content

Releases: forgesworn/anvil

v0.8.2

12 Apr 22:01

Choose a tag to compare

Bug Fixes

  • strip lifecycle-script stdout before parsing npm pack --json

v0.8.1

12 Apr 21:46

Choose a tag to compare

Bug Fixes

  • remove invalid runner.temp from publish job-level env

v0.8.0

12 Apr 20:49

Choose a tag to compare

Features

  • bridge auto-release to release.yml via workflow_dispatch

v0.7.0

12 Apr 20:39

Choose a tag to compare

Features

  • chain auto-release publish into release.yml via workflow_call
  • release workflow accepts explicit tag input

v0.6.0

12 Apr 20:25

Choose a tag to compare

Features

  • create or update Release to support chained auto-release flow

v0.5.1

12 Apr 11:19

Choose a tag to compare

Bug Fixes

  • include repo root in context7 indexed folders

v0.5.0

12 Apr 09:45

Choose a tag to compare

Features

  • conventional commit versioning, security fixes, wider ecosystem positioning

Bug Fixes

  • add .env, key material, and IDE patterns to .gitignore
  • harden CI/CD workflows against expression injection and credential leaks
  • harden scripts against injection, path traversal, and fragile patterns

v0.4.1 — fix scoped-package verify URL

11 Apr 12:49

Choose a tag to compare

Patch release. Fixes a scoped-package URL bug in the artefact integrity verify recipe, surfaced during the Phase 3 pilot on @forgesworn/shamir-words@1.1.0. Adds a bats regression test so it cannot silently come back.

What was broken

The verify recipe in the GitHub Release body used the local npm pack filename in the registry URL path. For unscoped packages this worked because the local pack name equals the registry-side name (e.g. nsec-tree-1.5.0.tgz). For scoped packages npm flattens the scope with a dash in the local filename but the registry serves the tarball at the unscoped basename within the scope-namespaced path:

local pack:    scope-pkg-1.0.0.tgz
registry url:  https://registry.npmjs.org/@scope/pkg/-/pkg-1.0.0.tgz

Consumers who copy-pasted the verification recipe from the release body would get a 404 on scoped packages.

Only the URL was wrong. The hashes recorded in the integrity block (sha256 and npm sha512 integrity) were and remain correct, and they still match the registry tarball byte-for-byte. The bug was purely cosmetic for the verification UX, not for the integrity claim itself.

What's fixed

steps/update-release.sh now strips the scope prefix from the package name with ${name##*/} and rebuilds the URL filename as ${basename}-${version}.tgz. For unscoped packages the construction produces the same filename as before, so behaviour for nsec-tree, geohash-kit, and other unscoped consumers is unchanged.

Existing already-published scoped releases on v0.4.0 can have their release body retroactively corrected via gh release edit.

What's new

test/update-release.bats adds four regression tests covering:

  • @forgesworn/shamir-words@1.1.0 (the original Phase 3 bug)
  • nsec-tree@1.5.0 (unscoped, behaviour-unchanged guard)
  • @noble/hashes@1.4.2 (different scope shape)
  • gh release upload --clobber invocation (idempotency check)

The tests use a fake gh on PATH that captures every invocation; assertions are made on both the positive URL (must appear) and the negative URL (the broken form must not appear inside any registry URL).

Migration

None. Bumping @v0.4.0 to @v0.4.1 is a drop-in replacement. Anyone pinning @v0 already has the fix — the floating tag was moved to the fix commit before this patch release was cut.

Stats

  • 1 file changed (test only); fix commit was already on main
  • 63/63 bats tests pass (4 new)
  • shellcheck clean
  • Phase 1, 2, and 3 pilots complete: nsec-tree (unscoped), geohash-kit (unscoped), @forgesworn/shamir-words (scoped, surfaced this bug)

Full diff: v0.4.0...v0.4.1

v0.4.0 — multi-runner reproducible-build attestation

11 Apr 12:42

Choose a tag to compare

Multi-runner reproducible-build attestation. The v0.4 flagship. The reusable workflow now runs the release as a four-job DAG and publishes only when two parallel builds on independent CI runners produce byte-identical tarballs. None of semantic-release, @changesets/cli, release-it, release-please, or np offers this today.

What's new

  • Reusable workflow split into a four-job DAG: build-a (full gates + record), build-b (build + record only), reproduce (compare sha256s), publish (publish-npm + publish-jsr + update-release). Both build jobs upload their tarball as a CI artifact; the reproduce job downloads both and compares; the publish job downloads the canonical build-a tarball and uploads exactly that to npm.
  • steps/normalise-mtimes.sh walks the working tree and touches every file and directory to SOURCE_DATE_EPOCH (derived from git log -1 --format=%ct), so npm pack produces tar headers that are byte-equal between independent runs. Belt-and-braces — even if a build tool ignores SOURCE_DATE_EPOCH, the file stamps are uniform by the time we pack. Cross-platform GNU + BSD touch. Excludes node_modules and .git.
  • steps/compare-tarball-meta.sh is the reproducibility gate. Compares two tarball.meta files. Under default reproducibility-mode: strict, a sha256 mismatch is a hard failure: both hashes are printed, the diff between the two tar listings is dumped, common non-determinism causes are listed, and the publish is blocked. Under warn the mismatch is logged but the release proceeds. Under off the second build is skipped entirely (v0.3 single-runner behaviour).
  • Canonical tarball is uploaded as a GitHub Release asset. Consumers now have two independent sources for the bytes (npm registry + GitHub Releases) and can hash-compare against either using the verify recipe in the release body.
  • "Reproducible build" header line is prepended above the integrity block in the release body when the reproduce gate matched. Cosmetic but makes the property visible without parsing the integrity block.

Migration

Bumping the pin to @v0.4.0 enables reproducibility-mode: strict by default. Most well-behaved JS builds are accidentally reproducible — the common failure modes are timestamps and sorted-by-fs paths, both of which v0.4's mtime normalisation handles for free. Libraries with Date.now() or __BUILD_TIMESTAMP__-style build-time injection need to fix their build before the upgrade lands.

The safer path:

jobs:
  release:
    uses: forgesworn/release-action/.github/workflows/release.yml@v0.4.0
    with:
      vector-test-command: npm run test:vectors
      reproducibility-mode: warn

After a couple of green releases under warn, drop the input to take the default strict. Full walkthrough including diagnostic recipes for common non-determinism causes lives in docs/migration-from-v0.3.md.

What's not changing

  • Composite action (uses: forgesworn/release-action@v0.4.0) stays at v0.3-equivalent semantics: all gates, record-tarball, integrity block, but no reproduce gate because composite actions cannot define multi-job DAGs. Use the reusable workflow for the v0.4 flagship guarantee. The composite remains as a power-user escape hatch only.
  • Trusted publisher configuration is unchanged. The OIDC trusted-publisher record still points at your repo and your caller workflow file, not at forgesworn/release-action.
  • All v0.3 inputs (vector-test-command, strict-action-pins, etc.) work unchanged.

Known limits

  • Single OS only. Both builds run on ubuntu-24.04. Cross-OS reproducibility is a stronger claim that adds correctness burden on consumers and is out of scope for v0.4.
  • Two-run sample size. A non-determinism source that fires probabilistically (one in a thousand) won't reliably surface in two runs. Cost-of-CI-minutes trade-off.
  • SOURCE_DATE_EPOCH is opt-in for build tools. We can't force esbuild/rollup/webpack/tsc to honour it. Mtime normalisation closes the file-stamp gap unconditionally; embedded build-time strings inside compiled output remain the consumer's bug to fix and reliably fail the gate.

Why this matters

Two independent runners arriving at the same bytes is a stronger claim than SLSA provenance. Provenance attests one runner built these bytes once. The reproduce gate attests two runners building the same commit arrive at the same bytes — the actual byte-identical-output property that crypto-library customers care about and the property the rest of the JS release tooling ecosystem currently leaves to hope.

Stats

  • 2 new step scripts (normalise-mtimes.sh, compare-tarball-meta.sh)
  • 1 modified for reproducibility (record-tarball.sh)
  • 16 new bats tests (59 total, all passing)
  • shellcheck clean
  • Zero new dependencies; still pure bash + jq + gh + npm
  • ~1250 lines of bash across all step scripts (still inside the 30-minute audit budget)
  • New design doc and migration guide

Full diff: v0.3.0...v0.4.0

v0.3.0 — artefact integrity bundle

11 Apr 12:18

Choose a tag to compare

Artefact integrity bundle. Two new pre-publish gates and a rewired publish path that lets downstream consumers verify the registry tarball byte-for-byte against what CI built.

What's new

  • record-tarball packs the release artefact once into a known location, parses npm pack --json for the sha512 integrity, hashes the tarball with sha256, and writes tarball.meta for the rest of the pipeline to consume.
  • publish-npm now uploads the exact tarball recorded above rather than re-packing. The bytes the registry receives are byte-identical to the bytes the integrity block reports. On a clean re-run of an already-published release, the registry's dist.integrity is compared to the recorded value: a match exits silently, a mismatch fails the workflow loudly. That scenario is registry tarball substitution and you want to know about it on the next CI run.
  • update-release appends an Artefact integrity block to the GitHub Release body containing filename, size, sha256, sha512 (npm format), and a one-line curl | shasum recipe. Anyone can fetch the registry tarball and confirm byte-equivalence with what CI built.
  • verify-action-pins walks .github/workflows/*.yml in your repo and warns on any uses: owner/repo@ref line whose ref is not a 40-char hex SHA. Warn-only by default. New strict-action-pins: true input promotes warnings to a hard failure. forgesworn/release-action is exempt by name; see THREAT-MODEL.md for the rationale.

Migration

None required. The new gates are additive and verify-action-pins is warn-only by default. Existing v0.2.x consumers can bump their pin to v0.3.0 (or stay on @v0) without changing any caller-workflow input.

If you want the strict pin gate:

with:
  vector-test-command: npm run test:vectors
  strict-action-pins: true

Known limits

  • The recorded sha256 is a single-runner integrity anchor, not a reproducibility proof. Two runners building the same commit may still produce two different hashes today, due to embedded timestamps and path leakage. Cross-runner reproducible-build is a planned v0.4 theme. The integrity block defends against registry tarball substitution, which does not require build-level determinism to detect.
  • verify-action-pins cannot statically resolve dynamic uses: ${{ matrix.action }} lines and silently skips them. Audit those templates separately.

Verify a release tarball

Every release body now ends with a verify recipe like:

curl -sLO https://registry.npmjs.org/<pkg>/-/<pkg>-1.2.3.tgz
shasum -a 256 <pkg>-1.2.3.tgz

Compare the result to the sha256: line in the release body. They should match exactly.

Stats

  • 2 new step scripts (record-tarball.sh, verify-action-pins.sh)
  • 13 new bats tests (43 total, all passing)
  • shellcheck clean
  • Zero new dependencies; still pure bash + jq + gh + npm
  • ~1000 lines of bash across all step scripts (still inside the 30-minute audit budget)