@kungfu-tech/buildchain 2.8.8-alpha.0 → 2.8.8-alpha.2

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.
@@ -114,7 +114,7 @@
114
114
  "route": "/actions/promote-buildchain-ref",
115
115
  "category": "action",
116
116
  "sourcePath": "actions/promote-buildchain-ref/README.md",
117
- "digest": "sha256:d826cb94712c1103ff46d05ea9f66d998bc93f6657ffca7309e120853f4e7437",
117
+ "digest": "sha256:30c25348d05f77bc8765eb2d90521a72284789cd004c7af04a40b2e367785454",
118
118
  "headings": [
119
119
  {
120
120
  "level": 1,
@@ -132,7 +132,7 @@
132
132
  "anchor": "publish-transactions"
133
133
  }
134
134
  ],
135
- "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`;\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;\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`, and `v3.0-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\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\nare also routed through pull requests. The action creates an internal\n`buildchain/version-state/...` branch and PR, then stops before moving tags. Once\nthat PR is reviewed, checked, and merged, the next promotion run verifies that\nthe merge only changed declared version-state files from the legal source\nparent, then moves exact and floating refs.\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 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.\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\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\n```\n\nThe action outputs `transaction-id`, `transaction-state`,\n`transaction-exact-tag`, `transaction-release-sha`, `transaction-state-ref`,\n`transaction-state-sha`, `transaction-state-path`, `publish-evidence-path`, and\n`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 promotion run.\nSet `github-release: \"true\"` when the semver promotion should also publish the\nexact-tag GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `transaction-exact-tag`, applies deterministic semver metadata\n(`prerelease=true` and `make_latest=false` for prerelease tags, latest for stable\ntags), and uploads the publish evidence file plus generated release passport\nassets. If the transaction is not complete yet, the action defers GitHub Release\npublication to the next idempotent 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.\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 a generated version-state PR is merged, the current\nchannel head can be a merge commit that contains or corresponds to the recorded\n`release_material_sha`; it does not have to equal the original `source_sha` or\nthe transaction `release_sha`. Reruns accept exact tags that already point at\nthe transaction release/material SHA or the finalized channel head, and continue\nmoving any missing floating tags or dev/alpha refs before marking the\ntransaction `complete`.\n\nNormal reruns accept already-published artifacts only when evidence matches.\nMissing required artifacts can be published on the next run. Conflicting\nartifacts put the transaction into `repair_required`; `abandoned` and\n`failed_permanently` also fail closed unless `publish-transaction-override` is\nset for a controlled repair.\n\nIn strict buildchain promotion, ref movement is also gated by the old ABV\ngovernance semantics:\n\n- the target channel branch protection details must be readable, must enforce\n protection for administrators, and must require approving PR review plus the\n strict `check` job from the `Verify` workflow;\n- alpha promotion must come from a merged same-repository PR\n `dev/vN/vN.M -> alpha/vN/vN.M`;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`;\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.\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`. Bare 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`,\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."
135
+ "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`;\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;\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`, and `v3.0-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\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\nmust still complete inside the promotion automation. The action updates\nBuildchain-managed channel protection before generated bookkeeping, adds the\nauthenticated promotion token user or app to the bypass allowlist, then applies\nthe generated version-state commit directly. If GitHub still rejects the direct\nupdate, promotion fails with a configuration diagnostic instead of opening a\npost-publish human PR.\n\nFor Buildchain-owned automation, callers may pass\n`branch-protection-bypass-apps`, `branch-protection-bypass-users`, or\n`branch-protection-bypass-teams`. The action still configures managed\n`dev/vN/vN.M`, `alpha/vN/vN.M`, and `release/vN/vN.M` branches with one\nrequired approving review, strict 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. Direct action calls automatically include the current\npromotion token's authenticated user or app when GitHub exposes it; explicit\ninputs are supplemental allowlist entries for less discoverable release\nauthorities.\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 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.\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\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\n```\n\nThe action outputs `transaction-id`, `transaction-state`,\n`transaction-exact-tag`, `transaction-release-sha`, `transaction-state-ref`,\n`transaction-state-sha`, `transaction-state-path`, `publish-evidence-path`, and\n`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. It must not require\na human version-state PR.\nSet `github-release: \"true\"` when the semver promotion should also publish the\nexact-tag GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `transaction-exact-tag`, applies deterministic semver metadata\n(`prerelease=true` and `make_latest=false` for prerelease tags, latest for stable\ntags), and uploads the publish evidence file plus generated release passport\nassets. If the transaction is not complete yet, the action defers GitHub Release\npublication to the next idempotent 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.\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\nartifacts put the transaction into `repair_required`; `abandoned` and\n`failed_permanently` also fail closed unless `publish-transaction-override` is\nset for a controlled repair.\n\nIn strict buildchain promotion, ref movement is also gated by the old ABV\ngovernance semantics:\n\n- the target channel branch protection details must be readable, must enforce\n protection for administrators, and must require approving PR review plus the\n strict `check` job from the `Verify` workflow;\n- alpha promotion must come from a merged same-repository PR\n `dev/vN/vN.M -> alpha/vN/vN.M`;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`;\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.\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 also auto-discovers the current token's authenticated user\nor app and adds it to the managed bypass allowlist, so consumers do not have to\ndeclare the same release authority twice.\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`. Bare 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`,\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."
136
136
  },
137
137
  {
138
138
  "id": "action:report-buildchain-issue",
@@ -385,7 +385,7 @@
385
385
  "route": "/docs/lifecycle-protocol",
386
386
  "category": "manual",
387
387
  "sourcePath": "docs/lifecycle-protocol.md",
388
- "digest": "sha256:02e1be97935c0d9520ea22e8ef0dc61d9b860c347f07127074600cdca02713ce",
388
+ "digest": "sha256:4d1ab96137beea2497b838018dcce793081c09dbcbbfd8ed2123cc58749f95cb",
389
389
  "headings": [
390
390
  {
391
391
  "level": 1,
@@ -468,7 +468,7 @@
468
468
  "anchor": "design-boundaries"
469
469
  }
470
470
  ],
471
- "markdown": "# Lifecycle Protocol\n\nBuildchain uses `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\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\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\"\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- 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.verify]\nshell = \"bash\"\nscript = \"\"\"\nset -euo pipefail\npnpm run check\ngit diff --check\n\"\"\"\n```\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\nOn protected alpha and release branches, the generated version-state commit is\nmerged through a normal pull request. This keeps review requirements,\nconversation resolution, strict status checks, and admin enforcement intact.\nAfter that PR lands, Buildchain verifies that the version-state PR changed only\ndeclared version files from the legal channel-promotion parent before it moves\ntags.\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.\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.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."
471
+ "markdown": "# Lifecycle Protocol\n\nBuildchain uses `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\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\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\"\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- 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.verify]\nshell = \"bash\"\nscript = \"\"\"\nset -euo pipefail\npnpm run check\ngit diff --check\n\"\"\"\n```\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\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 adds the\nauthenticated promotion token user or app to the managed bypass allowlist for\ngenerated release bookkeeping. If the direct generated update is rejected,\npromotion fails with a token/protection diagnostic instead of asking humans to\nreview a 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.\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.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."
472
472
  },
473
473
  {
474
474
  "id": "manual:map",
@@ -645,7 +645,7 @@
645
645
  "route": "/docs/publish-transaction",
646
646
  "category": "manual",
647
647
  "sourcePath": "docs/publish-transaction.md",
648
- "digest": "sha256:dd45cf98f0847e3968f150a09bed84aa1f59cb2687ac988e9519f08dc9c91877",
648
+ "digest": "sha256:d4758e222935f595f7d8a73fe2c33383898c0f760c5e5051c4f772db06491e7c",
649
649
  "headings": [
650
650
  {
651
651
  "level": 1,
@@ -703,7 +703,7 @@
703
703
  "anchor": "build-images-follow-up"
704
704
  }
705
705
  ],
706
- "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.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_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## 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|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\nIf protected branches require a generated version-state PR, the transaction can\nstop in `finalizing` and output `finalization-needed=true`. A later run can\nresume from the same transaction state and complete ref movement without\nrepublishing matching artifacts.\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\nversion-state merge commit that contains or corresponds to the transaction's\n`release_material_sha`; it does not have to equal the original `source_sha` or\nthe transaction `release_sha`. Exact tags are accepted when they already point\nat the transaction release/material SHA 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\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- 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."
706
+ "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.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_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## 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|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\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 must finish generated version-state bookkeeping\nwith the promotion token directly; they must not require a post-publish human\nPR.\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\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- 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."
707
707
  },
708
708
  {
709
709
  "id": "manual:release-candidate",
@@ -788,7 +788,7 @@
788
788
  "route": "/docs/release-governance",
789
789
  "category": "manual",
790
790
  "sourcePath": "docs/release-governance.md",
791
- "digest": "sha256:c7348c64e83ea1d154955e2fc0f7318a1c5582a3dfa6bdc5728958ddb418e92d",
791
+ "digest": "sha256:53c969c38ede9c68e279ca191f2d5661e1d943225d7faa798179569670434ef9",
792
792
  "headings": [
793
793
  {
794
794
  "level": 1,
@@ -866,7 +866,7 @@
866
866
  "anchor": "operational-reading-order"
867
867
  }
868
868
  ],
869
- "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.\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 release passport consumes generated KFD-1 witnesses, KFD-2 public claim\n JSON, and KFD-3 collaboration-interface witnesses 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 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## 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\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`, and `v2.0-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.\n\nThis keeps the test channel self-describing. If a consumer checks out\n`v2.0-alpha`, the manifests and exact alpha tag agree.\n\nIf `dev/vX/vX.Y` has already advanced while the generated alpha version-state PR\nwas under review, Buildchain records `skipped-non-fast-forward` for the dev sync\nand still completes the exact and floating alpha tags for the reviewed alpha\ncommit. Later dev changes must go through their own dev-to-alpha promotion\ninstead of rewinding dev.\n\nIf alpha finalization is resumed after the version-state PR is merged,\nBuildchain accepts the current alpha head as a merge commit that contains the\nrecorded release material. An already-created exact alpha tag may point at the\ntransaction release/material SHA or at the finalized alpha head; missing\nfloating alpha tags 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.\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\nIf release finalization is resumed after the version-state PR is merged,\nBuildchain applies the same recovery rule: the current release head may be a\nmerge commit that contains the recorded release material, existing exact tags\nand alpha/dev refs are accepted when they match the transaction, and missing\nfloating `vX.Y` or `vX` tags are retried idempotently before completion.\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\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.\nAfter each merge, the next PR is re-evaluated before it can move the protected\ndev branch. This prevents one merge from silently making the next candidate\nstale or conflicting.\n\nThe required check should be the `check` job. Repositories can keep that job\nname stable 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\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\n ready-label: ready\n block-labels: blocked,do-not-merge\n max-merges: 1\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\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\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 currently consumes `version.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\nProtected release-line branches keep their normal review gate. When generated\nversion state would move a protected alpha or release branch, Buildchain creates\na version-state PR instead of bypassing branch protection. After that PR is\nreviewed, checked, and merged, the next promotion run verifies that only\ndeclared version-state files changed from the legally merged source parent, then\nmoves the exact and floating tags.\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- 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- 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\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.\n\nSemver GitHub Release publication is owned by `promote-buildchain-ref`, not by\nconsumer shell glue. Consumers normally use the `release-candidate-promote.yml`\nwrapper, 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 exact-tag GitHub Release and uploads the generated\n`buildchain.release.json`, release-passport assets, and publish evidence. Semver\nprerelease tags are marked `prerelease=true` and `make_latest=false`; stable\nsemver tags are marked latest. This is the supported path for downstream\n`release.published` propagation across semver, major, and promote-only release\ncandidate publication models.\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. `actions/promote-buildchain-ref/README.md`\n6. `actions/promote-buildchain-ref/src/`\n7. `docs/migration-inventory.md`\n\nThat path gives the policy first, the workflow trigger second, and the action\nimplementation last."
869
+ "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 release passport consumes generated KFD-1 witnesses, KFD-2 public claim\n JSON, and KFD-3 collaboration-interface witnesses 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 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## 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\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`, and `v2.0-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.\n\nThis keeps the test channel self-describing. If a consumer checks out\n`v2.0-alpha`, the manifests and exact alpha tag agree.\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.\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\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\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\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.\nAfter each merge, the next PR is re-evaluated before it can move the protected\ndev branch. This prevents one merge from silently making the next candidate\nstale or conflicting.\n\nThe required check should be the `check` job. Repositories can keep that job\nname stable 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\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\n ready-label: ready\n block-labels: blocked,do-not-merge\n max-merges: 1\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\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\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 currently consumes `version.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\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, strict GitHub Actions checks, administrator\nenforcement, conversation resolution, no force pushes, and no deletions. 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 must opt into the same controlled bypass with\n`branch-protection-bypass-apps`, `branch-protection-bypass-users`, or\n`branch-protection-bypass-teams`; the action also adds the current promotion\ntoken's authenticated user or app to the managed bypass allowlist. If direct\ngenerated bookkeeping is still rejected, Buildchain fails with a\ntoken/protection diagnostic instead of creating a post-publish PR. Buildchain's\nown promotion workflow reads `BUILDCHAIN_PROMOTION_BYPASS_APPS`,\n`BUILDCHAIN_PROMOTION_BYPASS_USERS`, and\n`BUILDCHAIN_PROMOTION_BYPASS_TEAMS` repository variables so the declared bypass\nidentity can match the actual `BUILDCHAIN_PROMOTION_TOKEN` actor, but consumers\ndo not need to duplicate that actor manually when the token identity is\ndiscoverable.\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- 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\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.\n\nSemver GitHub Release publication is owned by `promote-buildchain-ref`, not by\nconsumer shell glue. Consumers normally use the `release-candidate-promote.yml`\nwrapper, 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 exact-tag GitHub Release and uploads the generated\n`buildchain.release.json`, release-passport assets, and publish evidence. Semver\nprerelease tags are marked `prerelease=true` and `make_latest=false`; stable\nsemver tags are marked latest. This is the supported path for downstream\n`release.published` propagation across semver, major, and promote-only release\ncandidate publication models.\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. `actions/promote-buildchain-ref/README.md`\n6. `actions/promote-buildchain-ref/src/`\n7. `docs/migration-inventory.md`\n\nThat path gives the policy first, the workflow trigger second, and the action\nimplementation last."
870
870
  },
871
871
  {
872
872
  "id": "manual:release-passport",
@@ -971,7 +971,7 @@
971
971
  "route": "/docs/reusable-build-surface",
972
972
  "category": "manual",
973
973
  "sourcePath": "docs/reusable-build-surface.md",
974
- "digest": "sha256:8c3e7a5e099c8eb0715bb6f58af90d261f8cb83d384f3281b8763f340277582b",
974
+ "digest": "sha256:aae753c7fcf4bcb13c08af7b31cc83d898d36e41248b5f710ecee61035de3e8e",
975
975
  "headings": [
976
976
  {
977
977
  "level": 1,
@@ -1049,7 +1049,7 @@
1049
1049
  "anchor": "fixture"
1050
1050
  }
1051
1051
  ],
1052
- "markdown": "# Reusable Build Surface\n\nBuildchain v2 provides a reusable build workflow for repositories that need\nBuildchain's release semantics but cannot be described as a simple Node package.\nThe first target shape is `libnode`: expensive native builds, multiple operating\nsystems, self-hosted runner labels, and release artifacts that must be auditable.\n\n## Workflow\n\nStable consumers call:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n working-directory: .\n artifact-name: libnode\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n artifact-name-template: \"{artifact}-{platform}-{sha}\"\n artifact-paths: |\n dist\n build/stage\n expected-artifacts-json: >-\n {\"minFiles\":2,\"requiredPaths\":[\"dist/libnode.tar.gz\",\"dist/checksums.txt\"]}\n process-summary-path: .buildchain/diagnostics/process-summary.json\n release-candidate: true\n publish-channel: release\n publish-source-ref: publish-gate/release/v22/v22.22/22.22.3-kf.0\n```\n\n`runner-preset` is the stable first-class surface for known runner fleets:\n\n| Preset | Platforms |\n| ----------------------- | ------------------------------------------------------------------------ |\n| `github-hosted` | `ubuntu-24.04`, `macos-latest`, `windows-2022` |\n| `kungfu-v4-self-hosted` | Kungfu Linux x64, macOS ARM64, and Windows x64 self-hosted runner labels |\n| `custom` | Requires `platforms-json` |\n\nCallers can still provide a custom matrix with `platforms-json`. Each platform\nobject has:\n\n| Field | Meaning |\n| -------- | ------------------------------------------------- |\n| `id` | Stable artifact/platform key, such as `linux-x64` |\n| `name` | Human-readable job name |\n| `runner` | JSON string passed to `runs-on` after `fromJSON` |\n\nThe runner field is intentionally a JSON string so callers can pass either\nGitHub-hosted runners or multi-label self-hosted runners without Buildchain\nguessing the labels.\n\nOnly include platforms that should run. GitHub schedules matrix jobs before\nsteps execute, so a disabled entry with unavailable runner labels can still\nblock the workflow queue.\n\n## Linux Job Containers\n\nLinux build platforms can run inside a digest-pinned job container while macOS\nand Windows keep using native runners. This is the recommended way to remove\nmoving Linux runner prerequisites from Buildchain consumers: the Linux host only\nneeds a GitHub Actions runner, Docker, and network access; common verification\ntools come from the image contract.\n\nUse the Kungfu verification image for lifecycle stages that need Git, jq,\nPython, uv, and fnm, but do not need native compilation:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n```\n\n`kungfu-verify` resolves to:\n\n```text\nghcr.io/kungfu-systems/build-images/kungfu-verify@sha256:11f0ba64267ce88174a4f73a9bf833ff4e9c59cd16ec3d08a6432a06c2be6fb1\n```\n\nCallers that own a different Linux image can pass it explicitly:\n\n```yaml\nwith:\n linux-container-preset: custom\n linux-container-image: ghcr.io/example/project-build@sha256:<digest>\n```\n\n`linux-container-image` should be pinned by digest. A floating tag makes the\nrunner surface mutable and weakens Buildchain's release evidence.\n\nThe workflow splits the matrix into two build jobs:\n\n- Linux platforms go to `build-linux-container` when a Linux container is\n configured.\n- All other platforms go to `build-native`.\n\nArtifact names, manifest paths, expected artifact checks, publish-source locks,\nand aggregate summaries are the same in both jobs. The split is an execution\ndetail, not a different artifact contract.\n\nThe container image provides `fnm` but does not preinstall Node. Buildchain uses\n`fnm` inside the container to install the requested `node-version` before it\nruns Buildchain runtime scripts or lifecycle actions.\n\nDo not use `kungfu-verify` for stages that need CMake, Ninja, ccache, Conan, or\nDocker image publishing. Those should use a heavier native-build image or remain\non a host runner until their image contract is explicit.\n\n## Buildchain Runtime Override\n\nStable consumers should keep the reusable workflow pinned to stable refs such as\n`@v2`. The optional `buildchain-ref` input is empty by default; empty means\nBuildchain resolves and executes the stable runtime selected by the workflow\nshell. The full train validation protocol is documented in\n[`runtime-train-validation.md`](runtime-train-validation.md).\n\nFor one-off manual validation, a trusted maintainer can run the caller workflow\nwith a temporary runtime override:\n\n```yaml\non:\n workflow_dispatch:\n inputs:\n buildchain-ref:\n description: \"Temporary Buildchain runtime ref for trusted manual validation\"\n required: false\n default: \"\"\n\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n buildchain-ref: ${{ inputs.buildchain-ref || '' }}\n```\n\nAllowed override refs are deliberately narrow:\n\n| Ref form | Meaning |\n| --- | --- |\n| `train/v2/v2.3/<capability>` | Temporary capability train under the active minor line |\n| `refs/heads/train/v2/v2.3/<capability>` | Explicit branch ref for the same train |\n| `<40-character SHA>` | Exact immutable Buildchain runtime commit |\n\nOverride requests fail closed unless the event is `workflow_dispatch` and the\nactor has write, maintain, or admin permission on the caller repository.\nPull requests, including same-repository pull requests and fork-originated pull\nrequests, cannot use `buildchain-ref` override. This keeps automated PR builds on\nthe stable runtime surface.\n\nEvery run resolves the runtime ref to an immutable SHA before checkout. The job\nsummary and aggregate build summary record the workflow shell ref, requested\nruntime ref, resolved runtime ref, runtime SHA, stability class, trust decision,\nand rollback ref. Train refs are development validation refs: they do not move\n`v2`, `vX.Y`, `vX.Y-alpha`, npm dist-tags, or production release refs, and they\nmust not be pinned as long-term production dependencies.\n\nRuntime override validates Buildchain runtime scripts, CLI code, local actions,\nconfig parsing, and lifecycle behavior. It cannot validate changes that require\nthe outer reusable workflow YAML itself to change, such as new jobs,\npermissions, workflow outputs, or matrix topology. Those changes need a canary\nworkflow path or a temporary explicit workflow ref.\n\n## Floating Ref Contract Lock\n\nStable consumers should use floating major refs such as `@v2`, but a floating\nref is not blind trust. Each released Buildchain ref carries a package-owned\nruntime contract world in `dist/site/buildchain-contract.json`. Consumers may\nkeep a small lock file, `buildchain.contract-lock.json`, recording the\nBuildchain ref, resolved SHA, contract digest, compatibility digest, accepted\nmajor line, and compatibility policy they reviewed.\n\nThe reusable build trust gate checks this lock before any heavy matrix job:\n\n1. resolve the Buildchain runtime ref, for example `v2`, to an immutable SHA;\n2. read `dist/site/buildchain-contract.json` from that checked-out Buildchain\n ref;\n3. read the consumer's `buildchain.contract-lock.json`;\n4. compare the accepted contract with the current contract.\n\nSHA drift alone is not a failure. `v2` is expected to advance. Buildchain only\nfails fast when the accepted contract is no longer compatible, for example a\nrequired input is removed, a required output disappears, a protected behavior\npromise changes, or the major line changes. Additive changes such as optional\ninputs, optional outputs, diagnostics, or documentation updates continue under\nthe default `major-compatible` policy.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n buildchain-contract-lock-path: buildchain.contract-lock.json\n buildchain-contract-compatibility-policy: major-compatible\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\nWhen compatible drift is detected, the build continues and Buildchain opens or\nupdates a low-priority issue in the consumer repository. The issue records the\nold SHA/digest, new SHA/digest, compatibility result, workflow run, and the next\naction: review the Buildchain release notes and update the lock. When breaking\ndrift is detected, the same issue path is used, but the trust gate fails before\nmatrix build or publish work starts. If the workflow token cannot write issues,\nBuildchain writes a copyable issue body into the job summary.\n\nThe lock is intentionally small. It does not copy the full contract. The full\ncontract remains in the Buildchain ref and package; the consumer records only\nwhat it accepted and the policy used to compare future floating-ref movement.\n\nWhen a Buildchain maintainer asks for downstream validation, the expected\nrequest is:\n\n```text\nBuildchain train ready: buildchain-ref=train/v2/v2.3/<capability>.\nKeep uses: ...@v2; run workflow_dispatch with that buildchain-ref and report the runtime evidence summary.\n```\n\nAfter validation succeeds, the Buildchain change should continue through the\nnormal mainline and release path. Do not treat the train as a pending merge\nitem; it is only a temporary fast-use, diagnostic, and rollback channel. It may\nremain for a retention window after release, with old trains handled by a\nseparate periodic cleanup task.\n\n## Workflow Outputs\n\nThe reusable workflow exposes the resolved contract:\n\n| Output | Meaning |\n| --------------------------------- | ------------------------------------------------------------------------------- |\n| `runner-preset` | Resolved preset, or `custom` when `platforms-json` was provided |\n| `platforms-json` | Exact matrix JSON used by the build job |\n| `platform-count` | Number of matrix platforms |\n| `linux-container-enabled` | `true` when Linux platforms are routed through a job container |\n| `linux-container-image` | Resolved digest-pinned Linux job container image |\n| `build-summary-artifact` | Uploaded aggregate summary artifact name |\n| `build-diagnostics-summary-artifact` | Uploaded aggregate diagnostics summary artifact name |\n| `release-candidate-passport-artifact` | Uploaded PR-stage release-candidate passport artifact name when `release-candidate` is enabled |\n| `release-candidate-passport-json` | Compact release-candidate passport JSON when `release-candidate` is enabled |\n| `build-summary-json` | Compact aggregate JSON with platform count, file count, and byte total |\n| `build-diagnostics-summary-json` | Compact aggregate diagnostics JSON with platform, lifecycle warning/error, diagnostics contract warning, and sidecar manifest warning totals |\n| `trusted-event` | `true` when the event is trusted enough to reach build runners |\n| `buildchain-runtime-ref` | Runtime ref selected after applying the empty-default or override policy |\n| `buildchain-runtime-sha` | Immutable Buildchain runtime commit used by all runtime checkouts |\n| `buildchain-runtime-class` | `stable`, `alpha`, `train`, `exact-sha`, or `development` |\n| `buildchain-runtime-override` | `true` when a non-empty `buildchain-ref` override was accepted |\n| `buildchain-runtime-trust-decision` | Runtime override trust decision |\n| `buildchain-contract-lock-status` | `unchanged`, `compatible-drift`, `breaking-drift`, `missing-lock`, `non-floating-runtime`, or first-release `runtime-contract-unavailable` |\n| `buildchain-contract-lock-drift` | `true` when the floating runtime SHA or contract digest changed |\n| `buildchain-contract-digest` | Current Buildchain runtime contract digest |\n| `publish-channel` | Resolved publish channel requested by the caller |\n| `publish-allowed` | `true` only when this event/ref may publish after verification |\n| `publish-reason` | Human-readable reason for the publish gate decision |\n| `publish-source-ref` | Gate source ref that was resolved before checkout |\n| `publish-source-sha` | Exact source commit used by checkout, build, verify, and artifacts |\n| `publish-source-locked` | `true` when a `publish-gate/*` source ref was explicitly locked |\n| `publish-source-channel` | `alpha`, `release`, `anchor`, or `major` parsed from the source ref |\n| `publish-source-line` | Product line parsed from source refs such as `v22/v22.22` |\n| `publish-source-consumer-version` | Consumer package version parsed from source refs |\n| `release-manifest-json` | Resolved release manifest including source lock, version state, and anchor data |\n\nThe aggregate summaries are intentionally artifacts as well as outputs. GitHub\nActions matrix outputs are not a reliable place to carry every platform's full\nmanifest, so Buildchain uploads each platform manifest and then emits one\naggregate build summary artifact after the matrix completes. Buildchain uploads\n`diagnostics-summary.json` as a separate aggregate diagnostics summary artifact,\na compact rollup of each platform's small diagnostics upload. The rollup keeps\nper-platform runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler-cache availability, lifecycle timing,\nprocess sampler context, and links back to the exact platform artifacts. Each\nplatform diagnostics upload includes `diagnostics.json`,\n`diagnostics-manifest.json`, the lifecycle `events.jsonl`, and process sampler\nsidecars when enabled, so slow-build diagnosis does not require downloading the\nbinary platform artifact or the aggregate build summary. The sidecar manifest\nrecords the uploaded diagnostics files with bytes and sha256 hashes. Each\n`diagnostics.json` also records the related binary artifact name, manifest\nartifact name, diagnostics artifact name, diagnostics sidecar manifest path, and\nplatform id in `links`, so a reviewer can navigate from the small diagnostics\nartifact back to the exact platform outputs when deeper inspection is needed.\nThe workflow output `build-diagnostics-summary-json` includes\n`diagnosticsContractWarningCount` and `diagnosticsManifestWarningCount` so\nrelease jobs can detect drifting diagnostics JSON contracts and missing or\ndrifting diagnostics sidecar manifests without downloading the per-platform\ndiagnostics artifacts first.\n\n## Artifact Transfer Relay\n\nBy default, platform jobs upload payloads, manifests, and diagnostics directly\nto GitHub artifacts:\n\n```yaml\nwith:\n artifact-transfer-mode: github-artifacts\n```\n\nLarge self-hosted native builds can opt into the first-class S3 relay path:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n runner-preset: kungfu-v4-self-hosted\n artifact-transfer-mode: s3-to-github-artifacts\n artifact-relay-s3-bucket: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET }}\n artifact-relay-s3-region: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_REGION }}\n artifact-relay-s3-prefix: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX }}\n```\n\nIn relay mode, each self-hosted platform job uploads the heavy payload files to\nS3 and uploads only a small `relay-manifest.json` to GitHub. A GitHub-hosted\n`relay-artifacts` job then assumes the configured download role, downloads the\npayloads from S3, verifies every file by SHA256, and re-uploads the normal\nGitHub artifacts under the same artifact names that direct mode uses. Downstream\nsummary, release-candidate, and promote-only workflows therefore continue to\nconsume GitHub artifacts and do not need custom S3 logic.\nThe relay implementation uses Node.js plus the standard AWS environment\ncredentials from GitHub OIDC; runner images and build containers do not need the\nAWS CLI installed.\n\nAfter the GitHub artifact uploads succeed, Buildchain deletes the S3 objects\nlisted in the relay manifest for that platform. If any download, verification,\nor GitHub artifact upload fails, cleanup is skipped so maintainers can inspect\nthe retained S3 payload. Configure a short bucket lifecycle expiration as a\ncost and cleanup backstop.\n\nThe relay configuration is intentionally generic. Buildchain does not hard-code\norganization buckets, regions, or role ARNs. Callers may pass explicit inputs,\nor set repository/organization variables and secrets using these names:\n\n| Variable or secret | Meaning |\n| --- | --- |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET` | Relay bucket name |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_REGION` | Relay bucket region |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX` | Relay object prefix; defaults to `buildchain-artifacts` |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_ROLE_ARN` | Shared OIDC role ARN for upload and download |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_UPLOAD_ROLE_ARN` | Upload OIDC role ARN for self-hosted build jobs |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_DOWNLOAD_ROLE_ARN` | Download OIDC role ARN for the GitHub-hosted relay job |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_OIDC_AUDIENCE` | Optional OIDC audience override |\n\nFor AWS China regions, Buildchain defaults the OIDC audience to\n`sts.amazonaws.com.cn`; other regions default to `sts.amazonaws.com`. The caller\nworkflow must allow `id-token: write`, and the target role trust policy should\nrestrict GitHub OIDC claims to the expected organization, repository, workflow,\nand branch/ref. The S3 permissions should be scoped to the relay bucket/prefix\nused by the repository.\nUpload roles need write/delete access under the relay prefix; download roles\nneed read access plus delete access for successful cleanup.\n\nRelay mode is opt-in and does not affect forks or open-source users that do not\nconfigure S3. Missing bucket, region, upload role, or download role values fail\nbefore the heavy build matrix is scheduled. Buildchain treats S3 as a transport\ncache, not as the final release evidence store; the final audit entry remains\nthe GitHub artifact set plus the Buildchain build summary and release-candidate\npassport.\n\nSet `release-candidate: true` when the successful reusable build is meant to be\nthe artifact source promoted later. Buildchain then uploads\n`release-candidate-passport.json` under the\n`<artifact-name>-release-candidate-<publish-source-sha>` artifact name. Promotion\njobs can pass that passport to `promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` so source, channel, platforms, and the\naggregate build-summary hash are checked before publish-gate side effects. The\npassport records the locked commit's Git tree SHA, so a post-merge channel HEAD\ncan be accepted only when it is tree-equivalent to the PR-stage build evidence.\n\n## Publish Gate\n\nBuildchain separates \"may build/verify\" from \"may publish.\" A same-repository\npull request may be trusted enough to run the build matrix, but it still must\nnot publish packages, S3 objects, release pages, or preview aliases. Publishing\nis allowed only when the caller explicitly requests a channel and the current\nevent/ref matches that channel.\n\nUse `publish-channel` to request a channel:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n publish-channel: release\n\n publish:\n needs: build\n if: ${{ needs.build.outputs.publish-allowed == 'true' }}\n runs-on: ubuntu-24.04\n steps:\n - run: ./scripts/publish.sh\n```\n\nDefault channels are:\n\n| Channel | Allowed refs |\n| --------- | ---------------------------------------------------------------------------------------------------- |\n| `none` | Never publishes; this is the default |\n| `alpha` | `alpha/vN/vN.M` branches or exact `vN.M.P-alpha.K` tags |\n| `release` | `release/vN/vN.M` branches or release tags such as `vN.M.P`, `vN.M`, `vN` |\n| `major` | `publish-gate/major`, legacy `major-gate`, or next-major release tags such as `vN.0.0`, `vN.0`, `vN` |\n\nPull request events always produce `publish-allowed=false`, even when the PR is\nfrom the same repository. Untrusted fork events also produce\n`publish-allowed=false`; with the default `untrusted-policy: fail`, the workflow\nthen fails before any build runner starts.\n\nProjects with their own channel names can pass `publish-refs-json`:\n\n```yaml\nwith:\n publish-channel: nightly\n publish-refs-json: >-\n {\"nightly\":[\"^refs/heads/nightly/v\\\\d+$\"]}\n```\n\nThe aggregate build summary includes the same publish gate decision under\n`publishGate`, so a downloaded artifact summary explains both what was built and\nwhy it was or was not eligible to publish.\n\n## Publish Source Lock\n\n`publish-channel` answers \"may this event publish?\" Source lock answers \"which\nsource tree is the publish decision about?\" A caller can pass `publish-source-ref`\nto bind a publish run to a reviewed gate branch before any checkout happens:\n\n| Ref | Meaning |\n| ------------------------------------------------ | --------------------------------------------------------------------------- |\n| `publish-gate/alpha/<line>/<consumer-version>` | Build and publish an alpha candidate for a consumer line |\n| `publish-gate/release/<line>/<consumer-version>` | Build and publish a production candidate for a consumer line |\n| `publish-gate/anchor` | Resolve an explicit anchor request; it does not publish artifacts by itself |\n| `publish-gate/major` | Gate the next major source state |\n| `major-gate` | Legacy compatibility alias for the major gate |\n\nFor alpha and release refs, `<line>` is intentionally allowed to contain `/`, so\nKungfu-style lines such as `v22/v22.22` stay readable. The final path segment is\nthe consumer-visible version, for example `22.22.3-kf.0`.\n\nThe reusable workflow resolves the branch tip to `publish-source-sha`, checks out\nthat SHA in every build job, and uses the same SHA in artifact names, manifests,\nand aggregate summaries. Reruns therefore rebuild the same source tree even if a\ngate branch moves later.\n\nBefore any heavy build matrix is scheduled, the workflow also verifies that the\ntarget channel ref implied by the source lock already points at\n`publish-source-sha` and that the target channel HEAD came from the required\nmerged same-repository channel PR. `publish-gate/alpha/<line>/<version>` must\nmatch `alpha/<line>` and have PR lineage `dev/<line> -> alpha/<line>`;\n`publish-gate/release/<line>/<version>` must match `release/<line>` and have PR\nlineage `alpha/<line> -> release/<line>`. If either check fails, the run fails\nfast with a diagnostic telling maintainers to merge the source commit through\nthe channel PR first. This keeps verify from spending runner time on a source\ntree that cannot legally enter the requested publish channel.\n\nThe resolved release manifest is uploaded as an artifact and emitted as\n`release-manifest-json`. It records:\n\n- source ref, source SHA, channel, line, and consumer version;\n- configured version strategy and configured version-state files;\n- each version file's value, with release gates failing closed if the configured\n files do not equal the consumer version;\n- anchor manifest summary for anchored/manual projects;\n- explicit anchor request JSON for `publish-gate/anchor`;\n- publish registry, dist-tag, and gate visibility metadata.\n\nPublish side-effect jobs should verify the lock immediately before publishing:\n\n```yaml\n- name: Verify publish gate did not move\n run: node .buildchain/runtime/scripts/verify-publish-source-lock.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nIf the branch tip no longer matches the manifest SHA, the publish job must fail\nclosed. Moving a gate branch creates a new publish decision and should produce a\nnew build run.\n\n## Release Candidate Promote-Only\n\nFor native package sets, the PR build is the only heavy build. When a PR targets\n`alpha/<line>` or `release/<line>`, `.build.yml` uploads a release-candidate\nbundle next to the platform artifacts. The bundle contains:\n\n- `release-candidate.passport.json`;\n- the aggregate `build-summary.json`;\n- copied platform manifest evidence for the built platforms.\n\nThe passport records two separate source identities:\n\n- `builtSourceSha` / `builtSourceTreeSha`: the PR-stage source that produced the\n artifacts, usually the PR merge ref;\n- `promotionChannelSha` / `promotionChannelTreeSha`: the post-merge channel\n commit used for publish authority.\n\nThe reusable promote wrapper resolves the merged PR, finds exactly one matching\nPR-stage release-candidate artifact, downloads it with the build summary and\npayload artifacts from the same PR-stage run, validates the payload count,\ncompares the built tree with the promotion channel tree, locks\n`publish-gate/{alpha,release,major}` to the promotion channel commit, and then\ncalls `actions/promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` and\n`require-publish-source-lock: \"true\"`. The wrapper passes the created\n`publish-gate/*` ref, target SHA, and `locked=true` into the promote action, so\nfloating `@v2` consumers receive publish-side source-lock drift protection by\ndefault. It does not call `.build.yml`, does not create a matrix, and must fail\nbefore publish if the RC evidence, payload set, or source-lock ref is missing or\nambiguous.\n\n```yaml\njobs:\n promote:\n uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v2\n secrets:\n buildchain-issue-app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n buildchain-issue-app-private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n with:\n channel: alpha\n target-ref: alpha/v22/v22.22\n artifact-name: libnode\n # Defaults to build.yml / Build. Override only when the PR-stage build\n # workflow uses a different file or display name.\n release-candidate-workflow-file: build.yml\n release-candidate-workflow-name: Build\n package-manager: npm\n publish-target: npm\n runner-preset: github-hosted\n trusted-publishing: true\n github-release: true\n required-status-check: check\n required-artifact-count: 3\n publish-dist-tag: alpha\n publish-package-set-order: platforms-first-main-last\n publish-package-main: \"@kungfu-tech/libnode\"\n release-passport-product-name: Libnode\n buildchain-contract-lock-path: buildchain.contract-lock.json\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\n`buildchain-issue-app-id` and `buildchain-issue-app-private-key` are optional\nbut recommended for cross-repository consumers. They should identify a GitHub\nApp installation with `issues: write` on `kungfu-systems/buildchain`; the\nwrapper mints the installation token before calling\n`actions/report-buildchain-issue`. Consumers can also pass a pre-minted\n`buildchain-issue-token`. If both are omitted, the wrapper falls back to\n`BUILDCHAIN_ISSUE_TOKEN`, `BUILDCHAIN_PROMOTION_TOKEN`, and then the consumer\nworkflow's `github.token`; the last fallback can only report issues when it has\nwrite access to the target Buildchain repository.\n\n`publish-required-artifacts-json` can still be passed explicitly for custom\npublish targets. For the default `publish-artifact-kind: npm` path, consumers do\nnot download artifacts or run repository scripts to build publish evidence. The\nwrapper downloads the PR-stage payload artifacts, finds the downloaded `.tgz`\npackages, reads each tarball's `package/package.json` for the real scoped\npackage name and version, computes the npm `sha512-...` integrity from the\ntarball bytes, marks the package matching `publish-package-main` as `role:\nmain`, marks the rest as `role: platform`, and passes the generated\n`publish-required-artifacts-json` to `promote-buildchain-ref` before any publish\nside effect. Downloaded platform manifests are still passed into the release\npassport unless `release-passport-platform-manifest-paths` is set explicitly.\nThe same Buildchain contract lock check runs before release-candidate\nresolution and before publish. A compatible `v2` drift leaves an issue in the\nconsumer repository but does not trigger a second heavy build; an incompatible\ndrift fails before publish side effects.\n\nThe wrapper publishes the promoted exact tag as a GitHub Release by default.\nAfter `promote-buildchain-ref` reports a complete release transaction, the\nwrapper creates or updates the exact-tag release, marks semver prerelease tags\nsuch as `v1.2.3-alpha.0`, `v1.2.3-rc.1`, or `v22.22.3-kf.3-alpha.7` as\n`prerelease=true` and `make_latest=false`, marks stable semver tags as latest,\nand uploads the publish evidence file plus every file in the generated release\npassport directory, including `buildchain.release.json` and `check-report.json`.\nConsumers do not need to hand-write `gh release` logic to trigger\n`release.published` propagation. Set `github-release: false` only for\nrepositories that intentionally do not maintain GitHub Releases.\nIf the transaction still needs protected-ref finalization, the wrapper defers\nGitHub Release creation until the later run that reaches `state=complete`.\n\nCustom publish jobs can also repeat the channel-ref preflight:\n\n```yaml\n- name: Verify publish channel ref still matches\n run: node .buildchain/runtime/scripts/verify-publish-channel-ref.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nAnchored/manual package release jobs should also make the Buildchain promotion\naction validate that publication is entering through the same\n`publish-gate/{alpha,release,major}` source-lock contract before any package\npublish side effect:\n\n```yaml\n- name: Promote release ref and publish npm package set\n uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\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```\n\n`target-ref` stays the Buildchain channel promotion target, such as\n`alpha/v22/v22.22`, `release/v22/v22.22`, or `publish-gate/major`.\n`publish-source-ref` is the reviewed source-lock branch that authorized this\nspecific package publication. For alpha and release package publications, the\nsource-lock branch must point at the exact channel-line commit that promotion is\nvalidating; it is not a replacement for `target-ref`.\n\nThis keeps the version bump commit, publish authorization, and auditable publish\nentrypoint on the Buildchain source-lock protocol. The CLI form\n`buildchain publish-source validate-anchored-release --json` is still useful for\ncustom publish scripts, but the preferred GitHub Actions gate is the promotion\naction input above. A publish job that still runs directly from `alpha/*` or\n`release/*` channel branches fails this check because those refs are channel\nstate, not publish-gate decisions.\n\n## Package-Set Publish Plan\n\nProjects that publish multiple packages should treat package publication as a\npackage-set operation. Buildchain's package-set planner uses these rules:\n\n- platform packages publish first;\n- the main package publishes last;\n- the dist-tag move happens only after the full package set is present;\n- reruns accept already-published packages only when package name, version, and\n integrity match;\n- an existing package with different integrity is a hard failure.\n\nThis keeps a consumer from observing a floating dist-tag that points to a main\npackage before all platform artifacts for the same source SHA are available.\n\n## Command Sources\n\nThe workflow runs `buildchain.toml` lifecycle stages by default:\n\n```toml\n[lifecycle.install]\ncommand = \"corepack yarn install --immutable\"\n\n[lifecycle.build]\ncommands = [\n \"corepack yarn make\",\n \"corepack yarn build\",\n]\n\n[lifecycle.verify]\ncommand = \"corepack yarn test\"\n```\n\nCallers can override any stage for one invocation:\n\n```yaml\nwith:\n build-command: cmake --build build --config Release\n verify-command: ctest --test-dir build --output-on-failure\n```\n\nThe reusable build workflow samples the build lifecycle by default and carries\nthe generated summary into the final verify diagnostics. Callers can override\nthe sidecar path or disable sampling:\n\n```yaml\nwith:\n sample-process-tree: true\n process-summary-path: .buildchain/diagnostics/process-summary.json\n process-sample-interval-ms: 15000\n requested-parallelism: 20\n```\n\nWhen `sample-process-tree` is true, Buildchain wraps either `build-command` or\nthe configured `lifecycle.build` stage with `buildchain sample process-tree`.\nThe path is relative to the checked-out workspace and is read again during the\nfinal verify lifecycle. Custom workflows can still write their own sampler\nsummary and pass `process-summary-path`; Buildchain reads the file after the\nlifecycle command finishes, so it may be produced during the same invocation.\nWhen the build stage is optional, the reusable workflow treats the default\nsampler path as optional during verify; an explicitly supplied\n`process-summary-path` remains required.\n\nFor custom workflows, use the action directly:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/run-lifecycle@v2\n with:\n stage: build\n required: \"true\"\n artifact-name: libnode-linux-x64-${{ github.sha }}\n artifact-paths: |\n dist\n build/stage\n```\n\n## Artifact Contract\n\nEach platform upload uses `artifact-name-template`. The default is:\n\n```text\n{artifact}-{platform}-{sha}\n```\n\nSupported placeholders are `{artifact}`, `{artifactName}`, `{platform}`,\n`{platformId}`, `{platformName}`, `{sha}`, `{shortSha}`, `{ref}`, `{runId}`,\nand `{runAttempt}`. Invalid GitHub artifact name characters are normalized to\n`-`, so `{ref}` remains deterministic even for refs such as\n`refs/heads/dev/v2/v2.0`.\n\nEach platform also writes and uploads:\n\n```text\n.buildchain/artifacts/<platform-id>/manifest.json\n.buildchain/artifacts/<platform-id>/summary.json\n```\n\nThe manifest schema is:\n\n```json\n{\n \"schemaVersion\": 1,\n \"contract\": \"kungfu-buildchain-artifact\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"platform\": {\n \"id\": \"linux-x64\",\n \"name\": \"Linux x64\",\n \"os\": \"Linux\",\n \"arch\": \"X64\"\n },\n \"git\": {\n \"repository\": \"kungfu-systems/libnode\",\n \"sha\": \"<sha>\",\n \"ref\": \"<ref>\",\n \"runId\": \"<run id>\",\n \"runAttempt\": \"<attempt>\"\n },\n \"lifecycle\": {\n \"stage\": \"verify\",\n \"commandSource\": \"buildchain.toml\",\n \"executed\": true\n },\n \"summary\": {\n \"contract\": \"kungfu-buildchain-artifact-summary\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"fileCount\": 1,\n \"totalBytes\": 1234,\n \"digest\": \"<hex>\"\n },\n \"expectedArtifacts\": {\n \"ok\": true,\n \"source\": \"expected-artifacts-json\",\n \"checks\": []\n },\n \"files\": [\n {\n \"path\": \"dist/example.zip\",\n \"size\": 1234,\n \"sha256\": \"<hex>\"\n }\n ]\n}\n```\n\nArtifact names do not include actor names, timestamps, or retry counters. Reruns\nproduce a new GitHub Actions run but keep the same source SHA/platform contract.\n\n`expected-artifacts-json` fails the build before upload when the artifact does\nnot match the caller's declared contract. Supported checks are:\n\n| Field | Meaning |\n| --------------- | ------------------------------------ |\n| `minFiles` | Minimum number of manifest files |\n| `maxFiles` | Maximum number of manifest files |\n| `minTotalBytes` | Minimum total byte count |\n| `requiredPaths` | Exact manifest paths that must exist |\n\n## Trusted Event Gate\n\nThe workflow has an explicit `trust-gate` job. By default, pull requests from\nforks fail before any build job can reach self-hosted runners, secrets,\npublishing credentials, or heavyweight build commands. Same-repository PRs,\nworkflow dispatches, and protected branch events can proceed.\n\nIf a repository wants fork PRs to skip rather than fail, it can set:\n\n```yaml\nwith:\n untrusted-policy: skip\n```\n\nDo not set `require-trusted-event: false` for workflows that use self-hosted\nrunners or secrets.\n\n`require-trusted-event` controls access to build runners. It does not override\nthe publish gate: pull requests remain non-publishing events.\n\n## Fixture\n\n`fixtures/libnode-shaped` is the contract fixture. It has:\n\n- `package.json` version state;\n- `buildchain.toml` with `install`, `build`, `verify`, and `publish`;\n- cross-platform Node scripts that create small `dist/` outputs;\n- `Build Surface Fixture` workflow coverage.\n\nThe fixture proves the reusable surface without running the real libnode native\nbuild."
1052
+ "markdown": "# Reusable Build Surface\n\nBuildchain v2 provides a reusable build workflow for repositories that need\nBuildchain's release semantics but cannot be described as a simple Node package.\nThe first target shape is `libnode`: expensive native builds, multiple operating\nsystems, self-hosted runner labels, and release artifacts that must be auditable.\n\n## Workflow\n\nStable consumers call:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n working-directory: .\n artifact-name: libnode\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n artifact-name-template: \"{artifact}-{platform}-{sha}\"\n artifact-paths: |\n dist\n build/stage\n expected-artifacts-json: >-\n {\"minFiles\":2,\"requiredPaths\":[\"dist/libnode.tar.gz\",\"dist/checksums.txt\"]}\n process-summary-path: .buildchain/diagnostics/process-summary.json\n release-candidate: true\n publish-channel: release\n publish-source-ref: publish-gate/release/v22/v22.22/22.22.3-kf.0\n```\n\n`runner-preset` is the stable first-class surface for known runner fleets:\n\n| Preset | Platforms |\n| ----------------------- | ------------------------------------------------------------------------ |\n| `github-hosted` | `ubuntu-24.04`, `macos-latest`, `windows-2022` |\n| `kungfu-v4-self-hosted` | Kungfu Linux x64, macOS ARM64, and Windows x64 self-hosted runner labels |\n| `custom` | Requires `platforms-json` |\n\nCallers can still provide a custom matrix with `platforms-json`. Each platform\nobject has:\n\n| Field | Meaning |\n| -------- | ------------------------------------------------- |\n| `id` | Stable artifact/platform key, such as `linux-x64` |\n| `name` | Human-readable job name |\n| `runner` | JSON string passed to `runs-on` after `fromJSON` |\n\nThe runner field is intentionally a JSON string so callers can pass either\nGitHub-hosted runners or multi-label self-hosted runners without Buildchain\nguessing the labels.\n\nOnly include platforms that should run. GitHub schedules matrix jobs before\nsteps execute, so a disabled entry with unavailable runner labels can still\nblock the workflow queue.\n\n## Linux Job Containers\n\nLinux build platforms can run inside a digest-pinned job container while macOS\nand Windows keep using native runners. This is the recommended way to remove\nmoving Linux runner prerequisites from Buildchain consumers: the Linux host only\nneeds a GitHub Actions runner, Docker, and network access; common verification\ntools come from the image contract.\n\nUse the Kungfu verification image for lifecycle stages that need Git, jq,\nPython, uv, and fnm, but do not need native compilation:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n```\n\n`kungfu-verify` resolves to:\n\n```text\nghcr.io/kungfu-systems/build-images/kungfu-verify@sha256:11f0ba64267ce88174a4f73a9bf833ff4e9c59cd16ec3d08a6432a06c2be6fb1\n```\n\nCallers that own a different Linux image can pass it explicitly:\n\n```yaml\nwith:\n linux-container-preset: custom\n linux-container-image: ghcr.io/example/project-build@sha256:<digest>\n```\n\n`linux-container-image` should be pinned by digest. A floating tag makes the\nrunner surface mutable and weakens Buildchain's release evidence.\n\nThe workflow splits the matrix into two build jobs:\n\n- Linux platforms go to `build-linux-container` when a Linux container is\n configured.\n- All other platforms go to `build-native`.\n\nArtifact names, manifest paths, expected artifact checks, publish-source locks,\nand aggregate summaries are the same in both jobs. The split is an execution\ndetail, not a different artifact contract.\n\nThe container image provides `fnm` but does not preinstall Node. Buildchain uses\n`fnm` inside the container to install the requested `node-version` before it\nruns Buildchain runtime scripts or lifecycle actions.\n\nDo not use `kungfu-verify` for stages that need CMake, Ninja, ccache, Conan, or\nDocker image publishing. Those should use a heavier native-build image or remain\non a host runner until their image contract is explicit.\n\n## Buildchain Runtime Override\n\nStable consumers should keep the reusable workflow pinned to stable refs such as\n`@v2`. The optional `buildchain-ref` input is empty by default; empty means\nBuildchain resolves and executes the stable runtime selected by the workflow\nshell. The full train validation protocol is documented in\n[`runtime-train-validation.md`](runtime-train-validation.md).\n\nFor one-off manual validation, a trusted maintainer can run the caller workflow\nwith a temporary runtime override:\n\n```yaml\non:\n workflow_dispatch:\n inputs:\n buildchain-ref:\n description: \"Temporary Buildchain runtime ref for trusted manual validation\"\n required: false\n default: \"\"\n\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n buildchain-ref: ${{ inputs.buildchain-ref || '' }}\n```\n\nAllowed override refs are deliberately narrow:\n\n| Ref form | Meaning |\n| --- | --- |\n| `train/v2/v2.3/<capability>` | Temporary capability train under the active minor line |\n| `refs/heads/train/v2/v2.3/<capability>` | Explicit branch ref for the same train |\n| `<40-character SHA>` | Exact immutable Buildchain runtime commit |\n\nOverride requests fail closed unless the event is `workflow_dispatch` and the\nactor has write, maintain, or admin permission on the caller repository.\nPull requests, including same-repository pull requests and fork-originated pull\nrequests, cannot use `buildchain-ref` override. This keeps automated PR builds on\nthe stable runtime surface.\n\nEvery run resolves the runtime ref to an immutable SHA before checkout. The job\nsummary and aggregate build summary record the workflow shell ref, requested\nruntime ref, resolved runtime ref, runtime SHA, stability class, trust decision,\nand rollback ref. Train refs are development validation refs: they do not move\n`v2`, `vX.Y`, `vX.Y-alpha`, npm dist-tags, or production release refs, and they\nmust not be pinned as long-term production dependencies.\n\nRuntime override validates Buildchain runtime scripts, CLI code, local actions,\nconfig parsing, and lifecycle behavior. It cannot validate changes that require\nthe outer reusable workflow YAML itself to change, such as new jobs,\npermissions, workflow outputs, or matrix topology. Those changes need a canary\nworkflow path or a temporary explicit workflow ref.\n\n## Floating Ref Contract Lock\n\nStable consumers should use floating major refs such as `@v2`, but a floating\nref is not blind trust. Each released Buildchain ref carries a package-owned\nruntime contract world in `dist/site/buildchain-contract.json`. Consumers may\nkeep a small lock file, `buildchain.contract-lock.json`, recording the\nBuildchain ref, resolved SHA, contract digest, compatibility digest, accepted\nmajor line, and compatibility policy they reviewed.\n\nThe reusable build trust gate checks this lock before any heavy matrix job:\n\n1. resolve the Buildchain runtime ref, for example `v2`, to an immutable SHA;\n2. read `dist/site/buildchain-contract.json` from that checked-out Buildchain\n ref;\n3. read the consumer's `buildchain.contract-lock.json`;\n4. compare the accepted contract with the current contract.\n\nSHA drift alone is not a failure. `v2` is expected to advance. Buildchain only\nfails fast when the accepted contract is no longer compatible, for example a\nrequired input is removed, a required output disappears, a protected behavior\npromise changes, or the major line changes. Additive changes such as optional\ninputs, optional outputs, diagnostics, or documentation updates continue under\nthe default `major-compatible` policy.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n buildchain-contract-lock-path: buildchain.contract-lock.json\n buildchain-contract-compatibility-policy: major-compatible\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\nWhen compatible drift is detected, the build continues and Buildchain opens or\nupdates a low-priority issue in the consumer repository. The issue records the\nold SHA/digest, new SHA/digest, compatibility result, workflow run, and the next\naction: review the Buildchain release notes and update the lock. When breaking\ndrift is detected, the same issue path is used, but the trust gate fails before\nmatrix build or publish work starts. If the workflow token cannot write issues,\nBuildchain writes a copyable issue body into the job summary.\n\nThe lock is intentionally small. It does not copy the full contract. The full\ncontract remains in the Buildchain ref and package; the consumer records only\nwhat it accepted and the policy used to compare future floating-ref movement.\n\nWhen a Buildchain maintainer asks for downstream validation, the expected\nrequest is:\n\n```text\nBuildchain train ready: buildchain-ref=train/v2/v2.3/<capability>.\nKeep uses: ...@v2; run workflow_dispatch with that buildchain-ref and report the runtime evidence summary.\n```\n\nAfter validation succeeds, the Buildchain change should continue through the\nnormal mainline and release path. Do not treat the train as a pending merge\nitem; it is only a temporary fast-use, diagnostic, and rollback channel. It may\nremain for a retention window after release, with old trains handled by a\nseparate periodic cleanup task.\n\n## Workflow Outputs\n\nThe reusable workflow exposes the resolved contract:\n\n| Output | Meaning |\n| --------------------------------- | ------------------------------------------------------------------------------- |\n| `runner-preset` | Resolved preset, or `custom` when `platforms-json` was provided |\n| `platforms-json` | Exact matrix JSON used by the build job |\n| `platform-count` | Number of matrix platforms |\n| `linux-container-enabled` | `true` when Linux platforms are routed through a job container |\n| `linux-container-image` | Resolved digest-pinned Linux job container image |\n| `build-summary-artifact` | Uploaded aggregate summary artifact name |\n| `build-diagnostics-summary-artifact` | Uploaded aggregate diagnostics summary artifact name |\n| `release-candidate-passport-artifact` | Uploaded PR-stage release-candidate passport artifact name when `release-candidate` is enabled |\n| `release-candidate-passport-json` | Compact release-candidate passport JSON when `release-candidate` is enabled |\n| `build-summary-json` | Compact aggregate JSON with platform count, file count, and byte total |\n| `build-diagnostics-summary-json` | Compact aggregate diagnostics JSON with platform, lifecycle warning/error, diagnostics contract warning, and sidecar manifest warning totals |\n| `trusted-event` | `true` when the event is trusted enough to reach build runners |\n| `buildchain-runtime-ref` | Runtime ref selected after applying the empty-default or override policy |\n| `buildchain-runtime-sha` | Immutable Buildchain runtime commit used by all runtime checkouts |\n| `buildchain-runtime-class` | `stable`, `alpha`, `train`, `exact-sha`, or `development` |\n| `buildchain-runtime-override` | `true` when a non-empty `buildchain-ref` override was accepted |\n| `buildchain-runtime-trust-decision` | Runtime override trust decision |\n| `buildchain-contract-lock-status` | `unchanged`, `compatible-drift`, `breaking-drift`, `missing-lock`, `non-floating-runtime`, or first-release `runtime-contract-unavailable` |\n| `buildchain-contract-lock-drift` | `true` when the floating runtime SHA or contract digest changed |\n| `buildchain-contract-digest` | Current Buildchain runtime contract digest |\n| `publish-channel` | Resolved publish channel requested by the caller |\n| `publish-allowed` | `true` only when this event/ref may publish after verification |\n| `publish-reason` | Human-readable reason for the publish gate decision |\n| `publish-source-ref` | Gate source ref that was resolved before checkout |\n| `publish-source-sha` | Exact source commit used by checkout, build, verify, and artifacts |\n| `publish-source-locked` | `true` when a `publish-gate/*` source ref was explicitly locked |\n| `publish-source-channel` | `alpha`, `release`, `anchor`, or `major` parsed from the source ref |\n| `publish-source-line` | Product line parsed from source refs such as `v22/v22.22` |\n| `publish-source-consumer-version` | Consumer package version parsed from source refs |\n| `release-manifest-json` | Resolved release manifest including source lock, version state, and anchor data |\n\nThe aggregate summaries are intentionally artifacts as well as outputs. GitHub\nActions matrix outputs are not a reliable place to carry every platform's full\nmanifest, so Buildchain uploads each platform manifest and then emits one\naggregate build summary artifact after the matrix completes. Buildchain uploads\n`diagnostics-summary.json` as a separate aggregate diagnostics summary artifact,\na compact rollup of each platform's small diagnostics upload. The rollup keeps\nper-platform runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler-cache availability, lifecycle timing,\nprocess sampler context, and links back to the exact platform artifacts. Each\nplatform diagnostics upload includes `diagnostics.json`,\n`diagnostics-manifest.json`, the lifecycle `events.jsonl`, and process sampler\nsidecars when enabled, so slow-build diagnosis does not require downloading the\nbinary platform artifact or the aggregate build summary. The sidecar manifest\nrecords the uploaded diagnostics files with bytes and sha256 hashes. Each\n`diagnostics.json` also records the related binary artifact name, manifest\nartifact name, diagnostics artifact name, diagnostics sidecar manifest path, and\nplatform id in `links`, so a reviewer can navigate from the small diagnostics\nartifact back to the exact platform outputs when deeper inspection is needed.\nThe workflow output `build-diagnostics-summary-json` includes\n`diagnosticsContractWarningCount` and `diagnosticsManifestWarningCount` so\nrelease jobs can detect drifting diagnostics JSON contracts and missing or\ndrifting diagnostics sidecar manifests without downloading the per-platform\ndiagnostics artifacts first.\n\n## Artifact Transfer Relay\n\nBy default, platform jobs upload payloads, manifests, and diagnostics directly\nto GitHub artifacts:\n\n```yaml\nwith:\n artifact-transfer-mode: github-artifacts\n```\n\nLarge self-hosted native builds can opt into the first-class S3 relay path:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n runner-preset: kungfu-v4-self-hosted\n artifact-transfer-mode: s3-to-github-artifacts\n artifact-relay-s3-bucket: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET }}\n artifact-relay-s3-region: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_REGION }}\n artifact-relay-s3-prefix: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX }}\n```\n\nIn relay mode, each self-hosted platform job uploads the heavy payload files to\nS3 and uploads only a small `relay-manifest.json` to GitHub. A GitHub-hosted\n`relay-artifacts` job then assumes the configured download role, downloads the\npayloads from S3, verifies every file by SHA256, and re-uploads the normal\nGitHub artifacts under the same artifact names that direct mode uses. Downstream\nsummary, release-candidate, and promote-only workflows therefore continue to\nconsume GitHub artifacts and do not need custom S3 logic.\nThe relay implementation uses Node.js plus the standard AWS environment\ncredentials from GitHub OIDC; runner images and build containers do not need the\nAWS CLI installed.\n\nAfter the GitHub artifact uploads succeed, Buildchain deletes the S3 objects\nlisted in the relay manifest for that platform. If any download, verification,\nor GitHub artifact upload fails, cleanup is skipped so maintainers can inspect\nthe retained S3 payload. Configure a short bucket lifecycle expiration as a\ncost and cleanup backstop.\n\nThe relay configuration is intentionally generic. Buildchain does not hard-code\norganization buckets, regions, or role ARNs. Callers may pass explicit inputs,\nor set repository/organization variables and secrets using these names:\n\n| Variable or secret | Meaning |\n| --- | --- |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET` | Relay bucket name |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_REGION` | Relay bucket region |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX` | Relay object prefix; defaults to `buildchain-artifacts` |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_ROLE_ARN` | Shared OIDC role ARN for upload and download |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_UPLOAD_ROLE_ARN` | Upload OIDC role ARN for self-hosted build jobs |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_DOWNLOAD_ROLE_ARN` | Download OIDC role ARN for the GitHub-hosted relay job |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_OIDC_AUDIENCE` | Optional OIDC audience override |\n\nFor AWS China regions, Buildchain defaults the OIDC audience to\n`sts.amazonaws.com.cn`; other regions default to `sts.amazonaws.com`. The caller\nworkflow must allow `id-token: write`, and the target role trust policy should\nrestrict GitHub OIDC claims to the expected organization, repository, workflow,\nand branch/ref. The S3 permissions should be scoped to the relay bucket/prefix\nused by the repository.\nUpload roles need write/delete access under the relay prefix; download roles\nneed read access plus delete access for successful cleanup.\n\nRelay mode is opt-in and does not affect forks or open-source users that do not\nconfigure S3. Missing bucket, region, upload role, or download role values fail\nbefore the heavy build matrix is scheduled. Buildchain treats S3 as a transport\ncache, not as the final release evidence store; the final audit entry remains\nthe GitHub artifact set plus the Buildchain build summary and release-candidate\npassport.\n\nSet `release-candidate: true` when the successful reusable build is meant to be\nthe artifact source promoted later. Buildchain then uploads\n`release-candidate-passport.json` under the\n`<artifact-name>-release-candidate-<publish-source-sha>` artifact name. Promotion\njobs can pass that passport to `promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` so source, channel, platforms, and the\naggregate build-summary hash are checked before publish-gate side effects. The\npassport records the locked commit's Git tree SHA, so a post-merge channel HEAD\ncan be accepted only when it is tree-equivalent to the PR-stage build evidence.\n\n## Publish Gate\n\nBuildchain separates \"may build/verify\" from \"may publish.\" A same-repository\npull request may be trusted enough to run the build matrix, but it still must\nnot publish packages, S3 objects, release pages, or preview aliases. Publishing\nis allowed only when the caller explicitly requests a channel and the current\nevent/ref matches that channel.\n\nUse `publish-channel` to request a channel:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v2\n with:\n publish-channel: release\n\n publish:\n needs: build\n if: ${{ needs.build.outputs.publish-allowed == 'true' }}\n runs-on: ubuntu-24.04\n steps:\n - run: ./scripts/publish.sh\n```\n\nDefault channels are:\n\n| Channel | Allowed refs |\n| --------- | ---------------------------------------------------------------------------------------------------- |\n| `none` | Never publishes; this is the default |\n| `alpha` | `alpha/vN/vN.M` branches or exact `vN.M.P-alpha.K` tags |\n| `release` | `release/vN/vN.M` branches or release tags such as `vN.M.P`, `vN.M`, `vN` |\n| `major` | `publish-gate/major`, legacy `major-gate`, or next-major release tags such as `vN.0.0`, `vN.0`, `vN` |\n\nPull request events always produce `publish-allowed=false`, even when the PR is\nfrom the same repository. Untrusted fork events also produce\n`publish-allowed=false`; with the default `untrusted-policy: fail`, the workflow\nthen fails before any build runner starts.\n\nProjects with their own channel names can pass `publish-refs-json`:\n\n```yaml\nwith:\n publish-channel: nightly\n publish-refs-json: >-\n {\"nightly\":[\"^refs/heads/nightly/v\\\\d+$\"]}\n```\n\nThe aggregate build summary includes the same publish gate decision under\n`publishGate`, so a downloaded artifact summary explains both what was built and\nwhy it was or was not eligible to publish.\n\n## Publish Source Lock\n\n`publish-channel` answers \"may this event publish?\" Source lock answers \"which\nsource tree is the publish decision about?\" A caller can pass `publish-source-ref`\nto bind a publish run to a reviewed gate branch before any checkout happens:\n\n| Ref | Meaning |\n| ------------------------------------------------ | --------------------------------------------------------------------------- |\n| `publish-gate/alpha/<line>/<consumer-version>` | Build and publish an alpha candidate for a consumer line |\n| `publish-gate/release/<line>/<consumer-version>` | Build and publish a production candidate for a consumer line |\n| `publish-gate/anchor` | Resolve an explicit anchor request; it does not publish artifacts by itself |\n| `publish-gate/major` | Gate the next major source state |\n| `major-gate` | Legacy compatibility alias for the major gate |\n\nFor alpha and release refs, `<line>` is intentionally allowed to contain `/`, so\nKungfu-style lines such as `v22/v22.22` stay readable. The final path segment is\nthe consumer-visible version, for example `22.22.3-kf.0`.\n\nThe reusable workflow resolves the branch tip to `publish-source-sha`, checks out\nthat SHA in every build job, and uses the same SHA in artifact names, manifests,\nand aggregate summaries. Reruns therefore rebuild the same source tree even if a\ngate branch moves later.\n\nBefore any heavy build matrix is scheduled, the workflow also verifies that the\ntarget channel ref implied by the source lock already points at\n`publish-source-sha` and that the target channel HEAD came from the required\nmerged same-repository channel PR. `publish-gate/alpha/<line>/<version>` must\nmatch `alpha/<line>` and have PR lineage `dev/<line> -> alpha/<line>`;\n`publish-gate/release/<line>/<version>` must match `release/<line>` and have PR\nlineage `alpha/<line> -> release/<line>`. If either check fails, the run fails\nfast with a diagnostic telling maintainers to merge the source commit through\nthe channel PR first. This keeps verify from spending runner time on a source\ntree that cannot legally enter the requested publish channel.\n\nThe resolved release manifest is uploaded as an artifact and emitted as\n`release-manifest-json`. It records:\n\n- source ref, source SHA, channel, line, and consumer version;\n- configured version strategy and configured version-state files;\n- each version file's value, with release gates failing closed if the configured\n files do not equal the consumer version;\n- anchor manifest summary for anchored/manual projects;\n- explicit anchor request JSON for `publish-gate/anchor`;\n- publish registry, dist-tag, and gate visibility metadata.\n\nPublish side-effect jobs should verify the lock immediately before publishing:\n\n```yaml\n- name: Verify publish gate did not move\n run: node .buildchain/runtime/scripts/verify-publish-source-lock.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nIf the branch tip no longer matches the manifest SHA, the publish job must fail\nclosed. Moving a gate branch creates a new publish decision and should produce a\nnew build run.\n\n## Release Candidate Promote-Only\n\nFor native package sets, the PR build is the only heavy build. When a PR targets\n`alpha/<line>` or `release/<line>`, `.build.yml` uploads a release-candidate\nbundle next to the platform artifacts. The bundle contains:\n\n- `release-candidate.passport.json`;\n- the aggregate `build-summary.json`;\n- copied platform manifest evidence for the built platforms.\n\nThe passport records two separate source identities:\n\n- `builtSourceSha` / `builtSourceTreeSha`: the PR-stage source that produced the\n artifacts, usually the PR merge ref;\n- `promotionChannelSha` / `promotionChannelTreeSha`: the post-merge channel\n commit used for publish authority.\n\nThe reusable promote wrapper resolves the merged PR, finds exactly one matching\nPR-stage release-candidate artifact, downloads it with the build summary and\npayload artifacts from the same PR-stage run, validates the payload count,\ncompares the built tree with the promotion channel tree, locks\n`publish-gate/{alpha,release,major}` to the promotion channel commit, and then\ncalls `actions/promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` and\n`require-publish-source-lock: \"true\"`. The wrapper passes the created\n`publish-gate/*` ref, target SHA, and `locked=true` into the promote action, so\nfloating `@v2` consumers receive publish-side source-lock drift protection by\ndefault. It also defaults `branch-protection-bypass-apps` to `github-actions`\nso the workflow automation can apply generated version-state and channel\nbookkeeping on protected `dev`/`alpha`/`release` branches after the reviewed\nchannel PR has merged; consumers using a different promotion identity can pass\n`branch-protection-bypass-users`, `branch-protection-bypass-teams`, or a\ndifferent app slug declaratively. It does not call `.build.yml`, does not create\na matrix, and must fail before publish if the RC evidence, payload set, or\nsource-lock ref is missing or ambiguous.\n\n```yaml\njobs:\n promote:\n uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v2\n secrets:\n buildchain-issue-app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n buildchain-issue-app-private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n with:\n channel: alpha\n target-ref: alpha/v22/v22.22\n artifact-name: libnode\n # Defaults to build.yml / Build. Override only when the PR-stage build\n # workflow uses a different file or display name.\n release-candidate-workflow-file: build.yml\n release-candidate-workflow-name: Build\n package-manager: npm\n publish-target: npm\n runner-preset: github-hosted\n trusted-publishing: true\n github-release: true\n required-status-check: check\n required-artifact-count: 3\n publish-dist-tag: alpha\n publish-package-set-order: platforms-first-main-last\n publish-package-main: \"@kungfu-tech/libnode\"\n release-passport-product-name: Libnode\n buildchain-contract-lock-path: buildchain.contract-lock.json\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\n`buildchain-issue-app-id` and `buildchain-issue-app-private-key` are optional\nbut recommended for cross-repository consumers. They should identify a GitHub\nApp installation with `issues: write` on `kungfu-systems/buildchain`; the\nwrapper mints the installation token before calling\n`actions/report-buildchain-issue`. Consumers can also pass a pre-minted\n`buildchain-issue-token`. If both are omitted, the wrapper falls back to\n`BUILDCHAIN_ISSUE_TOKEN`, `BUILDCHAIN_PROMOTION_TOKEN`, and then the consumer\nworkflow's `github.token`; the last fallback can only report issues when it has\nwrite access to the target Buildchain repository.\n\n`publish-required-artifacts-json` can still be passed explicitly for custom\npublish targets. For the default `publish-artifact-kind: npm` path, consumers do\nnot download artifacts or run repository scripts to build publish evidence. The\nwrapper downloads the PR-stage payload artifacts, finds the downloaded `.tgz`\npackages, reads each tarball's `package/package.json` for the real scoped\npackage name and version, computes the npm `sha512-...` integrity from the\ntarball bytes, marks the package matching `publish-package-main` as `role:\nmain`, marks the rest as `role: platform`, and passes the generated\n`publish-required-artifacts-json` to `promote-buildchain-ref` before any publish\nside effect. Downloaded platform manifests are still passed into the release\npassport unless `release-passport-platform-manifest-paths` is set explicitly.\nThe same Buildchain contract lock check runs before release-candidate\nresolution and before publish. A compatible `v2` drift leaves an issue in the\nconsumer repository but does not trigger a second heavy build; an incompatible\ndrift fails before publish side effects.\n\nThe wrapper publishes the promoted exact tag as a GitHub Release by default.\nAfter `promote-buildchain-ref` reports a complete release transaction, the\nwrapper creates or updates the exact-tag release, marks semver prerelease tags\nsuch as `v1.2.3-alpha.0`, `v1.2.3-rc.1`, or `v22.22.3-kf.3-alpha.7` as\n`prerelease=true` and `make_latest=false`, marks stable semver tags as latest,\nand uploads the publish evidence file plus every file in the generated release\npassport directory, including `buildchain.release.json` and `check-report.json`.\nConsumers do not need to hand-write `gh release` logic to trigger\n`release.published` propagation. Set `github-release: false` only for\nrepositories that intentionally do not maintain GitHub Releases.\nIf the transaction still needs protected-ref finalization, the wrapper defers\nGitHub Release creation until the later run that reaches `state=complete`.\n\nCustom publish jobs can also repeat the channel-ref preflight:\n\n```yaml\n- name: Verify publish channel ref still matches\n run: node .buildchain/runtime/scripts/verify-publish-channel-ref.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nAnchored/manual package release jobs should also make the Buildchain promotion\naction validate that publication is entering through the same\n`publish-gate/{alpha,release,major}` source-lock contract before any package\npublish side effect:\n\n```yaml\n- name: Promote release ref and publish npm package set\n uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2\n with:\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```\n\n`target-ref` stays the Buildchain channel promotion target, such as\n`alpha/v22/v22.22`, `release/v22/v22.22`, or `publish-gate/major`.\n`publish-source-ref` is the reviewed source-lock branch that authorized this\nspecific package publication. For alpha and release package publications, the\nsource-lock branch must point at the exact channel-line commit that promotion is\nvalidating; it is not a replacement for `target-ref`.\n\nThis keeps the version bump commit, publish authorization, and auditable publish\nentrypoint on the Buildchain source-lock protocol. The CLI form\n`buildchain publish-source validate-anchored-release --json` is still useful for\ncustom publish scripts, but the preferred GitHub Actions gate is the promotion\naction input above. A publish job that still runs directly from `alpha/*` or\n`release/*` channel branches fails this check because those refs are channel\nstate, not publish-gate decisions.\n\n## Package-Set Publish Plan\n\nProjects that publish multiple packages should treat package publication as a\npackage-set operation. Buildchain's package-set planner uses these rules:\n\n- platform packages publish first;\n- the main package publishes last;\n- the dist-tag move happens only after the full package set is present;\n- reruns accept already-published packages only when package name, version, and\n integrity match;\n- an existing package with different integrity is a hard failure.\n\nThis keeps a consumer from observing a floating dist-tag that points to a main\npackage before all platform artifacts for the same source SHA are available.\n\n## Command Sources\n\nThe workflow runs `buildchain.toml` lifecycle stages by default:\n\n```toml\n[lifecycle.install]\ncommand = \"corepack yarn install --immutable\"\n\n[lifecycle.build]\ncommands = [\n \"corepack yarn make\",\n \"corepack yarn build\",\n]\n\n[lifecycle.verify]\ncommand = \"corepack yarn test\"\n```\n\nCallers can override any stage for one invocation:\n\n```yaml\nwith:\n build-command: cmake --build build --config Release\n verify-command: ctest --test-dir build --output-on-failure\n```\n\nThe reusable build workflow samples the build lifecycle by default and carries\nthe generated summary into the final verify diagnostics. Callers can override\nthe sidecar path or disable sampling:\n\n```yaml\nwith:\n sample-process-tree: true\n process-summary-path: .buildchain/diagnostics/process-summary.json\n process-sample-interval-ms: 15000\n requested-parallelism: 20\n```\n\nWhen `sample-process-tree` is true, Buildchain wraps either `build-command` or\nthe configured `lifecycle.build` stage with `buildchain sample process-tree`.\nThe path is relative to the checked-out workspace and is read again during the\nfinal verify lifecycle. Custom workflows can still write their own sampler\nsummary and pass `process-summary-path`; Buildchain reads the file after the\nlifecycle command finishes, so it may be produced during the same invocation.\nWhen the build stage is optional, the reusable workflow treats the default\nsampler path as optional during verify; an explicitly supplied\n`process-summary-path` remains required.\n\nFor custom workflows, use the action directly:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/run-lifecycle@v2\n with:\n stage: build\n required: \"true\"\n artifact-name: libnode-linux-x64-${{ github.sha }}\n artifact-paths: |\n dist\n build/stage\n```\n\n## Artifact Contract\n\nEach platform upload uses `artifact-name-template`. The default is:\n\n```text\n{artifact}-{platform}-{sha}\n```\n\nSupported placeholders are `{artifact}`, `{artifactName}`, `{platform}`,\n`{platformId}`, `{platformName}`, `{sha}`, `{shortSha}`, `{ref}`, `{runId}`,\nand `{runAttempt}`. Invalid GitHub artifact name characters are normalized to\n`-`, so `{ref}` remains deterministic even for refs such as\n`refs/heads/dev/v2/v2.0`.\n\nEach platform also writes and uploads:\n\n```text\n.buildchain/artifacts/<platform-id>/manifest.json\n.buildchain/artifacts/<platform-id>/summary.json\n```\n\nThe manifest schema is:\n\n```json\n{\n \"schemaVersion\": 1,\n \"contract\": \"kungfu-buildchain-artifact\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"platform\": {\n \"id\": \"linux-x64\",\n \"name\": \"Linux x64\",\n \"os\": \"Linux\",\n \"arch\": \"X64\"\n },\n \"git\": {\n \"repository\": \"kungfu-systems/libnode\",\n \"sha\": \"<sha>\",\n \"ref\": \"<ref>\",\n \"runId\": \"<run id>\",\n \"runAttempt\": \"<attempt>\"\n },\n \"lifecycle\": {\n \"stage\": \"verify\",\n \"commandSource\": \"buildchain.toml\",\n \"executed\": true\n },\n \"summary\": {\n \"contract\": \"kungfu-buildchain-artifact-summary\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"fileCount\": 1,\n \"totalBytes\": 1234,\n \"digest\": \"<hex>\"\n },\n \"expectedArtifacts\": {\n \"ok\": true,\n \"source\": \"expected-artifacts-json\",\n \"checks\": []\n },\n \"files\": [\n {\n \"path\": \"dist/example.zip\",\n \"size\": 1234,\n \"sha256\": \"<hex>\"\n }\n ]\n}\n```\n\nArtifact names do not include actor names, timestamps, or retry counters. Reruns\nproduce a new GitHub Actions run but keep the same source SHA/platform contract.\n\n`expected-artifacts-json` fails the build before upload when the artifact does\nnot match the caller's declared contract. Supported checks are:\n\n| Field | Meaning |\n| --------------- | ------------------------------------ |\n| `minFiles` | Minimum number of manifest files |\n| `maxFiles` | Maximum number of manifest files |\n| `minTotalBytes` | Minimum total byte count |\n| `requiredPaths` | Exact manifest paths that must exist |\n\n## Trusted Event Gate\n\nThe workflow has an explicit `trust-gate` job. By default, pull requests from\nforks fail before any build job can reach self-hosted runners, secrets,\npublishing credentials, or heavyweight build commands. Same-repository PRs,\nworkflow dispatches, and protected branch events can proceed.\n\nIf a repository wants fork PRs to skip rather than fail, it can set:\n\n```yaml\nwith:\n untrusted-policy: skip\n```\n\nDo not set `require-trusted-event: false` for workflows that use self-hosted\nrunners or secrets.\n\n`require-trusted-event` controls access to build runners. It does not override\nthe publish gate: pull requests remain non-publishing events.\n\n## Fixture\n\n`fixtures/libnode-shaped` is the contract fixture. It has:\n\n- `package.json` version state;\n- `buildchain.toml` with `install`, `build`, `verify`, and `publish`;\n- cross-platform Node scripts that create small `dist/` outputs;\n- `Build Surface Fixture` workflow coverage.\n\nThe fixture proves the reusable surface without running the real libnode native\nbuild."
1053
1053
  },
1054
1054
  {
1055
1055
  "id": "manual:runtime-train-validation",
@@ -1683,7 +1683,7 @@
1683
1683
  "path": "docs/lifecycle-protocol.md",
1684
1684
  "plane": "use",
1685
1685
  "exists": true,
1686
- "digest": "sha256:02e1be97935c0d9520ea22e8ef0dc61d9b860c347f07127074600cdca02713ce"
1686
+ "digest": "sha256:4d1ab96137beea2497b838018dcce793081c09dbcbbfd8ed2123cc58749f95cb"
1687
1687
  },
1688
1688
  {
1689
1689
  "id": "reusable-build-surface",
@@ -1691,7 +1691,7 @@
1691
1691
  "path": "docs/reusable-build-surface.md",
1692
1692
  "plane": "use",
1693
1693
  "exists": true,
1694
- "digest": "sha256:8c3e7a5e099c8eb0715bb6f58af90d261f8cb83d384f3281b8763f340277582b"
1694
+ "digest": "sha256:aae753c7fcf4bcb13c08af7b31cc83d898d36e41248b5f710ecee61035de3e8e"
1695
1695
  },
1696
1696
  {
1697
1697
  "id": "publish-transaction",
@@ -1699,7 +1699,7 @@
1699
1699
  "path": "docs/publish-transaction.md",
1700
1700
  "plane": "verify",
1701
1701
  "exists": true,
1702
- "digest": "sha256:dd45cf98f0847e3968f150a09bed84aa1f59cb2687ac988e9519f08dc9c91877"
1702
+ "digest": "sha256:d4758e222935f595f7d8a73fe2c33383898c0f760c5e5051c4f772db06491e7c"
1703
1703
  },
1704
1704
  {
1705
1705
  "id": "release-governance",
@@ -1707,7 +1707,7 @@
1707
1707
  "path": "docs/release-governance.md",
1708
1708
  "plane": "why",
1709
1709
  "exists": true,
1710
- "digest": "sha256:c7348c64e83ea1d154955e2fc0f7318a1c5582a3dfa6bdc5728958ddb418e92d"
1710
+ "digest": "sha256:53c969c38ede9c68e279ca191f2d5661e1d943225d7faa798179569670434ef9"
1711
1711
  },
1712
1712
  {
1713
1713
  "id": "release-flow",