Skip to content

Release mechanics for v0.2.0, and drop a dependency that never ran - #34

Merged
reesebuilt merged 3 commits into
mainfrom
agent/4.2a-release
Aug 5, 2026
Merged

Release mechanics for v0.2.0, and drop a dependency that never ran#34
reesebuilt merged 3 commits into
mainfrom
agent/4.2a-release

Conversation

@reesebuilt

Copy link
Copy Markdown
Owner

Step 4.2a: release workflow and packaging metadata

Branch agent/4.2a-release, rebased onto f5e62b9. Cutting v0.2.0 becomes a tag push
and nothing else.

Test Suites: 34 passed, 34 total
Tests:       300 passed, 300 total

Conformance: 26 cases, typescript and go: 22 agreed, 4 declared divergence(s), 0 failure(s).

node scripts/check-workflow-pins.js after the rebase, so release.yml is judged by the
checker that now gates merges rather than assumed to satisfy it:

5 workflow file(s), 37 pinned action reference(s), 1 exempt, every file declares a
top-level permissions block.

The one exempt reference is the SLSA generator, matched by the allowlist entry 4.1 seeded.

What the workflow produces on a tag

build resolves the version, refuses to continue when the tag and package.json
disagree, runs lint/build/test/conformance, then emits the npm tarball, a CycloneDX
SBOM, five static agentwall-verify binaries, and checksums.txt. provenance
generates SLSA3 attestation over those subjects. release attaches everything with
notes taken verbatim from the CHANGELOG plus a Verify block. npm-publish publishes
the exact packed tarball over Trusted Publishing. docker builds, pushes and
cosign-signs the image digest.

workflow_dispatch runs build and provenance and stops before publishing, so the
pipeline is provable while no tag exists.

Proven locally, not asserted

Every workflow step below was extracted from the YAML with a parser and executed
verbatim, not paraphrased.

Cross-compiled with CGO_ENABLED=0 ... -trimpath -ldflags "-s -w -X main.verifierVersion=0.2.0"
on go1.22.12:

da7c9625bea3286e955fa34422accece34b1b7f1c86b6baa69b98a1413ed14fb  agentwall-verify-darwin-amd64
d24b2b75f0efba60261685f415ff0c0dcb471c05fe94602a2d4eb69f8ec419ac  agentwall-verify-darwin-arm64
1990e82a6cbc49c27963643b6e345ceaed6f7b62f5e090747e622f2734b3ee8a  agentwall-verify-linux-amd64
42859fd7753183de05a72d5901e53071ed7c458eb9bb47d27682c117a689fcc8  agentwall-verify-linux-arm64
3d0d315827ebd36083b7fe5e3077aae2bd14613b1434633a560247023ca6138b  agentwall-verify-windows-amd64.exe
5007ede5c0e619f140259d71c9f73275fb22263c8e9e579d663a2c0f344963c3  reesebuilt-agentwall-0.2.0.tgz
f2bb2a5c6092062fe33fe85dbf10fc1c53dce870e679cbf244402f986fc51a86  sbom.cdx.json

sha256sum -c checksums.txt reports OK on all seven. Sizes: linux/amd64 2652.1 KB,
linux/arm64 2560.1 KB, darwin/amd64 2733.3 KB, darwin/arm64 2613.9 KB,
windows/amd64 2788.5 KB, tarball 552.5 KB, SBOM 100.6 KB.

Rebuilding linux/amd64 and linux/arm64 from scratch produced byte-identical files
(cmp clean), so the reproducibility claim holds.

Three failures the workflow now catches

  1. Tag and package.json can disagree. Compared before anything is built.
  2. The version stamp can silently not apply. verifierVersion was a const, and
    the Go linker ignores -X on anything that is not a string var, so the ldflags in
    the plan would have been a no-op and a v0.3.0 build would have reported 0.2.0. It is
    now a var, TestVersionMatchesPackageJSON holds it in step with package.json
    (falsified: desyncing package.json to 0.3.0 fails the test with a readable message),
    and the workflow asks the built binary what it reports.
  3. The SBOM had a hole. cyclonedx-npm 6.0.0 drops a package that is not hoisted to
    the top level while keeping that package's own dependencies. The generated SBOM
    listed @pinojs/redact, atomic-sleep, split2 and the rest of pino's tail with no
    pino entry. The workflow now cross-checks the SBOM against npm ls --omit=dev --all
    and fails when a name is absent entirely (falsified: deleting pino from the SBOM
    exits 1). A package installed at two versions is reported, not fatal, because the
    name is still present.

A fourth was caught by running the step rather than reading it: staging the file list
in dist-release/.manifest put that file into its own checksum list, because a shell
redirection creates its target before the command on the left runs. sha256sum -c
would then have failed on a correct download. Fixed with mapfile.

Packaging metadata

@reesebuilt/agentwall 0.2.0, bin still agentwall, engines.node >=22.12.0,
publishConfig access public and provenance true, prepack builds because dist is
untracked, plus the repository, homepage and bugs fields that were absent.

npm pack --dry-run: 227 files, 567.5 kB packed, 1.7 MB unpacked. Contents are
dist/, public/, examples/, README, LICENSE, NOTICE, CHANGELOG, SECURITY,
package.json. No src/, tests/, scripts/, .github/, docs/, verifier/,
lockfile, dotfile, key or config with a secret in it.

files gains !public/assets/*.png. Those three PNGs are 2.4 MB and nothing in the
repository references them: the dashboard loads only the brand icons and three
tutorial webps, and the README hero points at docs/assets/, a different copy. They
are still in git; only the tarball stops carrying them.

Two dependency decisions

@types/node pinned to ^22.20.1, not 26. Agreed with the reasoning as given. Types
must track the minimum supported runtime: @types/node 26 describes APIs absent on
Node 22, so the compiler accepts code that crashes on the floor we advertise. Typecheck
passes on ^22.

pino removed. Verified before acting: zero from "pino" or require("pino") in
any tracked file, and the only occurrence in src/ is the word inside a comment in
file-sink.ts. Both LoggerLike types are locally defined structural types
(Pick<Console, "error" | "warn"> and a local interface), not pino types. The only
Fastify logger option we pass is { level }, which is stable across pino 7 through 10.

The decisive evidence: the lockfile resolved our declared pino@8.21.0 to top-level
node_modules/pino while Fastify used its own nested pino@9.14.0. The dependency we
declared was not even the logger that ran. Bumping it to 10.3.1 would have changed
nothing at runtime.

Removing it and regenerating the lockfile drops 11 transitive packages (the browserify
shim tail pino 8 carried: buffer, readable-stream, safe-buffer, string_decoder,
events, process, and friends) and lets Fastify's pino resolve to 10.3.1, which is
also what makes the SBOM correct. README and CONTRIBUTING both claimed "deliberately
four" runtime dependencies; both now say three and name Fastify as pino's owner.

Not mine, but blocking a tag

The docker job references ./Dockerfile, which Step 4.2b creates. 4.2b must merge
before any tag is pushed or that job fails. Job spec agreed with Step42bDocker over IRC:
linux/amd64 only, provenance: true, sign steps.push.outputs.digest rather than the
tag.

Per Step 4.1: slsa-framework/slsa-github-generator is referenced by tag, not SHA, and
needs the allowlist entry in scripts/check-workflow-pins.js. Step41Scorecard is
seeding it. Every other action is pinned by 40-hex SHA with a version comment; every job
declares a permissions block; the workflow-level default is contents: read.

Operator prerequisites

See the agent report. In short: npm Trusted Publishing and the reesebuilt scope, a
release GitHub environment, and one green workflow_dispatch dry run.

agentwall-agent and others added 3 commits August 5, 2026 00:51
Cutting the release becomes a tag push. release.yml builds the npm tarball,
five static agentwall-verify binaries, a CycloneDX SBOM, and checksums from
the tagged tree, attests them with SLSA provenance, and publishes to npm over
Trusted Publishing so no long-lived token exists to leak. workflow_dispatch
runs the same build and provenance and stops before publishing, so the
pipeline is proven while no tag exists.

Three failures the workflow now catches instead of shipping:

- The tag and package.json can disagree. They are compared before anything is
  built, because every downstream artifact would otherwise carry a different
  number than the release page.
- verifierVersion was a const, and the linker silently ignores -X on anything
  that is not a string var. It is now a var, a test holds it in step with
  package.json, and the workflow asks the built binary what it reports.
- cyclonedx-npm omits a package that is not hoisted to the top level while
  keeping that package's own dependencies, which produced an SBOM with pino
  missing and pino's dependency tail present. The SBOM is now checked against
  npm's view of the production tree and a missing name fails the release.

Packaging: name becomes @reesebuilt/agentwall (the unscoped name belongs to an
unrelated project), version 0.2.0, engines floor 22.12.0, and the repository,
homepage and bugs fields several tools read. The files list stops shipping
2.4 MB of PNGs that nothing in the repository references.

Removed the direct pino dependency. Nothing imports it; the only occurrence in
src/ was a word in a comment. Fastify owns the logger and depends on pino
itself, and our declaration only pinned a second unused copy of pino 8 beside
it. Runtime dependencies are three, and a clean install drops 11 transitive
packages. @types/node tracks the supported floor at ^22 rather than the newest
release, since types describing APIs the runtime lacks compile and then crash.

Test Suites: 34 passed, 34 total
Tests:       300 passed, 300 total
The generator's upload-assets job owns that permission and is guarded by
`if: inputs.upload-assets`, which is false here, so the caller granting it
was privilege the run never uses. The release job attaches the provenance.
@reesebuilt
reesebuilt merged commit d754b8b into main Aug 5, 2026
11 checks passed
@reesebuilt
reesebuilt deleted the agent/4.2a-release branch August 5, 2026 05:55
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