This document defines the release baseline for bytefolk
repositories. A repository may add stricter automation, but it may not skip an
applicable gate. Each repository declares which distribution channels it
supports; npm, GitHub Release, and GHCR are checked explicitly, but a repository
is not required to publish to a channel it does not support.
- Use Semantic Versioning 2.0.0:
MAJOR.MINOR.PATCH. - For a stable public API at version
1.0.0or later, an incompatible change requires the nextMAJORversion. A prerelease identifier does not reduce that requirement: previewing the change uses that next major line (for example,2.0.0-alpha.1), not a1.xprerelease. - While the public API is below
1.0.0, an incompatible change may use the nextMINORversion, but the instability and each breaking change must be called out explicitly in the release record and release notes. - One release issue selects one version. A failed or incorrect publication is corrected with a new version; a published package version or tag is never reused.
The release issue is the append-only control record. Before implementation it
records revision R1, a DEC marker, the version, candidate main baseline,
rollback criteria, and a channel matrix with one row for each of these
organization-known channels. After the release pull request merges, append its
exact full squash commit and exact-commit check evidence:
| Channel | Required declaration |
|---|---|
| npm | Exact package name and dist-tag, or N/A: <evidence-based reason> |
| GitHub Release | Expected asset inventory, or N/A: <evidence-based reason> |
| GHCR | Exact image name and tag/digest policy, or N/A: <evidence-based reason> |
N/A is valid only when the repository does not declare that channel for the
release. The reason and the repository policy or product decision supporting it
must be reviewable. A missing artifact on a declared channel is a failure, not
N/A.
Every release follows these six ordered phases. The mem release workflow is
the safety precedent for an asset-backed npm package: merge the reviewed
release machinery first, consume an existing immutable tag, publish assets as
a verified draft, and only then publish the dependent npm package.
- Release issue. Create a dedicated release issue through the standard
issue flow (placeholders backfilled and verified; see
ENGINEERING.md). Approve its version, declared-channel matrix, risk, and rollback plan. - Release preparation pull request. Move the applicable changelog or
versioned release-ledger entries out of
Unreleased, synchronize every public version surface, and add or harden the release workflow in this same pull request. Run all repository gates. A required CODEOWNER independently approves the exact final head, and the pull request is squash-merged through protectedmainwithout an administrator bypass. Versioned release content is therefore onmainbefore a tag exists; do not add a second, post-tag ledger pull request to redefine what the release contained. - Pre-tag gate and immutable tag. Record the release pull request's full
squash commit and the successful required checks for that exact commit.
Fetch
origin/main, prove the selected commit resolves exactly and is reachable fromorigin/main, prove the version is unused on every declared channel, and read back the release-tag rulesets. Only after all of those checks pass may the least-privileged authorized release actor create and push the tag once, as described in §Immutable tag gate. - Release workflow. The tagged commit contains a reviewed workflow already
merged on
main. That workflow consumes the existing annotated tag; it never creates, moves, or replaces one. It resolves the full tag ref, rejects a lightweight or non-maintag and any version mismatch, and builds from the exact peeled commit in a clean hosted environment. Where GitHub Release is declared, upload to a draft, read back the exact non-empty asset inventory and checksums, and make publication the final step. A manual retry names the same existing tag and re-runs every source check; any workflow change used for a retry first needs its own issue, pull request, checks, and independent review. A retry is never permission to create or repair a tag. - Publish and verify declared channels. Follow dependency order. For
example, an npm installer that downloads GitHub Release assets publishes
only after those assets and their checksums have been verified. Read every
declared channel back independently using §Channel evidence. Record an
evidence-backed
N/Afor each intentionally unsupported channel. - Close the release record. Append exact URLs, commits, digests,
integrity values, signature/provenance results, workflow runs, smoke tests,
and every approved
N/Ato the release issue. Close it only when all applicable rows pass. This post-publication receipt is evidence about the already versioned release, not a new code ledger that can change it.
Release v* tags must be protected by server-side rulesets before the first
release under this baseline:
- an active immutable-tag rule for
refs/tags/v*prohibits update and deletion with no bypass path; and - a separate creation rule restricts new
refs/tags/v*tags, with bypass only for the minimum repository role or release identity that must create one.
Read both rulesets back and attach their identifiers, active enforcement,
refs/tags/v* conditions, creation/update/deletion rules, and bypass actors to
the release issue. Broad write access, a local hook, and a documented promise
are not tag protection. Use the least-privileged eligible actor for the single
creation; administrative access is not a reason to bypass an unmet release
gate.
A repository that published releases before adopting this baseline records its current tag protections and any gap in a dedicated adoption or release issue. It must install and read back both required rulesets before its next release after adoption. Until that evidence is recorded, the repository may prepare reviewed release changes but must not create a new release tag or publish another version. Existing tags are not rewritten or deleted during adoption.
The ancestry and absence checks happen before git tag. Use a clean
checkout and substitute the full squash commit SHA and version below:
set -euo pipefail
version="${RELEASE_VERSION:?set RELEASE_VERSION to vX.Y.Z}"
release_commit="${RELEASE_COMMIT:?set RELEASE_COMMIT to the full squash SHA}"
git fetch --prune --no-tags origin \
refs/heads/main:refs/remotes/origin/main
resolved_commit="$(git rev-parse --verify "${release_commit}^{commit}")"
test "${resolved_commit}" = "${release_commit}"
test "$(git rev-parse HEAD)" = "${release_commit}"
test -z "$(git status --porcelain)"
git merge-base --is-ancestor \
"${release_commit}" refs/remotes/origin/main
test -z "$(git tag --list "${version}")"
test -z "$(git ls-remote --tags origin \
"refs/tags/${version}" "refs/tags/${version}^{}")"
# Confirm the GitHub Release and every declared registry version are also
# absent here. Only then create the first and only tag for this version.
git tag -a "${version}" "${release_commit}" -m "Release ${version}"
test "$(git rev-parse "${version}^{commit}")" = "${release_commit}"
git push origin "refs/tags/${version}:refs/tags/${version}"
remote_commit="$(git ls-remote --tags origin \
"refs/tags/${version}^{}" | awk '{print $1}')"
test "${remote_commit}" = "${release_commit}"The peeled ^{} lookup proves the remote tag is annotated and points to the
recorded commit. If a tag push has an ambiguous result, read the remote ref
before retrying. Never delete, force-push, move, or recreate a stable tag; fix
a bad release with a new issue and new PATCH version.
- Read back the tag name, target commit, draft/prerelease state, release URL, and exact asset names and non-zero sizes.
- Download assets into a fresh directory and verify the repository-declared checksums or signatures. Where binaries embed source metadata, verify the recorded VCS revision is the release commit and the tree is unmodified.
Trusted Publishing with GitHub Actions OIDC is the normal npm credential path.
Pin the publisher to the exact organization, repository, workflow filename,
and environment when one is used, and set npm's Allowed actions to
npm publish only. The independently reviewed workflow grants only the
permissions it needs, including id-token: write, and publishes with
provenance. Do not keep a long-lived npm token in repository, organization,
runner, or maintainer configuration.
An initial publication may require bootstrap authentication before npm lets the package configure a Trusted Publisher. That exception requires release-issue authorization. Prefer an interactive web login whose generated local session is isolated to a temporary user-config file and revoked immediately. If npm instead requires a granular token, restrict it to the single intended package (or the narrowest npm scope capable of creating that package), grant package read/write only, and select the shortest available expiry. Never place any credential in a repository file, command argument, workflow input, log, or issue comment. Publish once, complete registry and clean-consumer verification, and log out or revoke the bootstrap credential immediately. Configure the Trusted Publisher next, then land the OIDC workflow as a separate independently reviewed pull request; only a later real OIDC publication proves that configuration works.
npm view reads registry metadata; it does not verify a signature or
provenance attestation. Record and compare the exact package source, version,
Git commit, and registry integrity, then verify signatures/attestations from a
clean install with a supported, pinned npm version:
package=@scope/name
version=X.Y.Z
npm_cli_version="${NPM_CLI_VERSION:?pin an approved npm CLI version}"
npm_cli=(npx --yes "npm@${npm_cli_version}")
"${npm_cli[@]}" view "${package}@${version}" \
name version repository.url gitHead dist.integrity --json
consumer="$(mktemp -d)"
cd "${consumer}"
"${npm_cli[@]}" init --yes >/dev/null
"${npm_cli[@]}" install --ignore-scripts --save-exact "${package}@${version}"
"${npm_cli[@]}" ls "${package}" --depth=0
"${npm_cli[@]}" audit signaturesThe installed version and lockfile integrity must equal the registry readback,
the repository URL and gitHead must identify the expected source and release
commit, and npm audit signatures must succeed. Then run the
repository-defined first-use smoke test in that clean consumer; disabling
lifecycle scripts for installation must not silently replace that test.
- Read the manifest by the declared version tag and record its immutable digest and platform inventory.
- Pull and smoke-test by digest. Evidence for a mutable tag without its digest is insufficient.
- Changelogs are organized from Conventional Commits, grouped by
feat,fix, and breaking changes. The versioned entry merges before tagging. - Every release pull request's risk section states an executable rollback procedure. Rollback never reuses or moves a published tag or package version.
- Never declare a release successful before every applicable channel has been
read back and every
N/Ahas an approved, evidence-based reason. - Never use an administrator merge or protection bypass to compensate for a missing check, approval, CODEOWNER, ruleset, credential design, or release artifact.
- Release write operations (issue creation, tag push, and publication) are drafted and reviewed before execution, like any other governed change.
- Release commits must not carry automated-tool co-author trailers, per
ENGINEERING.md.