@kungfu-tech/buildchain 2.11.13-alpha.1 → 2.11.13-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.
@@ -37,6 +37,12 @@ version commit itself is written through the GitHub Git Data API so the ref
37
37
  graph is the durable source of truth. Repositories without any supported version
38
38
  state degrade to ref-only promotion only when strict version state is disabled.
39
39
 
40
+ JSON and TOML version entries whose declared key already matches the requested
41
+ version are treated as semantic no-ops. TOML changes use a parser-verified
42
+ lossless key edit, so repository formatting is preserved and formatter-only
43
+ release-preparation commits are not created. If a unique lossless edit cannot
44
+ be proven, promotion fails closed instead of rewriting the full TOML document.
45
+
40
46
  ## Dry Run
41
47
 
42
48
  Use `dry-run: "true"` or the CLI `buildchain release --dry-run` before merging a
@@ -4,7 +4,7 @@
4
4
  "product": {
5
5
  "name": "Buildchain",
6
6
  "package": "@kungfu-tech/buildchain",
7
- "version": "2.11.13-alpha.1",
7
+ "version": "2.11.13-alpha.2",
8
8
  "repository": "https://github.com/kungfu-systems/buildchain"
9
9
  },
10
10
  "majorLine": "v2",
@@ -434,7 +434,7 @@
434
434
  "manual entries carry source file digests so downstream sites and agents can detect stale hand-written documentation"
435
435
  ],
436
436
  "breakingDigest": "sha256:7d0d2819e3a3e72989d9c57b5efe9d0bc0a79bc0f2c82a0c7b9d6c5a211a91f2",
437
- "auditDigest": "sha256:a74f1e2c4c9a2e4dd16be3d7febd36e450aa7905b7c7c449c3800e3f6a64df2a"
437
+ "auditDigest": "sha256:81d7d8564d218792a51fbcbf4b28a8d40fc21632d2dde474df84c32f9813a5af"
438
438
  },
439
439
  {
440
440
  "contractVersion": 1,
@@ -460,5 +460,5 @@
460
460
  }
461
461
  ],
462
462
  "compatibilityDigest": "sha256:967df7e854fdf582294ddfb6403eff25c00db9518a24051e7d47cbd75527c785",
463
- "contractDigest": "sha256:c84a6ab4605ed94362c10df1982b2667fbb10895938166b6f1be8ba7bd299536"
463
+ "contractDigest": "sha256:4fc35e3d052bacc53e43c55f93139ce872ce8614b3d60922f4e7baf3f53d25ba"
464
464
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-bundle",
4
- "generatedAt": "2026-07-10T05:53:36.658Z",
5
- "publishedAt": "2026-07-10T05:53:36.658Z",
4
+ "generatedAt": "2026-07-10T07:15:25.340Z",
5
+ "publishedAt": "2026-07-10T07:15:25.340Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "6bd30d696e19cf2b391b190c734e55886daa6ee3",
22
+ "sourceRevision": "a4ad35e4d1298e50fe2b51720b67fed851050d7d",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "package": {
39
39
  "name": "@kungfu-tech/buildchain",
40
- "version": "2.11.13-alpha.1",
40
+ "version": "2.11.13-alpha.2",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "source": {
@@ -177,7 +177,7 @@
177
177
  ],
178
178
  "maturity": "stable",
179
179
  "sourcePath": "actions/promote-buildchain-ref/README.md",
180
- "digest": "sha256:f1ca20e536f7c0de7ed85784a6b08edfd9aeeb0d2653ec4cff1f4edc2553caf5",
180
+ "digest": "sha256:6572e4db19394eefa04c612ca4960bc964a5ae0009528fb75961b6f5c2c112d9",
181
181
  "headings": [
182
182
  {
183
183
  "level": 1,
@@ -195,7 +195,7 @@
195
195
  "anchor": "publish-transactions"
196
196
  }
197
197
  ],
198
- "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\nstill run through promotion automation first. The action updates\nBuildchain-managed channel protection before generated bookkeeping, adds the\nauthenticated promotion token user or app to the bypass allowlist, creates the\nconfigured required check on the exact generated version-state commit, then\ntries to apply that commit directly. If GitHub still rejects release\nfinalization bookkeeping, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR based on the current target channel head and\nreturns `finalization-needed=true`; a later idempotent promotion run can resume\nfrom the durable transaction state. Strict alpha promotion still fails with a\nconfiguration diagnostic instead of opening a post-publish human PR. Reusable\nwrapper callers should allow `checks: write` so the generated check is owned by\nGitHub Actions and matches the managed branch protection rule.\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 generated-ref-update-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 promote workflow serializes non-dry-run promotion intents per\nrepository and re-reads `target-ref` before checkout, dependency installation,\nrelease-candidate resolution, or publish-gate writes. If a queued intent asks\nfor an older SHA after the protected channel has advanced, the workflow records\nthe requested/current SHA pair, verifies that the current target is ahead of\nthe requested commit, and exits successfully as a superseded no-op. Diverged,\nbehind, or unreadable comparisons still fail closed.\nThe action repeats that check at its mutation boundary for governed promotion\ncalls, closing the race between workflow preflight and action start. Direct\nnon-governed calls and dry-runs keep the strict target mismatch error so local\ndiagnostics cannot silently reinterpret a stale request.\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`, `public-release-tag`, `transaction-release-sha`,\n`transaction-state-ref`, `transaction-state-sha`, `transaction-state-path`,\n`publish-evidence-path`, and `release-passport-path`, `release-passport-output-dir`,\n`release-passport-state-sha`, and `finalization-needed`.\n`transaction-state-ref` is the durable recovery location.\n`release-passport-state-sha` is the durable ref commit after the generated\n`release-passport/*` files have been uploaded into that recovery ref.\n`finalization-needed=true` means publish evidence is valid, but protected branch\nor ref finalization needs a later idempotent promotion run. For release\nfinalization, Buildchain may create a same-repository generated version-state\nPR when GitHub rejects the direct protected ref update; that PR is Buildchain\nbookkeeping, not a consumer-authored release change.\nSet `github-release: \"true\"` when the semver promotion should also publish the\npublic GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `public-release-tag`, applies deterministic 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. For anchored/manual package releases, `public-release-tag` is derived\nfrom the published package version, while `transaction-exact-tag` remains the\ninternal Buildchain transaction ref for recovery and audit. If the transaction is\nnot complete yet, the action defers GitHub Release publication to the next\nidempotent promotion run.\n\nAfter a publish transaction reaches `complete`, the action generates the unified\n`buildchain-release-passport` in `.buildchain/release-passport` by default and\npersists those files under `release-passport/` in the durable release-state ref.\nSet `release-passport-product-name` to record the consumer product name, for\nexample `Libnode`, instead of the default `Buildchain`.\nSet `release-passport-kfd-1-witness-jsons` to newline-separated KFD-1\ncontract-world witness JSON paths when released artifacts must prove\nbyte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from\n`@kungfu-tech/kfd`, freezes the witness, verifies artifact bytes, and writes the\nresult under the KFD-provided `kfd-1` passport section.\nSet `release-passport-kfd-2-claim-jsons` to newline-separated KFD-2 public\nrelease trust claim JSON paths when a release makes additional human/agent\nvisible claims. Buildchain requires each public claim to bind declared sources,\nmachine-readable evidence, hashes, artifact coordinates, verification results,\naudit boundary, responsibility state, and residual risk. Unbound claims fail\npassport verification; prose-only claims downgrade the KFD-2 audit.\nSet `release-passport-kfd-3-prebuild-witness-jsons` to newline-separated KFD-3\ncollaboration-interface pre-build witness paths, then provide artifact-side\nevidence with `release-passport-kfd-3-artifact-witness-jsons` or a\nproduct-owned `release-passport-kfd-3-artifact-verify-command` such as\n`kungfu agent verify --json`. Buildchain compares declared shipped public\nsurfaces with artifact-exposed public surfaces and writes the result under the\nKFD-provided `kfd-3` passport section.\nBuildchain's own release workflow sets `release-passport-buildchain-self-kfd:\n\"true\"`. In that mode the action generates Buildchain-owned KFD-1/2/3 witnesses\ninside the final version-state workspace, after the release transaction has\nmaterialized generated files such as `package.json` and `dist/site/*`. This\nkeeps self-hosted KFD witness hashes bound to the exact published package\ninstead of to a pre-promotion checkout.\nWhen present, the passport includes the aggregate build summary, platform\nartifact manifests, npm publish evidence, dist-tag promotion evidence, the\nrelease-state ref, trusted publishing metadata, and the Buildchain transaction\nresult. After the first passport upload, Buildchain backfills the durable\nrelease-state SHA into `buildchain.release.json` and persists the passport\nagain, so the consumer-side passport is a complete audit entrypoint. Set\n`release-passport: \"false\"` only for a controlled recovery run that must skip\npassport generation.\n\nFinalization recovery is anchored to the durable transaction, not to a single\nworkflow run SHA. After generated version-state bookkeeping is applied, the\ncurrent channel head can be the generated version-state commit or a historical\nmerge commit that contains or corresponds to the recorded `release_material_sha`;\nit does not have to equal the original `source_sha` or the transaction\n`release_sha`. Reruns accept exact tags that already point at the transaction\nrelease/material SHA or the finalized channel head, and continue moving any\nmissing floating tags or dev/alpha refs before marking the transaction\n`complete`.\n\nNormal reruns accept already-published artifacts only when evidence matches.\nMissing required artifacts can be published on the next run. Conflicting\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`, or from a strict same-line\n `publish-gate/alpha/vN/vN.M/<version> -> alpha/vN/vN.M` source-lock PR;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`, or from a strict same-line\n `publish-gate/release/vN/vN.M/<version> -> release/vN/vN.M` source-lock PR;\n- major promotion must come from a merged same-repository PR\n `release/vN/vN.M -> publish-gate/major`;\n- release promotion must have an exact alpha tag for the same patch line, and\n the release source tree must match that alpha tag tree, so release does not\n introduce new code after alpha;\n- anchored/manual release promotion may differ from that alpha tree only in\n declared `version.files` and the configured anchor manifest, and only when the\n checked-out release material has passed `lifecycle.verify` or the explicit\n `verification-command`;\n- generated release and next-alpha version-state trees can be verified locally\n with either the `verification-command` input or `buildchain.toml`\n `lifecycle.verify` before any tags or channel refs move.\n\nThe promotion workflow should use `BUILDCHAIN_PROMOTION_TOKEN` for non-dry-run\npromotion. The token is the buildchain equivalent of the old ABV runner release\nauthority: protected branch review and check rules guard human channel merges,\nwhile the reusable build trust gate now checks the source-lock channel HEAD and\nmerged same-repository PR lineage before heavy build runners start. This action\nstill independently rechecks PR lineage, alpha/release tree equivalence, and\ngenerated version-state verification before moving channel refs and tags.\nGenerated version-state direct ref updates can use a separate\n`generated-ref-update-token`; the reusable wrapper defaults it to\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token`. Consumers that protect\n`dev/*`, `alpha/*`, or `release/*` with one required review should configure\n`BUILDCHAIN_PROMOTION_TOKEN` as the bypass-capable release authority, so\npost-publish dev/alpha/release bookkeeping completes without a human PR.\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."
198
+ "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\nJSON and TOML version entries whose declared key already matches the requested\nversion are treated as semantic no-ops. TOML changes use a parser-verified\nlossless key edit, so repository formatting is preserved and formatter-only\nrelease-preparation commits are not created. If a unique lossless edit cannot\nbe proven, promotion fails closed instead of rewriting the full TOML document.\n\n## Dry Run\n\nUse `dry-run: \"true\"` or the CLI `buildchain release --dry-run` before merging a\nchannel PR when you need to understand what Buildchain would do. This dry-run is\nat the Buildchain release-line level. It explains:\n\n- the legal source branch for the target channel;\n- exact release or alpha tags that would be created or reused;\n- floating tags and channel branches that would move;\n- version-state files and verification lifecycle that would apply;\n- branch protection, PR lineage, and release-from-alpha checks;\n- publish transaction behavior when `lifecycle.publish` or\n `publish-transaction` is enabled.\n\nIt does not move refs, move tags, write package files, run publish commands, or\npublish npm packages. The GitHub action dry-run still calls GitHub APIs to\nresolve the current target SHA and concrete pending ref updates, but every\nwrite is reported as a dry-run update.\n\nRepositories whose package version is anchored to an explicitly selected\nupstream release can opt into manual next-anchor behavior:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\n```\n\nIn this mode, the action validates the configured version files and anchor\nmanifest through the repository's verify lifecycle, but it does not rewrite the\npackage version to match the Buildchain release tag. After a production\nrelease, it sets `next-anchor-required=true` and does not auto-create the next\nalpha branch or tag. The repository must create the next upstream anchor line\nexplicitly, then run the normal channel promotion flow for that line.\n\nWhen branch protection requires pull requests, generated version-state commits\nstill run through promotion automation first. The action updates\nBuildchain-managed channel protection before generated bookkeeping, adds the\nauthenticated promotion token user or app to the bypass allowlist, creates the\nconfigured required check on the exact generated version-state commit, then\ntries to apply that commit directly. If GitHub still rejects release\nfinalization bookkeeping, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR based on the current target channel head and\nreturns `finalization-needed=true`; a later idempotent promotion run can resume\nfrom the durable transaction state. Strict alpha promotion still fails with a\nconfiguration diagnostic instead of opening a post-publish human PR. Reusable\nwrapper callers should allow `checks: write` so the generated check is owned by\nGitHub Actions and matches the managed branch protection rule.\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 generated-ref-update-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 promote workflow serializes non-dry-run promotion intents per\nrepository and re-reads `target-ref` before checkout, dependency installation,\nrelease-candidate resolution, or publish-gate writes. If a queued intent asks\nfor an older SHA after the protected channel has advanced, the workflow records\nthe requested/current SHA pair, verifies that the current target is ahead of\nthe requested commit, and exits successfully as a superseded no-op. Diverged,\nbehind, or unreadable comparisons still fail closed.\nThe action repeats that check at its mutation boundary for governed promotion\ncalls, closing the race between workflow preflight and action start. Direct\nnon-governed calls and dry-runs keep the strict target mismatch error so local\ndiagnostics cannot silently reinterpret a stale request.\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`, `public-release-tag`, `transaction-release-sha`,\n`transaction-state-ref`, `transaction-state-sha`, `transaction-state-path`,\n`publish-evidence-path`, and `release-passport-path`, `release-passport-output-dir`,\n`release-passport-state-sha`, and `finalization-needed`.\n`transaction-state-ref` is the durable recovery location.\n`release-passport-state-sha` is the durable ref commit after the generated\n`release-passport/*` files have been uploaded into that recovery ref.\n`finalization-needed=true` means publish evidence is valid, but protected branch\nor ref finalization needs a later idempotent promotion run. For release\nfinalization, Buildchain may create a same-repository generated version-state\nPR when GitHub rejects the direct protected ref update; that PR is Buildchain\nbookkeeping, not a consumer-authored release change.\nSet `github-release: \"true\"` when the semver promotion should also publish the\npublic GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `public-release-tag`, applies deterministic 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. For anchored/manual package releases, `public-release-tag` is derived\nfrom the published package version, while `transaction-exact-tag` remains the\ninternal Buildchain transaction ref for recovery and audit. If the transaction is\nnot complete yet, the action defers GitHub Release publication to the next\nidempotent promotion run.\n\nAfter a publish transaction reaches `complete`, the action generates the unified\n`buildchain-release-passport` in `.buildchain/release-passport` by default and\npersists those files under `release-passport/` in the durable release-state ref.\nSet `release-passport-product-name` to record the consumer product name, for\nexample `Libnode`, instead of the default `Buildchain`.\nSet `release-passport-kfd-1-witness-jsons` to newline-separated KFD-1\ncontract-world witness JSON paths when released artifacts must prove\nbyte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from\n`@kungfu-tech/kfd`, freezes the witness, verifies artifact bytes, and writes the\nresult under the KFD-provided `kfd-1` passport section.\nSet `release-passport-kfd-2-claim-jsons` to newline-separated KFD-2 public\nrelease trust claim JSON paths when a release makes additional human/agent\nvisible claims. Buildchain requires each public claim to bind declared sources,\nmachine-readable evidence, hashes, artifact coordinates, verification results,\naudit boundary, responsibility state, and residual risk. Unbound claims fail\npassport verification; prose-only claims downgrade the KFD-2 audit.\nSet `release-passport-kfd-3-prebuild-witness-jsons` to newline-separated KFD-3\ncollaboration-interface pre-build witness paths, then provide artifact-side\nevidence with `release-passport-kfd-3-artifact-witness-jsons` or a\nproduct-owned `release-passport-kfd-3-artifact-verify-command` such as\n`kungfu agent verify --json`. Buildchain compares declared shipped public\nsurfaces with artifact-exposed public surfaces and writes the result under the\nKFD-provided `kfd-3` passport section.\nBuildchain's own release workflow sets `release-passport-buildchain-self-kfd:\n\"true\"`. In that mode the action generates Buildchain-owned KFD-1/2/3 witnesses\ninside the final version-state workspace, after the release transaction has\nmaterialized generated files such as `package.json` and `dist/site/*`. This\nkeeps self-hosted KFD witness hashes bound to the exact published package\ninstead of to a pre-promotion checkout.\nWhen present, the passport includes the aggregate build summary, platform\nartifact manifests, npm publish evidence, dist-tag promotion evidence, the\nrelease-state ref, trusted publishing metadata, and the Buildchain transaction\nresult. After the first passport upload, Buildchain backfills the durable\nrelease-state SHA into `buildchain.release.json` and persists the passport\nagain, so the consumer-side passport is a complete audit entrypoint. Set\n`release-passport: \"false\"` only for a controlled recovery run that must skip\npassport generation.\n\nFinalization recovery is anchored to the durable transaction, not to a single\nworkflow run SHA. After generated version-state bookkeeping is applied, the\ncurrent channel head can be the generated version-state commit or a historical\nmerge commit that contains or corresponds to the recorded `release_material_sha`;\nit does not have to equal the original `source_sha` or the transaction\n`release_sha`. Reruns accept exact tags that already point at the transaction\nrelease/material SHA or the finalized channel head, and continue moving any\nmissing floating tags or dev/alpha refs before marking the transaction\n`complete`.\n\nNormal reruns accept already-published artifacts only when evidence matches.\nMissing required artifacts can be published on the next run. Conflicting\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`, or from a strict same-line\n `publish-gate/alpha/vN/vN.M/<version> -> alpha/vN/vN.M` source-lock PR;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`, or from a strict same-line\n `publish-gate/release/vN/vN.M/<version> -> release/vN/vN.M` source-lock PR;\n- major promotion must come from a merged same-repository PR\n `release/vN/vN.M -> publish-gate/major`;\n- release promotion must have an exact alpha tag for the same patch line, and\n the release source tree must match that alpha tag tree, so release does not\n introduce new code after alpha;\n- anchored/manual release promotion may differ from that alpha tree only in\n declared `version.files` and the configured anchor manifest, and only when the\n checked-out release material has passed `lifecycle.verify` or the explicit\n `verification-command`;\n- generated release and next-alpha version-state trees can be verified locally\n with either the `verification-command` input or `buildchain.toml`\n `lifecycle.verify` before any tags or channel refs move.\n\nThe promotion workflow should use `BUILDCHAIN_PROMOTION_TOKEN` for non-dry-run\npromotion. The token is the buildchain equivalent of the old ABV runner release\nauthority: protected branch review and check rules guard human channel merges,\nwhile the reusable build trust gate now checks the source-lock channel HEAD and\nmerged same-repository PR lineage before heavy build runners start. This action\nstill independently rechecks PR lineage, alpha/release tree equivalence, and\ngenerated version-state verification before moving channel refs and tags.\nGenerated version-state direct ref updates can use a separate\n`generated-ref-update-token`; the reusable wrapper defaults it to\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token`. Consumers that protect\n`dev/*`, `alpha/*`, or `release/*` with one required review should configure\n`BUILDCHAIN_PROMOTION_TOKEN` as the bypass-capable release authority, so\npost-publish dev/alpha/release bookkeeping completes without a human PR.\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."
199
199
  },
200
200
  {
201
201
  "id": "action:report-buildchain-issue",
@@ -678,7 +678,7 @@
678
678
  ],
679
679
  "maturity": "stable",
680
680
  "sourcePath": "docs/lifecycle-protocol.md",
681
- "digest": "sha256:191d706801abe5cea7927042b9cd7989f19cf868e726f8ac5852dff9490aa14f",
681
+ "digest": "sha256:284af389a01e3a9e4ca1b6e717b2b69a6f92ea6563a095712a0bc01e67a372f0",
682
682
  "headings": [
683
683
  {
684
684
  "level": 1,
@@ -761,7 +761,7 @@
761
761
  "anchor": "design-boundaries"
762
762
  }
763
763
  ],
764
- "markdown": "# Lifecycle Protocol\n\nBuildchain uses `.buildchain/buildchain.toml` as the v2 repository configuration format.\nThe file is optional for simple JavaScript repositories, but it is the preferred\nway to describe release version state and lifecycle commands when a project is\nnot a plain pnpm, npm, or yarn workspace.\n\nFor compatibility, Buildchain still reads a legacy root `buildchain.toml` when\n`.buildchain/buildchain.toml` is absent. New repositories should use the\n`.buildchain/` layout so all Buildchain-owned local state lives under one\ndirectory.\n\nOnly TOML is supported in v2. YAML, JSON, and JavaScript config files are not\nloaded.\n\n## Minimal File\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n`schema = 1` is required. Buildchain fails closed when the schema is missing or\nunknown.\n\n## Version State\n\nVersion state is the source file evidence that matches a release tag. During\npromotion, Buildchain writes the selected release or prerelease version into the\nconfigured files, verifies the resulting tree, creates a source version commit,\nthen moves exact and floating refs.\n\nSupported version file types:\n\n| Type | Use case | Required fields |\n| --- | --- | --- |\n| `json` | `package.json`, JSON manifests | `path`, `key` |\n| `toml` | `pyproject.toml`, other TOML manifests | `path`, `key` |\n| `regex` | `CMakeLists.txt`, `conanfile.py`, plain version files | `path`, `pattern`, `replacement` |\n\n`key` is a dotted key path:\n\n```toml\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n```\n\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. Buildchain also creates the configured required\ncheck on the exact generated version-state commit before patching the protected\nref, then applies the protected ref update with the declared generated ref\nupdate token. The reusable wrapper uses\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token` for that protected\nbookkeeping update. If release finalization bookkeeping is still rejected,\nBuildchain creates or reuses a same-repository `buildchain/version-state/*` PR\nfrom the current target channel head and reports `finalization-needed=true` so\na later idempotent promotion run can resume. Strict alpha bookkeeping remains\nfail-fast with a token/protection diagnostic instead of asking humans to review\na post-publish version-state PR.\n\nFor `version.strategy = \"anchored\"` with `version.next = \"manual\"`, release\npromotion does not generate a Buildchain-owned version-state commit. In that\nmode, a protected `alpha -> release` PR may carry the declared `version.files`\nfrom the tested alpha package version to the final package version, and may\ncarry the configured `version.manifest` with it. Buildchain only accepts that\nrelease tree difference when the PR is the valid channel-promotion PR, the\nchanged paths are limited to those declared version files plus the anchor\nmanifest, and `lifecycle.verify` or `verification-command` has validated the\nchecked-out release material. Any code or undeclared file change still fails the\nrelease tree gate.\n\nThe action input `verification-command` remains supported. When it is provided,\nit overrides `lifecycle.verify` for that invocation.\n\n## Migration Preflight\n\nHeavy repositories can validate their Buildchain declaration before they are\nready to run the real build. `actions/validate-config` checks that\n`.buildchain/buildchain.toml` parses, configured version-state files exist, configured\nversion keys are strings, and required lifecycle stage names are declared.\nFor web-surface repositories it also validates `project`, `channels`, `deploy`,\n`retention`, and `security` declarations.\n\nIt does not run lifecycle commands. This is useful for repositories such as\n`libnode`, where `lifecycle.build` represents an expensive multi-platform native\nbuild and the first migration milestone is to prove the release metadata and\nlifecycle protocol without consuming build runners.\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-version-state: \"true\"\n require-lifecycle-stages: \"install,build,verify\"\n```\n\nWeb-surface repositories can use the same action without requiring version\nstate:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-lifecycle-stages: \"build,verify\"\n```\n\nThe action exposes project and deploy metadata through outputs such as\n`project-type`, `project-site`, `channels`, and `deploy-adapters-json`.\n\n## Web-Surface Projects\n\n`project.type = \"web-surface\"` is for sites, docs, browser apps, and operator\nconsoles whose release object is a deployed surface, not a package version.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-libkungfu-dev\"\nsite = \"libkungfu-dev\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.libkungfu.dev\"\nvisibility = \"ephemeral\"\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.libkungfu.dev\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://libkungfu.dev\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[surfaces.hub]\npath = \"/\"\nproduction_url = \"https://libkungfu.dev\"\nstaging_url = \"https://staging.libkungfu.dev\"\npreview_url_pattern = \"https://{alias}.preview.libkungfu.dev\"\n\n[surfaces.core]\npath = \"/core/\"\nproduction_url = \"https://core.libkungfu.dev\"\nstaging_url = \"https://core.staging.libkungfu.dev\"\npreview_url_pattern = \"https://core-{alias}.preview.libkungfu.dev\"\n\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"libkungfu-dev-production\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n```\n\nSee [Web-surface deployments](web-surface-deployments.md) for named surface host\nmappings, the manifest, preview alias, retention, cleanup, and dry-run deploy\ncontract.\n\n## Infra-Contract Projects\n\n`project.type = \"infra-contract\"` is for infrastructure contract repositories\nthat need provider-neutral desired, plan, approval, apply, observe, contract,\nand propagation evidence. `buildchain init --type infra-contract` wires\n`lifecycle.verify` to `buildchain infra-contract --mode ci`, which writes\nmutation-free plan, contract, propagation dry-run, evidence bundle, and\nverification artifacts under `.buildchain/`. The surface supports\nmanual-observed, observe-only, and mocked adapter fixtures without reading\ncloud state files or executing live infrastructure mutation. See\n[Infra Contract](infra-contract.md).\n\n## Examples\n\n### Node Workspace\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n### Python Package\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n\n[lifecycle.install]\ncommand = \"python -m pip install -e .[test]\"\n\n[lifecycle.build]\ncommand = \"python -m build\"\n\n[lifecycle.verify]\ncommands = [\n \"python -m build\",\n \"pytest\",\n]\n```\n\n### CMake and Conan\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n\n[lifecycle.configure]\ncommands = [\n \"conan install . --build=missing\",\n \"cmake -S . -B build -DCMAKE_BUILD_TYPE=Release\",\n]\n\n[lifecycle.build]\ncommand = \"cmake --build build --config Release\"\n\n[lifecycle.verify]\ncommands = [\n \"cmake --build build --config Release\",\n \"ctest --test-dir build --output-on-failure\",\n]\n```\n\n### Docker Image\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.build]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} .\"\n\n[lifecycle.verify]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:verify .\"\n```\n\nDocker publishing is an external side effect and should be gated by a release\nworkflow after version-state promotion has been verified.\n\n## Design Boundaries\n\nThe lifecycle protocol is also the command source for the reusable build\nsurface. `.github/workflows/.build.yml` runs `lifecycle.install`,\n`lifecycle.build`, and `lifecycle.verify` by default, while allowing callers to\noverride each stage with explicit workflow inputs. The underlying\n`actions/run-lifecycle` action can be used directly by repositories that need a\ncustom workflow but still want Buildchain's lifecycle and deterministic manifest\ncontract.\n\nBuildchain lifecycle commands are data, not executable configuration files.\nThey make release behavior reviewable in pull requests and keep the release\nfact chain simple:\n\n1. choose the channel branch and release line;\n2. generate a source version commit from declared version files;\n3. verify that exact tree;\n4. move exact tags and floating refs only after verification succeeds;\n5. run publish or deployment side effects in separately gated workflows."
764
+ "markdown": "# Lifecycle Protocol\n\nBuildchain uses `.buildchain/buildchain.toml` as the v2 repository configuration format.\nThe file is optional for simple JavaScript repositories, but it is the preferred\nway to describe release version state and lifecycle commands when a project is\nnot a plain pnpm, npm, or yarn workspace.\n\nFor compatibility, Buildchain still reads a legacy root `buildchain.toml` when\n`.buildchain/buildchain.toml` is absent. New repositories should use the\n`.buildchain/` layout so all Buildchain-owned local state lives under one\ndirectory.\n\nOnly TOML is supported in v2. YAML, JSON, and JavaScript config files are not\nloaded.\n\n## Minimal File\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n`schema = 1` is required. Buildchain fails closed when the schema is missing or\nunknown.\n\n## Version State\n\nVersion state is the source file evidence that matches a release tag. During\npromotion, Buildchain writes the selected release or prerelease version into the\nconfigured files, verifies the resulting tree, creates a source version commit,\nthen moves exact and floating refs.\n\nSupported version file types:\n\n| Type | Use case | Required fields |\n| --- | --- | --- |\n| `json` | `package.json`, JSON manifests | `path`, `key` |\n| `toml` | `pyproject.toml`, other TOML manifests | `path`, `key` |\n| `regex` | `CMakeLists.txt`, `conanfile.py`, plain version files | `path`, `pattern`, `replacement` |\n\n`key` is a dotted key path:\n\n```toml\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n```\n\nConfigured JSON and TOML version files are semantic no-ops when the declared\nkey already equals the requested version. Buildchain preserves the repository's\noriginal TOML bytes in that case instead of serializing the whole document and\ncreating formatter-only release state. When a TOML version really changes,\nBuildchain applies a parser-verified lossless edit to that key and fails closed\nif it cannot prove a unique edit; unrelated arrays, comments, and formatting\nremain repository-owned.\n\nRegex files must expose the current version through a named capture group called\n`version`:\n\n```toml\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n```\n\nIf `version.required = true`, promotion fails when no configured version files\nare available.\n\n### Anchored Manual Versions\n\nSome repositories do not derive their package version from the Buildchain\nrelease tag. `libnode` is the canonical example: the package version is anchored\nto an explicitly selected upstream Node.js release such as `22.22.3-kf.0`, while\nthe channel line may be `release/v22/v22.22`.\n\nThose repositories can opt into anchored manual semantics:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\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. Buildchain also creates the configured required\ncheck on the exact generated version-state commit before patching the protected\nref, then applies the protected ref update with the declared generated ref\nupdate token. The reusable wrapper uses\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token` for that protected\nbookkeeping update. If release finalization bookkeeping is still rejected,\nBuildchain creates or reuses a same-repository `buildchain/version-state/*` PR\nfrom the current target channel head and reports `finalization-needed=true` so\na later idempotent promotion run can resume. Strict alpha bookkeeping remains\nfail-fast with a token/protection diagnostic instead of asking humans to review\na post-publish version-state PR.\n\nFor `version.strategy = \"anchored\"` with `version.next = \"manual\"`, release\npromotion does not generate a Buildchain-owned version-state commit. In that\nmode, a protected `alpha -> release` PR may carry the declared `version.files`\nfrom the tested alpha package version to the final package version, and may\ncarry the configured `version.manifest` with it. Buildchain only accepts that\nrelease tree difference when the PR is the valid channel-promotion PR, the\nchanged paths are limited to those declared version files plus the anchor\nmanifest, and `lifecycle.verify` or `verification-command` has validated the\nchecked-out release material. Any code or undeclared file change still fails the\nrelease tree gate.\n\nThe action input `verification-command` remains supported. When it is provided,\nit overrides `lifecycle.verify` for that invocation.\n\n## Migration Preflight\n\nHeavy repositories can validate their Buildchain declaration before they are\nready to run the real build. `actions/validate-config` checks that\n`.buildchain/buildchain.toml` parses, configured version-state files exist, configured\nversion keys are strings, and required lifecycle stage names are declared.\nFor web-surface repositories it also validates `project`, `channels`, `deploy`,\n`retention`, and `security` declarations.\n\nIt does not run lifecycle commands. This is useful for repositories such as\n`libnode`, where `lifecycle.build` represents an expensive multi-platform native\nbuild and the first migration milestone is to prove the release metadata and\nlifecycle protocol without consuming build runners.\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-version-state: \"true\"\n require-lifecycle-stages: \"install,build,verify\"\n```\n\nWeb-surface repositories can use the same action without requiring version\nstate:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-lifecycle-stages: \"build,verify\"\n```\n\nThe action exposes project and deploy metadata through outputs such as\n`project-type`, `project-site`, `channels`, and `deploy-adapters-json`.\n\n## Web-Surface Projects\n\n`project.type = \"web-surface\"` is for sites, docs, browser apps, and operator\nconsoles whose release object is a deployed surface, not a package version.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-libkungfu-dev\"\nsite = \"libkungfu-dev\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.libkungfu.dev\"\nvisibility = \"ephemeral\"\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.libkungfu.dev\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://libkungfu.dev\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[surfaces.hub]\npath = \"/\"\nproduction_url = \"https://libkungfu.dev\"\nstaging_url = \"https://staging.libkungfu.dev\"\npreview_url_pattern = \"https://{alias}.preview.libkungfu.dev\"\n\n[surfaces.core]\npath = \"/core/\"\nproduction_url = \"https://core.libkungfu.dev\"\nstaging_url = \"https://core.staging.libkungfu.dev\"\npreview_url_pattern = \"https://core-{alias}.preview.libkungfu.dev\"\n\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"libkungfu-dev-production\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n```\n\nSee [Web-surface deployments](web-surface-deployments.md) for named surface host\nmappings, the manifest, preview alias, retention, cleanup, and dry-run deploy\ncontract.\n\n## Infra-Contract Projects\n\n`project.type = \"infra-contract\"` is for infrastructure contract repositories\nthat need provider-neutral desired, plan, approval, apply, observe, contract,\nand propagation evidence. `buildchain init --type infra-contract` wires\n`lifecycle.verify` to `buildchain infra-contract --mode ci`, which writes\nmutation-free plan, contract, propagation dry-run, evidence bundle, and\nverification artifacts under `.buildchain/`. The surface supports\nmanual-observed, observe-only, and mocked adapter fixtures without reading\ncloud state files or executing live infrastructure mutation. See\n[Infra Contract](infra-contract.md).\n\n## Examples\n\n### Node Workspace\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n### Python Package\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n\n[lifecycle.install]\ncommand = \"python -m pip install -e .[test]\"\n\n[lifecycle.build]\ncommand = \"python -m build\"\n\n[lifecycle.verify]\ncommands = [\n \"python -m build\",\n \"pytest\",\n]\n```\n\n### CMake and Conan\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n\n[lifecycle.configure]\ncommands = [\n \"conan install . --build=missing\",\n \"cmake -S . -B build -DCMAKE_BUILD_TYPE=Release\",\n]\n\n[lifecycle.build]\ncommand = \"cmake --build build --config Release\"\n\n[lifecycle.verify]\ncommands = [\n \"cmake --build build --config Release\",\n \"ctest --test-dir build --output-on-failure\",\n]\n```\n\n### Docker Image\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.build]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} .\"\n\n[lifecycle.verify]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:verify .\"\n```\n\nDocker publishing is an external side effect and should be gated by a release\nworkflow after version-state promotion has been verified.\n\n## Design Boundaries\n\nThe lifecycle protocol is also the command source for the reusable build\nsurface. `.github/workflows/.build.yml` runs `lifecycle.install`,\n`lifecycle.build`, and `lifecycle.verify` by default, while allowing callers to\noverride each stage with explicit workflow inputs. The underlying\n`actions/run-lifecycle` action can be used directly by repositories that need a\ncustom workflow but still want Buildchain's lifecycle and deterministic manifest\ncontract.\n\nBuildchain lifecycle commands are data, not executable configuration files.\nThey make release behavior reviewable in pull requests and keep the release\nfact chain simple:\n\n1. choose the channel branch and release line;\n2. generate a source version commit from declared version files;\n3. verify that exact tree;\n4. move exact tags and floating refs only after verification succeeds;\n5. run publish or deployment side effects in separately gated workflows."
765
765
  },
766
766
  {
767
767
  "id": "manual:map",
@@ -2341,7 +2341,7 @@
2341
2341
  "path": "docs/lifecycle-protocol.md",
2342
2342
  "plane": "use",
2343
2343
  "exists": true,
2344
- "digest": "sha256:191d706801abe5cea7927042b9cd7989f19cf868e726f8ac5852dff9490aa14f"
2344
+ "digest": "sha256:284af389a01e3a9e4ca1b6e717b2b69a6f92ea6563a095712a0bc01e67a372f0"
2345
2345
  },
2346
2346
  {
2347
2347
  "id": "reusable-build-surface",
@@ -21,7 +21,7 @@
21
21
  "contract": "kungfu-buildchain-public-surface-reverse-audit",
22
22
  "path": "dist/site/public-surface-audit.json",
23
23
  "status": "passed",
24
- "sha256": "b4f8f62e9db46d620bab4afc96a9630fc1cb2c9814fb2c2d7b3605852b7eb49a",
24
+ "sha256": "4f7a5b660e4043b3124bd73ff45685f928a56aa3c1e9927277456b41d2eefcbf",
25
25
  "summary": {
26
26
  "cliCommandCount": 74,
27
27
  "workflowCount": 37,
@@ -208,7 +208,7 @@
208
208
  "contract": "kungfu-buildchain-public-surface-reverse-audit",
209
209
  "path": "dist/site/public-surface-audit.json",
210
210
  "status": "passed",
211
- "sha256": "b4f8f62e9db46d620bab4afc96a9630fc1cb2c9814fb2c2d7b3605852b7eb49a",
211
+ "sha256": "4f7a5b660e4043b3124bd73ff45685f928a56aa3c1e9927277456b41d2eefcbf",
212
212
  "summary": {
213
213
  "cliCommandCount": 74,
214
214
  "workflowCount": 37,
@@ -4,7 +4,7 @@
4
4
  "product": {
5
5
  "id": "kungfu-tech-buildchain",
6
6
  "name": "@kungfu-tech/buildchain",
7
- "version": "2.11.13-alpha.1",
7
+ "version": "2.11.13-alpha.2",
8
8
  "repository": "https://github.com/kungfu-systems/buildchain"
9
9
  },
10
10
  "source": {
@@ -259,7 +259,7 @@
259
259
  "title": "Lifecycle protocol",
260
260
  "path": "docs/lifecycle-protocol.md",
261
261
  "plane": "use",
262
- "digest": "sha256:191d706801abe5cea7927042b9cd7989f19cf868e726f8ac5852dff9490aa14f",
262
+ "digest": "sha256:284af389a01e3a9e4ca1b6e717b2b69a6f92ea6563a095712a0bc01e67a372f0",
263
263
  "capabilityGroup": "reusable-build",
264
264
  "audience": [
265
265
  "consumer",
@@ -113,7 +113,7 @@
113
113
  ],
114
114
  "maturity": "stable",
115
115
  "sourcePath": "actions/promote-buildchain-ref/README.md",
116
- "digest": "sha256:f1ca20e536f7c0de7ed85784a6b08edfd9aeeb0d2653ec4cff1f4edc2553caf5",
116
+ "digest": "sha256:6572e4db19394eefa04c612ca4960bc964a5ae0009528fb75961b6f5c2c112d9",
117
117
  "headings": [
118
118
  {
119
119
  "level": 1,
@@ -131,7 +131,7 @@
131
131
  "anchor": "publish-transactions"
132
132
  }
133
133
  ],
134
- "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\nstill run through promotion automation first. The action updates\nBuildchain-managed channel protection before generated bookkeeping, adds the\nauthenticated promotion token user or app to the bypass allowlist, creates the\nconfigured required check on the exact generated version-state commit, then\ntries to apply that commit directly. If GitHub still rejects release\nfinalization bookkeeping, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR based on the current target channel head and\nreturns `finalization-needed=true`; a later idempotent promotion run can resume\nfrom the durable transaction state. Strict alpha promotion still fails with a\nconfiguration diagnostic instead of opening a post-publish human PR. Reusable\nwrapper callers should allow `checks: write` so the generated check is owned by\nGitHub Actions and matches the managed branch protection rule.\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 generated-ref-update-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 promote workflow serializes non-dry-run promotion intents per\nrepository and re-reads `target-ref` before checkout, dependency installation,\nrelease-candidate resolution, or publish-gate writes. If a queued intent asks\nfor an older SHA after the protected channel has advanced, the workflow records\nthe requested/current SHA pair, verifies that the current target is ahead of\nthe requested commit, and exits successfully as a superseded no-op. Diverged,\nbehind, or unreadable comparisons still fail closed.\nThe action repeats that check at its mutation boundary for governed promotion\ncalls, closing the race between workflow preflight and action start. Direct\nnon-governed calls and dry-runs keep the strict target mismatch error so local\ndiagnostics cannot silently reinterpret a stale request.\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`, `public-release-tag`, `transaction-release-sha`,\n`transaction-state-ref`, `transaction-state-sha`, `transaction-state-path`,\n`publish-evidence-path`, and `release-passport-path`, `release-passport-output-dir`,\n`release-passport-state-sha`, and `finalization-needed`.\n`transaction-state-ref` is the durable recovery location.\n`release-passport-state-sha` is the durable ref commit after the generated\n`release-passport/*` files have been uploaded into that recovery ref.\n`finalization-needed=true` means publish evidence is valid, but protected branch\nor ref finalization needs a later idempotent promotion run. For release\nfinalization, Buildchain may create a same-repository generated version-state\nPR when GitHub rejects the direct protected ref update; that PR is Buildchain\nbookkeeping, not a consumer-authored release change.\nSet `github-release: \"true\"` when the semver promotion should also publish the\npublic GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `public-release-tag`, applies deterministic 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. For anchored/manual package releases, `public-release-tag` is derived\nfrom the published package version, while `transaction-exact-tag` remains the\ninternal Buildchain transaction ref for recovery and audit. If the transaction is\nnot complete yet, the action defers GitHub Release publication to the next\nidempotent promotion run.\n\nAfter a publish transaction reaches `complete`, the action generates the unified\n`buildchain-release-passport` in `.buildchain/release-passport` by default and\npersists those files under `release-passport/` in the durable release-state ref.\nSet `release-passport-product-name` to record the consumer product name, for\nexample `Libnode`, instead of the default `Buildchain`.\nSet `release-passport-kfd-1-witness-jsons` to newline-separated KFD-1\ncontract-world witness JSON paths when released artifacts must prove\nbyte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from\n`@kungfu-tech/kfd`, freezes the witness, verifies artifact bytes, and writes the\nresult under the KFD-provided `kfd-1` passport section.\nSet `release-passport-kfd-2-claim-jsons` to newline-separated KFD-2 public\nrelease trust claim JSON paths when a release makes additional human/agent\nvisible claims. Buildchain requires each public claim to bind declared sources,\nmachine-readable evidence, hashes, artifact coordinates, verification results,\naudit boundary, responsibility state, and residual risk. Unbound claims fail\npassport verification; prose-only claims downgrade the KFD-2 audit.\nSet `release-passport-kfd-3-prebuild-witness-jsons` to newline-separated KFD-3\ncollaboration-interface pre-build witness paths, then provide artifact-side\nevidence with `release-passport-kfd-3-artifact-witness-jsons` or a\nproduct-owned `release-passport-kfd-3-artifact-verify-command` such as\n`kungfu agent verify --json`. Buildchain compares declared shipped public\nsurfaces with artifact-exposed public surfaces and writes the result under the\nKFD-provided `kfd-3` passport section.\nBuildchain's own release workflow sets `release-passport-buildchain-self-kfd:\n\"true\"`. In that mode the action generates Buildchain-owned KFD-1/2/3 witnesses\ninside the final version-state workspace, after the release transaction has\nmaterialized generated files such as `package.json` and `dist/site/*`. This\nkeeps self-hosted KFD witness hashes bound to the exact published package\ninstead of to a pre-promotion checkout.\nWhen present, the passport includes the aggregate build summary, platform\nartifact manifests, npm publish evidence, dist-tag promotion evidence, the\nrelease-state ref, trusted publishing metadata, and the Buildchain transaction\nresult. After the first passport upload, Buildchain backfills the durable\nrelease-state SHA into `buildchain.release.json` and persists the passport\nagain, so the consumer-side passport is a complete audit entrypoint. Set\n`release-passport: \"false\"` only for a controlled recovery run that must skip\npassport generation.\n\nFinalization recovery is anchored to the durable transaction, not to a single\nworkflow run SHA. After generated version-state bookkeeping is applied, the\ncurrent channel head can be the generated version-state commit or a historical\nmerge commit that contains or corresponds to the recorded `release_material_sha`;\nit does not have to equal the original `source_sha` or the transaction\n`release_sha`. Reruns accept exact tags that already point at the transaction\nrelease/material SHA or the finalized channel head, and continue moving any\nmissing floating tags or dev/alpha refs before marking the transaction\n`complete`.\n\nNormal reruns accept already-published artifacts only when evidence matches.\nMissing required artifacts can be published on the next run. Conflicting\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`, or from a strict same-line\n `publish-gate/alpha/vN/vN.M/<version> -> alpha/vN/vN.M` source-lock PR;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`, or from a strict same-line\n `publish-gate/release/vN/vN.M/<version> -> release/vN/vN.M` source-lock PR;\n- major promotion must come from a merged same-repository PR\n `release/vN/vN.M -> publish-gate/major`;\n- release promotion must have an exact alpha tag for the same patch line, and\n the release source tree must match that alpha tag tree, so release does not\n introduce new code after alpha;\n- anchored/manual release promotion may differ from that alpha tree only in\n declared `version.files` and the configured anchor manifest, and only when the\n checked-out release material has passed `lifecycle.verify` or the explicit\n `verification-command`;\n- generated release and next-alpha version-state trees can be verified locally\n with either the `verification-command` input or `buildchain.toml`\n `lifecycle.verify` before any tags or channel refs move.\n\nThe promotion workflow should use `BUILDCHAIN_PROMOTION_TOKEN` for non-dry-run\npromotion. The token is the buildchain equivalent of the old ABV runner release\nauthority: protected branch review and check rules guard human channel merges,\nwhile the reusable build trust gate now checks the source-lock channel HEAD and\nmerged same-repository PR lineage before heavy build runners start. This action\nstill independently rechecks PR lineage, alpha/release tree equivalence, and\ngenerated version-state verification before moving channel refs and tags.\nGenerated version-state direct ref updates can use a separate\n`generated-ref-update-token`; the reusable wrapper defaults it to\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token`. Consumers that protect\n`dev/*`, `alpha/*`, or `release/*` with one required review should configure\n`BUILDCHAIN_PROMOTION_TOKEN` as the bypass-capable release authority, so\npost-publish dev/alpha/release bookkeeping completes without a human PR.\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."
134
+ "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\nJSON and TOML version entries whose declared key already matches the requested\nversion are treated as semantic no-ops. TOML changes use a parser-verified\nlossless key edit, so repository formatting is preserved and formatter-only\nrelease-preparation commits are not created. If a unique lossless edit cannot\nbe proven, promotion fails closed instead of rewriting the full TOML document.\n\n## Dry Run\n\nUse `dry-run: \"true\"` or the CLI `buildchain release --dry-run` before merging a\nchannel PR when you need to understand what Buildchain would do. This dry-run is\nat the Buildchain release-line level. It explains:\n\n- the legal source branch for the target channel;\n- exact release or alpha tags that would be created or reused;\n- floating tags and channel branches that would move;\n- version-state files and verification lifecycle that would apply;\n- branch protection, PR lineage, and release-from-alpha checks;\n- publish transaction behavior when `lifecycle.publish` or\n `publish-transaction` is enabled.\n\nIt does not move refs, move tags, write package files, run publish commands, or\npublish npm packages. The GitHub action dry-run still calls GitHub APIs to\nresolve the current target SHA and concrete pending ref updates, but every\nwrite is reported as a dry-run update.\n\nRepositories whose package version is anchored to an explicitly selected\nupstream release can opt into manual next-anchor behavior:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\n```\n\nIn this mode, the action validates the configured version files and anchor\nmanifest through the repository's verify lifecycle, but it does not rewrite the\npackage version to match the Buildchain release tag. After a production\nrelease, it sets `next-anchor-required=true` and does not auto-create the next\nalpha branch or tag. The repository must create the next upstream anchor line\nexplicitly, then run the normal channel promotion flow for that line.\n\nWhen branch protection requires pull requests, generated version-state commits\nstill run through promotion automation first. The action updates\nBuildchain-managed channel protection before generated bookkeeping, adds the\nauthenticated promotion token user or app to the bypass allowlist, creates the\nconfigured required check on the exact generated version-state commit, then\ntries to apply that commit directly. If GitHub still rejects release\nfinalization bookkeeping, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR based on the current target channel head and\nreturns `finalization-needed=true`; a later idempotent promotion run can resume\nfrom the durable transaction state. Strict alpha promotion still fails with a\nconfiguration diagnostic instead of opening a post-publish human PR. Reusable\nwrapper callers should allow `checks: write` so the generated check is owned by\nGitHub Actions and matches the managed branch protection rule.\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 generated-ref-update-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 promote workflow serializes non-dry-run promotion intents per\nrepository and re-reads `target-ref` before checkout, dependency installation,\nrelease-candidate resolution, or publish-gate writes. If a queued intent asks\nfor an older SHA after the protected channel has advanced, the workflow records\nthe requested/current SHA pair, verifies that the current target is ahead of\nthe requested commit, and exits successfully as a superseded no-op. Diverged,\nbehind, or unreadable comparisons still fail closed.\nThe action repeats that check at its mutation boundary for governed promotion\ncalls, closing the race between workflow preflight and action start. Direct\nnon-governed calls and dry-runs keep the strict target mismatch error so local\ndiagnostics cannot silently reinterpret a stale request.\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`, `public-release-tag`, `transaction-release-sha`,\n`transaction-state-ref`, `transaction-state-sha`, `transaction-state-path`,\n`publish-evidence-path`, and `release-passport-path`, `release-passport-output-dir`,\n`release-passport-state-sha`, and `finalization-needed`.\n`transaction-state-ref` is the durable recovery location.\n`release-passport-state-sha` is the durable ref commit after the generated\n`release-passport/*` files have been uploaded into that recovery ref.\n`finalization-needed=true` means publish evidence is valid, but protected branch\nor ref finalization needs a later idempotent promotion run. For release\nfinalization, Buildchain may create a same-repository generated version-state\nPR when GitHub rejects the direct protected ref update; that PR is Buildchain\nbookkeeping, not a consumer-authored release change.\nSet `github-release: \"true\"` when the semver promotion should also publish the\npublic GitHub Release. After the release transaction reaches `complete` and\n`finalization-needed` is false, the action creates or updates the GitHub Release\nfor `public-release-tag`, applies deterministic 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. For anchored/manual package releases, `public-release-tag` is derived\nfrom the published package version, while `transaction-exact-tag` remains the\ninternal Buildchain transaction ref for recovery and audit. If the transaction is\nnot complete yet, the action defers GitHub Release publication to the next\nidempotent promotion run.\n\nAfter a publish transaction reaches `complete`, the action generates the unified\n`buildchain-release-passport` in `.buildchain/release-passport` by default and\npersists those files under `release-passport/` in the durable release-state ref.\nSet `release-passport-product-name` to record the consumer product name, for\nexample `Libnode`, instead of the default `Buildchain`.\nSet `release-passport-kfd-1-witness-jsons` to newline-separated KFD-1\ncontract-world witness JSON paths when released artifacts must prove\nbyte-for-byte KFD contract surfaces. Buildchain imports the KFD metadata from\n`@kungfu-tech/kfd`, freezes the witness, verifies artifact bytes, and writes the\nresult under the KFD-provided `kfd-1` passport section.\nSet `release-passport-kfd-2-claim-jsons` to newline-separated KFD-2 public\nrelease trust claim JSON paths when a release makes additional human/agent\nvisible claims. Buildchain requires each public claim to bind declared sources,\nmachine-readable evidence, hashes, artifact coordinates, verification results,\naudit boundary, responsibility state, and residual risk. Unbound claims fail\npassport verification; prose-only claims downgrade the KFD-2 audit.\nSet `release-passport-kfd-3-prebuild-witness-jsons` to newline-separated KFD-3\ncollaboration-interface pre-build witness paths, then provide artifact-side\nevidence with `release-passport-kfd-3-artifact-witness-jsons` or a\nproduct-owned `release-passport-kfd-3-artifact-verify-command` such as\n`kungfu agent verify --json`. Buildchain compares declared shipped public\nsurfaces with artifact-exposed public surfaces and writes the result under the\nKFD-provided `kfd-3` passport section.\nBuildchain's own release workflow sets `release-passport-buildchain-self-kfd:\n\"true\"`. In that mode the action generates Buildchain-owned KFD-1/2/3 witnesses\ninside the final version-state workspace, after the release transaction has\nmaterialized generated files such as `package.json` and `dist/site/*`. This\nkeeps self-hosted KFD witness hashes bound to the exact published package\ninstead of to a pre-promotion checkout.\nWhen present, the passport includes the aggregate build summary, platform\nartifact manifests, npm publish evidence, dist-tag promotion evidence, the\nrelease-state ref, trusted publishing metadata, and the Buildchain transaction\nresult. After the first passport upload, Buildchain backfills the durable\nrelease-state SHA into `buildchain.release.json` and persists the passport\nagain, so the consumer-side passport is a complete audit entrypoint. Set\n`release-passport: \"false\"` only for a controlled recovery run that must skip\npassport generation.\n\nFinalization recovery is anchored to the durable transaction, not to a single\nworkflow run SHA. After generated version-state bookkeeping is applied, the\ncurrent channel head can be the generated version-state commit or a historical\nmerge commit that contains or corresponds to the recorded `release_material_sha`;\nit does not have to equal the original `source_sha` or the transaction\n`release_sha`. Reruns accept exact tags that already point at the transaction\nrelease/material SHA or the finalized channel head, and continue moving any\nmissing floating tags or dev/alpha refs before marking the transaction\n`complete`.\n\nNormal reruns accept already-published artifacts only when evidence matches.\nMissing required artifacts can be published on the next run. Conflicting\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`, or from a strict same-line\n `publish-gate/alpha/vN/vN.M/<version> -> alpha/vN/vN.M` source-lock PR;\n- release promotion must come from a merged same-repository PR\n `alpha/vN/vN.M -> release/vN/vN.M`, or from a strict same-line\n `publish-gate/release/vN/vN.M/<version> -> release/vN/vN.M` source-lock PR;\n- major promotion must come from a merged same-repository PR\n `release/vN/vN.M -> publish-gate/major`;\n- release promotion must have an exact alpha tag for the same patch line, and\n the release source tree must match that alpha tag tree, so release does not\n introduce new code after alpha;\n- anchored/manual release promotion may differ from that alpha tree only in\n declared `version.files` and the configured anchor manifest, and only when the\n checked-out release material has passed `lifecycle.verify` or the explicit\n `verification-command`;\n- generated release and next-alpha version-state trees can be verified locally\n with either the `verification-command` input or `buildchain.toml`\n `lifecycle.verify` before any tags or channel refs move.\n\nThe promotion workflow should use `BUILDCHAIN_PROMOTION_TOKEN` for non-dry-run\npromotion. The token is the buildchain equivalent of the old ABV runner release\nauthority: protected branch review and check rules guard human channel merges,\nwhile the reusable build trust gate now checks the source-lock channel HEAD and\nmerged same-repository PR lineage before heavy build runners start. This action\nstill independently rechecks PR lineage, alpha/release tree equivalence, and\ngenerated version-state verification before moving channel refs and tags.\nGenerated version-state direct ref updates can use a separate\n`generated-ref-update-token`; the reusable wrapper defaults it to\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token`. Consumers that protect\n`dev/*`, `alpha/*`, or `release/*` with one required review should configure\n`BUILDCHAIN_PROMOTION_TOKEN` as the bypass-capable release authority, so\npost-publish dev/alpha/release bookkeeping completes without a human PR.\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."
135
135
  },
136
136
  {
137
137
  "id": "action:report-buildchain-issue",
@@ -614,7 +614,7 @@
614
614
  ],
615
615
  "maturity": "stable",
616
616
  "sourcePath": "docs/lifecycle-protocol.md",
617
- "digest": "sha256:191d706801abe5cea7927042b9cd7989f19cf868e726f8ac5852dff9490aa14f",
617
+ "digest": "sha256:284af389a01e3a9e4ca1b6e717b2b69a6f92ea6563a095712a0bc01e67a372f0",
618
618
  "headings": [
619
619
  {
620
620
  "level": 1,
@@ -697,7 +697,7 @@
697
697
  "anchor": "design-boundaries"
698
698
  }
699
699
  ],
700
- "markdown": "# Lifecycle Protocol\n\nBuildchain uses `.buildchain/buildchain.toml` as the v2 repository configuration format.\nThe file is optional for simple JavaScript repositories, but it is the preferred\nway to describe release version state and lifecycle commands when a project is\nnot a plain pnpm, npm, or yarn workspace.\n\nFor compatibility, Buildchain still reads a legacy root `buildchain.toml` when\n`.buildchain/buildchain.toml` is absent. New repositories should use the\n`.buildchain/` layout so all Buildchain-owned local state lives under one\ndirectory.\n\nOnly TOML is supported in v2. YAML, JSON, and JavaScript config files are not\nloaded.\n\n## Minimal File\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n`schema = 1` is required. Buildchain fails closed when the schema is missing or\nunknown.\n\n## Version State\n\nVersion state is the source file evidence that matches a release tag. During\npromotion, Buildchain writes the selected release or prerelease version into the\nconfigured files, verifies the resulting tree, creates a source version commit,\nthen moves exact and floating refs.\n\nSupported version file types:\n\n| Type | Use case | Required fields |\n| --- | --- | --- |\n| `json` | `package.json`, JSON manifests | `path`, `key` |\n| `toml` | `pyproject.toml`, other TOML manifests | `path`, `key` |\n| `regex` | `CMakeLists.txt`, `conanfile.py`, plain version files | `path`, `pattern`, `replacement` |\n\n`key` is a dotted key path:\n\n```toml\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n```\n\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. Buildchain also creates the configured required\ncheck on the exact generated version-state commit before patching the protected\nref, then applies the protected ref update with the declared generated ref\nupdate token. The reusable wrapper uses\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token` for that protected\nbookkeeping update. If release finalization bookkeeping is still rejected,\nBuildchain creates or reuses a same-repository `buildchain/version-state/*` PR\nfrom the current target channel head and reports `finalization-needed=true` so\na later idempotent promotion run can resume. Strict alpha bookkeeping remains\nfail-fast with a token/protection diagnostic instead of asking humans to review\na post-publish version-state PR.\n\nFor `version.strategy = \"anchored\"` with `version.next = \"manual\"`, release\npromotion does not generate a Buildchain-owned version-state commit. In that\nmode, a protected `alpha -> release` PR may carry the declared `version.files`\nfrom the tested alpha package version to the final package version, and may\ncarry the configured `version.manifest` with it. Buildchain only accepts that\nrelease tree difference when the PR is the valid channel-promotion PR, the\nchanged paths are limited to those declared version files plus the anchor\nmanifest, and `lifecycle.verify` or `verification-command` has validated the\nchecked-out release material. Any code or undeclared file change still fails the\nrelease tree gate.\n\nThe action input `verification-command` remains supported. When it is provided,\nit overrides `lifecycle.verify` for that invocation.\n\n## Migration Preflight\n\nHeavy repositories can validate their Buildchain declaration before they are\nready to run the real build. `actions/validate-config` checks that\n`.buildchain/buildchain.toml` parses, configured version-state files exist, configured\nversion keys are strings, and required lifecycle stage names are declared.\nFor web-surface repositories it also validates `project`, `channels`, `deploy`,\n`retention`, and `security` declarations.\n\nIt does not run lifecycle commands. This is useful for repositories such as\n`libnode`, where `lifecycle.build` represents an expensive multi-platform native\nbuild and the first migration milestone is to prove the release metadata and\nlifecycle protocol without consuming build runners.\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-version-state: \"true\"\n require-lifecycle-stages: \"install,build,verify\"\n```\n\nWeb-surface repositories can use the same action without requiring version\nstate:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-lifecycle-stages: \"build,verify\"\n```\n\nThe action exposes project and deploy metadata through outputs such as\n`project-type`, `project-site`, `channels`, and `deploy-adapters-json`.\n\n## Web-Surface Projects\n\n`project.type = \"web-surface\"` is for sites, docs, browser apps, and operator\nconsoles whose release object is a deployed surface, not a package version.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-libkungfu-dev\"\nsite = \"libkungfu-dev\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.libkungfu.dev\"\nvisibility = \"ephemeral\"\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.libkungfu.dev\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://libkungfu.dev\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[surfaces.hub]\npath = \"/\"\nproduction_url = \"https://libkungfu.dev\"\nstaging_url = \"https://staging.libkungfu.dev\"\npreview_url_pattern = \"https://{alias}.preview.libkungfu.dev\"\n\n[surfaces.core]\npath = \"/core/\"\nproduction_url = \"https://core.libkungfu.dev\"\nstaging_url = \"https://core.staging.libkungfu.dev\"\npreview_url_pattern = \"https://core-{alias}.preview.libkungfu.dev\"\n\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"libkungfu-dev-production\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n```\n\nSee [Web-surface deployments](web-surface-deployments.md) for named surface host\nmappings, the manifest, preview alias, retention, cleanup, and dry-run deploy\ncontract.\n\n## Infra-Contract Projects\n\n`project.type = \"infra-contract\"` is for infrastructure contract repositories\nthat need provider-neutral desired, plan, approval, apply, observe, contract,\nand propagation evidence. `buildchain init --type infra-contract` wires\n`lifecycle.verify` to `buildchain infra-contract --mode ci`, which writes\nmutation-free plan, contract, propagation dry-run, evidence bundle, and\nverification artifacts under `.buildchain/`. The surface supports\nmanual-observed, observe-only, and mocked adapter fixtures without reading\ncloud state files or executing live infrastructure mutation. See\n[Infra Contract](infra-contract.md).\n\n## Examples\n\n### Node Workspace\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n### Python Package\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n\n[lifecycle.install]\ncommand = \"python -m pip install -e .[test]\"\n\n[lifecycle.build]\ncommand = \"python -m build\"\n\n[lifecycle.verify]\ncommands = [\n \"python -m build\",\n \"pytest\",\n]\n```\n\n### CMake and Conan\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n\n[lifecycle.configure]\ncommands = [\n \"conan install . --build=missing\",\n \"cmake -S . -B build -DCMAKE_BUILD_TYPE=Release\",\n]\n\n[lifecycle.build]\ncommand = \"cmake --build build --config Release\"\n\n[lifecycle.verify]\ncommands = [\n \"cmake --build build --config Release\",\n \"ctest --test-dir build --output-on-failure\",\n]\n```\n\n### Docker Image\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.build]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} .\"\n\n[lifecycle.verify]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:verify .\"\n```\n\nDocker publishing is an external side effect and should be gated by a release\nworkflow after version-state promotion has been verified.\n\n## Design Boundaries\n\nThe lifecycle protocol is also the command source for the reusable build\nsurface. `.github/workflows/.build.yml` runs `lifecycle.install`,\n`lifecycle.build`, and `lifecycle.verify` by default, while allowing callers to\noverride each stage with explicit workflow inputs. The underlying\n`actions/run-lifecycle` action can be used directly by repositories that need a\ncustom workflow but still want Buildchain's lifecycle and deterministic manifest\ncontract.\n\nBuildchain lifecycle commands are data, not executable configuration files.\nThey make release behavior reviewable in pull requests and keep the release\nfact chain simple:\n\n1. choose the channel branch and release line;\n2. generate a source version commit from declared version files;\n3. verify that exact tree;\n4. move exact tags and floating refs only after verification succeeds;\n5. run publish or deployment side effects in separately gated workflows."
700
+ "markdown": "# Lifecycle Protocol\n\nBuildchain uses `.buildchain/buildchain.toml` as the v2 repository configuration format.\nThe file is optional for simple JavaScript repositories, but it is the preferred\nway to describe release version state and lifecycle commands when a project is\nnot a plain pnpm, npm, or yarn workspace.\n\nFor compatibility, Buildchain still reads a legacy root `buildchain.toml` when\n`.buildchain/buildchain.toml` is absent. New repositories should use the\n`.buildchain/` layout so all Buildchain-owned local state lives under one\ndirectory.\n\nOnly TOML is supported in v2. YAML, JSON, and JavaScript config files are not\nloaded.\n\n## Minimal File\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n`schema = 1` is required. Buildchain fails closed when the schema is missing or\nunknown.\n\n## Version State\n\nVersion state is the source file evidence that matches a release tag. During\npromotion, Buildchain writes the selected release or prerelease version into the\nconfigured files, verifies the resulting tree, creates a source version commit,\nthen moves exact and floating refs.\n\nSupported version file types:\n\n| Type | Use case | Required fields |\n| --- | --- | --- |\n| `json` | `package.json`, JSON manifests | `path`, `key` |\n| `toml` | `pyproject.toml`, other TOML manifests | `path`, `key` |\n| `regex` | `CMakeLists.txt`, `conanfile.py`, plain version files | `path`, `pattern`, `replacement` |\n\n`key` is a dotted key path:\n\n```toml\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n```\n\nConfigured JSON and TOML version files are semantic no-ops when the declared\nkey already equals the requested version. Buildchain preserves the repository's\noriginal TOML bytes in that case instead of serializing the whole document and\ncreating formatter-only release state. When a TOML version really changes,\nBuildchain applies a parser-verified lossless edit to that key and fails closed\nif it cannot prove a unique edit; unrelated arrays, comments, and formatting\nremain repository-owned.\n\nRegex files must expose the current version through a named capture group called\n`version`:\n\n```toml\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n```\n\nIf `version.required = true`, promotion fails when no configured version files\nare available.\n\n### Anchored Manual Versions\n\nSome repositories do not derive their package version from the Buildchain\nrelease tag. `libnode` is the canonical example: the package version is anchored\nto an explicitly selected upstream Node.js release such as `22.22.3-kf.0`, while\nthe channel line may be `release/v22/v22.22`.\n\nThose repositories can opt into anchored manual semantics:\n\n```toml\n[version]\nrequired = true\nstrategy = \"anchored\"\nnext = \"manual\"\nmanifest = \"libnode.release.json\"\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. Buildchain also creates the configured required\ncheck on the exact generated version-state commit before patching the protected\nref, then applies the protected ref update with the declared generated ref\nupdate token. The reusable wrapper uses\n`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token` for that protected\nbookkeeping update. If release finalization bookkeeping is still rejected,\nBuildchain creates or reuses a same-repository `buildchain/version-state/*` PR\nfrom the current target channel head and reports `finalization-needed=true` so\na later idempotent promotion run can resume. Strict alpha bookkeeping remains\nfail-fast with a token/protection diagnostic instead of asking humans to review\na post-publish version-state PR.\n\nFor `version.strategy = \"anchored\"` with `version.next = \"manual\"`, release\npromotion does not generate a Buildchain-owned version-state commit. In that\nmode, a protected `alpha -> release` PR may carry the declared `version.files`\nfrom the tested alpha package version to the final package version, and may\ncarry the configured `version.manifest` with it. Buildchain only accepts that\nrelease tree difference when the PR is the valid channel-promotion PR, the\nchanged paths are limited to those declared version files plus the anchor\nmanifest, and `lifecycle.verify` or `verification-command` has validated the\nchecked-out release material. Any code or undeclared file change still fails the\nrelease tree gate.\n\nThe action input `verification-command` remains supported. When it is provided,\nit overrides `lifecycle.verify` for that invocation.\n\n## Migration Preflight\n\nHeavy repositories can validate their Buildchain declaration before they are\nready to run the real build. `actions/validate-config` checks that\n`.buildchain/buildchain.toml` parses, configured version-state files exist, configured\nversion keys are strings, and required lifecycle stage names are declared.\nFor web-surface repositories it also validates `project`, `channels`, `deploy`,\n`retention`, and `security` declarations.\n\nIt does not run lifecycle commands. This is useful for repositories such as\n`libnode`, where `lifecycle.build` represents an expensive multi-platform native\nbuild and the first migration milestone is to prove the release metadata and\nlifecycle protocol without consuming build runners.\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-version-state: \"true\"\n require-lifecycle-stages: \"install,build,verify\"\n```\n\nWeb-surface repositories can use the same action without requiring version\nstate:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/validate-config@v2\n with:\n require-lifecycle-stages: \"build,verify\"\n```\n\nThe action exposes project and deploy metadata through outputs such as\n`project-type`, `project-site`, `channels`, and `deploy-adapters-json`.\n\n## Web-Surface Projects\n\n`project.type = \"web-surface\"` is for sites, docs, browser apps, and operator\nconsoles whose release object is a deployed surface, not a package version.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-libkungfu-dev\"\nsite = \"libkungfu-dev\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.libkungfu.dev\"\nvisibility = \"ephemeral\"\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.libkungfu.dev\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://libkungfu.dev\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[surfaces.hub]\npath = \"/\"\nproduction_url = \"https://libkungfu.dev\"\nstaging_url = \"https://staging.libkungfu.dev\"\npreview_url_pattern = \"https://{alias}.preview.libkungfu.dev\"\n\n[surfaces.core]\npath = \"/core/\"\nproduction_url = \"https://core.libkungfu.dev\"\nstaging_url = \"https://core.staging.libkungfu.dev\"\npreview_url_pattern = \"https://core-{alias}.preview.libkungfu.dev\"\n\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"libkungfu-dev-production\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n```\n\nSee [Web-surface deployments](web-surface-deployments.md) for named surface host\nmappings, the manifest, preview alias, retention, cleanup, and dry-run deploy\ncontract.\n\n## Infra-Contract Projects\n\n`project.type = \"infra-contract\"` is for infrastructure contract repositories\nthat need provider-neutral desired, plan, approval, apply, observe, contract,\nand propagation evidence. `buildchain init --type infra-contract` wires\n`lifecycle.verify` to `buildchain infra-contract --mode ci`, which writes\nmutation-free plan, contract, propagation dry-run, evidence bundle, and\nverification artifacts under `.buildchain/`. The surface supports\nmanual-observed, observe-only, and mocked adapter fixtures without reading\ncloud state files or executing live infrastructure mutation. See\n[Infra Contract](infra-contract.md).\n\n## Examples\n\n### Node Workspace\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.verify]\ncommands = [\n \"pnpm run check\",\n]\n```\n\n### Python Package\n\n```toml\nschema = 1\n\n[version]\nrequired = true\n\n[[version.files]]\ntype = \"toml\"\npath = \"pyproject.toml\"\nkey = \"project.version\"\n\n[lifecycle.install]\ncommand = \"python -m pip install -e .[test]\"\n\n[lifecycle.build]\ncommand = \"python -m build\"\n\n[lifecycle.verify]\ncommands = [\n \"python -m build\",\n \"pytest\",\n]\n```\n\n### CMake and Conan\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"regex\"\npath = \"CMakeLists.txt\"\npattern = 'project\\([^)]* VERSION (?<version>[^ )]+)'\nreplacement = '${version}'\n\n[lifecycle.configure]\ncommands = [\n \"conan install . --build=missing\",\n \"cmake -S . -B build -DCMAKE_BUILD_TYPE=Release\",\n]\n\n[lifecycle.build]\ncommand = \"cmake --build build --config Release\"\n\n[lifecycle.verify]\ncommands = [\n \"cmake --build build --config Release\",\n \"ctest --test-dir build --output-on-failure\",\n]\n```\n\n### Docker Image\n\n```toml\nschema = 1\n\n[[version.files]]\ntype = \"json\"\npath = \"package.json\"\nkey = \"version\"\n\n[lifecycle.build]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} .\"\n\n[lifecycle.verify]\ncommand = \"docker build -f Dockerfile -t kungfutrader/example:verify .\"\n```\n\nDocker publishing is an external side effect and should be gated by a release\nworkflow after version-state promotion has been verified.\n\n## Design Boundaries\n\nThe lifecycle protocol is also the command source for the reusable build\nsurface. `.github/workflows/.build.yml` runs `lifecycle.install`,\n`lifecycle.build`, and `lifecycle.verify` by default, while allowing callers to\noverride each stage with explicit workflow inputs. The underlying\n`actions/run-lifecycle` action can be used directly by repositories that need a\ncustom workflow but still want Buildchain's lifecycle and deterministic manifest\ncontract.\n\nBuildchain lifecycle commands are data, not executable configuration files.\nThey make release behavior reviewable in pull requests and keep the release\nfact chain simple:\n\n1. choose the channel branch and release line;\n2. generate a source version commit from declared version files;\n3. verify that exact tree;\n4. move exact tags and floating refs only after verification succeeds;\n5. run publish or deployment side effects in separately gated workflows."
701
701
  },
702
702
  {
703
703
  "id": "manual:map",
@@ -2504,7 +2504,7 @@
2504
2504
  "pageRegistryPath": "dist/site/page-registry.json",
2505
2505
  "cliRegistryDigest": "fb33fe38423f56ebf95919e2b58e13dc2f544aee10169bc87d7afe980bc1b142",
2506
2506
  "workflowRegistryDigest": "4041321df08640a802a87ff0fbe8584d95dc416945b43778727adb820cab9097",
2507
- "pageRegistryDigest": "303b9f332b3849ebfeae626d09a1238c5431e438df17b8eb1ff4aa7e686dbf20"
2507
+ "pageRegistryDigest": "f80056fc4305d3429fe34655cecc34303e59473dfad558f2449100189739283b"
2508
2508
  },
2509
2509
  "comparison": {
2510
2510
  "missingCliRegistry": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-10T05:53:36.658Z",
5
- "publishedAt": "2026-07-10T05:53:36.658Z",
4
+ "generatedAt": "2026-07-10T07:15:25.340Z",
5
+ "publishedAt": "2026-07-10T07:15:25.340Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "6bd30d696e19cf2b391b190c734e55886daa6ee3",
22
+ "sourceRevision": "a4ad35e4d1298e50fe2b51720b67fed851050d7d",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "package": {
34
34
  "name": "@kungfu-tech/buildchain",
35
- "version": "2.11.13-alpha.1",
35
+ "version": "2.11.13-alpha.2",
36
36
  "versionSource": "package.json#version"
37
37
  },
38
38
  "sourceKind": "package-site-bundle",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-manifest",
4
- "generatedAt": "2026-07-10T05:53:36.658Z",
5
- "publishedAt": "2026-07-10T05:53:36.658Z",
4
+ "generatedAt": "2026-07-10T07:15:25.340Z",
5
+ "publishedAt": "2026-07-10T07:15:25.340Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "6bd30d696e19cf2b391b190c734e55886daa6ee3",
22
+ "sourceRevision": "a4ad35e4d1298e50fe2b51720b67fed851050d7d",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "package": {
39
39
  "name": "@kungfu-tech/buildchain",
40
- "version": "2.11.13-alpha.1",
40
+ "version": "2.11.13-alpha.2",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -197,7 +197,7 @@
197
197
  "path": "docs/lifecycle-protocol.md",
198
198
  "plane": "use",
199
199
  "exists": true,
200
- "digest": "sha256:191d706801abe5cea7927042b9cd7989f19cf868e726f8ac5852dff9490aa14f"
200
+ "digest": "sha256:284af389a01e3a9e4ca1b6e717b2b69a6f92ea6563a095712a0bc01e67a372f0"
201
201
  },
202
202
  {
203
203
  "id": "reusable-build-surface",
@@ -59,6 +59,14 @@ path = "pyproject.toml"
59
59
  key = "project.version"
60
60
  ```
61
61
 
62
+ Configured JSON and TOML version files are semantic no-ops when the declared
63
+ key already equals the requested version. Buildchain preserves the repository's
64
+ original TOML bytes in that case instead of serializing the whole document and
65
+ creating formatter-only release state. When a TOML version really changes,
66
+ Buildchain applies a parser-verified lossless edit to that key and fails closed
67
+ if it cannot prove a unique edit; unrelated arrays, comments, and formatting
68
+ remain repository-owned.
69
+
62
70
  Regex files must expose the current version through a named capture group called
63
71
  `version`:
64
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.11.13-alpha.1",
3
+ "version": "2.11.13-alpha.2",
4
4
  "private": false,
5
5
  "description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
6
6
  "repository": "https://github.com/kungfu-systems/buildchain",
@@ -2,7 +2,8 @@ import { execSync } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
- import { parse, stringify } from "smol-toml";
5
+ import { isDeepStrictEqual } from "node:util";
6
+ import { parse } from "smol-toml";
6
7
  import {
7
8
  BUILDCHAIN_CONFIG_PATH,
8
9
  resolveBuildchainConfigPath,
@@ -103,6 +104,44 @@ function setByDottedKey(target, key, value) {
103
104
  current[segments[segments.length - 1]] = value;
104
105
  }
105
106
 
107
+ function updateTomlStringValuePreservingSource({ source, content, key, value, filePath }) {
108
+ const currentValue = getByDottedKey(content, key);
109
+ if (currentValue === value) {
110
+ return source;
111
+ }
112
+ if (typeof currentValue !== "string" || currentValue.length === 0) {
113
+ throw new Error(`Configured TOML version key is missing or empty: ${filePath}:${key}`);
114
+ }
115
+
116
+ const expected = structuredClone(content);
117
+ setByDottedKey(expected, key, value);
118
+ const candidates = [];
119
+ let offset = 0;
120
+ while (offset <= source.length - currentValue.length) {
121
+ const index = source.indexOf(currentValue, offset);
122
+ if (index === -1) {
123
+ break;
124
+ }
125
+ const candidate = `${source.slice(0, index)}${value}${source.slice(index + currentValue.length)}`;
126
+ try {
127
+ if (isDeepStrictEqual(parse(candidate), expected)) {
128
+ candidates.push(candidate);
129
+ }
130
+ } catch {
131
+ // Keep searching. Only a parser-confirmed single-field update is safe.
132
+ }
133
+ offset = index + currentValue.length;
134
+ }
135
+
136
+ if (candidates.length !== 1) {
137
+ throw new Error(
138
+ `Configured TOML version key cannot be updated losslessly: ${filePath}:${key} ` +
139
+ `(matching candidates: ${candidates.length})`,
140
+ );
141
+ }
142
+ return candidates[0];
143
+ }
144
+
106
145
  export function loadBuildchainConfig(cwd = process.cwd()) {
107
146
  const configPath = resolveBuildchainConfigPath(cwd);
108
147
  const filePath = path.join(cwd, configPath);
@@ -1359,14 +1398,23 @@ export function updateConfiguredVersionStateContents(files, version) {
1359
1398
  return files
1360
1399
  .map((file) => {
1361
1400
  let content;
1362
- if (file.type === "json") {
1401
+ if (
1402
+ (file.type === "json" || file.type === "toml") &&
1403
+ getByDottedKey(file.content, file.key) === version
1404
+ ) {
1405
+ content = file.source;
1406
+ } else if (file.type === "json") {
1363
1407
  const next = structuredClone(file.content);
1364
1408
  setByDottedKey(next, file.key, version);
1365
1409
  content = `${JSON.stringify(next, null, 2)}\n`;
1366
1410
  } else if (file.type === "toml") {
1367
- const next = structuredClone(file.content);
1368
- setByDottedKey(next, file.key, version);
1369
- content = stringify(next);
1411
+ content = updateTomlStringValuePreservingSource({
1412
+ source: file.source,
1413
+ content: file.content,
1414
+ key: file.key,
1415
+ value: version,
1416
+ filePath: file.path,
1417
+ });
1370
1418
  } else if (file.type === "regex") {
1371
1419
  content = file.source.replace(file.pattern, (...args) => {
1372
1420
  const groups = args.at(-1) || {};