promote-buildchain-ref
Internal buildchain action for promoting verified buildchain release-line and compatibility refs from buildchain release channels:
alpha/v2/v2.0creates or reuses the next exact prerelease tag such asv2.0.1-alpha.0, writes that version into package version state, points the alpha and dev channel branches at the version commit, then promotesv2.0-alphaand, when this is the highest published alpha minor,v3-alpha;release/v2/v2.0creates or reuses the next exact release tag such asv2.0.0, writes that version into package version state, points the release channel branch and release tags at the release commit, then prepares a second source commit for the next exact prerelease tag such asv2.0.1-alpha.0and points the alpha/dev channel branches plusv2.0-alphaat that prerelease commit, and movesv3-alphaonly if no higher v2 minor has published an alpha;publish-gate/majoraccepts a reviewed PR from a production release line such asrelease/v2/v2.0, writes the next major production version such asv3.0.0, pointspublish-gate/major,release/v3/v3.0,v3.0, andv3at that release commit, then preparesv3.0.1-alpha.0foralpha/v3/v3.0,dev/v3/v3.0,v3.0-alpha, andv3-alpha. The oldermajor-gatebranch name is a compatibility alias only.
The release branch name defines the minor line. For example,
release/v2/v2.1 creates v2.1.N, promotes v2.1, and promotes v2 only
when the next minor tag such as v2.2 does not already exist.
The action updates version state in lerna.json, root package.json, and
workspace package manifests discovered from package manager metadata
(package.json workspaces, lerna.json packages, or pnpm-workspace.yaml).
Package manager detection is adaptive (pnpm, npm, or yarn) and is recorded
in logs.
Repositories can also provide buildchain.toml to declare version-state files
and lifecycle.verify. TOML-configured version files take precedence over
package-manager discovery and can target JSON, TOML, or regex-based files. The
version commit itself is written through the GitHub Git Data API so the ref
graph is the durable source of truth. Repositories without any supported version
state degrade to ref-only promotion only when strict version state is disabled.
JSON and TOML version entries whose declared key already matches the requested version are treated as semantic no-ops. TOML changes use a parser-verified lossless key edit, so repository formatting is preserved and formatter-only release-preparation commits are not created. If a unique lossless edit cannot be proven, promotion fails closed instead of rewriting the full TOML document.
Dry Run
Use dry-run: "true" or the CLI buildchain release --dry-run before merging a
channel PR when you need to understand what Buildchain would do. This dry-run is
at the Buildchain release-line level. It explains:
- the legal source branch for the target channel;
- exact release or alpha tags that would be created or reused;
- floating tags and channel branches that would move;
- version-state files and verification lifecycle that would apply;
- branch protection, PR lineage, and release-from-alpha checks;
- publish transaction behavior when
lifecycle.publishorpublish-transactionis enabled.
It does not move refs, move tags, write package files, run publish commands, or publish npm packages. The GitHub action dry-run still calls GitHub APIs to resolve the current target SHA and concrete pending ref updates, but every write is reported as a dry-run update.
Repositories whose package version is anchored to an explicitly selected upstream release can opt into manual next-anchor behavior:
[version]
required = true
strategy = "anchored"
next = "manual"
manifest = "libnode.release.json"
In this mode, the action validates the configured version files and anchor
manifest through the repository’s verify lifecycle, but it does not rewrite the
package version to match the Buildchain release tag. After a production
release, it sets next-anchor-required=true and does not auto-create the next
alpha branch or tag. The repository must create the next upstream anchor line
explicitly, then run the normal channel promotion flow for that line.
When branch protection requires pull requests, generated version-state commits
still run through promotion automation first. The action updates
Buildchain-managed channel protection before generated bookkeeping, admits only
the exact github-actions App to the target-bound bypass allowlist, creates
every configured required check on the exact generated version-state commit, then
tries to apply that commit directly. If GitHub still rejects release
finalization bookkeeping, Buildchain creates or reuses a same-repository
buildchain/version-state/* PR based on the current target channel head and
returns finalization-needed=true; a later idempotent promotion run can resume
from the durable transaction state. Strict alpha uses the same protected
version-state PR recovery for its target and dev bookkeeping, and does not move
tags until those provider-enforced transactions land. Reusable wrapper callers
should allow checks: write so the generated checks are owned by GitHub Actions
and match the managed branch protection rule.
Stable promotion also protects concurrent development work. The reusable
wrapper checks out the exact current dev/vN/vN.M head as a reconciliation
workspace. If next-alpha bookkeeping cannot fast-forward that branch, the
action reruns the declared version-state generation and verification from that
dev tree, creates a two-parent reconciliation commit from the regenerated
files, and fails closed if the checkout moved before the mutation boundary.
This prevents generated projections from an older release tree from replacing
capabilities that reached dev while the release was in progress.
For Buildchain-owned automation, callers may pass
branch-protection-bypass-apps: github-actions. The action rejects every other
App slug and all user or team bypass actors. It configures managed
dev/vN/vN.M, alpha/vN/vN.M, and release/vN/vN.M branches with one
required approving review, Code Owner review, stale-review dismissal,
latest-push approval, exact App-bound GitHub Actions checks, admin enforcement,
conversation resolution, no force pushes, and no deletions; the bypass
allowance only lets the named automation identity apply generated version-state
or channel bookkeeping without a second human review after the reviewed channel
PR has already merged.
GitHub may return either 403 or a deliberately opaque 404 when a
non-administrator token reads the full branch-protection endpoint. In that
case, promotion reads the provider’s branch summary and accepts only an
already-protected branch that enforces the exact required check for everyone.
It does not interpret the opaque response as missing protection or try to
rewrite policy with a developer token. The independent publication-authority
audit remains responsible for the complete read-only governance proof.
Publish Transactions
Promotion can also own external publish side effects. Enable this only from a trusted channel workflow:
- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v3
with:
token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}
generated-ref-update-token: ${{ github.token }}
sha: ${{ github.sha }}
target-ref: release/v2/v2.0
publish-transaction: "true"
publish-mode: publish-final-version
publish-auth: trusted-publishing
publish-required-artifacts-json: >-
[
{"kind":"npm","name":"@kungfu-tech/buildchain","ref":"3.0.0","digest":"sha256:..."}
]
For anchored/manual package repositories that build through the reusable
workflow, keep the publish entrypoint on the publish-gate/* source-lock
contract:
- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v3
with:
token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}
sha: ${{ needs.build.outputs.publish-source-sha }}
target-ref: release/v22/v22.22
require-publish-source-lock: "true"
publish-source-ref: ${{ needs.build.outputs.publish-source-ref }}
publish-source-sha: ${{ needs.build.outputs.publish-source-sha }}
publish-source-locked: ${{ needs.build.outputs.publish-source-locked }}
publish-transaction: "true"
publish-mode: publish-final-version
publish-auth: trusted-publishing
target-ref remains the channel promotion target that must point at sha.
publish-source-ref is the reviewed source-lock branch that authorized this
specific package publication. Direct alpha/* or release/* channel refs are
not valid publish source locks when require-publish-source-lock is enabled,
and a mismatched publish-source-sha fails before any promotion or publish side effects begin.
Consumers that opt in to a product-owned final predicate set
require-publication-qualification: "true" and pass the exact sealed
publication-capability-json, complete publication-gate-aggregate-json, and
publication-qualification-receipt-json. The action validates their canonical
digests, predicate identity, freshness, nonce, source, version, channel, target,
and evidence bindings both before provider access and immediately before the
publish transaction. Missing or drifted receipts fail before mutation. The
reusable release-candidate-promote.yml workflow creates these values in a
separate credentialless consumer job; direct callers must preserve the same
contract and may pass previously consumed nonces through
publication-used-qualification-nonces-json.
The reusable promote workflow serializes non-dry-run promotion intents per
repository and re-reads target-ref before checkout, dependency installation,
release-candidate resolution, or publish-gate writes. If a queued intent asks
for an older SHA after the protected channel has advanced, the workflow records
the requested/current SHA pair, verifies that the current target is ahead of
the requested commit, and exits successfully as a superseded no-op. Diverged,
behind, or unreadable comparisons still fail closed.
After queued-intent revalidation, the reusable workflow runs the canonical
release-candidate resolver in metadata-only mode before installing candidate
dependencies or starting the full promotion job. The full job resolves and
downloads the evidence again before any publish-gate or publication mutation.
This preserves the final exact-evidence trust check while making missing or
stale PR-stage evidence fail early.
The action repeats that check at its mutation boundary for governed promotion
calls, closing the race between workflow preflight and action start. Direct
non-governed calls and dry-runs keep the strict target mismatch error so local
diagnostics cannot silently reinterpret a stale request.
Expected manual dry-run failures remain visible in the workflow result and do
not create automated workflow-friction issues; issue reporting is reserved for
non-dry-run promotion failures.
The reusable build workflow performs the cheaper channel-ref preflight earlier:
after source-lock resolution and before the build matrix, it requires the target
channel ref such as alpha/v22/v22.22 or release/v22/v22.22 to already point
at the locked publish-source-sha. If not, maintainers should merge the source
commit through the channel PR first.
For promote-only release candidates, attach the PR-stage reusable build evidence and fail before publish-gate side effects if it no longer matches:
- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v3
with:
token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}
sha: ${{ needs.build.outputs.publish-source-sha }}
target-ref: alpha/v22/v22.22
promote-only-release-candidate: "true"
release-candidate-passport-path: .buildchain/artifacts/release-candidate-passport.json
release-candidate-build-summary-path: .buildchain/artifacts/build-summary.json
The action validates repository, channel, source identity, platform matrix, and the aggregate build-summary hash before it writes version state, opens release-state, runs publish transaction logic, or moves tags/branches. Source identity accepts the exact source SHA, the PR merge ref SHA, or the promoted channel HEAD’s Git tree SHA matching the passport tree hash. If validation fails, run or attach the verified channel PR build first instead of promoting a stale or unproven artifact set.
When enabled, the action creates or resumes a release transaction keyed by
repository, version, source SHA, and target ref. It persists that transaction to
a machine-managed branch under buildchain/release-state/<version>, with
state.json and, once available, evidence.json. Fresh GitHub runners read
that durable ref before running publish, so reruns do not depend on a previous
runner’s local .buildchain directory.
Consumers whose publish lifecycle only assembles local release assets or
Passport inputs may set publish-rematerialize-on-resume: true. After
Buildchain restores and validates durable publish evidence, it replays that
consumer-owned lifecycle with the original transaction environment before
Passport collection. This is explicit opt-in because registry publication and
other provider mutations must not be replayed blindly; the option rejects
promote-existing-version.
The action runs lifecycle.publish from buildchain.toml or the explicit
publish-command input, then validates publish evidence before exact tags and
floating refs move. If durable state persistence fails, the action fails closed
before publish or public ref finalization.
publish-mode defaults to publish-final-version, the token-free path for
normal npm Trusted Publishing. Same-version alpha-to-latest recovery must be
declared as publish-mode: promote-existing-version and
publish-auth: npm-token; Buildchain runs npm whoami before it creates
release-state or moves any npm dist-tag. The Trusted Publishing mode is not
allowed to perform npm dist-tag add.
Buildchain itself uses this path for npm. Its lifecycle.publish runs
node scripts/npm-publish-transaction.mjs, which publishes
@kungfu-tech/buildchain through npm Trusted Publishing and writes npm
artifact evidence into the transaction before release refs move. The separate
.github/workflows/npm-publish.yml workflow is dry-run only.
Publish lifecycle environment:
BUILDCHAIN_VERSION
BUILDCHAIN_CHANNEL
BUILDCHAIN_SOURCE_SHA
BUILDCHAIN_TARGET_REF
BUILDCHAIN_RELEASE_STATE
BUILDCHAIN_EVIDENCE_DIR
BUILDCHAIN_RELEASE_SHA
BUILDCHAIN_RELEASE_MATERIAL_SHA
BUILDCHAIN_PUBLISH_TOOLING_SHA
BUILDCHAIN_PUBLISH_EVIDENCE
BUILDCHAIN_REQUIRED_ARTIFACTS
BUILDCHAIN_REQUIRED_ARTIFACTS is the normalized requirement array after the
action resolves a missing artifact ref to BUILDCHAIN_VERSION, or expands an
optional ref_template containing exactly one {version}, and binds any
declared provenance to the current release coordinate. Template expansion
happens after exact version selection; ambiguous or unsupported templates fail
before lifecycle.publish. Requirement descriptors may omit digest; final
publish evidence may not.
The action outputs transaction-id, transaction-state,
transaction-exact-tag, public-release-tag, transaction-release-sha,
transaction-state-ref, transaction-state-sha, transaction-state-path,
publish-evidence-path, and release-passport-path, release-passport-output-dir,
release-passport-state-sha, and finalization-needed.
transaction-state-ref is the durable recovery location.
release-passport-state-sha is the durable ref commit after the generated
release-passport/* files have been uploaded into that recovery ref.
finalization-needed=true means publish evidence is valid, but protected branch
or ref finalization needs a later idempotent promotion run. For release
finalization, Buildchain may create a same-repository generated version-state
PR when GitHub rejects the direct protected ref update; that PR is Buildchain
bookkeeping, not a consumer-authored release change.
Set github-release: "true" when the semver promotion should also publish the
public GitHub Release. After the release transaction reaches complete and
finalization-needed is false, the action creates or updates the GitHub Release
for public-release-tag, applies deterministic metadata from the authoritative
publication channel (alpha is a prerelease and never latest; release,
stable, and major are stable and latest), and uploads the publish evidence
file plus generated release passport assets. Tag syntax remains the fallback for
ordinary callers that do not supply publication intent. For anchored/manual
package releases, public-release-tag is derived
from the published package version, while transaction-exact-tag remains the
internal Buildchain transaction ref for recovery and audit. If the transaction is
not complete yet, the action defers GitHub Release publication to the next
idempotent promotion run.
After a publish transaction reaches complete, the action generates the unified
buildchain-release-passport in .buildchain/release-passport by default and
persists those files under release-passport/ in the durable release-state ref.
Set release-passport-product-name to record the consumer product name, for
example Libnode, instead of the default Buildchain.
Set release-passport-kfd-1-witness-jsons to newline-separated KFD-1
contract-world witness JSON paths when released artifacts must prove
byte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from
@kungfu-tech/kfd, freezes the witness, verifies artifact bytes, and writes the
result under the KFD-provided kfd-1 passport section.
Set release-passport-kfd-2-claim-jsons to newline-separated KFD-2 public
release trust claim JSON paths when a release makes additional human/agent
visible claims. Buildchain requires each public claim to bind declared sources,
machine-readable evidence, hashes, artifact coordinates, verification results,
audit boundary, responsibility state, and residual risk. Unbound claims fail
passport verification; prose-only claims downgrade the KFD-2 audit.
Set release-passport-kfd-3-prebuild-witness-jsons to newline-separated KFD-3
collaboration-interface pre-build witness paths, then provide artifact-side
evidence with release-passport-kfd-3-artifact-witness-jsons or a
product-owned release-passport-kfd-3-artifact-verify-command such as
kungfu agent verify --json. Buildchain compares declared shipped public
surfaces with artifact-exposed public surfaces and writes the result under the
KFD-provided kfd-3 passport section.
Set release-passport-kfd-support-matrix-json to the product-owned KFD-1…13
support matrix and release-passport-kfd-product-gate-jsons to the
newline-separated KFD-4, KFD-5, and KFD-7 gate results. Buildchain verifies
their exact source, freshness, KFD package revision, matrix barriers, and
gate/matrix agreement, then writes the immutable kfd-support.json sibling and
embeds the same projection in buildchain.release.json. This projection cannot
self-qualify or widen candidate, unsupported, draft, or non-shipped states.
Set release-passport-invariant-passport-jsons to one or more product-owned
invariant Passport paths, or set release-passport-invariant-passport-command
to a command that emits one canonical Passport JSON document. Buildchain does
not reinterpret product invariants: it verifies the declared semantic root,
requires a verified verdict, complete platform coverage, a clean exact source
revision, and then binds the result into buildchain.release.json. Missing,
stale, falsified, incomplete, dirty, or tampered Passport evidence fails the
release transaction closed.
Buildchain’s own release workflow sets release-passport-buildchain-self-kfd: "true". In that mode the action generates Buildchain-owned KFD-1/2/3 witnesses
inside the final version-state workspace, after the release transaction has
materialized generated files such as package.json and dist/site/*. This
keeps self-hosted KFD witness hashes bound to the exact published package
instead of to a pre-promotion checkout.
When present, the passport includes the aggregate build summary, platform
artifact manifests, npm publish evidence, dist-tag promotion evidence, the
release-state ref, trusted publishing metadata, and the Buildchain transaction
result. After the first passport upload, Buildchain backfills the durable
release-state SHA into buildchain.release.json and persists the passport
again, so the consumer-side passport is a complete audit entrypoint. Set
release-passport: "false" only for a controlled recovery run that must skip
passport generation.
Finalization recovery is anchored to the durable transaction, not to a single
workflow run SHA. After generated version-state bookkeeping is applied, the
current channel head can be the generated version-state commit or a historical
merge commit that contains or corresponds to the recorded release_material_sha;
it does not have to equal the original source_sha or the transaction
release_sha. Reruns accept exact tags that already point at the transaction
release/material SHA or the finalized channel head, and continue moving any
missing floating tags or dev/alpha refs before marking the transaction
complete.
Normal reruns accept already-published artifacts only when evidence matches.
Missing required artifacts can be published on the next run. Conflicting
refs, digests, or declared provenance put the transaction into
repair_required; abandoned and
failed_permanently also fail closed unless publish-transaction-override is
set for a controlled repair. The same override may replace a stale transaction
only when its version, exact tag, target ref, and channel are unchanged, the
transaction is not complete, and it contains no published artifacts or
evidence. This lets a newly admitted source retry a previously failed paper
publication without weakening already-published facts.
In strict buildchain promotion, ref movement is also gated by the old ABV governance semantics:
- when detailed target branch protection is readable, it must enforce
protection for administrators and require approving PR review plus the strict
checkjob from theVerifyworkflow; when GitHub withholds that administration endpoint from the workflow token, the exact transaction must instead prove a protected current head, same-repository merged PR, independent approval, and the required successfulcheckfrom its configured GitHub App; - post-publish channel reconciliation reuses an already qualifying provider-enforced policy when the workflow token cannot read or rewrite the administrative protection document, and fails closed if the public branch summary no longer carries the required check for everyone;
- alpha promotion must come from a merged same-repository PR
dev/vN/vN.M -> alpha/vN/vN.M, or from a strict same-linepublish-gate/alpha/vN/vN.M/<version> -> alpha/vN/vN.Msource-lock PR; - release promotion must come from a merged same-repository PR
alpha/vN/vN.M -> release/vN/vN.M, or from a strict same-linepublish-gate/release/vN/vN.M/<version> -> release/vN/vN.Msource-lock PR; - major promotion must come from a merged same-repository PR
release/vN/vN.M -> publish-gate/major; - release promotion must have an exact alpha tag for the same patch line, and the release source tree must match that alpha tag tree, so release does not introduce new code after alpha;
- anchored/manual release promotion may differ from that alpha tree only in
declared
version.filesand the configured anchor manifest, and only when the checked-out release material has passedlifecycle.verifyor the explicitverification-command; - generated release and next-alpha version-state trees can be verified locally
with either the
verification-commandinput orbuildchain.tomllifecycle.verifybefore any tags or channel refs move.
The reusable promotion workflow keeps governance reads, generated status checks,
and generated ref updates on the run-scoped github.token. When branch
protection rejects generated bookkeeping, it supplies BUILDCHAIN_PROMOTION_TOKEN
only through generated-pull-request-token so the same-repository recovery PR can
be listed or created without broadening the governance client. Protected branch
review and check rules guard human channel merges, while the reusable build trust
gate checks the source-lock channel HEAD and merged same-repository PR lineage
before heavy build runners start. This action still independently rechecks PR
lineage, alpha/release tree equivalence, and generated version-state verification
before moving channel refs and tags.
The reusable release-candidate-promote.yml wrapper defaults
branch-protection-bypass-apps to github-actions, so flow-internal promotion
can complete generated dev/alpha/release bookkeeping while ordinary human
pushes and PR merges remain governed by the one-review branch protection rule.
The promotion action rejects alternate App slugs and every user or team bypass,
so the mutation path cannot widen the authority descriptor.
The tag names intentionally follow the old ABV release semantics:
exact release tags are vX.Y.Z, exact alpha tags are vX.Y.Z-alpha.N, floating
release tags are minor/major tags such as v2.0 and v2, and floating alpha
tags are minor-line tags such as v2.0-alpha plus cross-minor major tags such
as v3-alpha. A major alpha tag only moves for the highest minor in that major
with a published alpha, so older-line maintenance cannot roll consumers back.
Bare tags such as 1.0.0 are not
maintained as buildchain release entrypoints.
Repository rulesets should protect exact tags, not every v* tag. A ruleset
such as refs/tags/v* also protects floating channel tags like v2.0-alpha and v3-alpha,
which Buildchain must update after exact tags and publish evidence are durable.
Use an exact-tag rule such as refs/tags/v*.*.* for immutable evidence tags and
leave floating channel tags mutable for the promotion token.