@kungfu-tech/buildchain 2.14.18-alpha.2 → 2.14.18-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/actions/promote-buildchain-ref/README.md +5 -4
- package/dist/site/buildchain-contract.json +6 -6
- package/dist/site/buildchain-site.json +15 -15
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/page-registry.json +8 -8
- package/dist/site/public-surface-audit.json +1 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +7 -7
- package/docs/lifecycle-protocol.md +6 -5
- package/docs/publish-transaction.md +6 -5
- package/docs/release-governance.md +6 -4
- package/package.json +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-bundle",
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-24T11:59:03.099Z",
|
|
5
|
+
"publishedAt": "2026-07-24T11:59:03.099Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "db7bcdcfcbec594eb7dfc0a910c03ea3bf531257",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "2.14.18-alpha.
|
|
40
|
+
"version": "2.14.18-alpha.4",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"source": {
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
],
|
|
207
207
|
"maturity": "stable",
|
|
208
208
|
"sourcePath": "actions/promote-buildchain-ref/README.md",
|
|
209
|
-
"digest": "sha256:
|
|
209
|
+
"digest": "sha256:2d03ceaec46ef68f116508ca5ac0f1609f59f5335e9681d258e2f090569c136a",
|
|
210
210
|
"headings": [
|
|
211
211
|
{
|
|
212
212
|
"level": 1,
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
"anchor": "publish-transactions"
|
|
225
225
|
}
|
|
226
226
|
],
|
|
227
|
-
"markdown": "# promote-buildchain-ref\n\nInternal buildchain action for promoting verified buildchain release-line and\ncompatibility refs from buildchain release channels:\n\n- `alpha/v2/v2.0` creates or reuses the next exact prerelease tag such as\n `v2.0.1-alpha.0`, writes that version into package version state, points the\n alpha and dev channel branches at the version commit, then promotes\n `v2.0-alpha` and, when this is the highest published alpha minor, `v2-alpha`;\n- `release/v2/v2.0` creates or reuses the next exact release tag such as\n `v2.0.0`, writes that version into package version state, points the release\n channel branch and release tags at the release commit, then prepares a second\n source commit for the next exact prerelease tag such as `v2.0.1-alpha.0` and\n points the alpha/dev channel branches plus `v2.0-alpha` at that prerelease\n commit, and moves `v2-alpha` only if no higher v2 minor has published an alpha;\n- `publish-gate/major` accepts a reviewed PR from a production release line such\n as `release/v2/v2.0`, writes the next major production version such as\n `v3.0.0`, points `publish-gate/major`, `release/v3/v3.0`, `v3.0`, and `v3`\n at that release commit, then prepares `v3.0.1-alpha.0` for\n `alpha/v3/v3.0`, `dev/v3/v3.0`, `v3.0-alpha`, and `v3-alpha`. The older `major-gate`\n branch name is a compatibility alias only.\n\nThe release branch name defines the minor line. For example,\n`release/v2/v2.1` creates `v2.1.N`, promotes `v2.1`, and promotes `v2` only\nwhen the next minor tag such as `v2.2` does not already exist.\n\nThe action updates version state in `lerna.json`, root `package.json`, and\nworkspace package manifests discovered from package manager metadata\n(`package.json` workspaces, `lerna.json` packages, or `pnpm-workspace.yaml`).\nPackage manager detection is adaptive (`pnpm`, `npm`, or `yarn`) and is recorded\nin logs.\n\nRepositories can also provide `buildchain.toml` to declare version-state files\nand `lifecycle.verify`. TOML-configured version files take precedence over\npackage-manager discovery and can target JSON, TOML, or regex-based files. The\nversion commit itself is written through the GitHub Git Data API so the ref\ngraph is the durable source of truth. Repositories without any supported version\nstate degrade to ref-only promotion only when strict version state is disabled.\n\nJSON and TOML version entries whose declared key already matches the requested\nversion are treated as semantic no-ops. TOML changes use a parser-verified\nlossless key edit, so repository formatting is preserved and formatter-only\nrelease-preparation commits are not created. If a unique lossless edit cannot\nbe proven, promotion fails closed instead of rewriting the full TOML document.\n\n## Dry Run\n\nUse `dry-run: \"true\"` or the CLI `buildchain release --dry-run` before merging a\nchannel PR when you need to understand what Buildchain would do. This dry-run is\nat the Buildchain release-line level. It explains:\n\n- the legal source branch for the target channel;\n- exact release or alpha tags that would be created or reused;\n- floating tags and channel branches that would move;\n- version-state files and verification lifecycle that would apply;\n- branch protection, PR lineage, and release-from-alpha checks;\n- publish transaction behavior when `lifecycle.publish` or\n `publish-transaction` is enabled.\n\nIt does not move refs, move tags, write package files, run publish commands, or\npublish npm packages. The GitHub action dry-run still calls GitHub APIs to\nresolve the current target SHA and concrete pending ref updates, but every\nwrite is reported as a dry-run update.\n\nRepositories whose package version is anchored to an explicitly selected\nupstream release can opt into manual next-anchor behavior:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\n```\n\nIn this mode, the action validates the configured version files and anchor\nmanifest through the repository's verify lifecycle, but it does not rewrite the\npackage version to match the Buildchain release tag. After a production\nrelease, it sets `next-anchor-required=true` and does not auto-create the next\nalpha branch or tag. The repository must create the next upstream anchor line\nexplicitly, then run the normal channel promotion flow for that line.\n\nWhen branch protection requires pull requests, generated version-state commits\nstill run through promotion automation first. The action updates\nBuildchain-managed channel protection before generated bookkeeping, admits only\nthe exact `github-actions` App to the target-bound bypass allowlist, creates\nevery configured required check on the exact generated version-state commit, then\ntries to apply that commit directly. If GitHub still rejects release\nfinalization bookkeeping, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR based on the current target channel head and\nreturns `finalization-needed=true`; a later idempotent promotion run can resume\nfrom the durable transaction state. Strict alpha promotion still fails with a\nconfiguration diagnostic instead of opening a post-publish human PR. Reusable\nwrapper callers should allow `checks: write` so the generated checks are owned by\nGitHub Actions and matches the managed branch protection rule.\n\nStable promotion also protects concurrent development work. The reusable\nwrapper checks out the exact current `dev/vN/vN.M` head as a reconciliation\nworkspace. If next-alpha bookkeeping cannot fast-forward that branch, the\naction reruns the declared version-state generation and verification from that\ndev tree, creates a two-parent reconciliation commit from the regenerated\nfiles, and fails closed if the checkout moved before the mutation boundary.\nThis prevents generated projections from an older release tree from replacing\ncapabilities that reached dev while the release was in progress.\n\nFor Buildchain-owned automation, callers may pass\n`branch-protection-bypass-apps: github-actions`. The action rejects every other\nApp slug and all user or team bypass actors. It configures managed\n`dev/vN/vN.M`, `alpha/vN/vN.M`, and `release/vN/vN.M` branches with one\nrequired approving review, Code Owner review, stale-review dismissal,\nlatest-push approval, exact App-bound GitHub Actions checks, admin enforcement,\nconversation resolution, no force pushes, and no deletions; the bypass\nallowance only lets the named automation identity apply generated version-state\nor channel bookkeeping without a second human review after the reviewed channel\nPR has already merged.\n\n## Publish Transactions\n\nPromotion can also own external publish side effects. Enable this only from a\ntrusted channel workflow:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\n token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n generated-ref-update-token: ${{ github.token }}\n sha: ${{ github.sha }}\n target-ref: release/v2/v2.0\n publish-transaction: \"true\"\n publish-mode: publish-final-version\n publish-auth: trusted-publishing\n publish-required-artifacts-json: >-\n [\n {\"kind\":\"npm\",\"name\":\"@kungfu-tech/buildchain\",\"ref\":\"2.0.0\",\"digest\":\"sha256:...\"}\n ]\n```\n\nFor anchored/manual package repositories that build through the reusable\nworkflow, keep the publish entrypoint on the `publish-gate/*` source-lock\ncontract:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\n token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n sha: ${{ needs.build.outputs.publish-source-sha }}\n target-ref: release/v22/v22.22\n require-publish-source-lock: \"true\"\n publish-source-ref: ${{ needs.build.outputs.publish-source-ref }}\n publish-source-sha: ${{ needs.build.outputs.publish-source-sha }}\n publish-source-locked: ${{ needs.build.outputs.publish-source-locked }}\n publish-transaction: \"true\"\n publish-mode: publish-final-version\n publish-auth: trusted-publishing\n```\n\n`target-ref` remains the channel promotion target that must point at `sha`.\n`publish-source-ref` is the reviewed source-lock branch that authorized this\nspecific package publication. Direct `alpha/*` or `release/*` channel refs are\nnot valid publish source locks when `require-publish-source-lock` is enabled,\nand a mismatched `publish-source-sha` fails before any promotion or publish side effects begin.\n\nConsumers that opt in to a product-owned final predicate set\n`require-publication-qualification: \"true\"` and pass the exact sealed\n`publication-capability-json`, complete `publication-gate-aggregate-json`, and\n`publication-qualification-receipt-json`. The action validates their canonical\ndigests, predicate identity, freshness, nonce, source, version, channel, target,\nand evidence bindings both before provider access and immediately before the\npublish transaction. Missing or drifted receipts fail before mutation. The\nreusable `release-candidate-promote.yml` workflow creates these values in a\nseparate credentialless consumer job; direct callers must preserve the same\ncontract and may pass previously consumed nonces through\n`publication-used-qualification-nonces-json`.\n\nThe reusable promote workflow serializes non-dry-run promotion intents per\nrepository and re-reads `target-ref` before checkout, dependency installation,\nrelease-candidate resolution, or publish-gate writes. If a queued intent asks\nfor an older SHA after the protected channel has advanced, the workflow records\nthe requested/current SHA pair, verifies that the current target is ahead of\nthe requested commit, and exits successfully as a superseded no-op. Diverged,\nbehind, or unreadable comparisons still fail closed.\nAfter queued-intent revalidation, the reusable workflow runs the canonical\nrelease-candidate resolver in metadata-only mode before installing candidate\ndependencies or starting the full promotion job. The full job resolves and\ndownloads the evidence again before any publish-gate or publication mutation.\nThis preserves the final exact-evidence trust check while making missing or\nstale PR-stage evidence fail early.\nThe action repeats that check at its mutation boundary for governed promotion\ncalls, closing the race between workflow preflight and action start. Direct\nnon-governed calls and dry-runs keep the strict target mismatch error so local\ndiagnostics cannot silently reinterpret a stale request.\nExpected manual dry-run failures remain visible in the workflow result and do\nnot create automated workflow-friction issues; issue reporting is reserved for\nnon-dry-run promotion failures.\nThe reusable build workflow performs the cheaper channel-ref preflight earlier:\nafter source-lock resolution and before the build matrix, it requires the target\nchannel ref such as `alpha/v22/v22.22` or `release/v22/v22.22` to already point\nat the locked `publish-source-sha`. If not, maintainers should merge the source\ncommit through the channel PR first.\n\nFor promote-only release candidates, attach the PR-stage reusable build evidence\nand fail before publish-gate side effects if it no longer matches:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\n token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n sha: ${{ needs.build.outputs.publish-source-sha }}\n target-ref: alpha/v22/v22.22\n promote-only-release-candidate: \"true\"\n release-candidate-passport-path: .buildchain/artifacts/release-candidate-passport.json\n release-candidate-build-summary-path: .buildchain/artifacts/build-summary.json\n```\n\nThe action validates repository, channel, source identity, platform matrix, and\nthe aggregate build-summary hash before it writes version state, opens\nrelease-state, runs publish transaction logic, or moves tags/branches. Source\nidentity accepts the exact source SHA, the PR merge ref SHA, or the promoted\nchannel HEAD's Git tree SHA matching the passport tree hash. If validation\nfails, run or attach the verified channel PR build first instead of promoting a\nstale or unproven artifact set.\n\nWhen enabled, the action creates or resumes a release transaction keyed by\nrepository, version, source SHA, and target ref. It persists that transaction to\na machine-managed branch under `buildchain/release-state/<version>`, with\n`state.json` and, once available, `evidence.json`. Fresh GitHub runners read\nthat durable ref before running publish, so reruns do not depend on a previous\nrunner's local `.buildchain` directory.\n\nConsumers whose publish lifecycle only assembles local release assets or\nPassport inputs may set `publish-rematerialize-on-resume: true`. After\nBuildchain restores and validates durable publish evidence, it replays that\nconsumer-owned lifecycle with the original transaction environment before\nPassport collection. This is explicit opt-in because registry publication and\nother provider mutations must not be replayed blindly; the option rejects\n`promote-existing-version`.\n\nThe action runs `lifecycle.publish` from `buildchain.toml` or the explicit\n`publish-command` input, then validates publish evidence before exact tags and\nfloating refs move. If durable state persistence fails, the action fails closed\nbefore publish or public ref finalization.\n\n`publish-mode` defaults to `publish-final-version`, the token-free path for\nnormal npm Trusted Publishing. Same-version alpha-to-latest recovery must be\ndeclared as `publish-mode: promote-existing-version` and\n`publish-auth: npm-token`; Buildchain runs `npm whoami` before it creates\nrelease-state or moves any `npm dist-tag`. The Trusted Publishing mode is not\nallowed to perform `npm dist-tag add`.\n\nBuildchain itself uses this path for npm. Its `lifecycle.publish` runs\n`node scripts/npm-publish-transaction.mjs`, which publishes\n`@kungfu-tech/buildchain` through npm Trusted Publishing and writes npm\nartifact evidence into the transaction before release refs move. The separate\n`.github/workflows/npm-publish.yml` workflow is dry-run only.\n\nPublish lifecycle environment:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_REQUIRED_ARTIFACTS\n```\n\n`BUILDCHAIN_REQUIRED_ARTIFACTS` is the normalized requirement array after the\naction resolves a missing artifact `ref` to `BUILDCHAIN_VERSION`, or expands an\noptional `ref_template` containing exactly one `{version}`, and binds any\ndeclared provenance to the current release coordinate. Template expansion\nhappens after exact version selection; ambiguous or unsupported templates fail\nbefore `lifecycle.publish`. Requirement descriptors may omit `digest`; final\npublish evidence may not.\n\nThe action outputs `transaction-id`, `transaction-state`,\n`transaction-exact-tag`, `public-release-tag`, `transaction-release-sha`,\n`transaction-state-ref`, `transaction-state-sha`, `transaction-state-path`,\n`publish-evidence-path`, and `release-passport-path`, `release-passport-output-dir`,\n`release-passport-state-sha`, and `finalization-needed`.\n`transaction-state-ref` is the durable recovery location.\n`release-passport-state-sha` is the durable ref commit after the generated\n`release-passport/*` files have been uploaded into that recovery ref.\n`finalization-needed=true` means publish evidence is valid, but protected branch\nor ref finalization needs a later idempotent promotion run. For release\nfinalization, Buildchain may create a same-repository generated version-state\nPR when GitHub rejects the direct protected ref update; that PR is Buildchain\nbookkeeping, not a consumer-authored release change.\nSet `github-release: \"true\"` when the semver promotion should also publish the\npublic GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `public-release-tag`, applies deterministic metadata from the authoritative\npublication channel (`alpha` is a prerelease and never latest; `release`,\n`stable`, and `major` are stable and latest), and uploads the publish evidence\nfile plus generated release passport assets. Tag syntax remains the fallback for\nordinary callers that do not supply publication intent. For anchored/manual\npackage releases, `public-release-tag` is derived\nfrom the published package version, while `transaction-exact-tag` remains the\ninternal Buildchain transaction ref for recovery and audit. If the transaction is\nnot complete yet, the action defers GitHub Release publication to the next\nidempotent promotion run.\n\nAfter a publish transaction reaches `complete`, the action generates the unified\n`buildchain-release-passport` in `.buildchain/release-passport` by default and\npersists those files under `release-passport/` in the durable release-state ref.\nSet `release-passport-product-name` to record the consumer product name, for\nexample `Libnode`, instead of the default `Buildchain`.\nSet `release-passport-kfd-1-witness-jsons` to newline-separated KFD-1\ncontract-world witness JSON paths when released artifacts must prove\nbyte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from\n`@kungfu-tech/kfd`, freezes the witness, verifies artifact bytes, and writes the\nresult under the KFD-provided `kfd-1` passport section.\nSet `release-passport-kfd-2-claim-jsons` to newline-separated KFD-2 public\nrelease trust claim JSON paths when a release makes additional human/agent\nvisible claims. Buildchain requires each public claim to bind declared sources,\nmachine-readable evidence, hashes, artifact coordinates, verification results,\naudit boundary, responsibility state, and residual risk. Unbound claims fail\npassport verification; prose-only claims downgrade the KFD-2 audit.\nSet `release-passport-kfd-3-prebuild-witness-jsons` to newline-separated KFD-3\ncollaboration-interface pre-build witness paths, then provide artifact-side\nevidence with `release-passport-kfd-3-artifact-witness-jsons` or a\nproduct-owned `release-passport-kfd-3-artifact-verify-command` such as\n`kungfu agent verify --json`. Buildchain compares declared shipped public\nsurfaces with artifact-exposed public surfaces and writes the result under the\nKFD-provided `kfd-3` passport section.\nSet `release-passport-invariant-passport-jsons` to one or more product-owned\ninvariant Passport paths, or set `release-passport-invariant-passport-command`\nto a command that emits one canonical Passport JSON document. Buildchain does\nnot reinterpret product invariants: it verifies the declared semantic root,\nrequires a `verified` verdict, complete platform coverage, a clean exact source\nrevision, and then binds the result into `buildchain.release.json`. Missing,\nstale, falsified, incomplete, dirty, or tampered Passport evidence fails the\nrelease transaction closed.\nBuildchain's own release workflow sets `release-passport-buildchain-self-kfd:\n\"true\"`. In that mode the action generates Buildchain-owned KFD-1/2/3 witnesses\ninside the final version-state workspace, after the release transaction has\nmaterialized generated files such as `package.json` and `dist/site/*`. This\nkeeps self-hosted KFD witness hashes bound to the exact published package\ninstead of to a pre-promotion checkout.\nWhen present, the passport includes the aggregate build summary, platform\nartifact manifests, npm publish evidence, dist-tag promotion evidence, the\nrelease-state ref, trusted publishing metadata, and the Buildchain transaction\nresult. After the first passport upload, Buildchain backfills the durable\nrelease-state SHA into `buildchain.release.json` and persists the passport\nagain, so the consumer-side passport is a complete audit entrypoint. Set\n`release-passport: \"false\"` only for a controlled recovery run that must skip\npassport generation.\n\nFinalization recovery is anchored to the durable transaction, not to a single\nworkflow run SHA. After generated version-state bookkeeping is applied, the\ncurrent channel head can be the generated version-state commit or a historical\nmerge commit that contains or corresponds to the recorded `release_material_sha`;\nit does not have to equal the original `source_sha` or the transaction\n`release_sha`. Reruns accept exact tags that already point at the transaction\nrelease/material SHA or the finalized channel head, and continue moving any\nmissing floating tags or dev/alpha refs before marking the transaction\n`complete`.\n\nNormal reruns accept already-published artifacts only when evidence matches.\nMissing required artifacts can be published on the next run. Conflicting\nrefs, digests, or declared provenance put the transaction into\n`repair_required`; `abandoned` and\n`failed_permanently` also fail closed unless `publish-transaction-override` is\nset for a controlled repair. The same override may replace a stale transaction\nonly when its version, exact tag, target ref, and channel are unchanged, the\ntransaction is not complete, and it contains no published artifacts or\nevidence. This lets a newly admitted source retry a previously failed paper\npublication without weakening already-published facts.\n\nIn strict buildchain promotion, ref movement is also gated by the old ABV\ngovernance semantics:\n\n- when detailed target branch protection is readable, it must enforce\n protection for administrators and require approving PR review plus the strict\n `check` job from the `Verify` workflow; when GitHub withholds that\n administration endpoint from the workflow token, the exact transaction must\n instead prove a protected current head, same-repository merged PR,\n independent approval, and the required successful `check` from its configured\n GitHub App;\n- post-publish channel reconciliation reuses an already qualifying\n provider-enforced policy when the workflow token cannot read or rewrite the\n administrative protection document, and fails closed if the public branch\n summary no longer carries the required check for everyone;\n- alpha promotion must come from a merged same-repository PR\n `dev/vN/vN.M -> alpha/vN/vN.M`, or from a strict same-line\n `publish-gate/alpha/vN/vN.M/<version> -> alpha/vN/vN.M` source-lock PR;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`, or from a strict same-line\n `publish-gate/release/vN/vN.M/<version> -> release/vN/vN.M` source-lock PR;\n- major promotion must come from a merged same-repository PR\n `release/vN/vN.M -> publish-gate/major`;\n- release promotion must have an exact alpha tag for the same patch line, and\n the release source tree must match that alpha tag tree, so release does not\n introduce new code after alpha;\n- anchored/manual release promotion may differ from that alpha tree only in\n declared `version.files` and the configured anchor manifest, and only when the\n checked-out release material has passed `lifecycle.verify` or the explicit\n `verification-command`;\n- generated release and next-alpha version-state trees can be verified locally\n with either the `verification-command` input or `buildchain.toml`\n `lifecycle.verify` before any tags or channel refs move.\n\nThe promotion workflow should use `BUILDCHAIN_PROMOTION_TOKEN` for non-dry-run\npromotion. The token is the buildchain equivalent of the old ABV runner release\nauthority: protected branch review and check rules guard human channel merges,\nwhile the reusable build trust gate now checks the source-lock channel HEAD and\nmerged same-repository PR lineage before heavy build runners start. This action\nstill independently rechecks PR lineage, alpha/release tree equivalence, and\ngenerated version-state verification before moving channel refs and tags.\nGenerated version-state direct ref updates can use a separate\n`generated-ref-update-token`; the reusable wrapper binds it to the run-scoped\n`github.token`.\nThe reusable `release-candidate-promote.yml` wrapper defaults\n`branch-protection-bypass-apps` to `github-actions`, so flow-internal promotion\ncan complete generated `dev`/`alpha`/`release` bookkeeping while ordinary human\npushes and PR merges remain governed by the one-review branch protection rule.\nThe promotion action rejects alternate App slugs and every user or team bypass,\nso the mutation path cannot widen the authority descriptor.\n\nThe tag names intentionally follow the old ABV release semantics:\nexact release tags are `vX.Y.Z`, exact alpha tags are `vX.Y.Z-alpha.N`, floating\nrelease tags are minor/major tags such as `v2.0` and `v2`, and floating alpha\ntags are minor-line tags such as `v2.0-alpha` plus cross-minor major tags such\nas `v2-alpha`. A major alpha tag only moves for the highest minor in that major\nwith a published alpha, so older-line maintenance cannot roll consumers back.\nBare tags such as `1.0.0` are not\nmaintained as buildchain release entrypoints.\n\nRepository rulesets should protect exact tags, not every `v*` tag. A ruleset\nsuch as `refs/tags/v*` also protects floating channel tags like `v2.0-alpha` and `v2-alpha`,\nwhich Buildchain must update after exact tags and publish evidence are durable.\nUse an exact-tag rule such as `refs/tags/v*.*.*` for immutable evidence tags and\nleave floating channel tags mutable for the promotion token."
|
|
227
|
+
"markdown": "# promote-buildchain-ref\n\nInternal buildchain action for promoting verified buildchain release-line and\ncompatibility refs from buildchain release channels:\n\n- `alpha/v2/v2.0` creates or reuses the next exact prerelease tag such as\n `v2.0.1-alpha.0`, writes that version into package version state, points the\n alpha and dev channel branches at the version commit, then promotes\n `v2.0-alpha` and, when this is the highest published alpha minor, `v2-alpha`;\n- `release/v2/v2.0` creates or reuses the next exact release tag such as\n `v2.0.0`, writes that version into package version state, points the release\n channel branch and release tags at the release commit, then prepares a second\n source commit for the next exact prerelease tag such as `v2.0.1-alpha.0` and\n points the alpha/dev channel branches plus `v2.0-alpha` at that prerelease\n commit, and moves `v2-alpha` only if no higher v2 minor has published an alpha;\n- `publish-gate/major` accepts a reviewed PR from a production release line such\n as `release/v2/v2.0`, writes the next major production version such as\n `v3.0.0`, points `publish-gate/major`, `release/v3/v3.0`, `v3.0`, and `v3`\n at that release commit, then prepares `v3.0.1-alpha.0` for\n `alpha/v3/v3.0`, `dev/v3/v3.0`, `v3.0-alpha`, and `v3-alpha`. The older `major-gate`\n branch name is a compatibility alias only.\n\nThe release branch name defines the minor line. For example,\n`release/v2/v2.1` creates `v2.1.N`, promotes `v2.1`, and promotes `v2` only\nwhen the next minor tag such as `v2.2` does not already exist.\n\nThe action updates version state in `lerna.json`, root `package.json`, and\nworkspace package manifests discovered from package manager metadata\n(`package.json` workspaces, `lerna.json` packages, or `pnpm-workspace.yaml`).\nPackage manager detection is adaptive (`pnpm`, `npm`, or `yarn`) and is recorded\nin logs.\n\nRepositories can also provide `buildchain.toml` to declare version-state files\nand `lifecycle.verify`. TOML-configured version files take precedence over\npackage-manager discovery and can target JSON, TOML, or regex-based files. The\nversion commit itself is written through the GitHub Git Data API so the ref\ngraph is the durable source of truth. Repositories without any supported version\nstate degrade to ref-only promotion only when strict version state is disabled.\n\nJSON and TOML version entries whose declared key already matches the requested\nversion are treated as semantic no-ops. TOML changes use a parser-verified\nlossless key edit, so repository formatting is preserved and formatter-only\nrelease-preparation commits are not created. If a unique lossless edit cannot\nbe proven, promotion fails closed instead of rewriting the full TOML document.\n\n## Dry Run\n\nUse `dry-run: \"true\"` or the CLI `buildchain release --dry-run` before merging a\nchannel PR when you need to understand what Buildchain would do. This dry-run is\nat the Buildchain release-line level. It explains:\n\n- the legal source branch for the target channel;\n- exact release or alpha tags that would be created or reused;\n- floating tags and channel branches that would move;\n- version-state files and verification lifecycle that would apply;\n- branch protection, PR lineage, and release-from-alpha checks;\n- publish transaction behavior when `lifecycle.publish` or\n `publish-transaction` is enabled.\n\nIt does not move refs, move tags, write package files, run publish commands, or\npublish npm packages. The GitHub action dry-run still calls GitHub APIs to\nresolve the current target SHA and concrete pending ref updates, but every\nwrite is reported as a dry-run update.\n\nRepositories whose package version is anchored to an explicitly selected\nupstream release can opt into manual next-anchor behavior:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\n```\n\nIn this mode, the action validates the configured version files and anchor\nmanifest through the repository's verify lifecycle, but it does not rewrite the\npackage version to match the Buildchain release tag. After a production\nrelease, it sets `next-anchor-required=true` and does not auto-create the next\nalpha branch or tag. The repository must create the next upstream anchor line\nexplicitly, then run the normal channel promotion flow for that line.\n\nWhen branch protection requires pull requests, generated version-state commits\nstill run through promotion automation first. The action updates\nBuildchain-managed channel protection before generated bookkeeping, admits only\nthe exact `github-actions` App to the target-bound bypass allowlist, creates\nevery configured required check on the exact generated version-state commit, then\ntries to apply that commit directly. If GitHub still rejects release\nfinalization bookkeeping, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR based on the current target channel head and\nreturns `finalization-needed=true`; a later idempotent promotion run can resume\nfrom the durable transaction state. Strict alpha uses the same protected\nversion-state PR recovery for its target and dev bookkeeping, and does not move\ntags until those provider-enforced transactions land. Reusable wrapper callers\nshould allow `checks: write` so the generated checks are owned by GitHub Actions\nand match the managed branch protection rule.\n\nStable promotion also protects concurrent development work. The reusable\nwrapper checks out the exact current `dev/vN/vN.M` head as a reconciliation\nworkspace. If next-alpha bookkeeping cannot fast-forward that branch, the\naction reruns the declared version-state generation and verification from that\ndev tree, creates a two-parent reconciliation commit from the regenerated\nfiles, and fails closed if the checkout moved before the mutation boundary.\nThis prevents generated projections from an older release tree from replacing\ncapabilities that reached dev while the release was in progress.\n\nFor Buildchain-owned automation, callers may pass\n`branch-protection-bypass-apps: github-actions`. The action rejects every other\nApp slug and all user or team bypass actors. It configures managed\n`dev/vN/vN.M`, `alpha/vN/vN.M`, and `release/vN/vN.M` branches with one\nrequired approving review, Code Owner review, stale-review dismissal,\nlatest-push approval, exact App-bound GitHub Actions checks, admin enforcement,\nconversation resolution, no force pushes, and no deletions; the bypass\nallowance only lets the named automation identity apply generated version-state\nor channel bookkeeping without a second human review after the reviewed channel\nPR has already merged.\n\n## Publish Transactions\n\nPromotion can also own external publish side effects. Enable this only from a\ntrusted channel workflow:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\n token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n generated-ref-update-token: ${{ github.token }}\n sha: ${{ github.sha }}\n target-ref: release/v2/v2.0\n publish-transaction: \"true\"\n publish-mode: publish-final-version\n publish-auth: trusted-publishing\n publish-required-artifacts-json: >-\n [\n {\"kind\":\"npm\",\"name\":\"@kungfu-tech/buildchain\",\"ref\":\"2.0.0\",\"digest\":\"sha256:...\"}\n ]\n```\n\nFor anchored/manual package repositories that build through the reusable\nworkflow, keep the publish entrypoint on the `publish-gate/*` source-lock\ncontract:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\n token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n sha: ${{ needs.build.outputs.publish-source-sha }}\n target-ref: release/v22/v22.22\n require-publish-source-lock: \"true\"\n publish-source-ref: ${{ needs.build.outputs.publish-source-ref }}\n publish-source-sha: ${{ needs.build.outputs.publish-source-sha }}\n publish-source-locked: ${{ needs.build.outputs.publish-source-locked }}\n publish-transaction: \"true\"\n publish-mode: publish-final-version\n publish-auth: trusted-publishing\n```\n\n`target-ref` remains the channel promotion target that must point at `sha`.\n`publish-source-ref` is the reviewed source-lock branch that authorized this\nspecific package publication. Direct `alpha/*` or `release/*` channel refs are\nnot valid publish source locks when `require-publish-source-lock` is enabled,\nand a mismatched `publish-source-sha` fails before any promotion or publish side effects begin.\n\nConsumers that opt in to a product-owned final predicate set\n`require-publication-qualification: \"true\"` and pass the exact sealed\n`publication-capability-json`, complete `publication-gate-aggregate-json`, and\n`publication-qualification-receipt-json`. The action validates their canonical\ndigests, predicate identity, freshness, nonce, source, version, channel, target,\nand evidence bindings both before provider access and immediately before the\npublish transaction. Missing or drifted receipts fail before mutation. The\nreusable `release-candidate-promote.yml` workflow creates these values in a\nseparate credentialless consumer job; direct callers must preserve the same\ncontract and may pass previously consumed nonces through\n`publication-used-qualification-nonces-json`.\n\nThe reusable promote workflow serializes non-dry-run promotion intents per\nrepository and re-reads `target-ref` before checkout, dependency installation,\nrelease-candidate resolution, or publish-gate writes. If a queued intent asks\nfor an older SHA after the protected channel has advanced, the workflow records\nthe requested/current SHA pair, verifies that the current target is ahead of\nthe requested commit, and exits successfully as a superseded no-op. Diverged,\nbehind, or unreadable comparisons still fail closed.\nAfter queued-intent revalidation, the reusable workflow runs the canonical\nrelease-candidate resolver in metadata-only mode before installing candidate\ndependencies or starting the full promotion job. The full job resolves and\ndownloads the evidence again before any publish-gate or publication mutation.\nThis preserves the final exact-evidence trust check while making missing or\nstale PR-stage evidence fail early.\nThe action repeats that check at its mutation boundary for governed promotion\ncalls, closing the race between workflow preflight and action start. Direct\nnon-governed calls and dry-runs keep the strict target mismatch error so local\ndiagnostics cannot silently reinterpret a stale request.\nExpected manual dry-run failures remain visible in the workflow result and do\nnot create automated workflow-friction issues; issue reporting is reserved for\nnon-dry-run promotion failures.\nThe reusable build workflow performs the cheaper channel-ref preflight earlier:\nafter source-lock resolution and before the build matrix, it requires the target\nchannel ref such as `alpha/v22/v22.22` or `release/v22/v22.22` to already point\nat the locked `publish-source-sha`. If not, maintainers should merge the source\ncommit through the channel PR first.\n\nFor promote-only release candidates, attach the PR-stage reusable build evidence\nand fail before publish-gate side effects if it no longer matches:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\n token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n sha: ${{ needs.build.outputs.publish-source-sha }}\n target-ref: alpha/v22/v22.22\n promote-only-release-candidate: \"true\"\n release-candidate-passport-path: .buildchain/artifacts/release-candidate-passport.json\n release-candidate-build-summary-path: .buildchain/artifacts/build-summary.json\n```\n\nThe action validates repository, channel, source identity, platform matrix, and\nthe aggregate build-summary hash before it writes version state, opens\nrelease-state, runs publish transaction logic, or moves tags/branches. Source\nidentity accepts the exact source SHA, the PR merge ref SHA, or the promoted\nchannel HEAD's Git tree SHA matching the passport tree hash. If validation\nfails, run or attach the verified channel PR build first instead of promoting a\nstale or unproven artifact set.\n\nWhen enabled, the action creates or resumes a release transaction keyed by\nrepository, version, source SHA, and target ref. It persists that transaction to\na machine-managed branch under `buildchain/release-state/<version>`, with\n`state.json` and, once available, `evidence.json`. Fresh GitHub runners read\nthat durable ref before running publish, so reruns do not depend on a previous\nrunner's local `.buildchain` directory.\n\nConsumers whose publish lifecycle only assembles local release assets or\nPassport inputs may set `publish-rematerialize-on-resume: true`. After\nBuildchain restores and validates durable publish evidence, it replays that\nconsumer-owned lifecycle with the original transaction environment before\nPassport collection. This is explicit opt-in because registry publication and\nother provider mutations must not be replayed blindly; the option rejects\n`promote-existing-version`.\n\nThe action runs `lifecycle.publish` from `buildchain.toml` or the explicit\n`publish-command` input, then validates publish evidence before exact tags and\nfloating refs move. If durable state persistence fails, the action fails closed\nbefore publish or public ref finalization.\n\n`publish-mode` defaults to `publish-final-version`, the token-free path for\nnormal npm Trusted Publishing. Same-version alpha-to-latest recovery must be\ndeclared as `publish-mode: promote-existing-version` and\n`publish-auth: npm-token`; Buildchain runs `npm whoami` before it creates\nrelease-state or moves any `npm dist-tag`. The Trusted Publishing mode is not\nallowed to perform `npm dist-tag add`.\n\nBuildchain itself uses this path for npm. Its `lifecycle.publish` runs\n`node scripts/npm-publish-transaction.mjs`, which publishes\n`@kungfu-tech/buildchain` through npm Trusted Publishing and writes npm\nartifact evidence into the transaction before release refs move. The separate\n`.github/workflows/npm-publish.yml` workflow is dry-run only.\n\nPublish lifecycle environment:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_REQUIRED_ARTIFACTS\n```\n\n`BUILDCHAIN_REQUIRED_ARTIFACTS` is the normalized requirement array after the\naction resolves a missing artifact `ref` to `BUILDCHAIN_VERSION`, or expands an\noptional `ref_template` containing exactly one `{version}`, and binds any\ndeclared provenance to the current release coordinate. Template expansion\nhappens after exact version selection; ambiguous or unsupported templates fail\nbefore `lifecycle.publish`. Requirement descriptors may omit `digest`; final\npublish evidence may not.\n\nThe action outputs `transaction-id`, `transaction-state`,\n`transaction-exact-tag`, `public-release-tag`, `transaction-release-sha`,\n`transaction-state-ref`, `transaction-state-sha`, `transaction-state-path`,\n`publish-evidence-path`, and `release-passport-path`, `release-passport-output-dir`,\n`release-passport-state-sha`, and `finalization-needed`.\n`transaction-state-ref` is the durable recovery location.\n`release-passport-state-sha` is the durable ref commit after the generated\n`release-passport/*` files have been uploaded into that recovery ref.\n`finalization-needed=true` means publish evidence is valid, but protected branch\nor ref finalization needs a later idempotent promotion run. For release\nfinalization, Buildchain may create a same-repository generated version-state\nPR when GitHub rejects the direct protected ref update; that PR is Buildchain\nbookkeeping, not a consumer-authored release change.\nSet `github-release: \"true\"` when the semver promotion should also publish the\npublic GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `public-release-tag`, applies deterministic metadata from the authoritative\npublication channel (`alpha` is a prerelease and never latest; `release`,\n`stable`, and `major` are stable and latest), and uploads the publish evidence\nfile plus generated release passport assets. Tag syntax remains the fallback for\nordinary callers that do not supply publication intent. For anchored/manual\npackage releases, `public-release-tag` is derived\nfrom the published package version, while `transaction-exact-tag` remains the\ninternal Buildchain transaction ref for recovery and audit. If the transaction is\nnot complete yet, the action defers GitHub Release publication to the next\nidempotent promotion run.\n\nAfter a publish transaction reaches `complete`, the action generates the unified\n`buildchain-release-passport` in `.buildchain/release-passport` by default and\npersists those files under `release-passport/` in the durable release-state ref.\nSet `release-passport-product-name` to record the consumer product name, for\nexample `Libnode`, instead of the default `Buildchain`.\nSet `release-passport-kfd-1-witness-jsons` to newline-separated KFD-1\ncontract-world witness JSON paths when released artifacts must prove\nbyte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from\n`@kungfu-tech/kfd`, freezes the witness, verifies artifact bytes, and writes the\nresult under the KFD-provided `kfd-1` passport section.\nSet `release-passport-kfd-2-claim-jsons` to newline-separated KFD-2 public\nrelease trust claim JSON paths when a release makes additional human/agent\nvisible claims. Buildchain requires each public claim to bind declared sources,\nmachine-readable evidence, hashes, artifact coordinates, verification results,\naudit boundary, responsibility state, and residual risk. Unbound claims fail\npassport verification; prose-only claims downgrade the KFD-2 audit.\nSet `release-passport-kfd-3-prebuild-witness-jsons` to newline-separated KFD-3\ncollaboration-interface pre-build witness paths, then provide artifact-side\nevidence with `release-passport-kfd-3-artifact-witness-jsons` or a\nproduct-owned `release-passport-kfd-3-artifact-verify-command` such as\n`kungfu agent verify --json`. Buildchain compares declared shipped public\nsurfaces with artifact-exposed public surfaces and writes the result under the\nKFD-provided `kfd-3` passport section.\nSet `release-passport-invariant-passport-jsons` to one or more product-owned\ninvariant Passport paths, or set `release-passport-invariant-passport-command`\nto a command that emits one canonical Passport JSON document. Buildchain does\nnot reinterpret product invariants: it verifies the declared semantic root,\nrequires a `verified` verdict, complete platform coverage, a clean exact source\nrevision, and then binds the result into `buildchain.release.json`. Missing,\nstale, falsified, incomplete, dirty, or tampered Passport evidence fails the\nrelease transaction closed.\nBuildchain's own release workflow sets `release-passport-buildchain-self-kfd:\n\"true\"`. In that mode the action generates Buildchain-owned KFD-1/2/3 witnesses\ninside the final version-state workspace, after the release transaction has\nmaterialized generated files such as `package.json` and `dist/site/*`. This\nkeeps self-hosted KFD witness hashes bound to the exact published package\ninstead of to a pre-promotion checkout.\nWhen present, the passport includes the aggregate build summary, platform\nartifact manifests, npm publish evidence, dist-tag promotion evidence, the\nrelease-state ref, trusted publishing metadata, and the Buildchain transaction\nresult. After the first passport upload, Buildchain backfills the durable\nrelease-state SHA into `buildchain.release.json` and persists the passport\nagain, so the consumer-side passport is a complete audit entrypoint. Set\n`release-passport: \"false\"` only for a controlled recovery run that must skip\npassport generation.\n\nFinalization recovery is anchored to the durable transaction, not to a single\nworkflow run SHA. After generated version-state bookkeeping is applied, the\ncurrent channel head can be the generated version-state commit or a historical\nmerge commit that contains or corresponds to the recorded `release_material_sha`;\nit does not have to equal the original `source_sha` or the transaction\n`release_sha`. Reruns accept exact tags that already point at the transaction\nrelease/material SHA or the finalized channel head, and continue moving any\nmissing floating tags or dev/alpha refs before marking the transaction\n`complete`.\n\nNormal reruns accept already-published artifacts only when evidence matches.\nMissing required artifacts can be published on the next run. Conflicting\nrefs, digests, or declared provenance put the transaction into\n`repair_required`; `abandoned` and\n`failed_permanently` also fail closed unless `publish-transaction-override` is\nset for a controlled repair. The same override may replace a stale transaction\nonly when its version, exact tag, target ref, and channel are unchanged, the\ntransaction is not complete, and it contains no published artifacts or\nevidence. This lets a newly admitted source retry a previously failed paper\npublication without weakening already-published facts.\n\nIn strict buildchain promotion, ref movement is also gated by the old ABV\ngovernance semantics:\n\n- when detailed target branch protection is readable, it must enforce\n protection for administrators and require approving PR review plus the strict\n `check` job from the `Verify` workflow; when GitHub withholds that\n administration endpoint from the workflow token, the exact transaction must\n instead prove a protected current head, same-repository merged PR,\n independent approval, and the required successful `check` from its configured\n GitHub App;\n- post-publish channel reconciliation reuses an already qualifying\n provider-enforced policy when the workflow token cannot read or rewrite the\n administrative protection document, and fails closed if the public branch\n summary no longer carries the required check for everyone;\n- alpha promotion must come from a merged same-repository PR\n `dev/vN/vN.M -> alpha/vN/vN.M`, or from a strict same-line\n `publish-gate/alpha/vN/vN.M/<version> -> alpha/vN/vN.M` source-lock PR;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`, or from a strict same-line\n `publish-gate/release/vN/vN.M/<version> -> release/vN/vN.M` source-lock PR;\n- major promotion must come from a merged same-repository PR\n `release/vN/vN.M -> publish-gate/major`;\n- release promotion must have an exact alpha tag for the same patch line, and\n the release source tree must match that alpha tag tree, so release does not\n introduce new code after alpha;\n- anchored/manual release promotion may differ from that alpha tree only in\n declared `version.files` and the configured anchor manifest, and only when the\n checked-out release material has passed `lifecycle.verify` or the explicit\n `verification-command`;\n- generated release and next-alpha version-state trees can be verified locally\n with either the `verification-command` input or `buildchain.toml`\n `lifecycle.verify` before any tags or channel refs move.\n\nThe promotion workflow should use `BUILDCHAIN_PROMOTION_TOKEN` for non-dry-run\npromotion. The token is the buildchain equivalent of the old ABV runner release\nauthority: protected branch review and check rules guard human channel merges,\nwhile the reusable build trust gate now checks the source-lock channel HEAD and\nmerged same-repository PR lineage before heavy build runners start. This action\nstill independently rechecks PR lineage, alpha/release tree equivalence, and\ngenerated version-state verification before moving channel refs and tags.\nGenerated version-state direct ref updates can use a separate\n`generated-ref-update-token`; the reusable wrapper binds it to the run-scoped\n`github.token`.\nThe reusable `release-candidate-promote.yml` wrapper defaults\n`branch-protection-bypass-apps` to `github-actions`, so flow-internal promotion\ncan complete generated `dev`/`alpha`/`release` bookkeeping while ordinary human\npushes and PR merges remain governed by the one-review branch protection rule.\nThe promotion action rejects alternate App slugs and every user or team bypass,\nso the mutation path cannot widen the authority descriptor.\n\nThe tag names intentionally follow the old ABV release semantics:\nexact release tags are `vX.Y.Z`, exact alpha tags are `vX.Y.Z-alpha.N`, floating\nrelease tags are minor/major tags such as `v2.0` and `v2`, and floating alpha\ntags are minor-line tags such as `v2.0-alpha` plus cross-minor major tags such\nas `v2-alpha`. A major alpha tag only moves for the highest minor in that major\nwith a published alpha, so older-line maintenance cannot roll consumers back.\nBare tags such as `1.0.0` are not\nmaintained as buildchain release entrypoints.\n\nRepository rulesets should protect exact tags, not every `v*` tag. A ruleset\nsuch as `refs/tags/v*` also protects floating channel tags like `v2.0-alpha` and `v2-alpha`,\nwhich Buildchain must update after exact tags and publish evidence are durable.\nUse an exact-tag rule such as `refs/tags/v*.*.*` for immutable evidence tags and\nleave floating channel tags mutable for the promotion token."
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
230
|
"id": "action:report-buildchain-issue",
|
|
@@ -897,7 +897,7 @@
|
|
|
897
897
|
],
|
|
898
898
|
"maturity": "stable",
|
|
899
899
|
"sourcePath": "docs/lifecycle-protocol.md",
|
|
900
|
-
"digest": "sha256:
|
|
900
|
+
"digest": "sha256:9c669d3d2d1926dc5b421207089cca85bac0a6dcdb5544c28a7e1659ba9ed5ee",
|
|
901
901
|
"headings": [
|
|
902
902
|
{
|
|
903
903
|
"level": 1,
|
|
@@ -980,7 +980,7 @@
|
|
|
980
980
|
"anchor": "design-boundaries"
|
|
981
981
|
}
|
|
982
982
|
],
|
|
983
|
-
"markdown": "# Lifecycle Protocol\n\nBuildchain uses `.buildchain/buildchain.toml` as the v2 repository configuration format.\nThe file is optional for simple JavaScript repositories, but it is the preferred\nway to describe release version state and lifecycle commands when a project is\nnot a plain pnpm, npm, or yarn workspace.\n\nFor compatibility, Buildchain still reads a legacy root `buildchain.toml` when\n`.buildchain/buildchain.toml` is absent. New repositories should use the\n`.buildchain/` layout so all Buildchain-owned local state lives under one\ndirectory.\n\nOnly TOML is supported in v2. YAML, JSON, and JavaScript config files are not\nloaded.\n\n## Minimal File\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n`schema = 1` is required. Buildchain fails closed when the schema is missing or\nunknown.\n\n## Version State\n\nVersion state is the source file evidence that matches a release tag. During\npromotion, Buildchain writes the selected release or prerelease version into the\nconfigured files, verifies the resulting tree, creates a source version commit,\nthen moves exact and floating refs.\n\nSupported version file types:\n\n| Type | Use case | Required fields |\n| --- | --- | --- |\n| `json` | `package.json`, JSON manifests | `path`, `key` |\n| `toml` | `pyproject.toml`, other TOML manifests | `path`, `key` |\n| `regex` | `CMakeLists.txt`, `conanfile.py`, plain version files | `path`, `pattern`, `replacement` |\n\n`key` is a dotted key path:\n\n```toml\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n```\n\nConfigured JSON and TOML version files are semantic no-ops when the declared\nkey already equals the requested version. Buildchain preserves the repository's\noriginal TOML bytes in that case instead of serializing the whole document and\ncreating formatter-only release state. When a TOML version really changes,\nBuildchain applies a parser-verified lossless edit to that key and fails closed\nif it cannot prove a unique edit; unrelated arrays, comments, and formatting\nremain repository-owned.\n\nRegex files must expose the current version through a named capture group called\n`version`:\n\n```toml\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n```\n\nIf `version.required = true`, promotion fails when no configured version files\nare available.\n\n### Anchored Manual Versions\n\nSome repositories do not derive their package version from the Buildchain\nrelease tag. `libnode` is the canonical example: the package version is anchored\nto an explicitly selected upstream Node.js release such as `22.22.3-kf.0`, while\nthe channel line may be `release/v22/v22.22`.\n\nThose repositories can opt into anchored manual semantics:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\nderived_files = [\n \"dist/version-witness.json\",\n]\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n```\n\nWith `strategy = \"anchored\"` and `next = \"manual\"`:\n\n- Buildchain validates the configured version files and anchor manifest, but it\n does not rewrite those files to the Buildchain release tag.\n- `lifecycle.verify` is the project-owned truth gate. It should compare the\n package version, anchor manifest, and upstream source/submodule state.\n- `version.derived_files` can declare committed witnesses or generated metadata\n whose bytes are derived by `lifecycle.version-state`. The field is valid only\n for anchored/manual projects that declare both `lifecycle.version-state` and\n `lifecycle.verify`.\n- release-candidate builds rerun derivation and verification before heavy\n platform builds, require the committed tree to remain unchanged, and bind the\n alpha/release tree identities plus declared file digests into controller and\n release-passport evidence.\n- release promotion permits differences from the tested alpha tree only in\n `version.files`, the anchor manifest, and declared `version.derived_files`;\n any other changed path fails closed before publication.\n- release promotion still creates the exact/floating production refs for the\n current line;\n- release promotion does not auto-create the next alpha branch or tag;\n- the action output `next-anchor-required` is `true`, signaling that the next\n upstream anchor line must be created explicitly by the repository.\n\nThe configured anchor manifest must be JSON or TOML. Buildchain does not\ninterpret project-specific field names; it only loads the manifest and exposes\nits top-level fields to validation summaries and lifecycle environment:\n\n```text\nBUILDCHAIN_VERSION_STRATEGY=anchored\nBUILDCHAIN_VERSION_NEXT=manual\nBUILDCHAIN_ANCHOR_MANIFEST=libnode.release.json\nBUILDCHAIN_ANCHOR_MANIFEST_JSON={\"nodeTag\":\"v22.22.3\",...}\n```\n\nThe upstream anchor decision remains outside Buildchain. A future line such as\n`dev/v24/v24.xx` should be created by an explicit repository workflow or human\ndecision after the upstream version has been selected and checked in.\n\n## Lifecycle Stages\n\nLifecycle stages are declarative shell commands. A stage can use exactly one of:\n\n- `command`: one shell command;\n- `commands`: multiple shell commands run in order;\n- `script`: a multiline shell script.\n\nAny command failure fails the stage. `timeout_minutes`, `retries`, `shell`, and\n`env` can be attached to a stage.\n\nDuring version-state verification, Buildchain also sets `BUILDCHAIN_VERSION` to\nthe release or prerelease version being verified.\n\n```toml\n[lifecycle.install]\ntimeout_minutes = 10\nretries = 3\ncommands = [\n \"pnpm install --frozen-lockfile\",\n]\n\n[lifecycle.build]\ncommands = [\n \"pnpm run build\",\n \"pnpm run package\",\n]\n\n[lifecycle.check]\ncommand = \"pnpm run check:source\"\n\n[lifecycle.verify]\nshell = \"bash\"\nscript = \"\"\"\nset -euo pipefail\npnpm run check\ngit diff --check\n\"\"\"\n```\n\n`lifecycle.check` is the repository-owned source-acceptance gate. It should\nvalidate the checked-out source revision without entering the build, artifact,\nor release lifecycle. Consumers can run it on GitHub-hosted Linux through\n`.github/workflows/check.yml@v2` with `mode: source`; the reusable workflow runs\nonly `lifecycle.install` and `lifecycle.check`. The default `mode: verify`\ncontinues to run `lifecycle.install` and `lifecycle.verify` for existing callers.\nBoth executed stages receive `BUILDCHAIN_CHECK_MODE=source` or\n`BUILDCHAIN_CHECK_MODE=verify`, so a repository whose normal install path can\ncompile native tooling can select a provisioning-only install path for source\nacceptance without weakening promotion installs.\nThe reusable job name remains `check`, and `upload-artifacts: false` disables\nevidence upload without changing the job conclusion used by branch protection.\n\nShared environment variables can be declared once:\n\n```toml\n[lifecycle.env]\nPYTHONUNBUFFERED = \"1\"\n```\n\nStage-specific environment variables override shared lifecycle environment:\n\n```toml\n[lifecycle.test]\ncommand = \"pytest\"\n\n[lifecycle.test.env]\nPYTHONPATH = \"src\"\n```\n\n### Publish Stage\n\n`lifecycle.publish` is the project-owned side-effect stage. It may call npm,\nPyPI, Conan, CMake packaging scripts, Docker/OCI registries, S3 uploaders, or\nany other publisher. Buildchain does not assume the tool; it assumes the\nevidence contract.\n\n```toml\n[lifecycle.publish]\nscript = \"\"\"\nset -euo pipefail\npython scripts/publish_wheels.py\nnode scripts/publish-images.mjs\nnode scripts/write-publish-evidence.mjs\n\"\"\"\n```\n\nWhen `actions/promote-buildchain-ref` runs with `publish-transaction: \"true\"`,\nthe publish stage receives the transaction identity plus the resolved publish\ncontract:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_PUBLISH_MODE\nBUILDCHAIN_PUBLISH_AUTH\nBUILDCHAIN_NPM_DIST_TAG\nBUILDCHAIN_PACKAGE_SET_ORDER\nBUILDCHAIN_PACKAGE_SET_MAIN_PACKAGE\n```\n\nThe stage must write publish evidence JSON. Buildchain validates that evidence\nbefore exact tags and floating refs move. In GitHub Actions, the promotion\naction also persists `state.json` and `evidence.json` to\n`refs/heads/buildchain/release-state/<version>` so fresh runners can recover\nwithout local workspace residue. See\n[`docs/publish-transaction.md`](publish-transaction.md) for the state machine,\nevidence schema, and recovery commands.\n\nFor npm packages, prefer:\n\n```toml\n[publish]\nmode = \"publish-final-version\"\nauth = \"trusted-publishing\"\ndist_tag = \"latest\"\n```\n\nUse `mode = \"promote-existing-version\"` only for explicit same-version\ndist-tag recovery, and pair it with `auth = \"npm-token\"`. Trusted Publishing\ndoes not authorize `npm dist-tag add`; Buildchain fails that combination before\nany publish transaction side effect.\n\n## Promotion Semantics\n\n`actions/promote-buildchain-ref` consumes `version.files`, `lifecycle.verify`,\nand optionally `lifecycle.publish`.\n\nThe verify stage runs after Buildchain has applied the generated version-state\nchanges to the local checkout, and before it creates release commits or moves\nrefs. After the command finishes, Buildchain checks that only declared\nversion-state files changed. This prevents verification from quietly adding\nextra source changes to the release commit.\n\nBuildchain-owned untracked runtime evidence is excluded only through an exact\ninternal allowlist. This includes contract-drift issue material under\n`.buildchain/contract-drift/` and the paper workflow's\n`.buildchain/publication-result.json`, alongside release-candidate, passport,\nrelease-state, KFD, and runtime evidence directories. Tracked changes, ordinary\nsource files, and undeclared `.buildchain/*` paths still fail version\nverification.\n\nOn protected alpha and release branches, the generated version-state commit is\napplied by the promotion automation after the reviewed channel PR has merged.\nBuildchain keeps review requirements, conversation resolution, strict status\nchecks, and admin enforcement for human channel changes, but admits only the\nexact GitHub Actions App to the target-bound bypass allowlist for generated\nrelease bookkeeping. Buildchain also creates the configured required\ncheck on the exact generated version-state commit before patching the protected\nref, then applies the protected ref update with the declared generated ref\nupdate token. The reusable wrapper uses the run-scoped `github.token` for that\nprotected bookkeeping update. If release finalization bookkeeping is still rejected,\nBuildchain creates or reuses a same-repository `buildchain/version-state/*` PR\nfrom the current target channel head and reports `finalization-needed=true` so\na later idempotent promotion run can resume. Strict alpha bookkeeping remains\nfail-fast with a token/protection diagnostic instead of asking humans to review\na post-publish version-state PR.\n\nFor `version.strategy = \"anchored\"` with `version.next = \"manual\"`, release\npromotion does not generate a Buildchain-owned version-state commit. In that\nmode, a protected `alpha -> release` PR may carry the declared `version.files`\nfrom the tested alpha package version to the final package version, and may\ncarry the configured `version.manifest` with it. Buildchain only accepts that\nrelease tree difference when the PR is the valid channel-promotion PR, the\nchanged paths are limited to those declared version files plus the anchor\nmanifest, and `lifecycle.verify` or `verification-command` has validated the\nchecked-out release material. Any code or undeclared file change still fails the\nrelease tree gate.\n\nThe action input `verification-command` remains supported. When it is provided,\nit overrides `lifecycle.verify` for that invocation. The override inherits\n`[lifecycle.env]` and `lifecycle.verify.env`; when the configured stage declares\n`shell`, it also inherits that shell. If the stage has no explicit shell,\nBuildchain preserves the platform-default shell for compatibility.\n\n## Migration Preflight\n\nHeavy repositories can validate their Buildchain declaration before they are\nready to run the real build. `actions/validate-config` checks that\n`.buildchain/buildchain.toml` parses, configured version-state files exist, configured\nversion keys are strings, and required lifecycle stage names are declared.\nFor web-surface repositories it also validates `project`, `channels`, `deploy`,\n`retention`, and `security` declarations.\n\nIt does not run lifecycle commands. This is useful for repositories such as\n`libnode`, where `lifecycle.build` represents an expensive multi-platform native\nbuild and the first migration milestone is to prove the release metadata and\nlifecycle protocol without consuming build runners.\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-version-state: \"true\"\n require-lifecycle-stages: \"install,build,verify\"\n```\n\nWeb-surface repositories can use the same action without requiring version\nstate:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-lifecycle-stages: \"build,verify\"\n```\n\nThe action exposes project and deploy metadata through outputs such as\n`project-type`, `project-site`, `channels`, and `deploy-adapters-json`.\n\n## Web-Surface Projects\n\n`project.type = \"web-surface\"` is for sites, docs, browser apps, and operator\nconsoles whose release object is a deployed surface, not a package version.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-libkungfu-dev\"\nsite = \"libkungfu-dev\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.libkungfu.dev\"\nvisibility = \"ephemeral\"\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.libkungfu.dev\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://libkungfu.dev\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[surfaces.hub]\npath = \"/\"\nproduction_url = \"https://libkungfu.dev\"\nstaging_url = \"https://staging.libkungfu.dev\"\npreview_url_pattern = \"https://{alias}.preview.libkungfu.dev\"\n\n[surfaces.core]\npath = \"/core/\"\nproduction_url = \"https://core.libkungfu.dev\"\nstaging_url = \"https://core.staging.libkungfu.dev\"\npreview_url_pattern = \"https://core-{alias}.preview.libkungfu.dev\"\n\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"libkungfu-dev-production\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n```\n\nSee [Web-surface deployments](web-surface-deployments.md) for named surface host\nmappings, the manifest, preview alias, retention, cleanup, and dry-run deploy\ncontract.\n\n## Infra-Contract Projects\n\n`project.type = \"infra-contract\"` is for infrastructure contract repositories\nthat need provider-neutral desired, plan, approval, apply, observe, contract,\nand propagation evidence. `buildchain init --type infra-contract` wires\n`lifecycle.verify` to `buildchain infra-contract --mode ci`, which writes\nmutation-free plan, contract, propagation dry-run, evidence bundle, and\nverification artifacts under `.buildchain/`. The surface supports\nmanual-observed, observe-only, and mocked adapter fixtures without reading\ncloud state files or executing live infrastructure mutation. See\n[Infra Contract](infra-contract.md).\n\n## Examples\n\n### Node Workspace\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n### Python Package\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n\n[lifecycle.install]\ncommand = \"python -m pip install -e .[test]\"\n\n[lifecycle.build]\ncommand = \"python -m build\"\n\n[lifecycle.verify]\ncommands = [\n \"python -m build\",\n \"pytest\",\n]\n```\n\n### CMake and Conan\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n\n[lifecycle.configure]\ncommands = [\n \"conan install . --build=missing\",\n \"cmake -S . -B build -DCMAKE_BUILD_TYPE=Release\",\n]\n\n[lifecycle.build]\ncommand = \"cmake --build build --config Release\"\n\n[lifecycle.verify]\ncommands = [\n \"cmake --build build --config Release\",\n \"ctest --test-dir build --output-on-failure\",\n]\n```\n\n### Docker Image\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.build]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} .\"\n\n[lifecycle.verify]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:verify .\"\n```\n\nDocker publishing is an external side effect and should be gated by a release\nworkflow after version-state promotion has been verified.\n\n## Design Boundaries\n\nThe lifecycle protocol is also the command source for the reusable build\nsurface. `.github/workflows/.build.yml` runs `lifecycle.install`,\n`lifecycle.build`, and `lifecycle.verify` by default, while allowing callers to\noverride each stage with explicit workflow inputs. The underlying\n`actions/run-lifecycle` action can be used directly by repositories that need a\ncustom workflow but still want Buildchain's lifecycle and deterministic manifest\ncontract.\n\nBuildchain lifecycle commands are data, not executable configuration files.\nThey make release behavior reviewable in pull requests and keep the release\nfact chain simple:\n\n1. choose the channel branch and release line;\n2. generate a source version commit from declared version files;\n3. verify that exact tree;\n4. move exact tags and floating refs only after verification succeeds;\n5. run publish or deployment side effects in separately gated workflows."
|
|
983
|
+
"markdown": "# Lifecycle Protocol\n\nBuildchain uses `.buildchain/buildchain.toml` as the v2 repository configuration format.\nThe file is optional for simple JavaScript repositories, but it is the preferred\nway to describe release version state and lifecycle commands when a project is\nnot a plain pnpm, npm, or yarn workspace.\n\nFor compatibility, Buildchain still reads a legacy root `buildchain.toml` when\n`.buildchain/buildchain.toml` is absent. New repositories should use the\n`.buildchain/` layout so all Buildchain-owned local state lives under one\ndirectory.\n\nOnly TOML is supported in v2. YAML, JSON, and JavaScript config files are not\nloaded.\n\n## Minimal File\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n`schema = 1` is required. Buildchain fails closed when the schema is missing or\nunknown.\n\n## Version State\n\nVersion state is the source file evidence that matches a release tag. During\npromotion, Buildchain writes the selected release or prerelease version into the\nconfigured files, verifies the resulting tree, creates a source version commit,\nthen moves exact and floating refs.\n\nSupported version file types:\n\n| Type | Use case | Required fields |\n| --- | --- | --- |\n| `json` | `package.json`, JSON manifests | `path`, `key` |\n| `toml` | `pyproject.toml`, other TOML manifests | `path`, `key` |\n| `regex` | `CMakeLists.txt`, `conanfile.py`, plain version files | `path`, `pattern`, `replacement` |\n\n`key` is a dotted key path:\n\n```toml\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n```\n\nConfigured JSON and TOML version files are semantic no-ops when the declared\nkey already equals the requested version. Buildchain preserves the repository's\noriginal TOML bytes in that case instead of serializing the whole document and\ncreating formatter-only release state. When a TOML version really changes,\nBuildchain applies a parser-verified lossless edit to that key and fails closed\nif it cannot prove a unique edit; unrelated arrays, comments, and formatting\nremain repository-owned.\n\nRegex files must expose the current version through a named capture group called\n`version`:\n\n```toml\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n```\n\nIf `version.required = true`, promotion fails when no configured version files\nare available.\n\n### Anchored Manual Versions\n\nSome repositories do not derive their package version from the Buildchain\nrelease tag. `libnode` is the canonical example: the package version is anchored\nto an explicitly selected upstream Node.js release such as `22.22.3-kf.0`, while\nthe channel line may be `release/v22/v22.22`.\n\nThose repositories can opt into anchored manual semantics:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\nderived_files = [\n \"dist/version-witness.json\",\n]\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n```\n\nWith `strategy = \"anchored\"` and `next = \"manual\"`:\n\n- Buildchain validates the configured version files and anchor manifest, but it\n does not rewrite those files to the Buildchain release tag.\n- `lifecycle.verify` is the project-owned truth gate. It should compare the\n package version, anchor manifest, and upstream source/submodule state.\n- `version.derived_files` can declare committed witnesses or generated metadata\n whose bytes are derived by `lifecycle.version-state`. The field is valid only\n for anchored/manual projects that declare both `lifecycle.version-state` and\n `lifecycle.verify`.\n- release-candidate builds rerun derivation and verification before heavy\n platform builds, require the committed tree to remain unchanged, and bind the\n alpha/release tree identities plus declared file digests into controller and\n release-passport evidence.\n- release promotion permits differences from the tested alpha tree only in\n `version.files`, the anchor manifest, and declared `version.derived_files`;\n any other changed path fails closed before publication.\n- release promotion still creates the exact/floating production refs for the\n current line;\n- release promotion does not auto-create the next alpha branch or tag;\n- the action output `next-anchor-required` is `true`, signaling that the next\n upstream anchor line must be created explicitly by the repository.\n\nThe configured anchor manifest must be JSON or TOML. Buildchain does not\ninterpret project-specific field names; it only loads the manifest and exposes\nits top-level fields to validation summaries and lifecycle environment:\n\n```text\nBUILDCHAIN_VERSION_STRATEGY=anchored\nBUILDCHAIN_VERSION_NEXT=manual\nBUILDCHAIN_ANCHOR_MANIFEST=libnode.release.json\nBUILDCHAIN_ANCHOR_MANIFEST_JSON={\"nodeTag\":\"v22.22.3\",...}\n```\n\nThe upstream anchor decision remains outside Buildchain. A future line such as\n`dev/v24/v24.xx` should be created by an explicit repository workflow or human\ndecision after the upstream version has been selected and checked in.\n\n## Lifecycle Stages\n\nLifecycle stages are declarative shell commands. A stage can use exactly one of:\n\n- `command`: one shell command;\n- `commands`: multiple shell commands run in order;\n- `script`: a multiline shell script.\n\nAny command failure fails the stage. `timeout_minutes`, `retries`, `shell`, and\n`env` can be attached to a stage.\n\nDuring version-state verification, Buildchain also sets `BUILDCHAIN_VERSION` to\nthe release or prerelease version being verified.\n\n```toml\n[lifecycle.install]\ntimeout_minutes = 10\nretries = 3\ncommands = [\n \"pnpm install --frozen-lockfile\",\n]\n\n[lifecycle.build]\ncommands = [\n \"pnpm run build\",\n \"pnpm run package\",\n]\n\n[lifecycle.check]\ncommand = \"pnpm run check:source\"\n\n[lifecycle.verify]\nshell = \"bash\"\nscript = \"\"\"\nset -euo pipefail\npnpm run check\ngit diff --check\n\"\"\"\n```\n\n`lifecycle.check` is the repository-owned source-acceptance gate. It should\nvalidate the checked-out source revision without entering the build, artifact,\nor release lifecycle. Consumers can run it on GitHub-hosted Linux through\n`.github/workflows/check.yml@v2` with `mode: source`; the reusable workflow runs\nonly `lifecycle.install` and `lifecycle.check`. The default `mode: verify`\ncontinues to run `lifecycle.install` and `lifecycle.verify` for existing callers.\nBoth executed stages receive `BUILDCHAIN_CHECK_MODE=source` or\n`BUILDCHAIN_CHECK_MODE=verify`, so a repository whose normal install path can\ncompile native tooling can select a provisioning-only install path for source\nacceptance without weakening promotion installs.\nThe reusable job name remains `check`, and `upload-artifacts: false` disables\nevidence upload without changing the job conclusion used by branch protection.\n\nShared environment variables can be declared once:\n\n```toml\n[lifecycle.env]\nPYTHONUNBUFFERED = \"1\"\n```\n\nStage-specific environment variables override shared lifecycle environment:\n\n```toml\n[lifecycle.test]\ncommand = \"pytest\"\n\n[lifecycle.test.env]\nPYTHONPATH = \"src\"\n```\n\n### Publish Stage\n\n`lifecycle.publish` is the project-owned side-effect stage. It may call npm,\nPyPI, Conan, CMake packaging scripts, Docker/OCI registries, S3 uploaders, or\nany other publisher. Buildchain does not assume the tool; it assumes the\nevidence contract.\n\n```toml\n[lifecycle.publish]\nscript = \"\"\"\nset -euo pipefail\npython scripts/publish_wheels.py\nnode scripts/publish-images.mjs\nnode scripts/write-publish-evidence.mjs\n\"\"\"\n```\n\nWhen `actions/promote-buildchain-ref` runs with `publish-transaction: \"true\"`,\nthe publish stage receives the transaction identity plus the resolved publish\ncontract:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_PUBLISH_MODE\nBUILDCHAIN_PUBLISH_AUTH\nBUILDCHAIN_NPM_DIST_TAG\nBUILDCHAIN_PACKAGE_SET_ORDER\nBUILDCHAIN_PACKAGE_SET_MAIN_PACKAGE\n```\n\nThe stage must write publish evidence JSON. Buildchain validates that evidence\nbefore exact tags and floating refs move. In GitHub Actions, the promotion\naction also persists `state.json` and `evidence.json` to\n`refs/heads/buildchain/release-state/<version>` so fresh runners can recover\nwithout local workspace residue. See\n[`docs/publish-transaction.md`](publish-transaction.md) for the state machine,\nevidence schema, and recovery commands.\n\nFor npm packages, prefer:\n\n```toml\n[publish]\nmode = \"publish-final-version\"\nauth = \"trusted-publishing\"\ndist_tag = \"latest\"\n```\n\nUse `mode = \"promote-existing-version\"` only for explicit same-version\ndist-tag recovery, and pair it with `auth = \"npm-token\"`. Trusted Publishing\ndoes not authorize `npm dist-tag add`; Buildchain fails that combination before\nany publish transaction side effect.\n\n## Promotion Semantics\n\n`actions/promote-buildchain-ref` consumes `version.files`, `lifecycle.verify`,\nand optionally `lifecycle.publish`.\n\nThe verify stage runs after Buildchain has applied the generated version-state\nchanges to the local checkout, and before it creates release commits or moves\nrefs. After the command finishes, Buildchain checks that only declared\nversion-state files changed. This prevents verification from quietly adding\nextra source changes to the release commit.\n\nBuildchain-owned untracked runtime evidence is excluded only through an exact\ninternal allowlist. This includes contract-drift issue material under\n`.buildchain/contract-drift/` and the paper workflow's\n`.buildchain/publication-result.json`, alongside release-candidate, passport,\nrelease-state, KFD, and runtime evidence directories. Tracked changes, ordinary\nsource files, and undeclared `.buildchain/*` paths still fail version\nverification.\n\nOn protected alpha and release branches, the generated version-state commit is\napplied by the promotion automation after the reviewed channel PR has merged.\nBuildchain keeps review requirements, conversation resolution, strict status\nchecks, and admin enforcement for human channel changes, but admits only the\nexact GitHub Actions App to the target-bound bypass allowlist for generated\nrelease bookkeeping. Buildchain also creates the configured required\ncheck on the exact generated version-state commit before patching the protected\nref, then applies the protected ref update with the declared generated ref\nupdate token. The reusable wrapper uses the run-scoped `github.token` for that\nprotected bookkeeping update. If finalization bookkeeping is still rejected,\nBuildchain creates or reuses a same-repository `buildchain/version-state/*` PR\nand reports `finalization-needed=true` so a later idempotent promotion run can\nresume. This fallback also applies to strict alpha target and dev\nreconciliation. The generated PR must pass the repository's normal protected\nchecks, review, and merge-queue policy; Buildchain never weakens that policy or\ntreats PR creation as completed finalization.\n\nFor `version.strategy = \"anchored\"` with `version.next = \"manual\"`, release\npromotion does not generate a Buildchain-owned version-state commit. In that\nmode, a protected `alpha -> release` PR may carry the declared `version.files`\nfrom the tested alpha package version to the final package version, and may\ncarry the configured `version.manifest` with it. Buildchain only accepts that\nrelease tree difference when the PR is the valid channel-promotion PR, the\nchanged paths are limited to those declared version files plus the anchor\nmanifest, and `lifecycle.verify` or `verification-command` has validated the\nchecked-out release material. Any code or undeclared file change still fails the\nrelease tree gate.\n\nThe action input `verification-command` remains supported. When it is provided,\nit overrides `lifecycle.verify` for that invocation. The override inherits\n`[lifecycle.env]` and `lifecycle.verify.env`; when the configured stage declares\n`shell`, it also inherits that shell. If the stage has no explicit shell,\nBuildchain preserves the platform-default shell for compatibility.\n\n## Migration Preflight\n\nHeavy repositories can validate their Buildchain declaration before they are\nready to run the real build. `actions/validate-config` checks that\n`.buildchain/buildchain.toml` parses, configured version-state files exist, configured\nversion keys are strings, and required lifecycle stage names are declared.\nFor web-surface repositories it also validates `project`, `channels`, `deploy`,\n`retention`, and `security` declarations.\n\nIt does not run lifecycle commands. This is useful for repositories such as\n`libnode`, where `lifecycle.build` represents an expensive multi-platform native\nbuild and the first migration milestone is to prove the release metadata and\nlifecycle protocol without consuming build runners.\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-version-state: \"true\"\n require-lifecycle-stages: \"install,build,verify\"\n```\n\nWeb-surface repositories can use the same action without requiring version\nstate:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-lifecycle-stages: \"build,verify\"\n```\n\nThe action exposes project and deploy metadata through outputs such as\n`project-type`, `project-site`, `channels`, and `deploy-adapters-json`.\n\n## Web-Surface Projects\n\n`project.type = \"web-surface\"` is for sites, docs, browser apps, and operator\nconsoles whose release object is a deployed surface, not a package version.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-libkungfu-dev\"\nsite = \"libkungfu-dev\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.libkungfu.dev\"\nvisibility = \"ephemeral\"\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.libkungfu.dev\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://libkungfu.dev\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[surfaces.hub]\npath = \"/\"\nproduction_url = \"https://libkungfu.dev\"\nstaging_url = \"https://staging.libkungfu.dev\"\npreview_url_pattern = \"https://{alias}.preview.libkungfu.dev\"\n\n[surfaces.core]\npath = \"/core/\"\nproduction_url = \"https://core.libkungfu.dev\"\nstaging_url = \"https://core.staging.libkungfu.dev\"\npreview_url_pattern = \"https://core-{alias}.preview.libkungfu.dev\"\n\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"libkungfu-dev-production\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n```\n\nSee [Web-surface deployments](web-surface-deployments.md) for named surface host\nmappings, the manifest, preview alias, retention, cleanup, and dry-run deploy\ncontract.\n\n## Infra-Contract Projects\n\n`project.type = \"infra-contract\"` is for infrastructure contract repositories\nthat need provider-neutral desired, plan, approval, apply, observe, contract,\nand propagation evidence. `buildchain init --type infra-contract` wires\n`lifecycle.verify` to `buildchain infra-contract --mode ci`, which writes\nmutation-free plan, contract, propagation dry-run, evidence bundle, and\nverification artifacts under `.buildchain/`. The surface supports\nmanual-observed, observe-only, and mocked adapter fixtures without reading\ncloud state files or executing live infrastructure mutation. See\n[Infra Contract](infra-contract.md).\n\n## Examples\n\n### Node Workspace\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n### Python Package\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n\n[lifecycle.install]\ncommand = \"python -m pip install -e .[test]\"\n\n[lifecycle.build]\ncommand = \"python -m build\"\n\n[lifecycle.verify]\ncommands = [\n \"python -m build\",\n \"pytest\",\n]\n```\n\n### CMake and Conan\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n\n[lifecycle.configure]\ncommands = [\n \"conan install . --build=missing\",\n \"cmake -S . -B build -DCMAKE_BUILD_TYPE=Release\",\n]\n\n[lifecycle.build]\ncommand = \"cmake --build build --config Release\"\n\n[lifecycle.verify]\ncommands = [\n \"cmake --build build --config Release\",\n \"ctest --test-dir build --output-on-failure\",\n]\n```\n\n### Docker Image\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.build]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} .\"\n\n[lifecycle.verify]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:verify .\"\n```\n\nDocker publishing is an external side effect and should be gated by a release\nworkflow after version-state promotion has been verified.\n\n## Design Boundaries\n\nThe lifecycle protocol is also the command source for the reusable build\nsurface. `.github/workflows/.build.yml` runs `lifecycle.install`,\n`lifecycle.build`, and `lifecycle.verify` by default, while allowing callers to\noverride each stage with explicit workflow inputs. The underlying\n`actions/run-lifecycle` action can be used directly by repositories that need a\ncustom workflow but still want Buildchain's lifecycle and deterministic manifest\ncontract.\n\nBuildchain lifecycle commands are data, not executable configuration files.\nThey make release behavior reviewable in pull requests and keep the release\nfact chain simple:\n\n1. choose the channel branch and release line;\n2. generate a source version commit from declared version files;\n3. verify that exact tree;\n4. move exact tags and floating refs only after verification succeeds;\n5. run publish or deployment side effects in separately gated workflows."
|
|
984
984
|
},
|
|
985
985
|
{
|
|
986
986
|
"id": "manual:map",
|
|
@@ -1324,7 +1324,7 @@
|
|
|
1324
1324
|
],
|
|
1325
1325
|
"maturity": "stable",
|
|
1326
1326
|
"sourcePath": "docs/publish-transaction.md",
|
|
1327
|
-
"digest": "sha256:
|
|
1327
|
+
"digest": "sha256:732e7da899ab0b532bda2f77181d753ee8af2e397db4b0eedaaa10657ade7b2a",
|
|
1328
1328
|
"headings": [
|
|
1329
1329
|
{
|
|
1330
1330
|
"level": 1,
|
|
@@ -1387,7 +1387,7 @@
|
|
|
1387
1387
|
"anchor": "build-images-follow-up"
|
|
1388
1388
|
}
|
|
1389
1389
|
],
|
|
1390
|
-
"markdown": "# Publish Transaction\n\nBuildchain release promotion is not just tag movement. A release can also publish\nexternal artifacts: npm packages, Python wheels, OCI images, binary archives,\nmetadata manifests, or site deployment records. Those side effects are harder\nthan Git refs because most registries are append-only: a failed rerun must know\nwhich artifacts already exist, which are still missing, and whether any existing\nartifact conflicts with the release material.\n\nBuildchain v2 models that work as a release transaction.\n\n## Why This Exists\n\nThe old ABV workflow made Git refs the visible release authority. That was\nenough when \"release\" meant \"create a version commit, move tags, and let\ndownstream jobs react.\" It is not enough when a single publish run must also\nupload packages and images.\n\nThe failure mode to avoid is:\n\n1. publish an external artifact;\n2. fail before moving the exact release tag or floating channel refs;\n3. rerun from a new job id with no memory of the artifact;\n4. either republish something different or move refs without proving the\n already-published artifact matches the release.\n\nThe transaction gives reruns a stable identity and a machine-readable state so\nBuildchain can resume safely. The identity is:\n\n```text\nrepository + version + source_sha + target_ref\n```\n\nIt is not the GitHub Actions run id.\n\n## Durable State\n\n`actions/promote-buildchain-ref` stores release transaction state in a\nmachine-managed Git branch:\n\n```text\nrefs/heads/buildchain/release-state/<version>\n```\n\nThe branch contains:\n\n```text\nstate.json\nevidence.json # present after publish evidence exists\n```\n\nThe local `.buildchain/release-state/...` and\n`.buildchain/release-evidence/...` files are working copies. They are useful for\nlocal inspection and lifecycle commands, but they are not the durable truth for\nGitHub-hosted reruns. On action startup, Buildchain reads the durable state ref\nfirst, restores the local working copies, and only then decides whether to\npublish, repair, or finalize.\n\nEvery meaningful state transition is written to the durable ref before public\nrelease refs move. Release-state GitHub API reads and writes use retry/backoff\nfor transient service failures such as HTTP 5xx responses, connection resets,\ntimeouts, and \"other side closed\" socket failures. If the durable write still\ncannot be persisted after retries, the action fails closed.\n\nDurable release-state refs reserve their exact version even when the public exact\ntag was never created. If a later machine run sees a failed or repair-required\nstate for `vX.Y.Z-alpha.N` and cannot resume it with the same transaction\nidentity, alpha version selection must advance to the next prerelease instead\nof reusing or overwriting that failed transaction slot.\n\n## Lifecycle\n\nRepositories declare publish work in `.buildchain/buildchain.toml`:\n\n```toml\n[publish]\nmode = \"publish-final-version\"\nauth = \"trusted-publishing\"\ndist_tag = \"latest\"\npackage_set_order = \"platforms-first-main-last\"\nmain_package = \"@kungfu-tech/libnode\"\n\n[lifecycle.publish]\ncommands = [\n \"python scripts/publish_wheels.py\",\n \"node scripts/publish-images.mjs\",\n \"node scripts/write-publish-evidence.mjs\",\n]\n```\n\n`actions/promote-buildchain-ref` runs `lifecycle.publish` only when\n`publish-transaction: \"true\"` is set or when a `publish-command` input is\nprovided. The action sets:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_REQUIRED_ARTIFACTS\nBUILDCHAIN_PUBLISH_MODE\nBUILDCHAIN_PUBLISH_AUTH\nBUILDCHAIN_NPM_DIST_TAG\nBUILDCHAIN_PACKAGE_SET_ORDER\nBUILDCHAIN_PACKAGE_SET_MAIN_PACKAGE\n```\n\nBuildchain itself uses this contract for npm publishing:\n\n```toml\n[lifecycle.publish]\ncommand = \"node scripts/npm-publish-transaction.mjs\"\n```\n\nThat script validates that `package.json` matches `BUILDCHAIN_VERSION`, runs\n`npm publish --access public --tag <BUILDCHAIN_NPM_DIST_TAG>` through npm Trusted\nPublishing, and writes npm artifact evidence before the promotion action moves\npublic refs.\n\n`BUILDCHAIN_RELEASE_MATERIAL_SHA` is the source material whose artifacts must\nmatch. `BUILDCHAIN_PUBLISH_TOOLING_SHA` identifies the publishing code. A repair\nrun may change tooling, but material drift fails closed.\n\n## Post-Publish Requirements And Artifact Provenance\n\n`publish-required-artifacts-json` is a pre-publish family declaration, not a\nrequest to guess registry digests. A descriptor must include `kind + name`; it\nmay omit `ref` and `digest`. The action resolves a missing `ref` to the exact\n`BUILDCHAIN_VERSION`. Registries whose exact refs add a stable prefix or suffix\nmay instead declare a `ref_template` containing exactly one `{version}`, such\nas `v{version}`. The template is expanded only after exact version selection,\nso a resumed alpha transaction receives the newly selected prerelease rather\nthan the checked-out version. Declaring both `ref` and `ref_template`, using\nanother placeholder, or leaving unmatched braces fails before\n`lifecycle.publish`. The action exports the normalized exact refs as\n`BUILDCHAIN_REQUIRED_ARTIFACTS`, runs `lifecycle.publish`, and then requires the\nfinal evidence to contain every exact member with a non-empty digest. Existing\ncallers may continue supplying exact refs and digests.\n\nOCI publishers can opt into strict per-artifact provenance by adding\n`action: built` or `action: reused`. Those artifacts carry two separate\ncoordinates:\n\n- `content`: the version, ref, source SHA, and material SHA that produced the\n immutable content;\n- `release`: the exact current version/ref, target ref, source SHA, and release\n material SHA that bind that content into this release.\n\nThis distinction permits truthful cross-version reuse without claiming that an\nold OCI config was rebuilt from current material. For an OCI artifact with an\naction, final evidence also requires `platform`, positive `contract_major`, and\n`verification` containing a public manifest result, exact ref and digest,\nplatform, contract major, optional parent digest, evidence location, and a\npassed named smoke policy. Buildchain cross-checks those values against the\nartifact and current transaction. Missing family members and ref, digest,\ncontent, release, or verification conflicts enter `repair_required` before\npublic refs move.\n\nExample reused OCI evidence entry (the pre-publish requirement may omit\n`ref`, `digest`, `release`, and the observed verification values):\n\n```json\n{\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/base-linux\",\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"action\": \"reused\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"content\": {\n \"version\": \"1.1.9\",\n \"ref\": \"1.1.9\",\n \"source_sha\": \"<source-sha>\",\n \"material_sha\": \"<material-sha>\"\n },\n \"release\": {\n \"version\": \"1.2.0-alpha.3\",\n \"ref\": \"1.2.0-alpha.3\",\n \"target_ref\": \"alpha/v1/v1.2\",\n \"source_sha\": \"<current-source-sha>\",\n \"material_sha\": \"<current-material-sha>\"\n },\n \"verification\": {\n \"public_manifest\": true,\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"evidence\": \"registry-inspect.json\",\n \"smoke\": {\n \"policy\": \"manifest-contract\",\n \"passed\": true,\n \"evidence\": \"smoke.json\"\n }\n }\n}\n```\n\n## Release Modes And Auth\n\nBuildchain distinguishes two npm release modes:\n\n| Mode | Use | Auth | npm operation |\n| --- | --- | --- | --- |\n| `publish-final-version` | normal alpha or stable publication | `trusted-publishing` | `npm publish --tag <alpha|vX.Y-alpha|latest>` |\n| `promote-existing-version` | same-version alpha-to-latest recovery | `npm-token` | `npm dist-tag add <pkg>@<version> latest` |\n\nThe normal libnode path is `publish-final-version`: publish an alpha package set\nsuch as `22.22.3-kf.3-alpha.0` with the `alpha` dist-tag, then publish a\ndistinct final package set such as `22.22.3-kf.3` with the `latest` dist-tag.\nGitHub-hosted npm Trusted Publishing can authorize those `npm publish` calls\nwhen the workflow grants `id-token: write`.\n\n`promote-existing-version` is deliberately separate. npm Trusted Publishing does\nnot authorize arbitrary registry-management operations such as `npm dist-tag\nadd`; it authorizes publish-time package provenance. Therefore same-version\npromotion must declare `auth = \"npm-token\"`. Buildchain runs an npm token\npreflight with `npm whoami` before it writes any release transaction state or\nmoves a dist-tag. Missing token auth fails early with a contract error instead\nof a late `E401` after publish evidence has started to move.\n\nFor package sets, `package_set_order = \"platforms-first-main-last\"` makes the\nmain package the visibility gate. Platform package side effects are planned or\nretried first, and the main package or main dist-tag move happens last.\n\nWhen the transaction reaches `complete`, `actions/promote-buildchain-ref`\ngenerates `.buildchain/release-passport/buildchain.release.json` and persists\nthe `release-passport/*` files into the durable `buildchain/release-state/...`\nref. The passport is the stable release artifact for agents and people: it\nlinks the package set, npm publish evidence, dist-tag evidence, build summary,\nplatform artifact manifests, trusted publishing metadata, release-state ref,\ndurable release-state SHA, and transaction result in one schema. Consumer\nrepositories can set `release-passport-product-name` so the passport names their\nproduct instead of the Buildchain default.\n\n## Evidence\n\nThe publish lifecycle must write JSON evidence. Buildchain validates common\nfields and required artifact identities before final refs move.\n\n```json\n{\n \"schema\": 1,\n \"version\": \"2.0.11\",\n \"channel\": \"release\",\n \"source_sha\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n \"release_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"target_ref\": \"release/v2/v2.0\",\n \"release_material_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"publish_tooling_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"artifacts\": [\n {\n \"group\": \"node\",\n \"kind\": \"npm\",\n \"name\": \"@kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n },\n {\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n }\n ]\n}\n```\n\nThe generic contract is intentionally small:\n\n- `version`, `channel`, `source_sha`, `release_sha`, and `target_ref` must match\n the promotion run;\n- dist-tag promotion evidence is written beside the publish evidence as\n `dist-tag-evidence.json` and is referenced from the generated passport;\n- required artifacts must appear in evidence;\n- evidence used by a GitHub-hosted rerun must either be stored in the durable\n state ref or be reconstructed by a machine-verifiable consumer command;\n- existing artifacts with the same identity and digest are accepted on rerun;\n- missing artifacts can be published by the next run;\n- an existing artifact with a different digest puts the transaction into\n `repair_required`.\n\nArtifact identity is `group + kind + name + ref`. A required artifact that omits\n`group` matches any group with the same `kind + name + ref`.\n\n## Registry Truth Contract\n\nBuildchain owns transaction orchestration, finalization ordering, durable state,\nand generic evidence validation. It does not embed registry clients for npm,\nPyPI, GHCR/OCI, GitHub Releases, S3, Conan, CMake packaging, or project-specific\ndownload pages.\n\nConsumer `lifecycle.publish` commands own registry truth. A valid consumer stage\nmust be idempotent and machine-verifiable:\n\n- inspect the target registry before publishing;\n- accept an existing exact artifact only when version, identity, digest, and\n release-source binding match;\n- publish missing required artifacts;\n- reject conflicting existing artifacts and write evidence that lets Buildchain\n move the transaction to `repair_required`;\n- write `BUILDCHAIN_PUBLISH_EVIDENCE` after every successful inspect/publish\n cycle;\n- leave floating aliases such as npm dist-tags, PyPI stable markers, OCI\n floating tags, GitHub Release \"published\" status, or download-page stable\n links to a finalization step after Buildchain evidence validation.\n\nThe first-class adapter surface is command-based. Projects may wrap npm, PyPI,\nGHCR/OCI, GitHub Release assets, archives, SBOMs, provenance, or checksums\nhowever they need, as long as they emit the common evidence contract.\n\n## States\n\nThe state machine is:\n\n```text\nprepared -> publishing -> published -> finalizing -> complete\n | | |\n v v v\n publish_failed repair_required failed_permanently\n |\n v\n abandoned\n```\n\nSupported states:\n\n| State | Meaning |\n| --- | --- |\n| `prepared` | Transaction identity was created, but publish has not started. |\n| `publishing` | Publish lifecycle is running or may have been interrupted. |\n| `publish_failed` | Publish command failed before valid evidence was produced. |\n| `published` | Evidence is valid; refs have not necessarily finalized. |\n| `finalizing` | Buildchain is moving exact/floating refs or needs a later run to do it. |\n| `complete` | Required evidence is valid and refs have finalized. |\n| `repair_required` | Existing evidence or artifact state conflicts with expected release material. |\n| `abandoned` | A human or controlled process abandoned this transaction, usually because a newer version supersedes it. |\n| `failed_permanently` | Recovery should not continue without explicit override. |\n\n`repair_required`, `abandoned`, and `failed_permanently` fail closed unless the\noperator passes an explicit override. That override is for controlled repair\nruns, not normal retry behavior.\n\n## Ref Ordering\n\nWhen publish transactions are enabled, promotion order is:\n\n1. verify target source and governance;\n2. create or reuse the version-state release commit;\n3. acquire or resume the release transaction;\n4. run `lifecycle.publish` or accept already-valid evidence;\n5. validate evidence and required artifacts;\n6. move exact release/prerelease tag;\n7. move floating tags and channel refs;\n8. mark the transaction `complete`.\n\nConsumer products that expose a signed well-known channel can opt into one\nadditional, deliberately final step with `publication-commit-command`. Before\nthat command runs, Buildchain has already completed the transaction, created\nthe public GitHub Release, and uploaded every release-passport file plus the\nexplicit PR-stage payload files selected by\n`github-release-payload-patterns`. The command is therefore a commit point for\ndiscovery authority, not another artifact publisher.\n\nThe command receives the exact version, source SHA, release SHA, release tag,\nrelease passport path, and downloaded payload directory through\n`BUILDCHAIN_PUBLICATION_COMMIT_*`. Optional consumer-owned dispatch/API\ncredentials and private signing material are exposed separately as\n`BUILDCHAIN_PUBLICATION_COMMIT_TOKEN` and\n`BUILDCHAIN_PUBLICATION_COMMIT_SIGNING_KEY`; Buildchain never logs, persists,\nor interprets either value. The command must write\n`.buildchain/publication-commit/evidence.json` (or another declared path below\n`.buildchain/`) with this contract:\n\n```json\n{\n \"schema\": \"kungfu-buildchain-publication-commit-evidence/v1\",\n \"status\": \"passed\",\n \"identity\": {\n \"version\": \"4.0.0-alpha.2\",\n \"sourceSha\": \"<source-sha>\",\n \"releaseSha\": \"<release-sha>\",\n \"releaseTag\": \"v4.0.0-alpha.2\"\n },\n \"publication\": {\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<64-lowercase-hex>\"\n },\n \"readback\": {\n \"status\": \"passed\",\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<same-root>\"\n },\n \"recovery\": {\n \"previousAuthority\": \"preserved\",\n \"rollbackReference\": \"sha256:<previous-root>\"\n }\n}\n```\n\nBuildchain rejects stale evidence, identity drift, non-public or mutable URLs,\nread-back root drift, and missing recovery evidence. It also rejects\n`standalone-binary-distribution=true` with a final commit command because that\nwould queue product mutations after the authority moved. On any command or\nread-back failure, the consumer must leave the previous well-known document\nauthoritative; Buildchain does not retry the command behind a successful\nreceipt.\n\nIf protected branch finalization is interrupted after publish evidence is\nvalid, the transaction can stop in `finalizing` and output\n`finalization-needed=true`. A later run resumes from the same transaction state\nand completes ref movement without republishing matching artifacts. New\nBuildchain-managed promotions first try to finish generated version-state\nbookkeeping with the promotion token directly. Before patching a protected\ngenerated bookkeeping ref, Buildchain emits every configured required check on\nthe exact generated version-state commit so branch protection can\nvalidate the automation path without a second build, then uses the generated\nref update token for the protected ref PATCH. If release finalization\nbookkeeping is still rejected, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR based on the current target channel head and\nleaves the transaction resumable with `finalization-needed=true`. Strict alpha\nbookkeeping remains fail-fast. The reusable wrapper binds that token to the\nrun-scoped `github.token` and rejects user, team, or alternate App bypass\nactors.\n\nIf finalization fails after an exact Git tag, a channel branch, or dev/alpha\nsync ref has already moved, the next run reads the durable `finalizing` state\nand continues from the recorded transaction. The current workflow SHA may be a\ngenerated version-state commit, or a historical version-state merge commit, that\ncontains or corresponds to the transaction's `release_material_sha`; it does not\nhave to equal the original `source_sha` or the transaction `release_sha`. Exact\ntags are accepted when they already point at the transaction release/material\nSHA or the finalized channel head. Floating\nchannel tags and dev/alpha refs are then retried idempotently, and the\ntransaction is marked `complete` only after those public refs are consistent.\nWriting `complete` clears any stale `failure` value from earlier attempts, so\nthe durable `state.json` represents the successful final state instead of the\nlast transient error seen before a rerun.\nAn exact tag at an unrelated SHA is still a material conflict and blocks\nrecovery.\n\nFor anchored package versions, the package version and internal line tag are\nseparate transaction coordinates. A retry can correct a stale internal tag on\nan unfinished `published` or `finalizing` transaction only when its validated\nevidence and complete artifact set match the same package version, source,\nrelease material, and target. Buildchain additionally requires that the stale\ntag does not already point at the transaction and that the newly selected tag\nis unclaimed or already points at accepted release material. No registry publish\ncommand is rerun during this exact-tag rebind.\n\nGoverned retries distinguish unrelated channel advancement from advancement\nmade by their own durable transaction. An unrelated descendant remains an\nauditable `superseded-promotion` no-op. When the target ref is exactly the\nrecorded `release_sha` for the requested source, target, and expected version,\nBuildchain resumes finalization, restores publish evidence, and emits the\nrelease-passport paths needed by downstream controller receipts.\n\nPublication authority planning applies the same occupied-version rule as the\nlater mutation step. If a current alpha transaction already contains published\nmaterial and regenerating version state would create new release material, the\nplanner advances to the next alpha before sealing authority. It never seals the\nold published version and then lets the publisher discover a different version\ninside the mutation boundary.\n\nIf finalization fails after an exact Git tag is created, the next run reads the\ndurable `finalizing` state, verifies the exact tag points at the recorded\nrelease SHA, and retries the remaining floating refs. An exact tag at a\ndifferent SHA is a material conflict and blocks recovery.\n\n## CLI Recovery\n\nLocal recovery commands operate on the same state/evidence files:\n\n```bash\nnode scripts/release-transaction.mjs inspect --version v2.0.11\nnode scripts/release-transaction.mjs recover --version v2.0.11\nnode scripts/release-transaction.mjs finalize --version v2.0.11\nnode scripts/release-transaction.mjs abort --version v2.0.11 --superseded-by v2.0.12\n```\n\nThe CLI is a diagnostic and local repair surface. It reports the durable\n`state_ref`, but remote durable-ref writes and public Git ref finalization are\nowned by `actions/promote-buildchain-ref`, because that action runs inside the\nsame governed GitHub permissions and branch-protection checks as release\npromotion. In other words, CLI `finalize` can mark the local transaction state\ncomplete after valid evidence; the machine-operated public finalization path is\nto rerun the promotion action.\n\nWhen no state file exists, creation commands also require:\n\n```bash\n--repository kungfu-systems/buildchain \\\n--source-sha <sha> \\\n--release-sha <sha> \\\n--target-ref release/v2/v2.0 \\\n--channel release\n```\n\n## Build-Images Follow-Up\n\n`build-images` should consume this contract rather than inventing a separate\nworkflow rule. The expected integration shape is:\n\n- image build writes OCI digests into publish evidence;\n- required image families are passed through `publish-required-artifacts-json`\n before their final digests are known;\n- mixed built/reused evidence preserves content provenance separately from the\n current release binding;\n- reruns check GHCR or the target registry and accept existing images only when\n tag and digest match;\n- preview or alpha image tags remain non-stable until the transaction evidence\n validates;\n- production image aliases move only after all required image artifacts are\n present and the Buildchain exact release tag has finalized."
|
|
1390
|
+
"markdown": "# Publish Transaction\n\nBuildchain release promotion is not just tag movement. A release can also publish\nexternal artifacts: npm packages, Python wheels, OCI images, binary archives,\nmetadata manifests, or site deployment records. Those side effects are harder\nthan Git refs because most registries are append-only: a failed rerun must know\nwhich artifacts already exist, which are still missing, and whether any existing\nartifact conflicts with the release material.\n\nBuildchain v2 models that work as a release transaction.\n\n## Why This Exists\n\nThe old ABV workflow made Git refs the visible release authority. That was\nenough when \"release\" meant \"create a version commit, move tags, and let\ndownstream jobs react.\" It is not enough when a single publish run must also\nupload packages and images.\n\nThe failure mode to avoid is:\n\n1. publish an external artifact;\n2. fail before moving the exact release tag or floating channel refs;\n3. rerun from a new job id with no memory of the artifact;\n4. either republish something different or move refs without proving the\n already-published artifact matches the release.\n\nThe transaction gives reruns a stable identity and a machine-readable state so\nBuildchain can resume safely. The identity is:\n\n```text\nrepository + version + source_sha + target_ref\n```\n\nIt is not the GitHub Actions run id.\n\n## Durable State\n\n`actions/promote-buildchain-ref` stores release transaction state in a\nmachine-managed Git branch:\n\n```text\nrefs/heads/buildchain/release-state/<version>\n```\n\nThe branch contains:\n\n```text\nstate.json\nevidence.json # present after publish evidence exists\n```\n\nThe local `.buildchain/release-state/...` and\n`.buildchain/release-evidence/...` files are working copies. They are useful for\nlocal inspection and lifecycle commands, but they are not the durable truth for\nGitHub-hosted reruns. On action startup, Buildchain reads the durable state ref\nfirst, restores the local working copies, and only then decides whether to\npublish, repair, or finalize.\n\nEvery meaningful state transition is written to the durable ref before public\nrelease refs move. Release-state GitHub API reads and writes use retry/backoff\nfor transient service failures such as HTTP 5xx responses, connection resets,\ntimeouts, and \"other side closed\" socket failures. If the durable write still\ncannot be persisted after retries, the action fails closed.\n\nDurable release-state refs reserve their exact version even when the public exact\ntag was never created. If a later machine run sees a failed or repair-required\nstate for `vX.Y.Z-alpha.N` and cannot resume it with the same transaction\nidentity, alpha version selection must advance to the next prerelease instead\nof reusing or overwriting that failed transaction slot.\n\n## Lifecycle\n\nRepositories declare publish work in `.buildchain/buildchain.toml`:\n\n```toml\n[publish]\nmode = \"publish-final-version\"\nauth = \"trusted-publishing\"\ndist_tag = \"latest\"\npackage_set_order = \"platforms-first-main-last\"\nmain_package = \"@kungfu-tech/libnode\"\n\n[lifecycle.publish]\ncommands = [\n \"python scripts/publish_wheels.py\",\n \"node scripts/publish-images.mjs\",\n \"node scripts/write-publish-evidence.mjs\",\n]\n```\n\n`actions/promote-buildchain-ref` runs `lifecycle.publish` only when\n`publish-transaction: \"true\"` is set or when a `publish-command` input is\nprovided. The action sets:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_REQUIRED_ARTIFACTS\nBUILDCHAIN_PUBLISH_MODE\nBUILDCHAIN_PUBLISH_AUTH\nBUILDCHAIN_NPM_DIST_TAG\nBUILDCHAIN_PACKAGE_SET_ORDER\nBUILDCHAIN_PACKAGE_SET_MAIN_PACKAGE\n```\n\nBuildchain itself uses this contract for npm publishing:\n\n```toml\n[lifecycle.publish]\ncommand = \"node scripts/npm-publish-transaction.mjs\"\n```\n\nThat script validates that `package.json` matches `BUILDCHAIN_VERSION`, runs\n`npm publish --access public --tag <BUILDCHAIN_NPM_DIST_TAG>` through npm Trusted\nPublishing, and writes npm artifact evidence before the promotion action moves\npublic refs.\n\n`BUILDCHAIN_RELEASE_MATERIAL_SHA` is the source material whose artifacts must\nmatch. `BUILDCHAIN_PUBLISH_TOOLING_SHA` identifies the publishing code. A repair\nrun may change tooling, but material drift fails closed.\n\n## Post-Publish Requirements And Artifact Provenance\n\n`publish-required-artifacts-json` is a pre-publish family declaration, not a\nrequest to guess registry digests. A descriptor must include `kind + name`; it\nmay omit `ref` and `digest`. The action resolves a missing `ref` to the exact\n`BUILDCHAIN_VERSION`. Registries whose exact refs add a stable prefix or suffix\nmay instead declare a `ref_template` containing exactly one `{version}`, such\nas `v{version}`. The template is expanded only after exact version selection,\nso a resumed alpha transaction receives the newly selected prerelease rather\nthan the checked-out version. Declaring both `ref` and `ref_template`, using\nanother placeholder, or leaving unmatched braces fails before\n`lifecycle.publish`. The action exports the normalized exact refs as\n`BUILDCHAIN_REQUIRED_ARTIFACTS`, runs `lifecycle.publish`, and then requires the\nfinal evidence to contain every exact member with a non-empty digest. Existing\ncallers may continue supplying exact refs and digests.\n\nOCI publishers can opt into strict per-artifact provenance by adding\n`action: built` or `action: reused`. Those artifacts carry two separate\ncoordinates:\n\n- `content`: the version, ref, source SHA, and material SHA that produced the\n immutable content;\n- `release`: the exact current version/ref, target ref, source SHA, and release\n material SHA that bind that content into this release.\n\nThis distinction permits truthful cross-version reuse without claiming that an\nold OCI config was rebuilt from current material. For an OCI artifact with an\naction, final evidence also requires `platform`, positive `contract_major`, and\n`verification` containing a public manifest result, exact ref and digest,\nplatform, contract major, optional parent digest, evidence location, and a\npassed named smoke policy. Buildchain cross-checks those values against the\nartifact and current transaction. Missing family members and ref, digest,\ncontent, release, or verification conflicts enter `repair_required` before\npublic refs move.\n\nExample reused OCI evidence entry (the pre-publish requirement may omit\n`ref`, `digest`, `release`, and the observed verification values):\n\n```json\n{\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/base-linux\",\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"action\": \"reused\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"content\": {\n \"version\": \"1.1.9\",\n \"ref\": \"1.1.9\",\n \"source_sha\": \"<source-sha>\",\n \"material_sha\": \"<material-sha>\"\n },\n \"release\": {\n \"version\": \"1.2.0-alpha.3\",\n \"ref\": \"1.2.0-alpha.3\",\n \"target_ref\": \"alpha/v1/v1.2\",\n \"source_sha\": \"<current-source-sha>\",\n \"material_sha\": \"<current-material-sha>\"\n },\n \"verification\": {\n \"public_manifest\": true,\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"evidence\": \"registry-inspect.json\",\n \"smoke\": {\n \"policy\": \"manifest-contract\",\n \"passed\": true,\n \"evidence\": \"smoke.json\"\n }\n }\n}\n```\n\n## Release Modes And Auth\n\nBuildchain distinguishes two npm release modes:\n\n| Mode | Use | Auth | npm operation |\n| --- | --- | --- | --- |\n| `publish-final-version` | normal alpha or stable publication | `trusted-publishing` | `npm publish --tag <alpha|vX.Y-alpha|latest>` |\n| `promote-existing-version` | same-version alpha-to-latest recovery | `npm-token` | `npm dist-tag add <pkg>@<version> latest` |\n\nThe normal libnode path is `publish-final-version`: publish an alpha package set\nsuch as `22.22.3-kf.3-alpha.0` with the `alpha` dist-tag, then publish a\ndistinct final package set such as `22.22.3-kf.3` with the `latest` dist-tag.\nGitHub-hosted npm Trusted Publishing can authorize those `npm publish` calls\nwhen the workflow grants `id-token: write`.\n\n`promote-existing-version` is deliberately separate. npm Trusted Publishing does\nnot authorize arbitrary registry-management operations such as `npm dist-tag\nadd`; it authorizes publish-time package provenance. Therefore same-version\npromotion must declare `auth = \"npm-token\"`. Buildchain runs an npm token\npreflight with `npm whoami` before it writes any release transaction state or\nmoves a dist-tag. Missing token auth fails early with a contract error instead\nof a late `E401` after publish evidence has started to move.\n\nFor package sets, `package_set_order = \"platforms-first-main-last\"` makes the\nmain package the visibility gate. Platform package side effects are planned or\nretried first, and the main package or main dist-tag move happens last.\n\nWhen the transaction reaches `complete`, `actions/promote-buildchain-ref`\ngenerates `.buildchain/release-passport/buildchain.release.json` and persists\nthe `release-passport/*` files into the durable `buildchain/release-state/...`\nref. The passport is the stable release artifact for agents and people: it\nlinks the package set, npm publish evidence, dist-tag evidence, build summary,\nplatform artifact manifests, trusted publishing metadata, release-state ref,\ndurable release-state SHA, and transaction result in one schema. Consumer\nrepositories can set `release-passport-product-name` so the passport names their\nproduct instead of the Buildchain default.\n\n## Evidence\n\nThe publish lifecycle must write JSON evidence. Buildchain validates common\nfields and required artifact identities before final refs move.\n\n```json\n{\n \"schema\": 1,\n \"version\": \"2.0.11\",\n \"channel\": \"release\",\n \"source_sha\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n \"release_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"target_ref\": \"release/v2/v2.0\",\n \"release_material_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"publish_tooling_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"artifacts\": [\n {\n \"group\": \"node\",\n \"kind\": \"npm\",\n \"name\": \"@kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n },\n {\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n }\n ]\n}\n```\n\nThe generic contract is intentionally small:\n\n- `version`, `channel`, `source_sha`, `release_sha`, and `target_ref` must match\n the promotion run;\n- dist-tag promotion evidence is written beside the publish evidence as\n `dist-tag-evidence.json` and is referenced from the generated passport;\n- required artifacts must appear in evidence;\n- evidence used by a GitHub-hosted rerun must either be stored in the durable\n state ref or be reconstructed by a machine-verifiable consumer command;\n- existing artifacts with the same identity and digest are accepted on rerun;\n- missing artifacts can be published by the next run;\n- an existing artifact with a different digest puts the transaction into\n `repair_required`.\n\nArtifact identity is `group + kind + name + ref`. A required artifact that omits\n`group` matches any group with the same `kind + name + ref`.\n\n## Registry Truth Contract\n\nBuildchain owns transaction orchestration, finalization ordering, durable state,\nand generic evidence validation. It does not embed registry clients for npm,\nPyPI, GHCR/OCI, GitHub Releases, S3, Conan, CMake packaging, or project-specific\ndownload pages.\n\nConsumer `lifecycle.publish` commands own registry truth. A valid consumer stage\nmust be idempotent and machine-verifiable:\n\n- inspect the target registry before publishing;\n- accept an existing exact artifact only when version, identity, digest, and\n release-source binding match;\n- publish missing required artifacts;\n- reject conflicting existing artifacts and write evidence that lets Buildchain\n move the transaction to `repair_required`;\n- write `BUILDCHAIN_PUBLISH_EVIDENCE` after every successful inspect/publish\n cycle;\n- leave floating aliases such as npm dist-tags, PyPI stable markers, OCI\n floating tags, GitHub Release \"published\" status, or download-page stable\n links to a finalization step after Buildchain evidence validation.\n\nThe first-class adapter surface is command-based. Projects may wrap npm, PyPI,\nGHCR/OCI, GitHub Release assets, archives, SBOMs, provenance, or checksums\nhowever they need, as long as they emit the common evidence contract.\n\n## States\n\nThe state machine is:\n\n```text\nprepared -> publishing -> published -> finalizing -> complete\n | | |\n v v v\n publish_failed repair_required failed_permanently\n |\n v\n abandoned\n```\n\nSupported states:\n\n| State | Meaning |\n| --- | --- |\n| `prepared` | Transaction identity was created, but publish has not started. |\n| `publishing` | Publish lifecycle is running or may have been interrupted. |\n| `publish_failed` | Publish command failed before valid evidence was produced. |\n| `published` | Evidence is valid; refs have not necessarily finalized. |\n| `finalizing` | Buildchain is moving exact/floating refs or needs a later run to do it. |\n| `complete` | Required evidence is valid and refs have finalized. |\n| `repair_required` | Existing evidence or artifact state conflicts with expected release material. |\n| `abandoned` | A human or controlled process abandoned this transaction, usually because a newer version supersedes it. |\n| `failed_permanently` | Recovery should not continue without explicit override. |\n\n`repair_required`, `abandoned`, and `failed_permanently` fail closed unless the\noperator passes an explicit override. That override is for controlled repair\nruns, not normal retry behavior.\n\n## Ref Ordering\n\nWhen publish transactions are enabled, promotion order is:\n\n1. verify target source and governance;\n2. create or reuse the version-state release commit;\n3. acquire or resume the release transaction;\n4. run `lifecycle.publish` or accept already-valid evidence;\n5. validate evidence and required artifacts;\n6. move exact release/prerelease tag;\n7. move floating tags and channel refs;\n8. mark the transaction `complete`.\n\nConsumer products that expose a signed well-known channel can opt into one\nadditional, deliberately final step with `publication-commit-command`. Before\nthat command runs, Buildchain has already completed the transaction, created\nthe public GitHub Release, and uploaded every release-passport file plus the\nexplicit PR-stage payload files selected by\n`github-release-payload-patterns`. The command is therefore a commit point for\ndiscovery authority, not another artifact publisher.\n\nThe command receives the exact version, source SHA, release SHA, release tag,\nrelease passport path, and downloaded payload directory through\n`BUILDCHAIN_PUBLICATION_COMMIT_*`. Optional consumer-owned dispatch/API\ncredentials and private signing material are exposed separately as\n`BUILDCHAIN_PUBLICATION_COMMIT_TOKEN` and\n`BUILDCHAIN_PUBLICATION_COMMIT_SIGNING_KEY`; Buildchain never logs, persists,\nor interprets either value. The command must write\n`.buildchain/publication-commit/evidence.json` (or another declared path below\n`.buildchain/`) with this contract:\n\n```json\n{\n \"schema\": \"kungfu-buildchain-publication-commit-evidence/v1\",\n \"status\": \"passed\",\n \"identity\": {\n \"version\": \"4.0.0-alpha.2\",\n \"sourceSha\": \"<source-sha>\",\n \"releaseSha\": \"<release-sha>\",\n \"releaseTag\": \"v4.0.0-alpha.2\"\n },\n \"publication\": {\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<64-lowercase-hex>\"\n },\n \"readback\": {\n \"status\": \"passed\",\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<same-root>\"\n },\n \"recovery\": {\n \"previousAuthority\": \"preserved\",\n \"rollbackReference\": \"sha256:<previous-root>\"\n }\n}\n```\n\nBuildchain rejects stale evidence, identity drift, non-public or mutable URLs,\nread-back root drift, and missing recovery evidence. It also rejects\n`standalone-binary-distribution=true` with a final commit command because that\nwould queue product mutations after the authority moved. On any command or\nread-back failure, the consumer must leave the previous well-known document\nauthoritative; Buildchain does not retry the command behind a successful\nreceipt.\n\nIf protected branch finalization is interrupted after publish evidence is\nvalid, the transaction can stop in `finalizing` and output\n`finalization-needed=true`. A later run resumes from the same transaction state\nand completes ref movement without republishing matching artifacts. New\nBuildchain-managed promotions first try to finish generated version-state\nbookkeeping with the promotion token directly. Before patching a protected\ngenerated bookkeeping ref, Buildchain emits every configured required check on\nthe exact generated version-state commit so branch protection can\nvalidate the automation path without a second build, then uses the generated\nref update token for the protected ref PATCH. If release finalization\nbookkeeping is still rejected, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR and leaves the transaction resumable with\n`finalization-needed=true`. Strict alpha uses the same protected PR fallback\nfor both its target channel and subsequent dev reconciliation. A later\nidempotent run continues only after the provider shows that the PR reached the\nprotected branch. The reusable wrapper binds that token to the run-scoped\n`github.token` and rejects user, team, or alternate App bypass actors.\n\nIf finalization fails after an exact Git tag, a channel branch, or dev/alpha\nsync ref has already moved, the next run reads the durable `finalizing` state\nand continues from the recorded transaction. The current workflow SHA may be a\ngenerated version-state commit, or a historical version-state merge commit, that\ncontains or corresponds to the transaction's `release_material_sha`; it does not\nhave to equal the original `source_sha` or the transaction `release_sha`. Exact\ntags are accepted when they already point at the transaction release/material\nSHA or the finalized channel head. Floating\nchannel tags and dev/alpha refs are then retried idempotently, and the\ntransaction is marked `complete` only after those public refs are consistent.\nWriting `complete` clears any stale `failure` value from earlier attempts, so\nthe durable `state.json` represents the successful final state instead of the\nlast transient error seen before a rerun.\nAn exact tag at an unrelated SHA is still a material conflict and blocks\nrecovery.\n\nFor anchored package versions, the package version and internal line tag are\nseparate transaction coordinates. A retry can correct a stale internal tag on\nan unfinished `published` or `finalizing` transaction only when its validated\nevidence and complete artifact set match the same package version, source,\nrelease material, and target. Buildchain additionally requires that the stale\ntag does not already point at the transaction and that the newly selected tag\nis unclaimed or already points at accepted release material. No registry publish\ncommand is rerun during this exact-tag rebind.\n\nGoverned retries distinguish unrelated channel advancement from advancement\nmade by their own durable transaction. An unrelated descendant remains an\nauditable `superseded-promotion` no-op. When the target ref is exactly the\nrecorded `release_sha` for the requested source, target, and expected version,\nBuildchain resumes finalization, restores publish evidence, and emits the\nrelease-passport paths needed by downstream controller receipts.\n\nPublication authority planning applies the same occupied-version rule as the\nlater mutation step. If a current alpha transaction already contains published\nmaterial and regenerating version state would create new release material, the\nplanner advances to the next alpha before sealing authority. It never seals the\nold published version and then lets the publisher discover a different version\ninside the mutation boundary.\n\nIf finalization fails after an exact Git tag is created, the next run reads the\ndurable `finalizing` state, verifies the exact tag points at the recorded\nrelease SHA, and retries the remaining floating refs. An exact tag at a\ndifferent SHA is a material conflict and blocks recovery.\n\n## CLI Recovery\n\nLocal recovery commands operate on the same state/evidence files:\n\n```bash\nnode scripts/release-transaction.mjs inspect --version v2.0.11\nnode scripts/release-transaction.mjs recover --version v2.0.11\nnode scripts/release-transaction.mjs finalize --version v2.0.11\nnode scripts/release-transaction.mjs abort --version v2.0.11 --superseded-by v2.0.12\n```\n\nThe CLI is a diagnostic and local repair surface. It reports the durable\n`state_ref`, but remote durable-ref writes and public Git ref finalization are\nowned by `actions/promote-buildchain-ref`, because that action runs inside the\nsame governed GitHub permissions and branch-protection checks as release\npromotion. In other words, CLI `finalize` can mark the local transaction state\ncomplete after valid evidence; the machine-operated public finalization path is\nto rerun the promotion action.\n\nWhen no state file exists, creation commands also require:\n\n```bash\n--repository kungfu-systems/buildchain \\\n--source-sha <sha> \\\n--release-sha <sha> \\\n--target-ref release/v2/v2.0 \\\n--channel release\n```\n\n## Build-Images Follow-Up\n\n`build-images` should consume this contract rather than inventing a separate\nworkflow rule. The expected integration shape is:\n\n- image build writes OCI digests into publish evidence;\n- required image families are passed through `publish-required-artifacts-json`\n before their final digests are known;\n- mixed built/reused evidence preserves content provenance separately from the\n current release binding;\n- reruns check GHCR or the target registry and accept existing images only when\n tag and digest match;\n- preview or alpha image tags remain non-stable until the transaction evidence\n validates;\n- production image aliases move only after all required image artifacts are\n present and the Buildchain exact release tag has finalized."
|
|
1391
1391
|
},
|
|
1392
1392
|
{
|
|
1393
1393
|
"id": "manual:readme-badges",
|
|
@@ -1542,7 +1542,7 @@
|
|
|
1542
1542
|
],
|
|
1543
1543
|
"maturity": "stable",
|
|
1544
1544
|
"sourcePath": "docs/release-governance.md",
|
|
1545
|
-
"digest": "sha256:
|
|
1545
|
+
"digest": "sha256:5aa7fef2c831d14b4d1df4cddb45c6ce97929d8530eb14e3b3dfa3dee5e0d651",
|
|
1546
1546
|
"headings": [
|
|
1547
1547
|
{
|
|
1548
1548
|
"level": 1,
|
|
@@ -1635,7 +1635,7 @@
|
|
|
1635
1635
|
"anchor": "operational-reading-order"
|
|
1636
1636
|
}
|
|
1637
1637
|
],
|
|
1638
|
-
"markdown": "# Release Governance\n\nBuildchain v2 preserves the release semantics of the older ABV workflow while\nmoving the implementation into one modern repository.\n\nThe central idea is simple: a reviewed merge into a release channel is the\nrelease intent. Automation must then create the version-state commit, exact tag,\nfloating tag, and next alpha state that make that intent true in Git.\n\n## Design Problem\n\nKungfu release automation has to keep four facts aligned:\n\n1. The source tree that was reviewed.\n2. The package version recorded in manifests such as `package.json` or\n `lerna.json`.\n3. The exact immutable release or prerelease tag.\n4. The floating channel refs that consumers actually use.\n\nIf any one of these facts is updated by hand, the system can split:\n\n- a consumer can fetch `v2.0` and receive a tree whose package version still\n says the previous release;\n- a maintainer can move `v2` without producing an exact `v2.0.N` audit tag;\n- an alpha can be promoted to production even though the release tree is not the\n same tree that was tested;\n- a protected branch merge can succeed while the follow-up version commit is\n missing, or a flow-internal generated `dev`/`alpha`/`release` ref update can\n fail after publish because the automation identity was not declared in the\n branch-protection review bypass allowance.\n\nThe older ABV workflow addressed this by letting GitHub PRs drive release\nstate. Buildchain keeps that choice because it makes release intent reviewable,\nobservable, and recoverable from Git history.\n\n## What ABV Contributed\n\nThe old ABV model was not just \"bump a version number.\" It encoded a governance\nloop:\n\n- release branches are named as channels: `dev`, `alpha`, `release`, and the\n administrative `publish-gate/major`;\n- a PR from one channel to the next is the release request;\n- verify jobs check that the branch pair is valid before merge;\n- a maintainer review is required before the branch moves;\n- after merge, automation writes the version change and moves tags;\n- exact tags and floating refs are aligned with the resulting commit;\n- the next development channel is prepared automatically.\n\nABV also kept the version-state mutation in the repository. For JavaScript\nrepositories that usually meant changing `lerna.json` and/or `package.json`.\nThat commit is important because the tag alone is not enough evidence: the\nsource tree should also declare the version that the tag advertises.\n\nBuildchain v2 treats that as a hard semantic requirement for its own release\nline.\n\n## Buildchain Implementation\n\nBuildchain implements the same governance loop with:\n\n- `.github/workflows/release-verify.yml` for PR verification;\n- `.github/workflows/buildchain-ref-promotion.yml` for post-verify ref\n promotion; this workflow dogfoods the declarative\n `release-candidate-promote.yml` wrapper and does not hand-wire resolver,\n artifact download, publish-gate, or promote action steps;\n- Buildchain self promotion enables `release-passport-buildchain-self-kfd`, so\n the promote action generates KFD-1 witnesses, KFD-2 public claim JSON, and\n KFD-3 collaboration-interface witnesses from the final version-state workspace\n before release passport finalization. The witness hashes therefore bind to the\n exact published package and site facts from\n `packages/core/buildchain-kfd-claims.js` instead of relying on prose release\n notes;\n- `actions/promote-buildchain-ref` for branch, tag, version-state, and\n governance checks;\n- package-manager adapters that can update version state for pnpm, npm, and\n yarn style repositories;\n- `buildchain.toml` lifecycle configuration for repositories whose version\n state or verification commands are not Node package-manager defaults.\n\nThe implementation is intentionally stricter than a local release script:\n\n- manual workflow dispatch can only do dry-run promotion;\n- non-dry-run promotion must be driven by a completed `Verify` workflow;\n- target branch protection details must be readable, and branch protection must\n apply to administrators as well as regular contributors;\n- `alpha/vX/vX.Y` and `release/vX/vX.Y` branch protection must require both the\n general `check` context and the Release Verify aggregate `verify` context, so\n an invalid channel pair cannot merge even when repository checks pass;\n- release targets keep those checks non-strict with respect to source-branch\n ancestry: generated channel bookkeeping intentionally makes the source and\n target histories diverge, while the pair-specific `verify` context validates\n the legal channel transition;\n- alpha promotion must come from a merged same-repository PR from\n `dev/vX/vX.Y` to `alpha/vX/vX.Y`;\n- release promotion must come from a merged same-repository PR from\n `alpha/vX/vX.Y` to `release/vX/vX.Y`;\n- major promotion must come from a merged same-repository PR from\n `release/vX/vX.Y` to `publish-gate/major`;\n- release promotion requires an existing same-patch alpha tag and checks the\n release source tree against that tested alpha tree;\n- generated version-state commits are verified before refs move.\n\n## Reconciling a protected line without rebuilding\n\nThe public `build.yml` channel router ends with a top-level job named\n`Summarize build contract`. Keeping this aggregate at the public router boundary\nprevents its required check context from changing when the internal reusable\nbuild workflow gains another nesting layer.\n\nFor an already-tested pull request whose protected target still requires an\nolder Buildchain aggregate context, inspect the exact candidate SHA first:\n\n```bash\nGH_TOKEN=\"$(gh auth token)\" npx @kungfu-tech/buildchain@latest \\\n release-governance reconcile \\\n --repository kungfu-systems/example \\\n --branch release/v2/v2.14 \\\n --candidate-sha <tested-pr-head-sha> \\\n --json\n```\n\nThe dry run reads the successful checks emitted for that SHA and reports the\nexact expected/actual context pair. It chooses the shallowest successful\n`Summarize build contract` context, so a new top-level router aggregate wins\nover the nested internal build summary. To apply the plan, rerun the same\ncommand with `--apply` using a token that can update branch protection.\n\nReconciliation changes only the required-status-check subresource. It replaces\nstale Buildchain aggregate contexts, preserves unrelated checks and strictness,\nand does not modify review requirements, administrator enforcement,\nconversation resolution, force-push policy, or deletion policy. The candidate\nmust still be the head of a pull request targeting the named managed branch;\nthe command fails closed otherwise. This lets a previously successful candidate\ncontinue from the same SHA without another native build or an administrator\nmerge bypass.\n\nRepositories may also expose a small caller workflow around\n`.github/workflows/release-governance-reconcile.yml@v2`. Pass `branch`,\n`candidate-sha`, and `apply`, and provide `governance-token` through the caller's\nsecrets. The reusable workflow uploads the JSON reconciliation receipt.\n\nExact publication planning installs the checked-out promotion source's declared\ndependencies before version-state verification. This keeps the pre-authority\nversion plan on the same package-manager boundary as the later promotion job,\nincluding repositories whose verification commands import production packages.\nThe planning pass may materialize and verify declared derived files locally,\nbut dry-run never creates Git blobs, trees, commits, refs, or tags.\n\nPromotion intents are serialized globally per caller repository with\n`cancel-in-progress: false`. A queued intent re-reads its protected target ref\nbefore checkout, dependency installation, release-candidate resolution, or any\npublish-gate/ref mutation. If the ref already points at a newer SHA, that older\nintent is no longer release authority: the workflow records the requested and\ncurrent SHAs, proves that the current target is ahead of the requested commit,\nand completes as a `target-ref-advanced` superseded no-op. Diverged or behind\ncomparisons are not superseded transactions and still fail closed.\nMissing refs, unreadable repository state, invalid channels, governance\nfailures, and artifact mismatches still fail closed. The promote action repeats\nthe target check at the mutation boundary, so a ref that advances after the\nworkflow preflight cannot receive a second set of publication side effects.\n\n## Version Lines\n\nKungfu uses Python-like version lines where a minor line can represent a\nlong-lived product train. A line such as `v2.0` can produce many production\npatch releases:\n\n```text\nv2.0.0\nv2.0.1\nv2.0.2\n...\nv2.0.1234\n```\n\nThis is why Buildchain maintains both exact and floating refs:\n\n- `v2.0.2` is immutable release evidence;\n- `v2.0` is the latest production release on the `2.0` line;\n- `v2` is the selected stable major-line entrypoint;\n- `v2.0.3-alpha.0` is immutable alpha evidence;\n- `v2.0-alpha` is the latest test channel for the `2.0` line.\n- `v2-alpha` is the latest test channel on the highest published alpha minor in major `2`.\n\nA release does not mean \"minor is complete.\" It means \"this patch on this minor\nline is now production.\"\n\nGitHub repository rules must preserve that distinction. Exact tags such as\n`v2.0.2` and `v2.0.3-alpha.0` should be immutable. Floating channel tags such as\n`v2`, `v2.0`, `v2.0-alpha`, and `v2-alpha` must remain movable by the Buildchain promotion\ntoken after governance checks and publish evidence pass. A tag ruleset that\nprotects every `refs/tags/v*` ref is too broad because it also locks the\nfloating channel tags that Buildchain is required to update. Prefer exact-tag\npatterns such as `refs/tags/v*.*.*` for immutable release evidence, while\nleaving floating channel tags under Buildchain automation control.\n\n## Alpha Semantics\n\nAn alpha merge is:\n\n```text\ndev/vX/vX.Y -> alpha/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Computes the next prerelease for the minor line.\n2. Writes version state such as `vX.Y.Z-alpha.N`.\n3. Verifies the generated version-state tree.\n4. Creates or reuses the exact alpha tag.\n5. Moves `alpha/vX/vX.Y` to the generated alpha commit.\n6. Moves `dev/vX/vX.Y` to the same generated alpha commit when this is a\n fast-forward update.\n7. Moves `vX.Y-alpha` to the same generated alpha commit.\n8. Moves `vX-alpha` only when `X.Y` is the highest minor in major `X` with a published alpha; older minor alpha work records a skip and cannot move the major channel backwards.\n\nThe npm channel follows the same ownership rule. The highest alpha minor publishes\nwith dist-tag `alpha`; maintenance alphas on an older minor publish with the\nline-specific dist-tag `vX.Y-alpha` so they cannot roll the global `alpha`\nchannel backward. Exact prerelease versions remain installable directly.\n\nThis keeps the test channel self-describing. If a consumer checks out\n`v2.0-alpha` or `v2-alpha`, the manifests and exact alpha tag agree. The major\nalpha ref removes routine consumer edits when Buildchain opens a newer minor,\nwhile exact tags and SHAs remain the reproducible audit choice.\n\n### Buildchain Alpha Self-Dogfood\n\nBuildchain continuously consumes its own current major alpha through\n`.github/workflows/buildchain-alpha-self-dogfood.yml`. Both lanes call the\nreleased channel router at `build.yml@v2-alpha`. The auto lane must resolve\n`v2-alpha`; the explicit stable lane must resolve `v2`. Both execute the same\ndeclared install, build, and verify fixture, proving that a single consumer\nsurface routes to distinct released runtimes without duplicating lifecycle\nconfiguration in the consumer.\n\nThe reusable build trust gate reads `job.workflow_ref`, which identifies the\ncalled workflow and its selected ref. It does not infer the runtime from\n`github.workflow_ref`, because GitHub defines that context as the caller\nworkflow identity during a reusable call.\n\nThe router selects `.buildchain/alpha-contract-lock.json` for alpha and\n`.buildchain/contract-lock.json` for stable. The alpha lock records the exact\nreviewed alpha SHA and compatibility digest; it does not replace the stable\nconsumer lock. A later alpha with only compatible additive drift continues,\nwhile a changed breaking digest fails until the new alpha contract is reviewed.\n\nThe evidence job resolves `v2-alpha` and `v2` through the GitHub refs API,\ncompares those immutable SHAs with the reusable workflow outputs, verifies the\n`alpha` and `stable` classifications, and uploads a JSON evidence artifact.\nThe canary runs after successful Buildchain ref promotion, on a daily fallback\nschedule, and on manual dispatch. It shares the release-promotion concurrency\ngroup, so another promotion cannot move the floating refs between runtime\nresolution and evidence comparison.\n\nThis is a post-publication consumer canary, not a release bootstrap. Source\nverification still runs the current commit, and\n`buildchain-ref-promotion.yml` still passes the verified exact SHA into the\npromotion workflow. Patrol, dev-merge, repair, and promotion defaults remain on\nstable or exact refs so a broken alpha cannot prevent Buildchain from publishing\nits fix. When Buildchain opens a new major, inventory validation requires this\nworkflow's fixed GitHub `uses` refs to move from `vN`/`vN-alpha` to the new\nmajor; GitHub does not allow expressions in a reusable-workflow `uses` ref.\n\nIf `dev/vX/vX.Y` has already advanced before generated alpha version-state\nbookkeeping can sync back, Buildchain records `skipped-non-fast-forward` for the\ndev sync and still completes the exact and floating alpha tags for the reviewed\nalpha commit. Later dev changes must go through their own dev-to-alpha\npromotion instead of rewinding dev. The normal path is direct: after alpha\nmerges, Buildchain applies the generated version-state commit to alpha and then\nfast-forwards dev to the same commit without a human version-state PR.\n\nIf alpha finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain accepts the current alpha head as the generated\ncommit, or as a historical merge commit that contains the recorded release\nmaterial. An already-created exact alpha tag may point at the transaction\nrelease/material SHA or at the finalized alpha head; missing floating alpha\ntags are retried before the transaction becomes `complete`.\n\n## Release Semantics\n\nA release merge is:\n\n```text\nalpha/vX/vX.Y -> release/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Finds the same-patch alpha tag that was tested.\n2. Checks that the release source tree matches that alpha tag tree, excluding\n only generated version-state differences.\n3. Writes final release version state such as `vX.Y.Z`.\n4. Verifies the generated release tree.\n5. Creates or reuses the exact release tag `vX.Y.Z`.\n6. Moves `release/vX/vX.Y` to the exact release commit.\n7. Moves `vX.Y` to the exact release commit.\n8. Moves `vX` when this minor line should be the stable major entrypoint.\n9. Prepares the next alpha version-state commit, such as\n `vX.Y.(Z+1)-alpha.0`.\n10. Moves `alpha/vX/vX.Y`, `dev/vX/vX.Y`, and `vX.Y-alpha` to that next alpha\n commit.\n11. Moves `vX-alpha` to that next alpha only when this remains the highest published alpha minor.\n\nThe historical alpha tree comparison remains the default stable source gate.\nA promote-only stable run may accept a broader reviewed release PR only when\nthe downloaded RC passport proves that the PR's exact target tree is the tree\nthat completed the PR-stage build. Buildchain also requires the target commit\nto belong to a merged same-repository PR into the selected release branch and\nrecords the accepted commit, tree, RC source, alpha source, and PR as promotion\nevidence. A stale passport, a different target tree, a generated final release\ncommit, or an unreviewed target commit still falls through to the normal\nalpha-tree and declared version-state checks.\n\nThe production channel and the test channel therefore intentionally diverge\nafter release: production stays on the release commit, while alpha/dev continue\nat the next prerelease commit.\n\n### Stable Release Throttle And Canary Gate\n\nBuildchain's own stable channel has an additional pre-publication gate. It is\nevaluated after the PR-stage release candidate has been resolved and before the\npublish-gate ref, package registry, exact stable tag, or floating stable refs\nare mutated. Train refs and alpha promotion do not execute this gate.\n\nAll alpha and release promotions first run a metadata-only release-candidate\npreflight after queued-intent revalidation. The preflight uses the same resolver\nand exact target SHA as the publication job, but does not download payloads or\ninstall the candidate repository dependencies. Missing channel PRs, stale\nworkflow runs, expired passport pairs, and insufficient payload artifact sets\ntherefore fail before the full promotion job starts. The publication job still\ndownloads and validates the exact evidence again at the trust boundary; the\npreflight moves failure earlier without weakening the final check.\n\nThe policy is versioned in `.buildchain/stable-release-policy.json`. A stable\ncandidate is allowed only when all of these facts are true:\n\n- the candidate resolves to an immutable exact alpha tag and its GitHub\n prerelease timestamp;\n- at least 24 hours have passed since the preceding stable patch on the same\n minor line;\n- the preceding stable-to-alpha comparison contains a product or public\n contract path, not only version, test, evidence, or retrospective changes;\n- the version-bound impact record has a non-empty summary and at least one\n surface impact;\n- the `Build Surface Fixture` release-candidate run succeeded;\n- `site-libkungfu-dev` completed its no-apply `Buildchain Stable Canary` and an allowed\n maintainer attested that successful run on the exact alpha SHA through the\n `buildchain-canary/site-libkungfu-dev` commit-status context;\n- the canary runtime input is exactly the candidate alpha tag or the 40-character\n commit SHA resolved from that tag; a successful status pointing at another\n workflow, repository, tag, floating ref, or SHA is rejected as mismatched;\n- at least one hour has elapsed after the last required canary completed.\n\nThe machine report is written to\n`.buildchain/release-passport/stable-release-gate.json`. A passing report is\nuploaded with the stable release passport. A blocked run writes the same report\nbefore failing, so the missing or stale condition is inspectable without\nopening a publication transaction.\n\nGitHub's Actions run REST object does not expose `workflow_dispatch` inputs.\nBuildchain therefore verifies the run's `workflow_id` against the authoritative\nworkflow metadata, then reads the exact runtime from the workflow-owned\n`<workflow name> / <runtime ref>` run-name when no input field is available.\nAn explicit API input, when present, remains authoritative and cannot be\noverridden by the display name.\n\nThe cooldown is a minimum interval, not an instruction to release every day.\nCompatible work should still be batched until a stable release has a concrete\nconsumer need. Changing the interval, canary set, attestors, product path\nboundary, or soak time is a reviewed policy change.\n\nRepositories that want a predictable scheduled stable window can use\n[`Stable Candidate Patrol`](stable-candidate-patrol.md). It persists each exact\nalpha independently, qualifies it after repository-declared checks and soak,\nand selects the newest qualified non-revoked candidate. A newer soaking alpha\ndoes not invalidate an older qualified candidate. The selected tree enters the\nexisting strict `publish-gate/release/<line>/<version> -> release/<line>` PR\npath, so scheduled selection changes release intent timing without weakening\nsource locks, review, verification, publish transactions, or passports.\n\nIf release finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain applies the same recovery rule: the current\nrelease head may be the generated commit, or a historical merge commit that\ncontains the recorded release material, existing exact tags and alpha/dev refs\nare accepted when they match the transaction, and missing floating `vX.Y` or\n`vX` tags are retried idempotently before completion.\n\nOnce the durable release transaction is `complete` and the exact/floating\nstable refs have moved, next-alpha preparation is post-release bookkeeping. A\nfailure there records `deferred-post-release-bookkeeping` and\n`next-anchor-required` instead of retroactively reporting the stable release as\nfailed. Generated next-alpha merges use the current dev tree as their base and\noverlay only declared version-state paths, preserving concurrent dev changes.\n\n## Major Gate Semantics\n\nA major gate merge is:\n\n```text\nrelease/vX/vX.Y -> publish-gate/major\n```\n\n`publish-gate/major` is the explicit replacement for the older ABV `main`\nchannel. The name is intentionally operational: it is a gate for a rare\nadministrator decision, not the active trunk. Keeping this decision in the same\nPR UI as alpha and release promotion keeps the human workflow simple while\navoiding the misleading meaning of `main`. The older `major-gate` branch name is\na compatibility alias only.\n\nBuildchain then:\n\n1. Verifies the source is a merged same-repository PR from a protected release\n line into `publish-gate/major`.\n2. Writes the next major production version state, such as `v(X+1).0.0`.\n3. Creates or reuses the exact release tag `v(X+1).0.0`.\n4. Moves `publish-gate/major` and `release/v(X+1)/v(X+1).0` to that release commit.\n5. Moves `v(X+1).0` and `v(X+1)` to that release commit.\n6. Prepares the next alpha version-state commit, such as\n `v(X+1).0.1-alpha.0`.\n7. Moves `alpha/v(X+1)/v(X+1).0`, `dev/v(X+1)/v(X+1).0`, and\n `v(X+1).0-alpha` to that next alpha commit.\n\nChecking out `publish-gate/major` should therefore look like a frozen release\nstate, not like a branch where day-to-day source work continues. Day-to-day\nsource work continues on `dev/vX/vX.Y`.\n\n## Protected Dev Branches\n\n`dev/vX/vX.Y` is a protected development channel, not a scratch branch. Normal\nsource changes should be made on work branches such as `feature/*`, `fix/*`,\n`chore/*`, `docs/*`, `ci/*`, or `refactor/*`, then reviewed through a pull\nrequest into the target dev line.\n\nThis keeps the earliest development channel audit-friendly:\n\n- the version line being changed is visible in the PR base branch;\n- CI and required checks run before the channel moves;\n- branch protection can prevent direct pushes and stale merges;\n- later `dev -> alpha -> release` promotion inherits a reviewable source\n lineage instead of trying to reconstruct how the dev branch changed.\n\nWhen required checks take longer than the normal dev-channel commit interval,\nclassic strict up-to-date protection can become a non-converging retry loop:\neach base update invalidates a completed check set and rebasing restarts the\nsame slow checks. Buildchain supports GitHub merge queues for that channel\nshape. The queue validates the projected merged result and serializes the final\nref update, so concurrent channel movement no longer invalidates the candidate.\n\nEvery required workflow must handle both `pull_request` and `merge_group`\nbefore the queue is enabled. Queue runs do not provide\n`github.event.pull_request`; required workflows must use the checked-out\n`github.sha` or event-neutral source facts. The governance command is dry-run by\ndefault and refuses to enable a queue when a declared required workflow lacks\neither trigger or still reads the pull-request-only payload directly:\n\n```bash\nbuildchain dev merge-queue \\\n --repository owner/repository \\\n --branch dev/v4/v4.0 \\\n --workflow .github/workflows/source-acceptance.yml \\\n --workflow .github/workflows/affected-native-pr.yml \\\n --bypass-app dedicated-release-app\n```\n\nAfter reviewing the plan, repeat with `--apply`. Buildchain creates or updates\nan exact-branch `merge_queue` ruleset first, then changes only the classic\nrequired-status-check policy from strict to loose. Reviews, administrator\nenforcement, conversation resolution, required check identities, force-push\nprotection, and deletion protection remain owned by the existing branch\nprotection. The ruleset uses the first merge method that the repository itself\nallows, and fails closed when the repository has no enabled merge method.\nRe-running the command is idempotent.\n\nThe repository policy can be declared once instead of repeated as CLI flags:\n\n```toml\n[governance.dev.merge_queue]\nmode = \"inherit\"\nrequired_workflows = [\".github/workflows/verify.yml\"]\n```\n\n`enabled` explicitly requires Buildchain to create or update an exact-branch\nqueue; `inherit` copies queue parameters and bypass actors from the repository's\ncurrent default dev branch; `disabled` prevents automatic queue creation. An\nabsent declaration behaves as `inherit` during release-line bootstrap so a new\nmajor or minor line does not silently lose governance already active on the\nprevious line. Required status-check identities still come from the new\nbranch's own classic protection rather than being copied from the old branch.\n\nMerge-queue rules also reject generated post-publish version-state ref updates.\nWhen the sealed promotion workflow uses a dedicated GitHub App, user, or team\nalready declared by release governance, repeat `--bypass-app`, `--bypass-user`,\nor `--bypass-team` to project that exact actor into the ruleset. Bypass actors\nare never inferred and broad repository or organization roles are not accepted.\nThis keeps ordinary feature PRs on the predecessor-aligned queue path while the\nsealed publication authority can finish its machine-verified bookkeeping. The\ndry-run receipt exposes the exact actor IDs before `--apply` changes GitHub.\n\nBuildchain provides the reusable\n`.github/workflows/dev-pr-auto-merge.yml` workflow for repositories that want a\nscheduled or manual \"merge ready dev PRs\" pass. The consumer repository owns\nthe trigger schedule, but the merge decision is declared through workflow\ninputs: target dev branch, required status/check names, ready and block labels,\nallowed work-branch prefixes, review requirements, maximum merges per run,\nmerge method, and dry-run mode.\n\nThe workflow defaults are conservative. A PR is skipped unless it targets the\nconfigured dev line, is not a draft, has the ready label, has no block label,\ncomes from the same repository, uses an allowed work-branch prefix, has a\ncurrent approval, is mergeable, and has the configured required checks passing.\n`landing-mode: auto` reads the target branch's native merge-queue state. When a\nqueue exists, Buildchain never calls the direct merge endpoint: it admits at\nmost one PR against the observed target-branch SHA and immutable PR head, then\ncalls GraphQL `enqueuePullRequest` with `expectedHeadOid`. GitHub's\n`merge_group` checks remain the final authority for the projected merge.\n\nThe admission receipt records the expected and observed base/head SHAs, policy\nchecks, decision, reason, and active predecessor. Buildchain re-reads the base,\nhead, mergeability, and native queue immediately before enqueueing. Base or\nhead drift fails closed, an active queue entry blocks admission, and a rejected\nready predecessor leaves its PR open while later PRs receive\n`blocked-by-predecessor`. Workflow concurrency serializes Buildchain-owned\nadmission runs; GitHub still owns the atomic queue and protected-ref update.\nRepositories may explicitly select `landing-mode: direct` only when the target\nbranch has no native queue. Queue presence always disables the direct path.\n\nThe canonical consumer required check context is `check / check`, matching the\nreusable workflow call plus its `check` job. Buildchain's own `Verify` workflow\nemits the repository-local context `check`, so Buildchain self-promotion,\nrelease-line bootstrap, and dogfood patrol wrappers pass `check` explicitly.\nRelease governance preserves the exact context emitted by the repository and\nrecords branch-protection policy before/after facts; it does not rewrite one\ncontext shape into the other. Consumer repositories can keep their context\nstable while changing the actual verification command declaratively in\n`buildchain.toml`:\n\n```toml\n[lifecycle.install]\ncommand = \"cargo fetch --locked\"\n\n[lifecycle.verify]\ncommand = \"cargo test --workspace --locked\"\n```\n\nConsumers that want Buildchain to own the check wrapper can call\n`.github/workflows/check.yml@v2`. The wrapper runs the declared\n`lifecycle.install` and `lifecycle.verify` stages and fails the `check` job when\neither declaration is missing or the command exits non-zero.\n\nDevelopment pull requests that need source acceptance without product build or\nartifact verification can opt into `mode: source`. That mode runs only\n`lifecycle.install` and `lifecycle.check` on GitHub-hosted `ubuntu-24.04` while\npreserving the stable `check / check` required-check context. Existing callers\nremain on `mode: verify` by default, and callers may set\n`upload-artifacts: false` without weakening the job conclusion.\n\nTypical consumer wrapper:\n\n```yaml\nname: Merge Ready Dev PRs\n\non:\n schedule:\n - cron: \"17 * * * *\"\n workflow_dispatch:\n inputs:\n dry-run:\n type: boolean\n default: true\n\njobs:\n merge-dev:\n uses: kungfu-systems/buildchain/.github/workflows/dev-pr-auto-merge.yml@v2\n permissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n with:\n target-branch: dev/v2/v2.6\n required-status-checks: check / check\n ready-label: ready\n block-labels: blocked,do-not-merge\n max-merges: 1\n landing-mode: auto\n dry-run: ${{ inputs.dry-run || false }}\n```\n\n## Buildchain Patrol\n\n`dev-pr-auto-merge.yml` remains the focused merge primitive. For repositories\nthat want a stable day-to-day operations contract, Buildchain also exposes a\npatrol workflow family:\n\n| Workflow | Intended cadence | Default intent |\n| --- | --- | --- |\n| `.github/workflows/patrol-daily.yml` | daily | lightweight inspection plus ready dev PR maintenance |\n| `.github/workflows/patrol-weekly.yml` | weekly | release-state, passport, gate, and stale-state health checks as they are added |\n| `.github/workflows/patrol-monthly.yml` | monthly | governance, permission, branch-protection, and workflow drift checks as they are added |\n| `.github/workflows/patrol-observed-evidence.yml` | caller-selected schedule | validated immutable observation plus atomic last-known-good publication; no per-refresh PR |\n| `.github/workflows/stable-candidate-patrol.yml` | repository-selected release window | qualify immutable alpha candidates and open the exact source-lock stable PR |\n\nThe cadence names describe patrol intensity, not release cadence:\n\n- daily patrol can run every day without implying a daily release;\n- weekly patrol is for medium-cost maintenance and audit checks;\n- monthly patrol is for structural drift checks that should not block ordinary\n development velocity.\n\nStable Candidate Patrol is separate from those maintenance cadences because its\ncaller-owned cron is a release-intent window. Its candidate ledger and selection\nremain generic; registry-specific side effects still run through the normal\nrepository `lifecycle.publish` transaction. See\n[`stable-candidate-patrol.md`](stable-candidate-patrol.md).\n\nObserved data that is mechanically regenerated and path-scoped uses the\nseparate [`Observed Evidence Patrol`](observed-evidence-patrol.md) contract.\nIts one-time mechanism changes remain reviewed, while steady-state snapshot\nrefreshes publish directly from trusted default-branch schedule/manual callers.\n\nConsumers should schedule thin callers and keep their YAML declarative. For\nexample:\n\n```yaml\nname: Buildchain Daily Patrol\n\non:\n schedule:\n - cron: \"17 2 * * *\"\n workflow_dispatch:\n\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-daily.yml@v2\n with:\n dry-run: false\n max-actions: 1\n```\n\nWeekly and monthly callers use the matching wrapper:\n\n```yaml\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-weekly.yml@v2\n with:\n dry-run: true\n```\n\nAll three wrappers default to the `v2` floating Buildchain runtime. When\n`target-branch` is omitted, the caller's current/default branch selects the\nactive semver dev line, so consumers do not pin patrol to a stale minor branch.\nThe separate workflow names keep consumer schedules readable and stable while\nBuildchain adds new checks behind the cadence wrappers.\n\n## Package-Manager Adapters\n\nOld ABV assumed JavaScript repositories with root version state and often\nLerna. Buildchain keeps the version-state contract but does not assume every\nrepository is yarn/Lerna.\n\nThe promotion action discovers and updates:\n\n- root `package.json`;\n- `lerna.json`;\n- package manifests from `package.json` workspaces;\n- package manifests from `lerna.json` packages;\n- package manifests from `pnpm-workspace.yaml`.\n\nIt then runs the repository's detected package manager semantics where needed:\n\n- pnpm repositories use pnpm-oriented workspace discovery;\n- npm repositories use npm/package-lock semantics where present;\n- yarn repositories use yarn-style metadata where present.\n\nFor Buildchain itself, version state is required. For a consumer repository that\nhas no package manifest, the same action can degrade to ref-only behavior only\nwhen that is explicitly allowed by the caller.\n\n## Lifecycle Configuration\n\n`buildchain.toml` is the v2 user configuration format. It lets a repository\ndeclare version-state files and lifecycle commands without pretending every\nproject is a Node workspace. Supported version files include JSON, TOML, and\nregex-based files such as `CMakeLists.txt` or `conanfile.py`.\n\nThe promotion action consumes `version.files`, optional anchored/manual\n`version.derived_files`, and `lifecycle.verify`.\nThe verify stage runs after generated version-state changes are applied locally\nand before any release refs move. If `verification-command` is passed directly\nto the action, that explicit command overrides `lifecycle.verify`.\n\nAnchored/manual repositories may use `version.derived_files` for committed\nversion witnesses that are regenerated by `lifecycle.version-state`. The\nrelease-candidate build verifies those witnesses before heavy builds and records\ntheir digests with the exact alpha and release tree identities. Promotion then\naccepts only declared version files, the anchor manifest, and those derived\nfiles as differences from the tested alpha tree; release passports preserve the\nsame material binding.\n\nProtected release-line branches keep their normal human review gate. Managed\n`dev/vN/vN.M`, `alpha/vN/vN.M`, and `release/vN/vN.M` branches are configured\nwith one required approving review, required GitHub Actions checks, administrator\nenforcement, conversation resolution, no force pushes, and no deletions. Each\ntarget uses the exact check set, GitHub App identity, and strictness declared by\nthe governance authority descriptor. The\nreusable `release-candidate-promote.yml` wrapper defaults\n`branch-protection-bypass-apps` to `github-actions`, which lets the workflow's\nautomation identity apply generated version-state or post-publish channel\nbookkeeping after the reviewed channel PR has merged. Direct\n`promote-buildchain-ref` callers may opt into that one controlled bypass with\n`branch-protection-bypass-apps: github-actions`; every other App slug and all\nuser or team bypass actors are rejected. Before\npatching a protected generated bookkeeping ref, the action creates the\nfull configured required-check set on the exact generated version-state commit, so strict\nstatus checks are satisfied by machine-verifiable Buildchain evidence rather\nthan a human PR. The protected ref PATCH itself uses the generated ref update\ntoken; the reusable wrapper binds it to the run-scoped `github.token`. If direct generated\nrelease finalization bookkeeping is still rejected, Buildchain creates or\nreuses a same-repository `buildchain/version-state/*` PR based on the current\ntarget channel head and records `finalization-needed=true` in the durable\ntransaction output. Strict alpha bookkeeping still fails with a\ntoken/protection diagnostic instead of creating a post-publish PR.\n\nFor a stable release, the wrapper also checks out the exact current development\nchannel into `.buildchain/reconciliation/dev`. When the prepared next-alpha\ncommit cannot fast-forward dev because reviewed work landed concurrently, the\npromotion action applies the next version to that checkout, regenerates every\ndeclared derived version-state file, reruns the verification lifecycle, and\nonly then creates the two-parent reconciliation commit. A checkout/current-ref\nSHA mismatch blocks reconciliation instead of committing stale projections.\nBuildchain's own promotion workflow accepts only\n`BUILDCHAIN_PROMOTION_BYPASS_APPS=github-actions`, defaulting to that exact App\nwhen the variable is absent. Buildchain's release-line bootstrap uses the\nadministrator-scoped promotion token only to configure protection; branch\ncreation and generated ref updates use the run-scoped token. New channel\nprotection binds required checks to GitHub Actions App id `15368`, enables Code\nOwner, stale-review, and latest-push review gates, and admits no user or team\nbypass actor.\n\n## What This Guarantees\n\nWhen the loop succeeds, maintainers and consumers can rely on these facts:\n\n- every production release has an exact tag such as `v2.0.2`;\n- every production minor line has a floating tag such as `v2.0`;\n- every selected stable major has a floating tag such as `v2`;\n- every next-major release is driven by a reviewed `release -> publish-gate/major` PR,\n not a hidden manual button;\n- every test channel has an exact alpha tag such as `v2.0.3-alpha.0`;\n- every alpha minor line has a floating tag such as `v2.0-alpha`;\n- every major with a published alpha has a cross-minor floating tag such as `v2-alpha`, owned by its highest published alpha minor;\n- version manifests match the tag visible from the same commit;\n- production releases are derived from the alpha tree that was tested;\n- manual non-dry-run promotion cannot bypass PR review and verification;\n- flow-internal automation bypasses apply only to declared GitHub Apps, users,\n or teams on Buildchain-managed channel branch protection, while one-review\n protection remains enforced for humans;\n- admin users cannot make a channel promotion valid by temporarily bypassing\n branch protection.\n\nThis is the practical meaning of \"governance closed loop\" in Buildchain: the\ndecision, code, version state, and Git refs close over the same evidence chain.\n\n## What This Does Not Do\n\nBuildchain release promotion does not embed registry clients or product-specific\npublish logic. When publish transactions are enabled, `promote-buildchain-ref`\ncan run the consumer's `lifecycle.publish` command and own the transaction,\nevidence validation, durable recovery state, and ref finalization order. The\nconsumer repository still owns registry truth: npm, PyPI, OCI, S3, Conan, CMake\npackaging, download pages, dist-tags, and similar side effects must be\nimplemented by project lifecycle commands that emit Buildchain publish evidence.\n\nDurable transaction recovery is also bound to the exact publication version\nplanned for the current run. An unfinished transaction may be resumed from the\ncurrent source or its history only when its recorded version matches that plan;\nan older failed transaction that happens to be an ancestor cannot reserve its\nold exact tag for a newer package publication.\n\nThe exact tag is also part of the durable transaction identity. If an anchored\npackage publication completed registry side effects under a stale internal tag\nselection, a retry may rebind the unfinished `published` or `finalizing`\ntransaction to the newly planned internal tag only when the package version,\nsource, release material, target, complete artifact set, and evidence all still\nmatch; the stale tag must not point at the transaction, and the requested tag\nmust be absent or already point at accepted release material. This preserves an\nimmutable tag that represents a completed transaction while allowing a tag\ncollision discovered after registry publication to recover without republishing.\n\nEvery Buildchain publish model that can run registry side effects must bind the\npublish entrypoint to an immutable `publish-gate/*` source lock. The reusable\n`release-candidate-promote.yml@v2` wrapper creates or updates that gate ref and\npasses `require-publish-source-lock`, `publish-source-ref`,\n`publish-source-sha`, and `publish-source-locked` to\n`promote-buildchain-ref`. Direct action callers must pass the same four inputs\nfrom the reusable build outputs. Workflows that only collect passports or run\ndry-run package checks do not move publish refs and are not publish-gate\npublication models. A dry-run of `release-candidate-promote.yml` computes and\nreports the exact `publish-gate/*` source lock that a real promotion would use,\nbut does not read, create, or move that ref.\n\nSemver GitHub Release publication is owned by `promote-buildchain-ref`, not by\nconsumer shell glue. Consumers normally use the `release-candidate-promote.yml`\ngenerated channel router, where GitHub Release publication is enabled by default and can be\ndisabled with `github-release: false`; the wrapper passes that declaration to\nthe action. After the publish transaction reaches `complete`, Buildchain creates\nor updates the public GitHub Release and uploads the generated\n`buildchain.release.json`, release-passport assets, and publish evidence. The\nauthoritative publication channel controls GitHub metadata: alpha is marked\n`prerelease=true` and `make_latest=false`; release/stable/major is marked latest.\nSemver tag syntax remains the fallback for ordinary callers without explicit\npublication intent. For anchored/manual package releases, the public\nrelease tag defaults to `v<publishedVersion>` while the internal exact\ntransaction tag remains in the release passport and release-state ref. This is\nthe supported path for downstream\n`release.published` propagation across semver, major, and promote-only release\ncandidate publication models.\n\nPublished GitHub Release assets are immutable evidence. A repeated promotion\npreserves an existing asset when its SHA-256 digest matches the regenerated\nbytes, uploads only missing assets, and fails with an immutable-release\ncollision when a same-name asset has different bytes. It never deletes and\nreplaces an existing asset during retry or duplicate workflow delivery.\n\nProduct payloads are included only through the explicit\n`github-release-payload-patterns` input. Patterns match basenames inside the\ndownloaded PR-stage RC payload bundle; zero matches or duplicate public\nbasenames fail closed. This preserves the exact PR-built bytes instead of\nrebuilding archives during promotion.\n\nConsumers with a signed well-known discovery document can additionally provide\n`publication-commit-command`. The advanced promotion workflow validates its\ntopology before any publish-gate or release mutation, then runs it only after\nthe GitHub Release and its immutable payload/passport assets exist. The command\nmust publicly read back the exact new payload root and emit\n`kungfu-buildchain-publication-commit-evidence/v1`; that evidence is copied\ninto the controller artifact and exposed as workflow outputs. The previous\nauthority must remain valid on every failure. Deferred standalone binary\ndistribution is incompatible with this mode because the discovery authority\nmust be the final product mutation.\n\nBuildchain also does not maintain bare exact tags such as `1.0.0`. The supported\nexact release and alpha refs are v-prefixed:\n\n```text\nv2.0.0\nv2.0.1-alpha.0\n```\n\n## Operational Reading Order\n\nWhen debugging or extending release behavior, read in this order:\n\n1. `docs/release-flow.md`\n2. `.github/workflows/release-verify.yml`\n3. `.github/workflows/buildchain-ref-promotion.yml`\n4. `.github/workflows/release-candidate-promote.yml`\n5. `.github/workflows/.release-candidate-promote.yml`\n6. `actions/promote-buildchain-ref/README.md`\n7. `actions/promote-buildchain-ref/src/`\n8. `docs/migration-inventory.md`\n\nThat path gives the policy first, the workflow trigger second, and the action\nimplementation last."
|
|
1638
|
+
"markdown": "# Release Governance\n\nBuildchain v2 preserves the release semantics of the older ABV workflow while\nmoving the implementation into one modern repository.\n\nThe central idea is simple: a reviewed merge into a release channel is the\nrelease intent. Automation must then create the version-state commit, exact tag,\nfloating tag, and next alpha state that make that intent true in Git.\n\n## Design Problem\n\nKungfu release automation has to keep four facts aligned:\n\n1. The source tree that was reviewed.\n2. The package version recorded in manifests such as `package.json` or\n `lerna.json`.\n3. The exact immutable release or prerelease tag.\n4. The floating channel refs that consumers actually use.\n\nIf any one of these facts is updated by hand, the system can split:\n\n- a consumer can fetch `v2.0` and receive a tree whose package version still\n says the previous release;\n- a maintainer can move `v2` without producing an exact `v2.0.N` audit tag;\n- an alpha can be promoted to production even though the release tree is not the\n same tree that was tested;\n- a protected branch merge can succeed while the follow-up version commit is\n missing, or a flow-internal generated `dev`/`alpha`/`release` ref update can\n fail after publish because the automation identity was not declared in the\n branch-protection review bypass allowance.\n\nThe older ABV workflow addressed this by letting GitHub PRs drive release\nstate. Buildchain keeps that choice because it makes release intent reviewable,\nobservable, and recoverable from Git history.\n\n## What ABV Contributed\n\nThe old ABV model was not just \"bump a version number.\" It encoded a governance\nloop:\n\n- release branches are named as channels: `dev`, `alpha`, `release`, and the\n administrative `publish-gate/major`;\n- a PR from one channel to the next is the release request;\n- verify jobs check that the branch pair is valid before merge;\n- a maintainer review is required before the branch moves;\n- after merge, automation writes the version change and moves tags;\n- exact tags and floating refs are aligned with the resulting commit;\n- the next development channel is prepared automatically.\n\nABV also kept the version-state mutation in the repository. For JavaScript\nrepositories that usually meant changing `lerna.json` and/or `package.json`.\nThat commit is important because the tag alone is not enough evidence: the\nsource tree should also declare the version that the tag advertises.\n\nBuildchain v2 treats that as a hard semantic requirement for its own release\nline.\n\n## Buildchain Implementation\n\nBuildchain implements the same governance loop with:\n\n- `.github/workflows/release-verify.yml` for PR verification;\n- `.github/workflows/buildchain-ref-promotion.yml` for post-verify ref\n promotion; this workflow dogfoods the declarative\n `release-candidate-promote.yml` wrapper and does not hand-wire resolver,\n artifact download, publish-gate, or promote action steps;\n- Buildchain self promotion enables `release-passport-buildchain-self-kfd`, so\n the promote action generates KFD-1 witnesses, KFD-2 public claim JSON, and\n KFD-3 collaboration-interface witnesses from the final version-state workspace\n before release passport finalization. The witness hashes therefore bind to the\n exact published package and site facts from\n `packages/core/buildchain-kfd-claims.js` instead of relying on prose release\n notes;\n- `actions/promote-buildchain-ref` for branch, tag, version-state, and\n governance checks;\n- package-manager adapters that can update version state for pnpm, npm, and\n yarn style repositories;\n- `buildchain.toml` lifecycle configuration for repositories whose version\n state or verification commands are not Node package-manager defaults.\n\nThe implementation is intentionally stricter than a local release script:\n\n- manual workflow dispatch can only do dry-run promotion;\n- non-dry-run promotion must be driven by a completed `Verify` workflow;\n- target branch protection details must be readable, and branch protection must\n apply to administrators as well as regular contributors;\n- `alpha/vX/vX.Y` and `release/vX/vX.Y` branch protection must require both the\n general `check` context and the Release Verify aggregate `verify` context, so\n an invalid channel pair cannot merge even when repository checks pass;\n- release targets keep those checks non-strict with respect to source-branch\n ancestry: generated channel bookkeeping intentionally makes the source and\n target histories diverge, while the pair-specific `verify` context validates\n the legal channel transition;\n- alpha promotion must come from a merged same-repository PR from\n `dev/vX/vX.Y` to `alpha/vX/vX.Y`;\n- release promotion must come from a merged same-repository PR from\n `alpha/vX/vX.Y` to `release/vX/vX.Y`;\n- major promotion must come from a merged same-repository PR from\n `release/vX/vX.Y` to `publish-gate/major`;\n- release promotion requires an existing same-patch alpha tag and checks the\n release source tree against that tested alpha tree;\n- generated version-state commits are verified before refs move.\n\n## Reconciling a protected line without rebuilding\n\nThe public `build.yml` channel router ends with a top-level job named\n`Summarize build contract`. Keeping this aggregate at the public router boundary\nprevents its required check context from changing when the internal reusable\nbuild workflow gains another nesting layer.\n\nFor an already-tested pull request whose protected target still requires an\nolder Buildchain aggregate context, inspect the exact candidate SHA first:\n\n```bash\nGH_TOKEN=\"$(gh auth token)\" npx @kungfu-tech/buildchain@latest \\\n release-governance reconcile \\\n --repository kungfu-systems/example \\\n --branch release/v2/v2.14 \\\n --candidate-sha <tested-pr-head-sha> \\\n --json\n```\n\nThe dry run reads the successful checks emitted for that SHA and reports the\nexact expected/actual context pair. It chooses the shallowest successful\n`Summarize build contract` context, so a new top-level router aggregate wins\nover the nested internal build summary. To apply the plan, rerun the same\ncommand with `--apply` using a token that can update branch protection.\n\nReconciliation changes only the required-status-check subresource. It replaces\nstale Buildchain aggregate contexts, preserves unrelated checks and strictness,\nand does not modify review requirements, administrator enforcement,\nconversation resolution, force-push policy, or deletion policy. The candidate\nmust still be the head of a pull request targeting the named managed branch;\nthe command fails closed otherwise. This lets a previously successful candidate\ncontinue from the same SHA without another native build or an administrator\nmerge bypass.\n\nRepositories may also expose a small caller workflow around\n`.github/workflows/release-governance-reconcile.yml@v2`. Pass `branch`,\n`candidate-sha`, and `apply`, and provide `governance-token` through the caller's\nsecrets. The reusable workflow uploads the JSON reconciliation receipt.\n\nExact publication planning installs the checked-out promotion source's declared\ndependencies before version-state verification. This keeps the pre-authority\nversion plan on the same package-manager boundary as the later promotion job,\nincluding repositories whose verification commands import production packages.\nThe planning pass may materialize and verify declared derived files locally,\nbut dry-run never creates Git blobs, trees, commits, refs, or tags.\n\nPromotion intents are serialized globally per caller repository with\n`cancel-in-progress: false`. A queued intent re-reads its protected target ref\nbefore checkout, dependency installation, release-candidate resolution, or any\npublish-gate/ref mutation. If the ref already points at a newer SHA, that older\nintent is no longer release authority: the workflow records the requested and\ncurrent SHAs, proves that the current target is ahead of the requested commit,\nand completes as a `target-ref-advanced` superseded no-op. Diverged or behind\ncomparisons are not superseded transactions and still fail closed.\nMissing refs, unreadable repository state, invalid channels, governance\nfailures, and artifact mismatches still fail closed. The promote action repeats\nthe target check at the mutation boundary, so a ref that advances after the\nworkflow preflight cannot receive a second set of publication side effects.\n\n## Version Lines\n\nKungfu uses Python-like version lines where a minor line can represent a\nlong-lived product train. A line such as `v2.0` can produce many production\npatch releases:\n\n```text\nv2.0.0\nv2.0.1\nv2.0.2\n...\nv2.0.1234\n```\n\nThis is why Buildchain maintains both exact and floating refs:\n\n- `v2.0.2` is immutable release evidence;\n- `v2.0` is the latest production release on the `2.0` line;\n- `v2` is the selected stable major-line entrypoint;\n- `v2.0.3-alpha.0` is immutable alpha evidence;\n- `v2.0-alpha` is the latest test channel for the `2.0` line.\n- `v2-alpha` is the latest test channel on the highest published alpha minor in major `2`.\n\nA release does not mean \"minor is complete.\" It means \"this patch on this minor\nline is now production.\"\n\nGitHub repository rules must preserve that distinction. Exact tags such as\n`v2.0.2` and `v2.0.3-alpha.0` should be immutable. Floating channel tags such as\n`v2`, `v2.0`, `v2.0-alpha`, and `v2-alpha` must remain movable by the Buildchain promotion\ntoken after governance checks and publish evidence pass. A tag ruleset that\nprotects every `refs/tags/v*` ref is too broad because it also locks the\nfloating channel tags that Buildchain is required to update. Prefer exact-tag\npatterns such as `refs/tags/v*.*.*` for immutable release evidence, while\nleaving floating channel tags under Buildchain automation control.\n\n## Alpha Semantics\n\nAn alpha merge is:\n\n```text\ndev/vX/vX.Y -> alpha/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Computes the next prerelease for the minor line.\n2. Writes version state such as `vX.Y.Z-alpha.N`.\n3. Verifies the generated version-state tree.\n4. Creates or reuses the exact alpha tag.\n5. Moves `alpha/vX/vX.Y` to the generated alpha commit.\n6. Moves `dev/vX/vX.Y` to the same generated alpha commit when this is a\n fast-forward update.\n7. Moves `vX.Y-alpha` to the same generated alpha commit.\n8. Moves `vX-alpha` only when `X.Y` is the highest minor in major `X` with a published alpha; older minor alpha work records a skip and cannot move the major channel backwards.\n\nThe npm channel follows the same ownership rule. The highest alpha minor publishes\nwith dist-tag `alpha`; maintenance alphas on an older minor publish with the\nline-specific dist-tag `vX.Y-alpha` so they cannot roll the global `alpha`\nchannel backward. Exact prerelease versions remain installable directly.\n\nThis keeps the test channel self-describing. If a consumer checks out\n`v2.0-alpha` or `v2-alpha`, the manifests and exact alpha tag agree. The major\nalpha ref removes routine consumer edits when Buildchain opens a newer minor,\nwhile exact tags and SHAs remain the reproducible audit choice.\n\n### Buildchain Alpha Self-Dogfood\n\nBuildchain continuously consumes its own current major alpha through\n`.github/workflows/buildchain-alpha-self-dogfood.yml`. Both lanes call the\nreleased channel router at `build.yml@v2-alpha`. The auto lane must resolve\n`v2-alpha`; the explicit stable lane must resolve `v2`. Both execute the same\ndeclared install, build, and verify fixture, proving that a single consumer\nsurface routes to distinct released runtimes without duplicating lifecycle\nconfiguration in the consumer.\n\nThe reusable build trust gate reads `job.workflow_ref`, which identifies the\ncalled workflow and its selected ref. It does not infer the runtime from\n`github.workflow_ref`, because GitHub defines that context as the caller\nworkflow identity during a reusable call.\n\nThe router selects `.buildchain/alpha-contract-lock.json` for alpha and\n`.buildchain/contract-lock.json` for stable. The alpha lock records the exact\nreviewed alpha SHA and compatibility digest; it does not replace the stable\nconsumer lock. A later alpha with only compatible additive drift continues,\nwhile a changed breaking digest fails until the new alpha contract is reviewed.\n\nThe evidence job resolves `v2-alpha` and `v2` through the GitHub refs API,\ncompares those immutable SHAs with the reusable workflow outputs, verifies the\n`alpha` and `stable` classifications, and uploads a JSON evidence artifact.\nThe canary runs after successful Buildchain ref promotion, on a daily fallback\nschedule, and on manual dispatch. It shares the release-promotion concurrency\ngroup, so another promotion cannot move the floating refs between runtime\nresolution and evidence comparison.\n\nThis is a post-publication consumer canary, not a release bootstrap. Source\nverification still runs the current commit, and\n`buildchain-ref-promotion.yml` still passes the verified exact SHA into the\npromotion workflow. Patrol, dev-merge, repair, and promotion defaults remain on\nstable or exact refs so a broken alpha cannot prevent Buildchain from publishing\nits fix. When Buildchain opens a new major, inventory validation requires this\nworkflow's fixed GitHub `uses` refs to move from `vN`/`vN-alpha` to the new\nmajor; GitHub does not allow expressions in a reusable-workflow `uses` ref.\n\nIf `dev/vX/vX.Y` has already advanced before generated alpha version-state\nbookkeeping can sync back, Buildchain records `skipped-non-fast-forward` for the\ndev sync and still completes the exact and floating alpha tags for the reviewed\nalpha commit. Later dev changes must go through their own dev-to-alpha\npromotion instead of rewinding dev. The normal path is direct: after alpha\nmerges, Buildchain applies the generated version-state commit to alpha and then\nfast-forwards dev to the same commit without a human version-state PR.\n\nIf alpha finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain accepts the current alpha head as the generated\ncommit, or as a historical merge commit that contains the recorded release\nmaterial. An already-created exact alpha tag may point at the transaction\nrelease/material SHA or at the finalized alpha head; missing floating alpha\ntags are retried before the transaction becomes `complete`.\n\n## Release Semantics\n\nA release merge is:\n\n```text\nalpha/vX/vX.Y -> release/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Finds the same-patch alpha tag that was tested.\n2. Checks that the release source tree matches that alpha tag tree, excluding\n only generated version-state differences.\n3. Writes final release version state such as `vX.Y.Z`.\n4. Verifies the generated release tree.\n5. Creates or reuses the exact release tag `vX.Y.Z`.\n6. Moves `release/vX/vX.Y` to the exact release commit.\n7. Moves `vX.Y` to the exact release commit.\n8. Moves `vX` when this minor line should be the stable major entrypoint.\n9. Prepares the next alpha version-state commit, such as\n `vX.Y.(Z+1)-alpha.0`.\n10. Moves `alpha/vX/vX.Y`, `dev/vX/vX.Y`, and `vX.Y-alpha` to that next alpha\n commit.\n11. Moves `vX-alpha` to that next alpha only when this remains the highest published alpha minor.\n\nThe historical alpha tree comparison remains the default stable source gate.\nA promote-only stable run may accept a broader reviewed release PR only when\nthe downloaded RC passport proves that the PR's exact target tree is the tree\nthat completed the PR-stage build. Buildchain also requires the target commit\nto belong to a merged same-repository PR into the selected release branch and\nrecords the accepted commit, tree, RC source, alpha source, and PR as promotion\nevidence. A stale passport, a different target tree, a generated final release\ncommit, or an unreviewed target commit still falls through to the normal\nalpha-tree and declared version-state checks.\n\nThe production channel and the test channel therefore intentionally diverge\nafter release: production stays on the release commit, while alpha/dev continue\nat the next prerelease commit.\n\n### Stable Release Throttle And Canary Gate\n\nBuildchain's own stable channel has an additional pre-publication gate. It is\nevaluated after the PR-stage release candidate has been resolved and before the\npublish-gate ref, package registry, exact stable tag, or floating stable refs\nare mutated. Train refs and alpha promotion do not execute this gate.\n\nAll alpha and release promotions first run a metadata-only release-candidate\npreflight after queued-intent revalidation. The preflight uses the same resolver\nand exact target SHA as the publication job, but does not download payloads or\ninstall the candidate repository dependencies. Missing channel PRs, stale\nworkflow runs, expired passport pairs, and insufficient payload artifact sets\ntherefore fail before the full promotion job starts. The publication job still\ndownloads and validates the exact evidence again at the trust boundary; the\npreflight moves failure earlier without weakening the final check.\n\nThe policy is versioned in `.buildchain/stable-release-policy.json`. A stable\ncandidate is allowed only when all of these facts are true:\n\n- the candidate resolves to an immutable exact alpha tag and its GitHub\n prerelease timestamp;\n- at least 24 hours have passed since the preceding stable patch on the same\n minor line;\n- the preceding stable-to-alpha comparison contains a product or public\n contract path, not only version, test, evidence, or retrospective changes;\n- the version-bound impact record has a non-empty summary and at least one\n surface impact;\n- the `Build Surface Fixture` release-candidate run succeeded;\n- `site-libkungfu-dev` completed its no-apply `Buildchain Stable Canary` and an allowed\n maintainer attested that successful run on the exact alpha SHA through the\n `buildchain-canary/site-libkungfu-dev` commit-status context;\n- the canary runtime input is exactly the candidate alpha tag or the 40-character\n commit SHA resolved from that tag; a successful status pointing at another\n workflow, repository, tag, floating ref, or SHA is rejected as mismatched;\n- at least one hour has elapsed after the last required canary completed.\n\nThe machine report is written to\n`.buildchain/release-passport/stable-release-gate.json`. A passing report is\nuploaded with the stable release passport. A blocked run writes the same report\nbefore failing, so the missing or stale condition is inspectable without\nopening a publication transaction.\n\nGitHub's Actions run REST object does not expose `workflow_dispatch` inputs.\nBuildchain therefore verifies the run's `workflow_id` against the authoritative\nworkflow metadata, then reads the exact runtime from the workflow-owned\n`<workflow name> / <runtime ref>` run-name when no input field is available.\nAn explicit API input, when present, remains authoritative and cannot be\noverridden by the display name.\n\nThe cooldown is a minimum interval, not an instruction to release every day.\nCompatible work should still be batched until a stable release has a concrete\nconsumer need. Changing the interval, canary set, attestors, product path\nboundary, or soak time is a reviewed policy change.\n\nRepositories that want a predictable scheduled stable window can use\n[`Stable Candidate Patrol`](stable-candidate-patrol.md). It persists each exact\nalpha independently, qualifies it after repository-declared checks and soak,\nand selects the newest qualified non-revoked candidate. A newer soaking alpha\ndoes not invalidate an older qualified candidate. The selected tree enters the\nexisting strict `publish-gate/release/<line>/<version> -> release/<line>` PR\npath, so scheduled selection changes release intent timing without weakening\nsource locks, review, verification, publish transactions, or passports.\n\nIf release finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain applies the same recovery rule: the current\nrelease head may be the generated commit, or a historical merge commit that\ncontains the recorded release material, existing exact tags and alpha/dev refs\nare accepted when they match the transaction, and missing floating `vX.Y` or\n`vX` tags are retried idempotently before completion.\n\nOnce the durable release transaction is `complete` and the exact/floating\nstable refs have moved, next-alpha preparation is post-release bookkeeping. A\nfailure there records `deferred-post-release-bookkeeping` and\n`next-anchor-required` instead of retroactively reporting the stable release as\nfailed. Generated next-alpha merges use the current dev tree as their base and\noverlay only declared version-state paths, preserving concurrent dev changes.\n\n## Major Gate Semantics\n\nA major gate merge is:\n\n```text\nrelease/vX/vX.Y -> publish-gate/major\n```\n\n`publish-gate/major` is the explicit replacement for the older ABV `main`\nchannel. The name is intentionally operational: it is a gate for a rare\nadministrator decision, not the active trunk. Keeping this decision in the same\nPR UI as alpha and release promotion keeps the human workflow simple while\navoiding the misleading meaning of `main`. The older `major-gate` branch name is\na compatibility alias only.\n\nBuildchain then:\n\n1. Verifies the source is a merged same-repository PR from a protected release\n line into `publish-gate/major`.\n2. Writes the next major production version state, such as `v(X+1).0.0`.\n3. Creates or reuses the exact release tag `v(X+1).0.0`.\n4. Moves `publish-gate/major` and `release/v(X+1)/v(X+1).0` to that release commit.\n5. Moves `v(X+1).0` and `v(X+1)` to that release commit.\n6. Prepares the next alpha version-state commit, such as\n `v(X+1).0.1-alpha.0`.\n7. Moves `alpha/v(X+1)/v(X+1).0`, `dev/v(X+1)/v(X+1).0`, and\n `v(X+1).0-alpha` to that next alpha commit.\n\nChecking out `publish-gate/major` should therefore look like a frozen release\nstate, not like a branch where day-to-day source work continues. Day-to-day\nsource work continues on `dev/vX/vX.Y`.\n\n## Protected Dev Branches\n\n`dev/vX/vX.Y` is a protected development channel, not a scratch branch. Normal\nsource changes should be made on work branches such as `feature/*`, `fix/*`,\n`chore/*`, `docs/*`, `ci/*`, or `refactor/*`, then reviewed through a pull\nrequest into the target dev line.\n\nThis keeps the earliest development channel audit-friendly:\n\n- the version line being changed is visible in the PR base branch;\n- CI and required checks run before the channel moves;\n- branch protection can prevent direct pushes and stale merges;\n- later `dev -> alpha -> release` promotion inherits a reviewable source\n lineage instead of trying to reconstruct how the dev branch changed.\n\nWhen required checks take longer than the normal dev-channel commit interval,\nclassic strict up-to-date protection can become a non-converging retry loop:\neach base update invalidates a completed check set and rebasing restarts the\nsame slow checks. Buildchain supports GitHub merge queues for that channel\nshape. The queue validates the projected merged result and serializes the final\nref update, so concurrent channel movement no longer invalidates the candidate.\n\nEvery required workflow must handle both `pull_request` and `merge_group`\nbefore the queue is enabled. Queue runs do not provide\n`github.event.pull_request`; required workflows must use the checked-out\n`github.sha` or event-neutral source facts. The governance command is dry-run by\ndefault and refuses to enable a queue when a declared required workflow lacks\neither trigger or still reads the pull-request-only payload directly:\n\n```bash\nbuildchain dev merge-queue \\\n --repository owner/repository \\\n --branch dev/v4/v4.0 \\\n --workflow .github/workflows/source-acceptance.yml \\\n --workflow .github/workflows/affected-native-pr.yml \\\n --bypass-app dedicated-release-app\n```\n\nAfter reviewing the plan, repeat with `--apply`. Buildchain creates or updates\nan exact-branch `merge_queue` ruleset first, then changes only the classic\nrequired-status-check policy from strict to loose. Reviews, administrator\nenforcement, conversation resolution, required check identities, force-push\nprotection, and deletion protection remain owned by the existing branch\nprotection. The ruleset uses the first merge method that the repository itself\nallows, and fails closed when the repository has no enabled merge method.\nRe-running the command is idempotent.\n\nThe repository policy can be declared once instead of repeated as CLI flags:\n\n```toml\n[governance.dev.merge_queue]\nmode = \"inherit\"\nrequired_workflows = [\".github/workflows/verify.yml\"]\n```\n\n`enabled` explicitly requires Buildchain to create or update an exact-branch\nqueue; `inherit` copies queue parameters and bypass actors from the repository's\ncurrent default dev branch; `disabled` prevents automatic queue creation. An\nabsent declaration behaves as `inherit` during release-line bootstrap so a new\nmajor or minor line does not silently lose governance already active on the\nprevious line. Required status-check identities still come from the new\nbranch's own classic protection rather than being copied from the old branch.\n\nMerge-queue rules also reject generated post-publish version-state ref updates.\nWhen the sealed promotion workflow uses a dedicated GitHub App, user, or team\nalready declared by release governance, repeat `--bypass-app`, `--bypass-user`,\nor `--bypass-team` to project that exact actor into the ruleset. Bypass actors\nare never inferred and broad repository or organization roles are not accepted.\nThis keeps ordinary feature PRs on the predecessor-aligned queue path while the\nsealed publication authority can finish its machine-verified bookkeeping. The\ndry-run receipt exposes the exact actor IDs before `--apply` changes GitHub.\n\nBuildchain provides the reusable\n`.github/workflows/dev-pr-auto-merge.yml` workflow for repositories that want a\nscheduled or manual \"merge ready dev PRs\" pass. The consumer repository owns\nthe trigger schedule, but the merge decision is declared through workflow\ninputs: target dev branch, required status/check names, ready and block labels,\nallowed work-branch prefixes, review requirements, maximum merges per run,\nmerge method, and dry-run mode.\n\nThe workflow defaults are conservative. A PR is skipped unless it targets the\nconfigured dev line, is not a draft, has the ready label, has no block label,\ncomes from the same repository, uses an allowed work-branch prefix, has a\ncurrent approval, is mergeable, and has the configured required checks passing.\n`landing-mode: auto` reads the target branch's native merge-queue state. When a\nqueue exists, Buildchain never calls the direct merge endpoint: it admits at\nmost one PR against the observed target-branch SHA and immutable PR head, then\ncalls GraphQL `enqueuePullRequest` with `expectedHeadOid`. GitHub's\n`merge_group` checks remain the final authority for the projected merge.\n\nThe admission receipt records the expected and observed base/head SHAs, policy\nchecks, decision, reason, and active predecessor. Buildchain re-reads the base,\nhead, mergeability, and native queue immediately before enqueueing. Base or\nhead drift fails closed, an active queue entry blocks admission, and a rejected\nready predecessor leaves its PR open while later PRs receive\n`blocked-by-predecessor`. Workflow concurrency serializes Buildchain-owned\nadmission runs; GitHub still owns the atomic queue and protected-ref update.\nRepositories may explicitly select `landing-mode: direct` only when the target\nbranch has no native queue. Queue presence always disables the direct path.\n\nThe canonical consumer required check context is `check / check`, matching the\nreusable workflow call plus its `check` job. Buildchain's own `Verify` workflow\nemits the repository-local context `check`, so Buildchain self-promotion,\nrelease-line bootstrap, and dogfood patrol wrappers pass `check` explicitly.\nRelease governance preserves the exact context emitted by the repository and\nrecords branch-protection policy before/after facts; it does not rewrite one\ncontext shape into the other. Consumer repositories can keep their context\nstable while changing the actual verification command declaratively in\n`buildchain.toml`:\n\n```toml\n[lifecycle.install]\ncommand = \"cargo fetch --locked\"\n\n[lifecycle.verify]\ncommand = \"cargo test --workspace --locked\"\n```\n\nConsumers that want Buildchain to own the check wrapper can call\n`.github/workflows/check.yml@v2`. The wrapper runs the declared\n`lifecycle.install` and `lifecycle.verify` stages and fails the `check` job when\neither declaration is missing or the command exits non-zero.\n\nDevelopment pull requests that need source acceptance without product build or\nartifact verification can opt into `mode: source`. That mode runs only\n`lifecycle.install` and `lifecycle.check` on GitHub-hosted `ubuntu-24.04` while\npreserving the stable `check / check` required-check context. Existing callers\nremain on `mode: verify` by default, and callers may set\n`upload-artifacts: false` without weakening the job conclusion.\n\nTypical consumer wrapper:\n\n```yaml\nname: Merge Ready Dev PRs\n\non:\n schedule:\n - cron: \"17 * * * *\"\n workflow_dispatch:\n inputs:\n dry-run:\n type: boolean\n default: true\n\njobs:\n merge-dev:\n uses: kungfu-systems/buildchain/.github/workflows/dev-pr-auto-merge.yml@v2\n permissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n with:\n target-branch: dev/v2/v2.6\n required-status-checks: check / check\n ready-label: ready\n block-labels: blocked,do-not-merge\n max-merges: 1\n landing-mode: auto\n dry-run: ${{ inputs.dry-run || false }}\n```\n\n## Buildchain Patrol\n\n`dev-pr-auto-merge.yml` remains the focused merge primitive. For repositories\nthat want a stable day-to-day operations contract, Buildchain also exposes a\npatrol workflow family:\n\n| Workflow | Intended cadence | Default intent |\n| --- | --- | --- |\n| `.github/workflows/patrol-daily.yml` | daily | lightweight inspection plus ready dev PR maintenance |\n| `.github/workflows/patrol-weekly.yml` | weekly | release-state, passport, gate, and stale-state health checks as they are added |\n| `.github/workflows/patrol-monthly.yml` | monthly | governance, permission, branch-protection, and workflow drift checks as they are added |\n| `.github/workflows/patrol-observed-evidence.yml` | caller-selected schedule | validated immutable observation plus atomic last-known-good publication; no per-refresh PR |\n| `.github/workflows/stable-candidate-patrol.yml` | repository-selected release window | qualify immutable alpha candidates and open the exact source-lock stable PR |\n\nThe cadence names describe patrol intensity, not release cadence:\n\n- daily patrol can run every day without implying a daily release;\n- weekly patrol is for medium-cost maintenance and audit checks;\n- monthly patrol is for structural drift checks that should not block ordinary\n development velocity.\n\nStable Candidate Patrol is separate from those maintenance cadences because its\ncaller-owned cron is a release-intent window. Its candidate ledger and selection\nremain generic; registry-specific side effects still run through the normal\nrepository `lifecycle.publish` transaction. See\n[`stable-candidate-patrol.md`](stable-candidate-patrol.md).\n\nObserved data that is mechanically regenerated and path-scoped uses the\nseparate [`Observed Evidence Patrol`](observed-evidence-patrol.md) contract.\nIts one-time mechanism changes remain reviewed, while steady-state snapshot\nrefreshes publish directly from trusted default-branch schedule/manual callers.\n\nConsumers should schedule thin callers and keep their YAML declarative. For\nexample:\n\n```yaml\nname: Buildchain Daily Patrol\n\non:\n schedule:\n - cron: \"17 2 * * *\"\n workflow_dispatch:\n\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-daily.yml@v2\n with:\n dry-run: false\n max-actions: 1\n```\n\nWeekly and monthly callers use the matching wrapper:\n\n```yaml\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-weekly.yml@v2\n with:\n dry-run: true\n```\n\nAll three wrappers default to the `v2` floating Buildchain runtime. When\n`target-branch` is omitted, the caller's current/default branch selects the\nactive semver dev line, so consumers do not pin patrol to a stale minor branch.\nThe separate workflow names keep consumer schedules readable and stable while\nBuildchain adds new checks behind the cadence wrappers.\n\n## Package-Manager Adapters\n\nOld ABV assumed JavaScript repositories with root version state and often\nLerna. Buildchain keeps the version-state contract but does not assume every\nrepository is yarn/Lerna.\n\nThe promotion action discovers and updates:\n\n- root `package.json`;\n- `lerna.json`;\n- package manifests from `package.json` workspaces;\n- package manifests from `lerna.json` packages;\n- package manifests from `pnpm-workspace.yaml`.\n\nIt then runs the repository's detected package manager semantics where needed:\n\n- pnpm repositories use pnpm-oriented workspace discovery;\n- npm repositories use npm/package-lock semantics where present;\n- yarn repositories use yarn-style metadata where present.\n\nFor Buildchain itself, version state is required. For a consumer repository that\nhas no package manifest, the same action can degrade to ref-only behavior only\nwhen that is explicitly allowed by the caller.\n\n## Lifecycle Configuration\n\n`buildchain.toml` is the v2 user configuration format. It lets a repository\ndeclare version-state files and lifecycle commands without pretending every\nproject is a Node workspace. Supported version files include JSON, TOML, and\nregex-based files such as `CMakeLists.txt` or `conanfile.py`.\n\nThe promotion action consumes `version.files`, optional anchored/manual\n`version.derived_files`, and `lifecycle.verify`.\nThe verify stage runs after generated version-state changes are applied locally\nand before any release refs move. If `verification-command` is passed directly\nto the action, that explicit command overrides `lifecycle.verify`.\n\nAnchored/manual repositories may use `version.derived_files` for committed\nversion witnesses that are regenerated by `lifecycle.version-state`. The\nrelease-candidate build verifies those witnesses before heavy builds and records\ntheir digests with the exact alpha and release tree identities. Promotion then\naccepts only declared version files, the anchor manifest, and those derived\nfiles as differences from the tested alpha tree; release passports preserve the\nsame material binding.\n\nProtected release-line branches keep their normal human review gate. Managed\n`dev/vN/vN.M`, `alpha/vN/vN.M`, and `release/vN/vN.M` branches are configured\nwith one required approving review, required GitHub Actions checks, administrator\nenforcement, conversation resolution, no force pushes, and no deletions. Each\ntarget uses the exact check set, GitHub App identity, and strictness declared by\nthe governance authority descriptor. The\nreusable `release-candidate-promote.yml` wrapper defaults\n`branch-protection-bypass-apps` to `github-actions`, which lets the workflow's\nautomation identity apply generated version-state or post-publish channel\nbookkeeping after the reviewed channel PR has merged. Direct\n`promote-buildchain-ref` callers may opt into that one controlled bypass with\n`branch-protection-bypass-apps: github-actions`; every other App slug and all\nuser or team bypass actors are rejected. Before\npatching a protected generated bookkeeping ref, the action creates the\nfull configured required-check set on the exact generated version-state commit, so strict\nstatus checks are satisfied by machine-verifiable Buildchain evidence rather\nthan a human PR. The protected ref PATCH itself uses the generated ref update\ntoken; the reusable wrapper binds it to the run-scoped `github.token`. If direct generated\nrelease finalization bookkeeping is still rejected, Buildchain creates or\nreuses a same-repository `buildchain/version-state/*` PR and records\n`finalization-needed=true` in the durable transaction output. Strict alpha\nfollows the same provider-enforced PR path for its alpha and dev bookkeeping.\nThe PR remains subject to the declared review, required checks, and merge-queue\npolicy; publication resumes idempotently after that protected transaction\nlands.\n\nFor a stable release, the wrapper also checks out the exact current development\nchannel into `.buildchain/reconciliation/dev`. When the prepared next-alpha\ncommit cannot fast-forward dev because reviewed work landed concurrently, the\npromotion action applies the next version to that checkout, regenerates every\ndeclared derived version-state file, reruns the verification lifecycle, and\nonly then creates the two-parent reconciliation commit. A checkout/current-ref\nSHA mismatch blocks reconciliation instead of committing stale projections.\nBuildchain's own promotion workflow accepts only\n`BUILDCHAIN_PROMOTION_BYPASS_APPS=github-actions`, defaulting to that exact App\nwhen the variable is absent. Buildchain's release-line bootstrap uses the\nadministrator-scoped promotion token only to configure protection; branch\ncreation and generated ref updates use the run-scoped token. New channel\nprotection binds required checks to GitHub Actions App id `15368`, enables Code\nOwner, stale-review, and latest-push review gates, and admits no user or team\nbypass actor.\n\n## What This Guarantees\n\nWhen the loop succeeds, maintainers and consumers can rely on these facts:\n\n- every production release has an exact tag such as `v2.0.2`;\n- every production minor line has a floating tag such as `v2.0`;\n- every selected stable major has a floating tag such as `v2`;\n- every next-major release is driven by a reviewed `release -> publish-gate/major` PR,\n not a hidden manual button;\n- every test channel has an exact alpha tag such as `v2.0.3-alpha.0`;\n- every alpha minor line has a floating tag such as `v2.0-alpha`;\n- every major with a published alpha has a cross-minor floating tag such as `v2-alpha`, owned by its highest published alpha minor;\n- version manifests match the tag visible from the same commit;\n- production releases are derived from the alpha tree that was tested;\n- manual non-dry-run promotion cannot bypass PR review and verification;\n- flow-internal automation bypasses apply only to declared GitHub Apps, users,\n or teams on Buildchain-managed channel branch protection, while one-review\n protection remains enforced for humans;\n- admin users cannot make a channel promotion valid by temporarily bypassing\n branch protection.\n\nThis is the practical meaning of \"governance closed loop\" in Buildchain: the\ndecision, code, version state, and Git refs close over the same evidence chain.\n\n## What This Does Not Do\n\nBuildchain release promotion does not embed registry clients or product-specific\npublish logic. When publish transactions are enabled, `promote-buildchain-ref`\ncan run the consumer's `lifecycle.publish` command and own the transaction,\nevidence validation, durable recovery state, and ref finalization order. The\nconsumer repository still owns registry truth: npm, PyPI, OCI, S3, Conan, CMake\npackaging, download pages, dist-tags, and similar side effects must be\nimplemented by project lifecycle commands that emit Buildchain publish evidence.\n\nDurable transaction recovery is also bound to the exact publication version\nplanned for the current run. An unfinished transaction may be resumed from the\ncurrent source or its history only when its recorded version matches that plan;\nan older failed transaction that happens to be an ancestor cannot reserve its\nold exact tag for a newer package publication.\n\nThe exact tag is also part of the durable transaction identity. If an anchored\npackage publication completed registry side effects under a stale internal tag\nselection, a retry may rebind the unfinished `published` or `finalizing`\ntransaction to the newly planned internal tag only when the package version,\nsource, release material, target, complete artifact set, and evidence all still\nmatch; the stale tag must not point at the transaction, and the requested tag\nmust be absent or already point at accepted release material. This preserves an\nimmutable tag that represents a completed transaction while allowing a tag\ncollision discovered after registry publication to recover without republishing.\n\nEvery Buildchain publish model that can run registry side effects must bind the\npublish entrypoint to an immutable `publish-gate/*` source lock. The reusable\n`release-candidate-promote.yml@v2` wrapper creates or updates that gate ref and\npasses `require-publish-source-lock`, `publish-source-ref`,\n`publish-source-sha`, and `publish-source-locked` to\n`promote-buildchain-ref`. Direct action callers must pass the same four inputs\nfrom the reusable build outputs. Workflows that only collect passports or run\ndry-run package checks do not move publish refs and are not publish-gate\npublication models. A dry-run of `release-candidate-promote.yml` computes and\nreports the exact `publish-gate/*` source lock that a real promotion would use,\nbut does not read, create, or move that ref.\n\nSemver GitHub Release publication is owned by `promote-buildchain-ref`, not by\nconsumer shell glue. Consumers normally use the `release-candidate-promote.yml`\ngenerated channel router, where GitHub Release publication is enabled by default and can be\ndisabled with `github-release: false`; the wrapper passes that declaration to\nthe action. After the publish transaction reaches `complete`, Buildchain creates\nor updates the public GitHub Release and uploads the generated\n`buildchain.release.json`, release-passport assets, and publish evidence. The\nauthoritative publication channel controls GitHub metadata: alpha is marked\n`prerelease=true` and `make_latest=false`; release/stable/major is marked latest.\nSemver tag syntax remains the fallback for ordinary callers without explicit\npublication intent. For anchored/manual package releases, the public\nrelease tag defaults to `v<publishedVersion>` while the internal exact\ntransaction tag remains in the release passport and release-state ref. This is\nthe supported path for downstream\n`release.published` propagation across semver, major, and promote-only release\ncandidate publication models.\n\nPublished GitHub Release assets are immutable evidence. A repeated promotion\npreserves an existing asset when its SHA-256 digest matches the regenerated\nbytes, uploads only missing assets, and fails with an immutable-release\ncollision when a same-name asset has different bytes. It never deletes and\nreplaces an existing asset during retry or duplicate workflow delivery.\n\nProduct payloads are included only through the explicit\n`github-release-payload-patterns` input. Patterns match basenames inside the\ndownloaded PR-stage RC payload bundle; zero matches or duplicate public\nbasenames fail closed. This preserves the exact PR-built bytes instead of\nrebuilding archives during promotion.\n\nConsumers with a signed well-known discovery document can additionally provide\n`publication-commit-command`. The advanced promotion workflow validates its\ntopology before any publish-gate or release mutation, then runs it only after\nthe GitHub Release and its immutable payload/passport assets exist. The command\nmust publicly read back the exact new payload root and emit\n`kungfu-buildchain-publication-commit-evidence/v1`; that evidence is copied\ninto the controller artifact and exposed as workflow outputs. The previous\nauthority must remain valid on every failure. Deferred standalone binary\ndistribution is incompatible with this mode because the discovery authority\nmust be the final product mutation.\n\nBuildchain also does not maintain bare exact tags such as `1.0.0`. The supported\nexact release and alpha refs are v-prefixed:\n\n```text\nv2.0.0\nv2.0.1-alpha.0\n```\n\n## Operational Reading Order\n\nWhen debugging or extending release behavior, read in this order:\n\n1. `docs/release-flow.md`\n2. `.github/workflows/release-verify.yml`\n3. `.github/workflows/buildchain-ref-promotion.yml`\n4. `.github/workflows/release-candidate-promote.yml`\n5. `.github/workflows/.release-candidate-promote.yml`\n6. `actions/promote-buildchain-ref/README.md`\n7. `actions/promote-buildchain-ref/src/`\n8. `docs/migration-inventory.md`\n\nThat path gives the policy first, the workflow trigger second, and the action\nimplementation last."
|
|
1639
1639
|
},
|
|
1640
1640
|
{
|
|
1641
1641
|
"id": "manual:release-passport",
|
|
@@ -2842,7 +2842,7 @@
|
|
|
2842
2842
|
"path": "docs/lifecycle-protocol.md",
|
|
2843
2843
|
"plane": "use",
|
|
2844
2844
|
"exists": true,
|
|
2845
|
-
"digest": "sha256:
|
|
2845
|
+
"digest": "sha256:9c669d3d2d1926dc5b421207089cca85bac0a6dcdb5544c28a7e1659ba9ed5ee"
|
|
2846
2846
|
},
|
|
2847
2847
|
{
|
|
2848
2848
|
"id": "reusable-build-surface",
|
|
@@ -2858,7 +2858,7 @@
|
|
|
2858
2858
|
"path": "docs/publish-transaction.md",
|
|
2859
2859
|
"plane": "verify",
|
|
2860
2860
|
"exists": true,
|
|
2861
|
-
"digest": "sha256:
|
|
2861
|
+
"digest": "sha256:732e7da899ab0b532bda2f77181d753ee8af2e397db4b0eedaaa10657ade7b2a"
|
|
2862
2862
|
},
|
|
2863
2863
|
{
|
|
2864
2864
|
"id": "release-governance",
|
|
@@ -2866,7 +2866,7 @@
|
|
|
2866
2866
|
"path": "docs/release-governance.md",
|
|
2867
2867
|
"plane": "why",
|
|
2868
2868
|
"exists": true,
|
|
2869
|
-
"digest": "sha256:
|
|
2869
|
+
"digest": "sha256:5aa7fef2c831d14b4d1df4cddb45c6ce97929d8530eb14e3b3dfa3dee5e0d651"
|
|
2870
2870
|
},
|
|
2871
2871
|
{
|
|
2872
2872
|
"id": "release-flow",
|