@kungfu-tech/buildchain 3.0.2 → 3.0.3-alpha.0

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.
Files changed (52) hide show
  1. package/actions/report-buildchain-issue/README.md +1 -1
  2. package/contracts/buildchain-v2-residuals-v1.json +191 -0
  3. package/dist/site/buildchain-contract.json +58 -35
  4. package/dist/site/buildchain-site.json +33 -33
  5. package/dist/site/controller-registry.json +24 -3
  6. package/dist/site/kfd-claims.json +11 -6
  7. package/dist/site/kfd-upstream-aggregate.json +1 -1
  8. package/dist/site/manual-registry.json +7 -7
  9. package/dist/site/node-api-registry.json +6 -6
  10. package/dist/site/page-registry.json +22 -22
  11. package/dist/site/public-surface-audit.json +10 -5
  12. package/dist/site/publication-registry.json +4 -4
  13. package/dist/site/site-manifest.json +11 -11
  14. package/dist/site/workflow-registry.json +8 -3
  15. package/docs/auditable-demo.md +19 -1
  16. package/docs/consumer-issue-reporting.md +1 -1
  17. package/docs/dev-alpha-candidate-patrol.md +24 -10
  18. package/docs/github-governance-authority.md +23 -30
  19. package/docs/observed-evidence-patrol.md +28 -12
  20. package/docs/release-governance.md +8 -1
  21. package/docs/reusable-build-surface.md +124 -64
  22. package/docs/shifu-gate-profiles.md +7 -1
  23. package/docs/versioning.md +41 -21
  24. package/docs/web-surface-deployments.md +20 -1
  25. package/package.json +1 -1
  26. package/packages/core/artifact-signing.js +1 -0
  27. package/packages/core/buildchain-contract.js +1 -0
  28. package/packages/core/controller-evidence.js +2 -0
  29. package/packages/core/github-governance-authority.js +25 -17
  30. package/packages/core/publication-control-plane-audit.js +28 -0
  31. package/packages/core/release-passport.js +36 -27
  32. package/packages/core/stable-release-gate.js +4 -1
  33. package/scripts/artifact-signing-delegation.mjs +268 -0
  34. package/scripts/audit-github-governance.mjs +32 -13
  35. package/scripts/audit-publication-control-plane.mjs +17 -8
  36. package/scripts/auditable-demo.mjs +147 -2
  37. package/scripts/buildchain-patrol.mjs +1 -1
  38. package/scripts/check-inventory.mjs +1 -0
  39. package/scripts/dev-alpha-candidate-patrol.mjs +20 -0
  40. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  41. package/scripts/finalize-native-artifact-signing-result.mjs +114 -31
  42. package/scripts/gate-profile-core.mjs +6 -1
  43. package/scripts/inspect-artifact-signing-requests.mjs +45 -14
  44. package/scripts/observed-evidence.mjs +151 -33
  45. package/scripts/reconcile-github-governance.mjs +8 -1
  46. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  47. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  48. package/scripts/runtime-ref-core.mjs +13 -2
  49. package/scripts/seal-artifact-signing-requests.mjs +51 -6
  50. package/scripts/stable-candidate-qualification.mjs +8 -0
  51. package/scripts/verify-artifact-signing-results.mjs +26 -1
  52. package/scripts/web-surface-production-decision.mjs +19 -3
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-bundle",
4
- "generatedAt": "2026-07-30T07:24:06.316Z",
5
- "publishedAt": "2026-07-30T07:24:06.316Z",
4
+ "generatedAt": "2026-07-30T09:49:04.119Z",
5
+ "publishedAt": "2026-07-30T09:49:04.119Z",
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": "1aeb9191f607a7223f8ab63c96427b605ecda9be",
22
+ "sourceRevision": "5770bd104905c6a7ac9fe6b0513ca9e24e5814b4",
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": "3.0.2",
40
+ "version": "3.0.3-alpha.0",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "source": {
@@ -255,7 +255,7 @@
255
255
  ],
256
256
  "maturity": "stable",
257
257
  "sourcePath": "actions/report-buildchain-issue/README.md",
258
- "digest": "sha256:8698d41afb3d884c09413ac7dd69035994273da2523cd08ada4d714fd66a8341",
258
+ "digest": "sha256:359276d97a6a92bfec3a58e469c2a414ba8d4174701082eecccc4382d85ceaa1",
259
259
  "headings": [
260
260
  {
261
261
  "level": 1,
@@ -263,7 +263,7 @@
263
263
  "anchor": "report-buildchain-issue"
264
264
  }
265
265
  ],
266
- "markdown": "# report-buildchain-issue\n\nCreate or update a Buildchain repository issue from a consumer workflow or from\nBuildchain's own workflow-friction feedback loop.\n\nThis action is intended for consumer repositories that need to report\nBuildchain-owned failures with enough evidence for maintainers to act. It\nrequires a token that can write issues on the target Buildchain repository.\nFor cross-repository consumers, generate that token with a GitHub App\ninstallation token or another scoped credential owned by the consumer\norganization.\n\n```yaml\n- uses: actions/create-github-app-token@v2\n id: buildchain-issue-token\n with:\n app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n owner: kungfu-systems\n repositories: buildchain\n\n- uses: kungfu-systems/buildchain/actions/report-buildchain-issue@v3\n if: failure()\n with:\n token: ${{ steps.buildchain-issue-token.outputs.token }}\n summary: \"Reusable build failed before artifact finalization\"\n failure-code: reusable-build-failed\n buildchain-ref: ${{ inputs.buildchain-ref || 'v2' }}\n diagnostics-path: .buildchain/artifacts/diagnostics.json\n```\n\nThe action computes a stable fingerprint from the consumer repository,\nworkflow, job, failure code, and Buildchain ref. When an open issue already\nexists for that fingerprint, it comments with the new run instead of opening a\nduplicate issue.\n\nBy default issue reporting is fail-soft:\n\n- `fail-on-error: \"false\"` prevents a reporting outage from hiding the original\n build failure.\n- transient GitHub API 429/5xx errors and connection failures are retried.\n- if a configured label is missing, issue creation is retried without labels.\n- if issue creation/commenting is still unavailable, the action writes the full\n copyable issue title, fingerprint, and body into the workflow summary.\n- common token, private-key, password, and authorization values are redacted\n before submission.\n\nUse `dry-run: \"true\"` to verify the computed fingerprint and body shape without\ncalling GitHub.\n\nFor Buildchain-owned workflow friction, use `report-kind: workflow-friction`.\nThis uses a separate marker and default labels so duplicate PRs, duplicate\nbuilds, transient API failures, stale release-state, or missing RC evidence can\nbe grouped without mixing with consumer failure reports:\n\n```yaml\npermissions:\n issues: write\n\nsteps:\n - uses: actions/create-github-app-token@v2\n id: buildchain-issue-token\n with:\n app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n owner: kungfu-systems\n repositories: buildchain\n\n - uses: kungfu-systems/buildchain/actions/report-buildchain-issue@v3\n if: failure()\n with:\n token: ${{ steps.buildchain-issue-token.outputs.token || secrets.BUILDCHAIN_ISSUE_TOKEN || secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token }}\n report-kind: workflow-friction\n target-repository: kungfu-systems/buildchain\n repository: ${{ github.repository }}\n workflow: ${{ github.workflow }}\n run-id: ${{ github.run_id }}\n run-attempt: ${{ github.run_attempt }}\n channel: alpha/v2/v2.4\n source-sha: ${{ github.sha }}\n friction-class: duplicate-build\n related-runs-json: ${{ steps.classify.outputs.related-runs-json }}\n heavy-builds-json: ${{ steps.classify.outputs.heavy-builds-json }}\n comment-cooldown-hours: \"24\"\n```\n\n`comment-cooldown-hours` is optional. When it is greater than zero, Buildchain\nstill deduplicates by fingerprint but skips adding another comment if the\nexisting issue already received a recent update."
266
+ "markdown": "# report-buildchain-issue\n\nCreate or update a Buildchain repository issue from a consumer workflow or from\nBuildchain's own workflow-friction feedback loop.\n\nThis action is intended for consumer repositories that need to report\nBuildchain-owned failures with enough evidence for maintainers to act. It\nrequires a token that can write issues on the target Buildchain repository.\nFor cross-repository consumers, generate that token with a GitHub App\ninstallation token or another scoped credential owned by the consumer\norganization.\n\n```yaml\n- uses: actions/create-github-app-token@v2\n id: buildchain-issue-token\n with:\n app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n owner: kungfu-systems\n repositories: buildchain\n\n- uses: kungfu-systems/buildchain/actions/report-buildchain-issue@v3\n if: failure()\n with:\n token: ${{ steps.buildchain-issue-token.outputs.token }}\n summary: \"Reusable build failed before artifact finalization\"\n failure-code: reusable-build-failed\n buildchain-ref: ${{ inputs.buildchain-ref || 'v3' }}\n diagnostics-path: .buildchain/artifacts/diagnostics.json\n```\n\nThe action computes a stable fingerprint from the consumer repository,\nworkflow, job, failure code, and Buildchain ref. When an open issue already\nexists for that fingerprint, it comments with the new run instead of opening a\nduplicate issue.\n\nBy default issue reporting is fail-soft:\n\n- `fail-on-error: \"false\"` prevents a reporting outage from hiding the original\n build failure.\n- transient GitHub API 429/5xx errors and connection failures are retried.\n- if a configured label is missing, issue creation is retried without labels.\n- if issue creation/commenting is still unavailable, the action writes the full\n copyable issue title, fingerprint, and body into the workflow summary.\n- common token, private-key, password, and authorization values are redacted\n before submission.\n\nUse `dry-run: \"true\"` to verify the computed fingerprint and body shape without\ncalling GitHub.\n\nFor Buildchain-owned workflow friction, use `report-kind: workflow-friction`.\nThis uses a separate marker and default labels so duplicate PRs, duplicate\nbuilds, transient API failures, stale release-state, or missing RC evidence can\nbe grouped without mixing with consumer failure reports:\n\n```yaml\npermissions:\n issues: write\n\nsteps:\n - uses: actions/create-github-app-token@v2\n id: buildchain-issue-token\n with:\n app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n owner: kungfu-systems\n repositories: buildchain\n\n - uses: kungfu-systems/buildchain/actions/report-buildchain-issue@v3\n if: failure()\n with:\n token: ${{ steps.buildchain-issue-token.outputs.token || secrets.BUILDCHAIN_ISSUE_TOKEN || secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token }}\n report-kind: workflow-friction\n target-repository: kungfu-systems/buildchain\n repository: ${{ github.repository }}\n workflow: ${{ github.workflow }}\n run-id: ${{ github.run_id }}\n run-attempt: ${{ github.run_attempt }}\n channel: alpha/v2/v2.4\n source-sha: ${{ github.sha }}\n friction-class: duplicate-build\n related-runs-json: ${{ steps.classify.outputs.related-runs-json }}\n heavy-builds-json: ${{ steps.classify.outputs.heavy-builds-json }}\n comment-cooldown-hours: \"24\"\n```\n\n`comment-cooldown-hours` is optional. When it is greater than zero, Buildchain\nstill deduplicates by fingerprint but skips adding another comment if the\nexisting issue already received a recent update."
267
267
  },
268
268
  {
269
269
  "id": "action:run-lifecycle",
@@ -390,7 +390,7 @@
390
390
  ],
391
391
  "maturity": "preview",
392
392
  "sourcePath": "docs/auditable-demo.md",
393
- "digest": "sha256:0e8cf2162c2e1f83cfe3a6171e3055f494cab5f03fdcbec3854e1793678e6908",
393
+ "digest": "sha256:6af21e489bc3d2e9e2d01838926f1fbab6d74d92446d1f9cfc5eb08e090dd3a5",
394
394
  "headings": [
395
395
  {
396
396
  "level": 1,
@@ -428,7 +428,7 @@
428
428
  "anchor": "failure-evidence"
429
429
  }
430
430
  ],
431
- "markdown": "---\nstatus: draft\nperiod: 2026-07\ntheme: auditable-demo-pipeline\ndoc_type: technical-contract\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-28\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-25\n invisible_context_boundary: No hidden model build, parameter count, or private corpus is asserted.\n---\n\n# Auditable Demo Pipeline\n\nBuildchain's auditable demo workflow turns an exact GitHub build artifact into\ntwo distinct evidence products:\n\n1. a required qualified Gate bundle; and\n2. an optional rendered media bundle that can exist only after that exact Gate\n bundle passes.\n\nThe public reusable workflow is\n`.github/workflows/.auditable-demo.yml`. It is consumer-neutral: Buildchain\ndoes not know how a Kungfu, library, service, or application artifact should be\ninterpreted. The consumer owns a small checked-in executable adapter.\n\n## Authority Boundary\n\nThe retained build output is authoritative. The adapter reads that exact\nartifact and projects three files:\n\n```text\ncomplete-transcript.txt\npublic-projection.json\nscene.json\n```\n\nThe adapter must not rebuild or rerun the product. It receives:\n\n```text\n--artifact-root PATH\n--output PATH\n--source-coordinate PATH\n```\n\n`--source-coordinate` identifies the caller repository, run, artifact id,\nartifact name, upload digest, expiry, and exact source SHA. The workflow finds\nexactly one live artifact with the requested name in the current caller run and\nrejects a digest mismatch before invoking the adapter. Callers must pass the\ndigest emitted by their own `upload-artifact` step; a name resolved later from\nthe Actions API is discovery evidence, not a substitute for that producer\noutput.\n\nBuildchain's reusable build workflow exposes `artifact-coordinates-json` after\nall resolved platform uploads complete. That producer-owned output binds every\nplatform id to its same-run artifact id, name, upload digest, URL, and expiry,\nso a consumer that delegates its build to Buildchain can pass an exact\ncoordinate without rediscovering authority in a downstream job. The build\naggregate fails closed if any declared platform lacks one live, digest-bearing\nartifact coordinate. The compact coordinate set is sorted by platform id so\ndownstream machine consumers do not depend on matrix completion order.\n\nThe adapter runs with a disposable Home/XDG/npm prefix, a minimal environment,\nand no GitHub, npm, or cloud credential injection. It must be a regular,\nnon-symlink, executable file inside the exact checked-out consumer source.\n\n## Required Gate\n\nThe Gate:\n\n- checks out the exact consumer source and exact called-workflow SHA;\n- resolves and downloads one exact same-run GitHub Artifact;\n- invokes the checked-in adapter by argv, never as an evaluated shell string;\n- rejects undeclared adapter outputs, symlinks, invalid UTF-8, invalid scene or\n projection schemas, out-of-range transcript references, and oversized input;\n- derives a one-second compatibility scene from the consumer projection;\n- anonymously pulls an immutable `image@sha256:digest` renderer;\n- runs it as non-root with `--network none`, a read-only root filesystem, and a\n bounded tmpfs;\n- verifies the renderer manifest, media probe, exact input roots, exact output\n member set, and complete checksums;\n- uploads a content-addressed qualified bundle plus an independent GitHub\n Artifact id, URL, archive digest, and expiry-bearing source coordinate.\n\nThe Gate bundle contains the complete consumer transcript/projection/scene,\nsource artifact coordinate, adapter identity, bounded renderer evidence, a\npassed gate receipt, and checksums covering every member exactly once.\n\n## Selective Render\n\n`render-media: true` enables the second job. It downloads the just-uploaded Gate\nbundle by its content-addressed name, recomputes the Gate member root, verifies\nthe exact source SHA and renderer digest, and only then renders the complete\nqualified scene.\n\nThe media bundle contains MP4, WebM, GIF, poster, probe, renderer manifest,\nrenderer checksums, passed Gate receipt, a versioned media receipt, and\ndistribution checksums. A web-delivery profile also retains\n`media-inspection.json`, whose content root is bound into the receipt.\n`render-media: false` does not weaken or skip the Gate.\n\n## Media Qualification Profiles\n\nThe single machine-readable source is\n`contracts/auditable-demo-media-profiles-v1.json`. Callers select one reviewed\nprofile through `media-profile`; they cannot pass ffmpeg commands, codec flags,\nshell fragments, arbitrary profile paths, or transcoding instructions.\n\n| Profile | Meaning |\n| --- | --- |\n| `archive-v1` | Default compatibility contract. Retains the exact renderer outputs and classifies GIF as README compatibility evidence without making a browser-delivery claim. |\n| `web-delivery-v1` | Independently qualifies H.264 MP4 and VP9 WebM playback sources, forbids audio, requires exact scene dimensions and bounded duration/frame-rate drift, checks per-rendition byte ceilings, and proves MP4 `moov` precedes `mdat`. PNG remains the lossless evidence poster. |\n| `site-hero-v1` | Extends `web-delivery-v1` and additionally requires a qualified WebP browser poster. The current Build Images v1 renderer does not emit that member, so selecting this profile fails closed until the producer adds it. |\n\nFor web-delivery profiles, Buildchain runs its own fixed `ffprobe` invocation\ninside the same immutable, network-disabled renderer image. That command is\nBuildchain-controlled; the producer cannot inject flags. The resulting witness\nrecords exact roots and byte counts plus container, codec, pixel format,\ndimensions, duration, frame rate, audio stream count, and progressive-download\nevidence. Finalization re-hashes the retained bytes, rechecks the witness root,\nand parses MP4 top-level boxes itself. The producer's `media-probe.json.passed`\nfield remains supporting evidence, never sufficient authority.\n\nThe default `archive-v1` path preserves the existing v1 media receipt exactly.\nAn explicitly selected web-delivery profile emits a v2 media receipt with a\ncontent-addressed rendition list and explicit roles and MIME types. Agents and\nsite builds select `primary-video`,\n`alternate-video`, `browser-poster`, or evidence-only roles from that receipt;\nthey do not infer semantics from extensions or filenames. Additional responsive\nrenditions are accepted only when the immutable renderer manifest declares the\nbounded `build-images.auditable-demo-web-delivery/v1` role and MIME metadata and\nthe selected Buildchain profile supplies the byte ceiling; producer metadata\ncannot raise that ceiling. Unbound outputs,\nduplicate singleton roles, unknown profiles, or unsupported required versions\nfail closed.\n\nInitial byte ceilings are derived from the checked-in\n`auditable-demo-web-delivery-v1` fixture rendered by Build Images\n`v1.3.0-alpha.16` at its exact source SHA and image digest. GIF, MP4, WebM, and\nPNG ceilings are the next power of two above sixteen times the measured member\nbytes. The not-yet-produced WebP poster uses eight times the measured lossless\nPNG as its conservative proxy. The path-scoped qualification workflow\nregenerates the content-addressed evidence and fails on any byte or fact drift.\n\n## Consumer Example\n\nThe build job must expose both the exact artifact name and the digest returned\nby `upload-artifact`:\n\n```yaml\njobs:\n build:\n runs-on: ubuntu-24.04\n outputs:\n artifact-name: product-linux-${{ github.sha }}\n artifact-digest: ${{ steps.upload.outputs.artifact-digest }}\n steps:\n - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09\n - run: ./scripts/build-product\n - id: upload\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n with:\n name: product-linux-${{ github.sha }}\n path: dist\n if-no-files-found: error\n retention-days: 14\n compression-level: 0\n\n auditable-demo:\n needs: build\n permissions:\n actions: read\n contents: read\n uses: kungfu-systems/buildchain/.github/workflows/.auditable-demo.yml@BUILDCHAIN_EXACT_SHA\n with:\n source-ref: ${{ github.sha }}\n source-artifact-name: ${{ needs.build.outputs.artifact-name }}\n source-artifact-digest: ${{ needs.build.outputs.artifact-digest }}\n adapter-path: scripts/auditable-demo-adapter\n renderer-image: ghcr.io/kungfu-systems/build-images/demo-renderer@sha256:RENDERER_DIGEST\n render-media: false\n media-profile: archive-v1\n```\n\nReplace both placeholders with reviewed immutable SHAs or digests. An eligible\nbuild should always call the reusable workflow. Selection policy changes only\n`render-media`; it must never condition away the Gate job.\n\nUse `web-delivery-v1` only when the rendered bundle is intended to become a\nqualified web-delivery source. Use `site-hero-v1` when an optimized browser\nposter is also required. Profile qualification does not prove browser playback,\nresponsive layout, reduced-motion behavior, accessibility, or production\ndeployment; those remain site responsibilities.\n\n## Failure Evidence\n\nGate and render jobs use bounded timeouts and non-cancelling concurrency.\nDiagnostics artifacts are attempted with `always()` so adapter stdout/stderr\nand the resolved source coordinate remain available when qualification fails.\nNo production deployment, publication authority, token, or provider mutation\nis part of this workflow."
431
+ "markdown": "---\nstatus: draft\nperiod: 2026-07\ntheme: auditable-demo-pipeline\ndoc_type: technical-contract\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-28\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-25\n invisible_context_boundary: No hidden model build, parameter count, or private corpus is asserted.\n---\n\n# Auditable Demo Pipeline\n\nBuildchain's auditable demo workflow turns an exact GitHub build artifact into\ntwo distinct evidence products:\n\n1. a required qualified Gate bundle; and\n2. an optional rendered media bundle that can exist only after that exact Gate\n bundle passes.\n\nThe public reusable workflow is\n`.github/workflows/.auditable-demo.yml`. It is consumer-neutral: Buildchain\ndoes not know how a Kungfu, library, service, or application artifact should be\ninterpreted. The consumer owns a small checked-in executable adapter.\n\n## Authority Boundary\n\nThe retained build output is authoritative. The adapter reads that exact\nartifact and projects three files:\n\n```text\ncomplete-transcript.txt\npublic-projection.json\nscene.json\n```\n\nIt may additionally emit one declared `terminal-capture.json` using\n`kungfu.terminal-capture/v1`. The optional capture is bounded to 60 seconds,\nfixed 80-200 by 24-80 terminal cells, 10,000 events, and 4 MiB of canonical\nbase64 bytes. It must contain a passed completion sentinel and an explicitly\nempty authority-grant list. Existing three-file adapters remain valid.\n\nTerminal bytes are volatile observations, not Work, Warrant, capability, or\npublication authority. First-party or System identity, KFD compliance, Product\nSystem metadata, package metadata, scan output, registry history, and\nstandalone generation remain non-authoritative unless an exact higher-level\ncontract independently admits them.\n\nThe adapter must not rebuild or rerun the product. It receives:\n\n```text\n--artifact-root PATH\n--output PATH\n--source-coordinate PATH\n```\n\n`--source-coordinate` identifies the caller repository, run, artifact id,\nartifact name, upload digest, expiry, and exact source SHA. The workflow finds\nexactly one live artifact with the requested name in the current caller run and\nrejects a digest mismatch before invoking the adapter. Callers must pass the\ndigest emitted by their own `upload-artifact` step; a name resolved later from\nthe Actions API is discovery evidence, not a substitute for that producer\noutput.\n\nBuildchain's reusable build workflow exposes `artifact-coordinates-json` after\nall resolved platform uploads complete. That producer-owned output binds every\nplatform id to its same-run artifact id, name, upload digest, URL, and expiry,\nso a consumer that delegates its build to Buildchain can pass an exact\ncoordinate without rediscovering authority in a downstream job. The build\naggregate fails closed if any declared platform lacks one live, digest-bearing\nartifact coordinate. The compact coordinate set is sorted by platform id so\ndownstream machine consumers do not depend on matrix completion order.\n\nThe adapter runs with a disposable Home/XDG/npm prefix, a minimal environment,\nand no GitHub, npm, or cloud credential injection. It must be a regular,\nnon-symlink, executable file inside the exact checked-out consumer source.\n\n## Required Gate\n\nThe Gate:\n\n- checks out the exact consumer source and exact called-workflow SHA;\n- resolves and downloads one exact same-run GitHub Artifact;\n- invokes the checked-in adapter by argv, never as an evaluated shell string;\n- rejects undeclared adapter outputs, symlinks, invalid UTF-8, invalid scene or\n projection or terminal-capture schemas, implicit capture grants,\n out-of-range transcript references, and oversized input;\n- derives a one-second compatibility scene from the consumer projection;\n- anonymously pulls an immutable `image@sha256:digest` renderer;\n- runs it as non-root with `--network none`, a read-only root filesystem, and a\n bounded tmpfs;\n- verifies the renderer manifest, media probe, exact input roots, exact output\n member set, and complete checksums;\n- uploads a content-addressed qualified bundle plus an independent GitHub\n Artifact id, URL, archive digest, and expiry-bearing source coordinate.\n\nThe Gate bundle contains the complete consumer transcript/projection/scene,\nsource artifact coordinate, adapter identity, bounded renderer evidence, a\npassed gate receipt, and checksums covering every member exactly once.\n\n## Selective Render\n\n`render-media: true` enables the second job. It downloads the just-uploaded Gate\nbundle by its content-addressed name, recomputes the Gate member root, verifies\nthe exact source SHA and renderer digest, and only then renders the complete\nqualified scene.\n\nThe media bundle contains MP4, WebM, GIF, poster, probe, renderer manifest,\nrenderer checksums, passed Gate receipt, a versioned media receipt, and\ndistribution checksums. A web-delivery profile also retains\n`media-inspection.json`, whose content root is bound into the receipt.\n`render-media: false` does not weaken or skip the Gate.\n\nWhen the Gate bundle contains a qualified terminal capture, the render job\npasses it read-only to the immutable renderer. The renderer manifest binds the\ncapture root and terminal-state-machine version, but raw capture bytes remain\nin the Gate bundle rather than being copied into the public media bundle.\n\n## Media Qualification Profiles\n\nThe single machine-readable source is\n`contracts/auditable-demo-media-profiles-v1.json`. Callers select one reviewed\nprofile through `media-profile`; they cannot pass ffmpeg commands, codec flags,\nshell fragments, arbitrary profile paths, or transcoding instructions.\n\n| Profile | Meaning |\n| --- | --- |\n| `archive-v1` | Default compatibility contract. Retains the exact renderer outputs and classifies GIF as README compatibility evidence without making a browser-delivery claim. |\n| `web-delivery-v1` | Independently qualifies H.264 MP4 and VP9 WebM playback sources, forbids audio, requires exact scene dimensions and bounded duration/frame-rate drift, checks per-rendition byte ceilings, and proves MP4 `moov` precedes `mdat`. PNG remains the lossless evidence poster. |\n| `site-hero-v1` | Extends `web-delivery-v1` and additionally requires a qualified WebP browser poster. The current Build Images v1 renderer does not emit that member, so selecting this profile fails closed until the producer adds it. |\n\nFor web-delivery profiles, Buildchain runs its own fixed `ffprobe` invocation\ninside the same immutable, network-disabled renderer image. That command is\nBuildchain-controlled; the producer cannot inject flags. The resulting witness\nrecords exact roots and byte counts plus container, codec, pixel format,\ndimensions, duration, frame rate, audio stream count, and progressive-download\nevidence. Finalization re-hashes the retained bytes, rechecks the witness root,\nand parses MP4 top-level boxes itself. The producer's `media-probe.json.passed`\nfield remains supporting evidence, never sufficient authority.\n\nThe default `archive-v1` path preserves the existing v1 media receipt exactly.\nAn explicitly selected web-delivery profile emits a v2 media receipt with a\ncontent-addressed rendition list and explicit roles and MIME types. Agents and\nsite builds select `primary-video`,\n`alternate-video`, `browser-poster`, or evidence-only roles from that receipt;\nthey do not infer semantics from extensions or filenames. Additional responsive\nrenditions are accepted only when the immutable renderer manifest declares the\nbounded `build-images.auditable-demo-web-delivery/v1` role and MIME metadata and\nthe selected Buildchain profile supplies the byte ceiling; producer metadata\ncannot raise that ceiling. Unbound outputs,\nduplicate singleton roles, unknown profiles, or unsupported required versions\nfail closed.\n\nInitial byte ceilings are derived from the checked-in\n`auditable-demo-web-delivery-v1` fixture rendered by Build Images\n`v1.3.0-alpha.16` at its exact source SHA and image digest. GIF, MP4, WebM, and\nPNG ceilings are the next power of two above sixteen times the measured member\nbytes. The not-yet-produced WebP poster uses eight times the measured lossless\nPNG as its conservative proxy. The path-scoped qualification workflow\nregenerates the content-addressed evidence and fails on any byte or fact drift.\n\n## Consumer Example\n\nThe build job must expose both the exact artifact name and the digest returned\nby `upload-artifact`:\n\n```yaml\njobs:\n build:\n runs-on: ubuntu-24.04\n outputs:\n artifact-name: product-linux-${{ github.sha }}\n artifact-digest: ${{ steps.upload.outputs.artifact-digest }}\n steps:\n - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09\n - run: ./scripts/build-product\n - id: upload\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a\n with:\n name: product-linux-${{ github.sha }}\n path: dist\n if-no-files-found: error\n retention-days: 14\n compression-level: 0\n\n auditable-demo:\n needs: build\n permissions:\n actions: read\n contents: read\n uses: kungfu-systems/buildchain/.github/workflows/.auditable-demo.yml@BUILDCHAIN_EXACT_SHA\n with:\n source-ref: ${{ github.sha }}\n source-artifact-name: ${{ needs.build.outputs.artifact-name }}\n source-artifact-digest: ${{ needs.build.outputs.artifact-digest }}\n adapter-path: scripts/auditable-demo-adapter\n renderer-image: ghcr.io/kungfu-systems/build-images/demo-renderer@sha256:RENDERER_DIGEST\n render-media: false\n media-profile: archive-v1\n```\n\nReplace both placeholders with reviewed immutable SHAs or digests. An eligible\nbuild should always call the reusable workflow. Selection policy changes only\n`render-media`; it must never condition away the Gate job.\n\nUse `web-delivery-v1` only when the rendered bundle is intended to become a\nqualified web-delivery source. Use `site-hero-v1` when an optimized browser\nposter is also required. Profile qualification does not prove browser playback,\nresponsive layout, reduced-motion behavior, accessibility, or production\ndeployment; those remain site responsibilities.\n\n## Failure Evidence\n\nGate and render jobs use bounded timeouts and non-cancelling concurrency.\nDiagnostics artifacts are attempted with `always()` so adapter stdout/stderr\nand the resolved source coordinate remain available when qualification fails.\nNo production deployment, publication authority, token, or provider mutation\nis part of this workflow."
432
432
  },
433
433
  {
434
434
  "id": "manual:binary-distribution",
@@ -578,7 +578,7 @@
578
578
  ],
579
579
  "maturity": "stable",
580
580
  "sourcePath": "docs/consumer-issue-reporting.md",
581
- "digest": "sha256:96804310360c02f408fee9d540385d875a40d09bef046c3df369228d311a1cdd",
581
+ "digest": "sha256:20c33ab68e91227837626d1289d6c5540a318c8ff484d9a1f0ebe7dbe8626d02",
582
582
  "headings": [
583
583
  {
584
584
  "level": 1,
@@ -601,7 +601,7 @@
601
601
  "anchor": "javascript-api"
602
602
  }
603
603
  ],
604
- "markdown": "# Consumer Issue Reporting\n\nBuildchain ships a first-class issue reporting surface for consumer workflows.\nIt lets a repository open or update a Buildchain-owned GitHub issue when a\nBuildchain reusable workflow, action, or toolkit API produces a failure that\nneeds Buildchain maintainers.\n\n## Trust model\n\nThe consumer workflow must provide a token with issue-write access to the\ntarget repository. A repository's default `GITHUB_TOKEN` only writes to its own\nrepository, so cross-repository reports should use a GitHub App installation\ntoken scoped to:\n\n- the `kungfu-systems/buildchain` repository;\n- Issues: read and write;\n- no content write permission unless another workflow step needs it.\n\nThe recommended pattern is:\n\n```yaml\n- uses: actions/create-github-app-token@v2\n id: buildchain-issue-token\n with:\n app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n owner: kungfu-systems\n repositories: buildchain\n\n- uses: kungfu-systems/buildchain/actions/report-buildchain-issue@v3\n if: failure()\n with:\n token: ${{ steps.buildchain-issue-token.outputs.token }}\n summary: \"Buildchain reusable build failed before artifact finalization\"\n failure-code: reusable-build-failed\n diagnostics-path: .buildchain/artifacts/diagnostics.json\n buildchain-ref: ${{ inputs.buildchain-ref || 'v2' }}\n```\n\n## Behavior\n\n`report-buildchain-issue` builds an issue body with consumer repository,\nworkflow, ref, SHA, Buildchain ref/version, diagnostics links, and optional\ndetails. It computes a stable fingerprint and embeds a hidden marker:\n\n```text\nbuildchain-consumer-issue:fingerprint=<sha256-prefix>\n```\n\nWhen an open issue with the same marker exists, the action comments on it with\nthe new run evidence. Otherwise it creates a new issue. Consumers may pass an\nexplicit `fingerprint` when they need a different dedupe boundary.\n\nThe action is fail-soft by default. It retries GitHub API 429/5xx responses and\nconnection failures, redacts common secret/token/private-key patterns, truncates\nlarge bodies, and retries issue creation without labels if the target\nrepository does not have the configured labels yet.\n\nSet `fail-on-error: \"true\"` only when the reporting step is part of a release\ngate and missing Buildchain feedback should stop the workflow.\n\n## JavaScript API\n\nConsumer scripts can import the same implementation:\n\n```js\nimport {\n buildConsumerIssueReport,\n reportBuildchainIssue,\n} from \"@kungfu-tech/buildchain/issue-reporting\";\n\nconst result = await reportBuildchainIssue({\n token: process.env.BUILDCHAIN_ISSUE_TOKEN,\n summary: \"Native artifact manifest is incomplete\",\n failureCode: \"native-manifest-incomplete\",\n buildchainRef: \"v2\",\n diagnosticsPath: \".buildchain/artifacts/diagnostics.json\",\n});\n```\n\nUse `buildConsumerIssueReport` for dry-run validation, previewing redaction, or\nunit tests without calling GitHub."
604
+ "markdown": "# Consumer Issue Reporting\n\nBuildchain ships a first-class issue reporting surface for consumer workflows.\nIt lets a repository open or update a Buildchain-owned GitHub issue when a\nBuildchain reusable workflow, action, or toolkit API produces a failure that\nneeds Buildchain maintainers.\n\n## Trust model\n\nThe consumer workflow must provide a token with issue-write access to the\ntarget repository. A repository's default `GITHUB_TOKEN` only writes to its own\nrepository, so cross-repository reports should use a GitHub App installation\ntoken scoped to:\n\n- the `kungfu-systems/buildchain` repository;\n- Issues: read and write;\n- no content write permission unless another workflow step needs it.\n\nThe recommended pattern is:\n\n```yaml\n- uses: actions/create-github-app-token@v2\n id: buildchain-issue-token\n with:\n app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n owner: kungfu-systems\n repositories: buildchain\n\n- uses: kungfu-systems/buildchain/actions/report-buildchain-issue@v3\n if: failure()\n with:\n token: ${{ steps.buildchain-issue-token.outputs.token }}\n summary: \"Buildchain reusable build failed before artifact finalization\"\n failure-code: reusable-build-failed\n diagnostics-path: .buildchain/artifacts/diagnostics.json\n buildchain-ref: ${{ inputs.buildchain-ref || 'v3' }}\n```\n\n## Behavior\n\n`report-buildchain-issue` builds an issue body with consumer repository,\nworkflow, ref, SHA, Buildchain ref/version, diagnostics links, and optional\ndetails. It computes a stable fingerprint and embeds a hidden marker:\n\n```text\nbuildchain-consumer-issue:fingerprint=<sha256-prefix>\n```\n\nWhen an open issue with the same marker exists, the action comments on it with\nthe new run evidence. Otherwise it creates a new issue. Consumers may pass an\nexplicit `fingerprint` when they need a different dedupe boundary.\n\nThe action is fail-soft by default. It retries GitHub API 429/5xx responses and\nconnection failures, redacts common secret/token/private-key patterns, truncates\nlarge bodies, and retries issue creation without labels if the target\nrepository does not have the configured labels yet.\n\nSet `fail-on-error: \"true\"` only when the reporting step is part of a release\ngate and missing Buildchain feedback should stop the workflow.\n\n## JavaScript API\n\nConsumer scripts can import the same implementation:\n\n```js\nimport {\n buildConsumerIssueReport,\n reportBuildchainIssue,\n} from \"@kungfu-tech/buildchain/issue-reporting\";\n\nconst result = await reportBuildchainIssue({\n token: process.env.BUILDCHAIN_ISSUE_TOKEN,\n summary: \"Native artifact manifest is incomplete\",\n failureCode: \"native-manifest-incomplete\",\n buildchainRef: \"v2\",\n diagnosticsPath: \".buildchain/artifacts/diagnostics.json\",\n});\n```\n\nUse `buildConsumerIssueReport` for dry-run validation, previewing redaction, or\nunit tests without calling GitHub."
605
605
  },
606
606
  {
607
607
  "id": "manual:controller-evidence",
@@ -667,7 +667,7 @@
667
667
  ],
668
668
  "maturity": "stable",
669
669
  "sourcePath": "docs/dev-alpha-candidate-patrol.md",
670
- "digest": "sha256:d275b4dbcb5eca8d927e00aae1635acc2bd6337e88e052d76df2e52f7ebf6809",
670
+ "digest": "sha256:9f8fa0c89f833fe654cae47c19fcb49ac28c892a16e14e1582e0342c82954e61",
671
671
  "headings": [
672
672
  {
673
673
  "level": 1,
@@ -680,7 +680,7 @@
680
680
  "anchor": "reusable-workflow"
681
681
  }
682
682
  ],
683
- "markdown": "---\nstatus: preview\nperiod: ongoing\ntheme: dev-alpha-candidate-patrol\ndoc_type: architecture-and-usage\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-27\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-27\n visible_context: Existing Buildchain stable-candidate source locks, Kungfu exact-source Alpha preflight, Dev Patrol, and repository release governance.\n invisible_context_boundary: No credentials, private logs, or private configuration were used.\n---\n\n# Dev to Alpha Candidate Patrol\n\nBuildchain provides a reusable observation and single-flight PR controller for\nrepositories that promote a development branch into a protected Alpha branch.\nIt does not publish Alpha. The read-only observer reads the exact heads of both\nbranches, walks the\nbounded development history from newest to oldest (stopping early at the Alpha\nhead), and selects the newest commit that satisfies all of these conditions:\n\n- the source is strictly ahead of the recorded target head;\n- the latest completed Dev Patrol for that exact commit SHA succeeded;\n- the latest completed Alpha preflight for the same commit SHA succeeded; and\n- both runs are within the caller's evidence age limit.\n\nThe selected commit can be behind the observed development head when newer\ncommits have not completed both workflows yet. The decision binds the observed\nhead, selected SHA, and count of skipped newer commits. This makes a slow native\nverification lane live under continuous development without silently treating\nan unqualified head as releasable.\n\nHistory discovery is bounded to the newest 1000 development commits. The\ncontroller then compares the selected SHA to the exact Alpha head before it can\nbe eligible, so a bounded scan cannot turn a commit outside the promotion\nancestry into a candidate.\n\nThe decision is `kungfu-buildchain-channel-candidate-decision/v1`. It records the\nsource and target branches and SHAs, comparison distance, workflow paths, run\nidentities and attempts, completion times, URLs, policy, and a canonical decision\nroot. Missing, stale, failed, duplicate, or source-mismatched evidence fails\nclosed as an auditable `blocked` or `stale` observation and cannot enter\nsettlement.\n\nThe companion state is\n`kungfu-buildchain-dev-alpha-candidate-state/v1`. Its current state is one of:\n\n- `observed`: no exact candidate is currently settleable;\n- `eligible-for-settlement`: a qualified candidate exists and no managed Alpha\n candidate PR is active;\n- `active`: exactly one managed candidate PR is open;\n- `retained-next`: an active PR remains authoritative and the newest different\n qualified SHA is retained as `nextCandidate`;\n- `stale`: the available exact-SHA evidence pair is outside policy age; or\n- `blocked`: qualification or reconciliation failed closed.\n\nWhen a newer qualified SHA replaces an earlier `nextCandidate`, the state also\nrecords that earlier SHA as `supersededCandidate`. Every state carries exact\nrepository, source/target refs and SHAs, workflow-run evidence through the\ncandidate decision, and canonical decision/state roots.\n\n## Reusable workflow\n\nCall `.github/workflows/dev-alpha-candidate-patrol.yml` from a thin repository\nworkflow. Start with `dry-run: true`. The reusable workflow always runs an\n`observe` job with only Actions/content/pull-request read permissions. Once the\nrepository has proven that its two workflow names and branch topology produce\nexact same-SHA evidence, it may set `settlement-authorized: true` and\n`dry-run: false`. The older `create-pull-request` input remains a compatibility\nalias for settlement authorization.\n\nRepositories whose promotion policy requires a machine-readable PR declaration\ncan pass it through `pull-request-body-prefix`. Buildchain prepends that trusted,\nrepository-owned text only when it creates the candidate PR and preserves it\nwhen later controller observations update the managed state marker. Keep the\ndeclaration source-controlled in the caller workflow and update it whenever the\nqualified development delta changes its release or architecture settlement.\nBuildchain rejects a prefix that attempts to inject its managed controller\nmarker.\n\nThe separately permissioned `settle` job re-runs the exact observation before\nany write. With no active managed candidate, it creates one branch named from\nthe target branch and the first 12 characters of the full source SHA. An\nexisting branch must point to the same full SHA or the run fails. With one\nactive managed candidate, it only updates the machine-readable state marker in\nthat PR body so repeated events and rapid dev progress cannot create another\ncandidate PR or another heavy candidate build. Foreign human-authored Alpha PRs\nare ignored. More than one open Buildchain-managed candidate fails closed.\n\nThe PR body is the bounded durable controller state: it preserves the active\ncandidate and newest retained `nextCandidate` without introducing an always-on\nservice. Once the active PR settles or is abandoned, the next execution\nrecomputes current exact-SHA qualification and creates only the newest still\nfresh candidate. It never trusts a `workflow_run` trigger SHA as evidence.\n\nConsumers should invoke this workflow after relevant qualification workflow\ncompletion and from an offset periodic fallback. GitHub may delay scheduled\nruns, so the event path supplies low latency while the fallback supplies\nrecovery. Workflow concurrency plus the server-side open-PR reconciliation\nmakes duplicate or delayed events idempotent.\n\nThe workflow never moves the Alpha ref directly, merges or auto-merges the pull\nrequest, publishes npm, creates a Git tag or GitHub Release, or changes branch\nprotection. Those remain repository-owned protected settlement actions."
683
+ "markdown": "---\nstatus: preview\nperiod: ongoing\ntheme: dev-alpha-candidate-patrol\ndoc_type: architecture-and-usage\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-29\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-29\n visible_context: Existing Buildchain source locks, Kungfu exact-source Alpha preflight, Dev Patrol, repository release governance, and the consumer-owned settlement renderer threat model.\n invisible_context_boundary: No credentials, private logs, or private configuration were used.\n---\n\n# Dev to Alpha Candidate Patrol\n\nBuildchain provides a reusable observation and single-flight PR controller for\nrepositories that promote a development branch into a protected Alpha branch.\nIt does not publish Alpha. The read-only observer reads the exact heads of both\nbranches, walks the\nbounded development history from newest to oldest (stopping early at the Alpha\nhead), and selects the newest commit that satisfies all of these conditions:\n\n- the source is strictly ahead of the recorded target head;\n- the latest completed Dev Patrol for that exact commit SHA succeeded;\n- the latest completed Alpha preflight for the same commit SHA succeeded; and\n- both runs are within the caller's evidence age limit.\n\nThe selected commit can be behind the observed development head when newer\ncommits have not completed both workflows yet. The decision binds the observed\nhead, selected SHA, and count of skipped newer commits. This makes a slow native\nverification lane live under continuous development without silently treating\nan unqualified head as releasable.\n\nHistory discovery is bounded to the newest 1000 development commits. The\ncontroller then compares the selected SHA to the exact Alpha head before it can\nbe eligible, so a bounded scan cannot turn a commit outside the promotion\nancestry into a candidate.\n\nThe decision is `kungfu-buildchain-channel-candidate-decision/v1`. It records the\nsource and target branches and SHAs, comparison distance, workflow paths, run\nidentities and attempts, completion times, URLs, policy, and a canonical decision\nroot. Missing, stale, failed, duplicate, or source-mismatched evidence fails\nclosed as an auditable `blocked` or `stale` observation and cannot enter\nsettlement.\n\nThe companion state is\n`kungfu-buildchain-dev-alpha-candidate-state/v1`. Its current state is one of:\n\n- `observed`: no exact candidate is currently settleable;\n- `eligible-for-settlement`: a qualified candidate exists and no managed Alpha\n candidate PR is active;\n- `active`: exactly one managed candidate PR is open;\n- `retained-next`: an active PR remains authoritative and the newest different\n qualified SHA is retained as `nextCandidate`;\n- `stale`: the available exact-SHA evidence pair is outside policy age; or\n- `blocked`: qualification or reconciliation failed closed.\n\nWhen a newer qualified SHA replaces an earlier `nextCandidate`, the state also\nrecords that earlier SHA as `supersededCandidate`. Every state carries exact\nrepository, source/target refs and SHAs, workflow-run evidence through the\ncandidate decision, and canonical decision/state roots.\n\n## Reusable workflow\n\nCall `.github/workflows/dev-alpha-candidate-patrol.yml` from a thin repository\nworkflow. Start with `dry-run: true`. The reusable workflow always runs an\n`observe` job with only Actions/content/pull-request read permissions. Once the\nrepository has proven that its two workflow names and branch topology produce\nexact same-SHA evidence, it may set `settlement-authorized: true` and\n`dry-run: false`. The older `create-pull-request` input remains a compatibility\nalias for settlement authorization.\n\nRepositories whose promotion policy requires a machine-readable PR declaration\ncan pass static text through `pull-request-body-prefix`. When the declaration\ndepends on the exact qualified delta, use `pull-request-body-prefix-renderer`\ninstead. It names a repository-relative Node.js file in the consumer checkout.\nThe read-only `observe` job checks out the selected SHA with credentials disabled,\nruns the renderer with a reduced environment, and requires it to write UTF-8 text\nto `BUILDCHAIN_CHANNEL_PATROL_PR_BODY_PREFIX_OUTPUT`. The renderer also receives\nthe selected SHA plus source and target branch names. It may derive a declaration\nfrom the exact checkout and `origin/<target-branch>` without receiving the\npromotion token.\n\nStatic and rendered prefixes are mutually exclusive. A renderer failure, path\nescape, source-SHA mismatch, empty or oversized result, invalid UTF-8, or managed\ncontroller-marker injection fails before the write-permission job can run. The\nrendered bytes are retained with the read-only observation artifact and passed\nto `settle` as a job output, so the candidate PR is created with the correct\ndeclaration on its first write. Buildchain preserves that repository-owned text\nwhen later observations update only the managed state marker. Before any write,\n`settle` also requires its fresh observation to select the same SHA that produced\nthe rendered bytes. Concurrent qualification progress therefore fails closed\nand is recomputed by the next patrol instead of attaching a declaration to the\nwrong candidate.\n\nThe separately permissioned `settle` job re-runs the exact observation before\nany write. With no active managed candidate, it creates one branch named from\nthe target branch and the first 12 characters of the full source SHA. An\nexisting branch must point to the same full SHA or the run fails. With one\nactive managed candidate, it only updates the machine-readable state marker in\nthat PR body so repeated events and rapid dev progress cannot create another\ncandidate PR or another heavy candidate build. Foreign human-authored Alpha PRs\nare ignored. More than one open Buildchain-managed candidate fails closed.\n\nThe PR body is the bounded durable controller state: it preserves the active\ncandidate and newest retained `nextCandidate` without introducing an always-on\nservice. Once the active PR settles or is abandoned, the next execution\nrecomputes current exact-SHA qualification and creates only the newest still\nfresh candidate. It never trusts a `workflow_run` trigger SHA as evidence.\n\nConsumers should invoke this workflow after relevant qualification workflow\ncompletion and from an offset periodic fallback. GitHub may delay scheduled\nruns, so the event path supplies low latency while the fallback supplies\nrecovery. Workflow concurrency plus the server-side open-PR reconciliation\nmakes duplicate or delayed events idempotent.\n\nThe workflow never moves the Alpha ref directly, merges or auto-merges the pull\nrequest, publishes npm, creates a Git tag or GitHub Release, or changes branch\nprotection. Those remain repository-owned protected settlement actions."
684
684
  },
685
685
  {
686
686
  "id": "manual:github-artifact-attestation",
@@ -747,7 +747,7 @@
747
747
  ],
748
748
  "maturity": "preview",
749
749
  "sourcePath": "docs/github-governance-authority.md",
750
- "digest": "sha256:b8c3d9ad53c2509bbd95804ccc31f6aa89989c3aadf3a2b75d9c73aef168b7e8",
750
+ "digest": "sha256:304d0246d21bdd0f6a77183ac6839a5d1234d08d98bd5da9728c85a65b1ff7b7",
751
751
  "headings": [
752
752
  {
753
753
  "level": 1,
@@ -780,7 +780,7 @@
780
780
  "anchor": "mutation-and-rollback-boundary"
781
781
  }
782
782
  ],
783
- "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: github-governance-authority\ndoc_type: protocol\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: B\nreview_state: unreviewed\nlast_reviewed: 2026-07-24\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-24\n limits: Live GitHub state and account recovery remain provider-controlled and must be re-audited.\n---\n\n# GitHub Governance Authority\n\nBuildchain treats GitHub governance as an independently verified, fail-closed\nauthority boundary. A green CI run, a CODEOWNERS file, an API success response,\nor an administrator's assertion is not sufficient by itself. The verifier\ncombines the exact CODEOWNERS bytes from the target base branch, classic branch\nprotection, every applicable repository or organization ruleset, account role\nclasses, plan capability, required checks, and provider-read completeness into\none short-lived immutable receipt.\n\nThe machine contract is\n`@kungfu-tech/buildchain/github-governance-authority`. Its policy root covers\nthe managed-zone repository and target-ref admission rules, the exact\nrequired-check context/App bindings and strict-update semantics for every\npublic authoritative target, the dual-account authority split, protected\nverifier paths, native review requirements, break-glass constraints, and the\nexplicit trust boundary.\n\n## Trust boundary and non-claims\n\nThe trusted computing base contains GitHub service integrity, retained\norganization-owner recovery custody, the `kungfu-origin` review/governance\nidentity, the exact Buildchain verifier, and official publication identities.\nThe protocol does not claim resistance to compromise of GitHub itself,\ncompromise of all retained owner and recovery anchors, or malicious control of\nall independent trust anchors. A governance receipt grants no GitHub\npermission and is not a bearer credential.\n\n`dongkeren` is the development and pull-request author identity.\n`kungfu-origin` is the independent Code Owner and governance identity. A\nqualifying receipt requires the development identity to be active without an\nadministrator or maintainer role and requires the review identity to retain the\nadmitted governance role. Account recovery and retained root custody remain\noutside normal contributor and workflow paths.\n\n## Effective policy\n\nThe verifier evaluates native provider layers together. Every authoritative\ntarget must require:\n\n- a pull request, at least one independent Code Owner approval, and a fresh\n approval after the latest reviewable push;\n- administrator enforcement, resolved review conversations, and a non-empty\n required-check set whose exact contexts, GitHub App producer identities, and\n strict-update setting match the versioned target policy;\n- no unapproved bypass actor, force push, or protected-ref deletion. Managed\n dev/alpha/release ref bookkeeping may admit only the exact GitHub Actions App\n identity versioned for that target; user and team bypass actors remain\n non-qualifying;\n- exact last-match ownership of CODEOWNERS and the governance descriptor,\n collector, rollout planner, and scheduled audit workflow;\n- complete readable GitHub API evidence. Missing, forbidden, ambiguous, or\n malformed provider state is non-qualifying.\n\nRepository and organization rulesets are additive to classic branch\nprotection. Inspecting only one layer is insufficient because an applicable\nbypass or weaker update path in another layer can invalidate the effective\npolicy.\n\n## Repository and plan admission\n\nThe 2026-07-24 baseline contains 16 managed repositories: 13 public and three\nprivate. Public repository names are versioned in the descriptor. Private\nrepository names are never emitted in public evidence; their identities are\nrepresented by stable roots derived from the GitHub provider repository ID,\nindependent of the governance policy root. This prevents a policy revision from\nchanging repository identity or creating a circular admission dependency. A\nnewly discovered repository or target ref is non-authoritative until explicitly\nadmitted.\n\nThe descriptor also versions every active public merge target. The full audit\nevaluates one receipt per authoritative target rather than assuming the default\nbranch represents dev, alpha, release, or major publish-gate branches. The live\ndefault branch is always included even if it drifts outside the registry, in\nwhich case it is non-qualifying. Retained historical channels and generated\nper-release publish-gate refs are not silently deleted or promoted to current\nauthority; they require an explicit registry revision before they can qualify.\n\nFor an admitted private repository, the active target set is the current\ndefault branch plus existing alpha/release siblings on the same version line.\nIts required-check bindings remain non-qualifying until their sanitized binding\nroots are sealed into the private identity entry after supported native\nprotection exists.\n\nPublic repositories can qualify on supported Free, Team, or Enterprise\nenforcement. Private repositories and organization-wide rules require Team or\nEnterprise capability. On an unsupported plan they remain explicitly\n`non-authoritative-plan-capability-required` and publication-ineligible. The\nverifier does not replace missing native enforcement with CI or documentation,\nand the implementation never makes a private repository public as a\nworkaround.\n\n## Read-only audit\n\nRun the organization audit without mutation:\n\n```bash\nbuildchain audit github-governance \\\n --organization kungfu-systems \\\n --output github-governance.json \\\n --json\n```\n\nLimit a canary to one repository:\n\n```bash\nbuildchain audit github-governance \\\n --repository kungfu-systems/buildchain \\\n --target-ref dev/v3/v3.0 \\\n --require-qualifying \\\n --json\n```\n\nProtected merge and publication consumers verify the receipt against the exact\nrepository, target base ref, policy root, freshness window, and exact\nBuildchain verifier source revision. Non-dry-run publication does not trust a\ncaller-supplied JSON hash: it mints a bounded token for the dedicated read-only\ngovernance auditor GitHub App, recollects live provider state with the exact\nBuildchain runtime, requires the resulting single-repository/single-target\naudit to qualify, and consumes that independently generated receipt. Missing\nApp configuration or unreadable provider state denies publication before\nprovider mutation. The publication authority workflow is itself an explicit\nCode Owner path.\n\nThe publication authority job and every reusable-workflow caller grant the\nbuilt-in `GITHUB_TOKEN` only `actions: read`, `checks: read`, `contents: read`,\nand `pull-requests: read`. The dedicated auditor App independently recollects\nthe organization-wide governance receipt, while these job-scoped permissions\nallow the exact publication transaction audit to resolve required check runs\nand merged pull-request review lineage. Omitting either read permission makes\nthe transaction evidence incomplete and therefore non-qualifying.\n\nThe output is sanitized. Public repositories retain their public identity.\nPrivate repositories expose only an identity root, visibility class, target\nref, sanitized required-check bindings and fact roots, and a qualifying or\nnon-qualifying decision. Tokens, cookies, recovery material, private\nCODEOWNERS bytes, raw permission payloads, and credential-bearing URLs are\nnever included.\n\n## Mutation and rollback boundary\n\nLive role, ruleset, branch-protection, Actions, Environment, or repository\nchanges are separate from audit. Every mutation starts from a read-only\ninventory and a frozen rollback snapshot. A rollout plan binds both roots and\nlists the exact API operation, impact, expected observation, and inverse\noperation. Apply must stop on the first unexplained drift and must perform a\npost-change read-back before continuing to the next bounded canary.\n\nPlan one exact branch without mutation:\n\n```bash\nbuildchain github-governance plan \\\n --repository kungfu-systems/buildchain \\\n --branch dev/v3/v3.0 \\\n --required-check check \\\n --required-check-app-id check=15368 \\\n --required-approvals 1 \\\n --snapshot-output rollback.json \\\n --plan-output rollout.json\n```\n\nAn already protected check preserves its observed GitHub App binding. Every new\nrequired check must declare `--required-check-app-id <context>=<app-id>`;\ncontext-only replacement is rejected because it would broaden which producer\ncan satisfy the gate.\n\nClassic branch-protection bypass allowances and ruleset bypass actors are both\npart of the effective policy. Reconciliation writes explicit empty user, team,\nand App bypass lists and verifies those lists after apply; omitting the provider\nfield is not treated as removal because GitHub may preserve the prior value.\n\nThe plan prints a `planRoot`. Apply requires that exact root and stops if live\nprotection no longer matches the frozen inventory:\n\n```bash\nbuildchain github-governance apply \\\n --plan-json rollout.json \\\n --confirm-plan-root sha256:...\n```\n\nRollback is separately explicit and root-bound:\n\n```bash\nbuildchain github-governance rollback \\\n --plan-json rollout.json \\\n --confirm-rollback-root sha256:...\n```\n\nFor an admitted exact target, classic branch protection can also be compiled\ndirectly from the authority descriptor. This mode preserves both App-bound\nchecks and intentionally unbound check contexts such as Kungfu alpha's\n`build`, rather than guessing a provider App identity.\n\n```bash\nbuildchain github-governance protection-policy-plan \\\n --repository kungfu-systems/kungfu \\\n --branch alpha/v4/v4.0 \\\n --snapshot-output protection-rollback.json \\\n --plan-output protection-rollout.json\n\nbuildchain github-governance protection-policy-apply \\\n --plan-json protection-rollout.json \\\n --confirm-plan-root sha256:...\n\nbuildchain github-governance protection-policy-rollback \\\n --plan-json protection-rollout.json \\\n --confirm-rollback-root sha256:...\n```\n\nFor an admitted exact target, repository ruleset reconciliation compiles the\ntarget descriptor into the provider body. It replaces bypass actors with the\nexact provider-admitted desired set, requires fresh Code Owner approval and\nresolved review threads, and binds required checks plus strict-update semantics\nto the target policy. Newly synthesized managed rules include GitHub's explicit\ncanonical defaults so the frozen expected root matches provider read-back.\nRepository rulesets default to no bypass actors. The\ndescriptor's target-bound GitHub Actions allowance is an upper bound on\neffective provider state, not a requirement to add that actor to every\nprotection layer; when needed, the built-in App allowance is expressed by\nclassic branch protection. The target condition must contain exactly one\nbranch; unrelated rules and conditions are preserved in place.\n\n```bash\nbuildchain github-governance ruleset-policy-plan \\\n --repository kungfu-systems/buildchain \\\n --branch alpha/v2/v2.14 \\\n --ruleset-id 19518955 \\\n --snapshot-output ruleset-rollback.json \\\n --plan-output ruleset-rollout.json\n\nbuildchain github-governance ruleset-policy-apply \\\n --plan-json ruleset-rollout.json \\\n --confirm-plan-root sha256:...\n\nbuildchain github-governance ruleset-policy-rollback \\\n --plan-json ruleset-rollout.json \\\n --confirm-rollback-root sha256:...\n```\n\nThe narrower `ruleset-plan` mode changes only `bypass_actors`; it remains\navailable for a bypass-only canary, but it cannot prove that an effective\nruleset matches the target descriptor. Both modes require the frozen ruleset\nsnapshot root for rollback.\n\nPaid-plan purchase, billing, legal/account-owner decisions, and any operation\nthat could remove the last recoverable owner remain external human gates.\nBreak-glass is disabled by default and, if ever admitted, must be separately\nauthenticated, reason-bound, time-bounded, independently receipted, and\nfollowed by mandatory restoration and root comparison."
783
+ "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: github-governance-authority\ndoc_type: protocol\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: B\nreview_state: unreviewed\nlast_reviewed: 2026-07-30\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-30\n limits: Live GitHub state and account recovery remain provider-controlled and must be re-audited.\n---\n\n# GitHub Governance Authority\n\nBuildchain treats GitHub governance as an independently verified, fail-closed\nauthority boundary. A green CI run, a CODEOWNERS file, an API success response,\nor an administrator's assertion is not sufficient by itself. The verifier\ncombines the exact CODEOWNERS bytes from the target base branch, classic branch\nprotection, every applicable repository or organization ruleset, account role\nclasses, plan capability, required checks, and provider-read completeness into\none short-lived immutable receipt.\n\nThe machine contract is\n`@kungfu-tech/buildchain/github-governance-authority`. Its policy root covers\nthe managed-zone repository and target-ref admission rules, the exact\nrequired-check context/App bindings and strict-update semantics for every\npublic authoritative target, the dual-account authority split, protected\nverifier paths, native review requirements, break-glass constraints, and the\nexplicit trust boundary.\n\n## Trust boundary and non-claims\n\nThe trusted computing base contains GitHub service integrity, retained\norganization-owner recovery custody, the `kungfu-origin` review/governance\nidentity, the exact Buildchain verifier, and official publication identities.\nThe protocol does not claim resistance to compromise of GitHub itself,\ncompromise of all retained owner and recovery anchors, or malicious control of\nall independent trust anchors. A governance receipt grants no GitHub\npermission and is not a bearer credential.\n\n`dongkeren` is the development and pull-request author identity.\n`kungfu-origin` is the independent Code Owner and governance identity. A\nqualifying receipt requires the development identity to be active without an\nadministrator or maintainer role and requires the review identity to retain the\nadmitted governance role. Account recovery and retained root custody remain\noutside normal contributor and workflow paths.\n\n## Effective policy\n\nThe verifier evaluates native provider layers together. Every authoritative\ntarget must require:\n\n- a pull request, at least one independent Code Owner approval, and a fresh\n approval after the latest reviewable push;\n- administrator enforcement, resolved review conversations, and a non-empty\n required-check set whose exact contexts, GitHub App producer identities, and\n strict-update setting match the versioned target policy;\n- no unapproved bypass actor, force push, or protected-ref deletion. Managed\n dev/alpha/release ref bookkeeping may admit only the exact GitHub Actions App\n identity versioned for that target; user and team bypass actors remain\n non-qualifying;\n- exact last-match ownership of CODEOWNERS and the governance descriptor,\n collector, rollout planner, and scheduled audit workflow;\n- complete readable GitHub API evidence. Missing, forbidden, ambiguous, or\n malformed provider state is non-qualifying.\n\nRepository and organization rulesets are additive to classic branch\nprotection. Inspecting only one layer is insufficient because an applicable\nbypass or weaker update path in another layer can invalidate the effective\npolicy.\n\nWhen an admitted default development branch uses GitHub merge queue, its\ncandidate-source, queue-lease, and final build checks are one exact authority\nset. The queue lease may remain intentionally unbound while workflow-produced\nchecks retain their GitHub Actions App binding; strictness must match the live\nqueue ruleset.\n\n## Repository and plan admission\n\nThe 2026-07-30 baseline contains 16 managed public repositories. The descriptor\nsets `managedVisibilities` to `public`; private repositories are outside the\nmanaged-zone inventory and do not produce governance receipts. Public\nrepository names are versioned in the descriptor. A newly discovered public\nrepository or target ref is non-authoritative until explicitly admitted.\n\nThe descriptor also versions every active public merge target. The full audit\nevaluates one receipt per authoritative target rather than assuming the default\nbranch represents dev, alpha, release, or major publish-gate branches. The live\ndefault branch is always included even if it drifts outside the registry, in\nwhich case it is non-qualifying. Retained historical channels and generated\nper-release publish-gate refs are not silently deleted or promoted to current\nauthority; they require an explicit registry revision before they can qualify.\n\nPublic repositories can qualify on supported Free, Team, or Enterprise\nenforcement. Organization-wide rules require Team or Enterprise capability.\nThe verifier does not reinterpret an excluded private repository as qualifying,\nreplace missing native enforcement with CI or documentation, or make a private\nrepository public as a workaround.\n\n## Read-only audit\n\nRun the organization audit without mutation:\n\n```bash\nbuildchain audit github-governance \\\n --organization kungfu-systems \\\n --output github-governance.json \\\n --json\n```\n\nLimit a canary to one repository:\n\n```bash\nbuildchain audit github-governance \\\n --repository kungfu-systems/buildchain \\\n --target-ref dev/v3/v3.0 \\\n --require-qualifying \\\n --json\n```\n\nProtected merge and publication consumers verify the receipt against the exact\nrepository, target base ref, policy root, freshness window, and exact\nBuildchain verifier source revision. Non-dry-run publication does not trust a\ncaller-supplied JSON hash: it mints a bounded token for the dedicated read-only\ngovernance auditor GitHub App, recollects live provider state with the exact\nBuildchain runtime, requires the resulting single-repository/single-target\naudit to qualify, and consumes that independently generated receipt. Missing\nApp configuration or unreadable provider state denies publication before\nprovider mutation. The publication authority workflow is itself an explicit\nCode Owner path.\n\nThe publication authority job and every reusable-workflow caller grant the\nbuilt-in `GITHUB_TOKEN` only `actions: read`, `checks: read`, `contents: read`,\nand `pull-requests: read`. The dedicated auditor App independently recollects\nthe organization-wide governance receipt, while these job-scoped permissions\nallow the exact publication transaction audit to resolve required check runs\nand merged pull-request review lineage. Omitting either read permission makes\nthe transaction evidence incomplete and therefore non-qualifying.\n\nThe output is sanitized. Managed public repositories retain their public\nidentity. Excluded private repositories produce no receipt or diagnostic.\nTokens, cookies, recovery material, private CODEOWNERS bytes, raw permission\npayloads, and credential-bearing URLs are never included.\n\n## Mutation and rollback boundary\n\nLive role, ruleset, branch-protection, Actions, Environment, or repository\nchanges are separate from audit. Every mutation starts from a read-only\ninventory and a frozen rollback snapshot. A rollout plan binds both roots and\nlists the exact API operation, impact, expected observation, and inverse\noperation. Apply must stop on the first unexplained drift and must perform a\npost-change read-back before continuing to the next bounded canary.\n\nPlan one exact branch without mutation:\n\n```bash\nbuildchain github-governance plan \\\n --repository kungfu-systems/buildchain \\\n --branch dev/v3/v3.0 \\\n --required-check check \\\n --required-check-app-id check=15368 \\\n --required-approvals 1 \\\n --snapshot-output rollback.json \\\n --plan-output rollout.json\n```\n\nAn already protected check preserves its observed GitHub App binding. Every new\nrequired check must declare `--required-check-app-id <context>=<app-id>`;\ncontext-only replacement is rejected because it would broaden which producer\ncan satisfy the gate.\n\nClassic branch-protection bypass allowances and ruleset bypass actors are both\npart of the effective policy. Reconciliation writes explicit empty user, team,\nand App bypass lists and verifies those lists after apply; omitting the provider\nfield is not treated as removal because GitHub may preserve the prior value.\n\nThe plan prints a `planRoot`. Apply requires that exact root and stops if live\nprotection no longer matches the frozen inventory:\n\n```bash\nbuildchain github-governance apply \\\n --plan-json rollout.json \\\n --confirm-plan-root sha256:...\n```\n\nRollback is separately explicit and root-bound:\n\n```bash\nbuildchain github-governance rollback \\\n --plan-json rollout.json \\\n --confirm-rollback-root sha256:...\n```\n\nFor an admitted exact target, classic branch protection can also be compiled\ndirectly from the authority descriptor. This mode preserves both App-bound\nchecks and intentionally unbound check contexts such as Kungfu release's\nlegacy `build`, rather than guessing a provider App identity.\n\n```bash\nbuildchain github-governance protection-policy-plan \\\n --repository kungfu-systems/kungfu \\\n --branch alpha/v4/v4.0 \\\n --snapshot-output protection-rollback.json \\\n --plan-output protection-rollout.json\n\nbuildchain github-governance protection-policy-apply \\\n --plan-json protection-rollout.json \\\n --confirm-plan-root sha256:...\n\nbuildchain github-governance protection-policy-rollback \\\n --plan-json protection-rollout.json \\\n --confirm-rollback-root sha256:...\n```\n\nFor an admitted exact target, repository ruleset reconciliation compiles the\ntarget descriptor into the provider body. It replaces bypass actors with the\nexact provider-admitted desired set, requires fresh Code Owner approval and\nresolved review threads, and binds required checks plus strict-update semantics\nto the target policy. Newly synthesized managed rules include GitHub's explicit\ncanonical defaults so the frozen expected root matches provider read-back.\nRepository rulesets default to no bypass actors. The\ndescriptor's target-bound GitHub Actions allowance is an upper bound on\neffective provider state, not a requirement to add that actor to every\nprotection layer; when needed, the built-in App allowance is expressed by\nclassic branch protection. The target condition must contain exactly one\nbranch; unrelated rules and conditions are preserved in place.\n\n```bash\nbuildchain github-governance ruleset-policy-plan \\\n --repository kungfu-systems/buildchain \\\n --branch alpha/v2/v2.14 \\\n --ruleset-id 19518955 \\\n --snapshot-output ruleset-rollback.json \\\n --plan-output ruleset-rollout.json\n\nbuildchain github-governance ruleset-policy-apply \\\n --plan-json ruleset-rollout.json \\\n --confirm-plan-root sha256:...\n\nbuildchain github-governance ruleset-policy-rollback \\\n --plan-json ruleset-rollout.json \\\n --confirm-rollback-root sha256:...\n```\n\nThe narrower `ruleset-plan` mode changes only `bypass_actors`; it remains\navailable for a bypass-only canary, but it cannot prove that an effective\nruleset matches the target descriptor. Both modes require the frozen ruleset\nsnapshot root for rollback.\n\nPaid-plan purchase, billing, legal/account-owner decisions, and any operation\nthat could remove the last recoverable owner remain external human gates.\nBreak-glass is disabled by default and, if ever admitted, must be separately\nauthenticated, reason-bound, time-bounded, independently receipted, and\nfollowed by mandatory restoration and root comparison."
784
784
  },
785
785
  {
786
786
  "id": "manual:homebrew",
@@ -1249,7 +1249,7 @@
1249
1249
  ],
1250
1250
  "maturity": "stable",
1251
1251
  "sourcePath": "docs/observed-evidence-patrol.md",
1252
- "digest": "sha256:dec743faa883f3a3356beb35f97e62098bda09577445e402a5db72159e525167",
1252
+ "digest": "sha256:9e806efee048dd4fbaa68349ed4200c61d6408b0470ff81a4616a454cc32248c",
1253
1253
  "headings": [
1254
1254
  {
1255
1255
  "level": 1,
@@ -1272,7 +1272,7 @@
1272
1272
  "anchor": "rollback-and-recovery"
1273
1273
  }
1274
1274
  ],
1275
- "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: observed-evidence-patrol\ndoc_type: contract\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-21\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-21\n invisible_context_boundary: No credentials, private logs, or unpublished evidence values are included.\n---\n\n# Observed Evidence Patrol\n\nObserved Evidence Patrol publishes a reproducible public observation without\ncreating a content PR for every refresh. It is for derived evidence whose\nmeaning is fully checked by the caller and whose publication safety can be\ndecided mechanically.\n\nThe caller generates a `kungfu-buildchain-observed-evidence-bundle`. The bundle\nbinds one `snapshot.id` to two byte-identical JSON files:\n\n- a versioned immutable object such as\n `dogfood-evidence/snapshots/<snapshotId>.json`;\n- a mutable last-known-good alias such as `dogfood-evidence.json`.\n\nBuildchain verifies both file digests and snapshot identities before receiving\nproduction authority. Apply then performs this order:\n\n1. conditionally create the immutable key with `If-None-Match: *`;\n2. read the immutable key back and verify its declared SHA-256 metadata;\n3. record the previous latest metadata as the rollback pointer;\n4. atomically replace the latest object;\n5. read latest back and verify the same snapshot and digest;\n6. invalidate only the declared viewer paths.\n\nAny generator, schema, digest, immutable-key, provider, or read-after-write\nfailure leaves the previous latest object in place. A colliding immutable key\nis reusable only when both snapshot identity and SHA-256 match; otherwise the\nrun fails without overwriting it.\n\n## Trust boundary\n\nThe reusable workflow admits only `schedule` or `workflow_dispatch` events on\nthe caller repository's default branch. It checks out that branch explicitly,\ndoes not persist Git credentials, and never runs pull-request or fork code. The\nconsumer owns its evidence semantics through the build and verify commands;\nBuildchain owns publication ordering and provider safety.\n\nProduction OIDC authority should be a dedicated role and Environment with no\nreview gate for steady-state refreshes. Its policy should allow only:\n\n- `s3:GetObject` and `s3:PutObject` on the exact latest key;\n- the same actions on the exact immutable snapshot prefix;\n- `cloudfront:CreateInvalidation` on the one distribution.\n\nIt must not receive bucket-wide delete, list, repository write, GitHub PR, or\ngeneral deployment authority. One-time workflow, IAM, schema, and page changes\nstill use normal review and release governance.\n\n## Ordinary site releases\n\nA site artifact that contains\n`.buildchain/observed-evidence-ownership.json` declares the paths owned by the\nPatrol channel. Web-surface deploy adds those paths to the S3 sync exclusion\nset, so a later full-site release cannot delete the snapshot archive or replace\nlatest with an older build fixture. The HTML page should project the latest\nJSON at runtime, retaining its committed copy only as an explicitly labelled\nfallback.\n\n## Rollback and recovery\n\nEvery receipt records the previous latest snapshot id, digest, ETag, and S3\nversion id when available. Rollback regenerates a bundle whose latest file is\nthe selected immutable snapshot and republishes it through the same validator;\noperators do not edit or delete immutable history. If CDN invalidation fails\nafter latest advances, rerunning the same bundle is idempotent and repairs edge\nconvergence."
1275
+ "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: observed-evidence-patrol\ndoc_type: contract\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-30\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-30\n invisible_context_boundary: No credentials, private logs, or unpublished evidence values are included.\n---\n\n# Observed Evidence Patrol\n\nObserved Evidence Patrol publishes a reproducible public observation without\ncreating a content PR for every refresh. It is for derived evidence whose\nmeaning is fully checked by the caller and whose publication safety can be\ndecided mechanically.\n\nThe caller generates a `kungfu-buildchain-observed-evidence-bundle`. The bundle\nbinds one `snapshot.id` to two byte-identical JSON files:\n\n- a versioned immutable object such as\n `dogfood-evidence/snapshots/<snapshotId>.json`;\n- a mutable last-known-good alias such as `dogfood-evidence.json`.\n\nThe bundle may also declare up to 16 derived mutable projections under\n`publication.projections`. Each projection declares its artifact-relative\n`source`, bounded destination `key`, exact `sha256`, `contentType`, and\n`cacheControl`. This supports static HTML such as `dogfood/index.html` without\nchanging the existing immutable/latest JSON contract.\n\nBuildchain verifies both file digests and snapshot identities before receiving\nproduction authority. Apply then performs this order:\n\n1. conditionally create the immutable key with `If-None-Match: *`;\n2. read the immutable key back and verify its declared SHA-256 metadata;\n3. record preceding version metadata for every declared mutable key;\n4. write and read back each derived projection in manifest order;\n5. atomically replace the latest object;\n6. read latest back and verify the same snapshot and digest;\n7. invalidate only the declared viewer paths.\n\nProjection-enabled publication requires bucket versioning before replacing an\nexisting mutable key. If a later projection, latest update, or invalidation\nfails, Buildchain restores preceding object versions in reverse order and\nremoves newly introduced declared projection keys. Rollback never lists the\nbucket and never touches keys outside the manifest.\n\nAny generator, schema, digest, immutable-key, provider, or read-after-write\nfailure leaves the previous latest object in place. A colliding immutable key\nis reusable only when both snapshot identity and SHA-256 match; otherwise the\nrun fails without overwriting it.\n\n## Trust boundary\n\nThe reusable workflow admits only `schedule` or `workflow_dispatch` events on\nthe caller repository's default branch. It checks out that branch explicitly,\ndoes not persist Git credentials, and never runs pull-request or fork code. The\nconsumer owns its evidence semantics through the build and verify commands;\nBuildchain owns publication ordering and provider safety.\n\nProduction OIDC authority should be a dedicated role and Environment with no\nreview gate for steady-state refreshes. Its policy should allow only:\n\n- `s3:GetObject` and `s3:PutObject` on the exact latest key;\n- the same actions on the exact immutable snapshot prefix;\n- for projection-enabled bundles, `s3:GetObjectVersion` and bounded\n `s3:DeleteObject` on the exact declared mutable keys;\n- `cloudfront:CreateInvalidation` on the one distribution.\n\nIt must not receive bucket-wide delete, list, repository write, GitHub PR, or\ngeneral deployment authority. One-time workflow, IAM, schema, and page changes\nstill use normal review and release governance.\n\n## Ordinary site releases\n\nA site artifact that contains\n`.buildchain/observed-evidence-ownership.json` declares the paths owned by the\nPatrol channel. Web-surface deploy adds those paths to the S3 sync exclusion\nset, so a later full-site release cannot delete the snapshot archive or replace\nlatest with an older build fixture. The HTML page should project the latest\nJSON at runtime, retaining its committed copy only as an explicitly labelled\nfallback.\n\n## Rollback and recovery\n\nEvery receipt records the previous latest and projection snapshot ids, digests,\nETags, and S3 version ids when available. A projection transaction rolls back\nautomatically when a later mutable step or CDN invalidation fails. Operators\ncan also regenerate a bundle whose latest file is the selected immutable\nsnapshot and republish it through the same validator; immutable history is\nnever overwritten or deleted. Legacy bundles without projections retain their\nexisting idempotent rerun behavior when CDN invalidation alone fails."
1276
1276
  },
1277
1277
  {
1278
1278
  "id": "manual:ownership",
@@ -1715,7 +1715,7 @@
1715
1715
  ],
1716
1716
  "maturity": "stable",
1717
1717
  "sourcePath": "docs/release-governance.md",
1718
- "digest": "sha256:42a45f51db7ea11658431718835e1a430959db2a5cc80fc4651681502fe6fb7c",
1718
+ "digest": "sha256:8c31e6daccd40ea4e7cb08e8fa3eb2f238006e331621aed45eddcb80aee5532f",
1719
1719
  "headings": [
1720
1720
  {
1721
1721
  "level": 1,
@@ -1808,7 +1808,7 @@
1808
1808
  "anchor": "operational-reading-order"
1809
1809
  }
1810
1810
  ],
1811
- "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: buildchain-release-governance\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-27\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-27\n invisible_context: not asserted\n---\n\n# Release Governance\n\nBuildchain v2 preserves the release semantics of the older ABV workflow while\nmoving the implementation into one modern repository.\n\nThe central idea is simple: a reviewed merge into a release channel is the\nrelease intent. Automation must then create the version-state commit, exact tag,\nfloating tag, and next alpha state that make that intent true in Git.\n\n## Design Problem\n\nKungfu release automation has to keep four facts aligned:\n\n1. The source tree that was reviewed.\n2. The package version recorded in manifests such as `package.json` or\n `lerna.json`.\n3. The exact immutable release or prerelease tag.\n4. The floating channel refs that consumers actually use.\n\nIf any one of these facts is updated by hand, the system can split:\n\n- a consumer can fetch `v2.0` and receive a tree whose package version still\n says the previous release;\n- a maintainer can move `v2` without producing an exact `v2.0.N` audit tag;\n- an alpha can be promoted to production even though the release tree is not the\n same tree that was tested;\n- a protected branch merge can succeed while the follow-up version commit is\n missing, or a flow-internal generated `dev`/`alpha`/`release` ref update can\n fail after publish because the automation identity was not declared in the\n branch-protection review bypass allowance.\n\nThe older ABV workflow addressed this by letting GitHub PRs drive release\nstate. Buildchain keeps that choice because it makes release intent reviewable,\nobservable, and recoverable from Git history.\n\n## What ABV Contributed\n\nThe old ABV model was not just \"bump a version number.\" It encoded a governance\nloop:\n\n- release branches are named as channels: `dev`, `alpha`, `release`, and the\n administrative `publish-gate/major`;\n- a PR from one channel to the next is the release request;\n- verify jobs check that the branch pair is valid before merge;\n- a maintainer review is required before the branch moves;\n- after merge, automation writes the version change and moves tags;\n- exact tags and floating refs are aligned with the resulting commit;\n- the next development channel is prepared automatically.\n\nABV also kept the version-state mutation in the repository. For JavaScript\nrepositories that usually meant changing `lerna.json` and/or `package.json`.\nThat commit is important because the tag alone is not enough evidence: the\nsource tree should also declare the version that the tag advertises.\n\nBuildchain v2 treats that as a hard semantic requirement for its own release\nline.\n\n## Buildchain Implementation\n\nBuildchain implements the same governance loop with:\n\n- `.github/workflows/release-verify.yml` for PR verification;\n- `.github/workflows/buildchain-ref-promotion.yml` for post-verify ref\n promotion; this workflow dogfoods the declarative\n `release-candidate-promote.yml` wrapper and does not hand-wire resolver,\n artifact download, publish-gate, or promote action steps;\n- Buildchain self promotion enables `release-passport-buildchain-self-kfd`, so\n the promote action generates KFD-1 witnesses, KFD-2 public claim JSON, and\n KFD-3 collaboration-interface witnesses from the final version-state workspace\n before release passport finalization. The witness hashes therefore bind to the\n exact published package and site facts from\n `packages/core/buildchain-kfd-claims.js` instead of relying on prose release\n notes;\n- `actions/promote-buildchain-ref` for branch, tag, version-state, and\n governance checks;\n- package-manager adapters that can update version state for pnpm, npm, and\n yarn style repositories;\n- `buildchain.toml` lifecycle configuration for repositories whose version\n state or verification commands are not Node package-manager defaults.\n\nThe implementation is intentionally stricter than a local release script:\n\n- manual workflow dispatch can only do dry-run promotion;\n- non-dry-run promotion must be driven by a completed `Verify` workflow;\n- target branch protection details must be readable, and branch protection must\n apply to administrators as well as regular contributors;\n- `alpha/vX/vX.Y` and `release/vX/vX.Y` branch protection must require both the\n general `check` context and the Release Verify aggregate `verify` context, so\n an invalid channel pair cannot merge even when repository checks pass;\n- release targets keep those checks non-strict with respect to source-branch\n ancestry: generated channel bookkeeping intentionally makes the source and\n target histories diverge, while the pair-specific `verify` context validates\n the legal channel transition;\n- alpha promotion must come from a merged same-repository PR from\n `dev/vX/vX.Y` to `alpha/vX/vX.Y`;\n- release promotion must come from a merged same-repository PR from\n `alpha/vX/vX.Y` to `release/vX/vX.Y`;\n- major promotion must come from a merged same-repository PR from\n `release/vX/vX.Y` to `publish-gate/major`;\n- release promotion requires an existing same-patch alpha tag and checks the\n release source tree against that tested alpha tree;\n- generated version-state commits are verified before refs move.\n\n## Reconciling a protected line without rebuilding\n\nThe public `build.yml` channel router ends with a top-level job named\n`Summarize build contract`. Keeping this aggregate at the public router boundary\nprevents its required check context from changing when the internal reusable\nbuild workflow gains another nesting layer.\n\nFor an already-tested pull request whose protected target still requires an\nolder Buildchain aggregate context, inspect the exact candidate SHA first:\n\n```bash\nGH_TOKEN=\"$(gh auth token)\" npx @kungfu-tech/buildchain@latest \\\n release-governance reconcile \\\n --repository kungfu-systems/example \\\n --branch release/v2/v2.14 \\\n --candidate-sha <tested-pr-head-sha> \\\n --json\n```\n\nThe dry run reads the successful checks emitted for that SHA and reports the\nexact expected/actual context pair. It chooses the shallowest successful\n`Summarize build contract` context, so a new top-level router aggregate wins\nover the nested internal build summary. To apply the plan, rerun the same\ncommand with `--apply` using a token that can update branch protection.\n\nReconciliation changes only the required-status-check subresource. It replaces\nstale Buildchain aggregate contexts, preserves unrelated checks and strictness,\nand does not modify review requirements, administrator enforcement,\nconversation resolution, force-push policy, or deletion policy. The candidate\nmust still be the head of a pull request targeting the named managed branch;\nthe command fails closed otherwise. This lets a previously successful candidate\ncontinue from the same SHA without another native build or an administrator\nmerge bypass.\n\nRepositories may also expose a small caller workflow around\n`.github/workflows/release-governance-reconcile.yml@v3`. Pass `branch`,\n`candidate-sha`, and `apply`, and provide `governance-token` through the caller's\nsecrets. The reusable workflow uploads the JSON reconciliation receipt.\n\nExact publication planning installs the checked-out promotion source's declared\ndependencies before version-state verification. This keeps the pre-authority\nversion plan on the same package-manager boundary as the later promotion job,\nincluding repositories whose verification commands import production packages.\nThe planning pass may materialize and verify declared derived files locally,\nbut dry-run never creates Git blobs, trees, commits, refs, or tags.\n\nPromotion intents are serialized globally per caller repository with\n`cancel-in-progress: false`. A queued intent re-reads its protected target ref\nbefore checkout, dependency installation, release-candidate resolution, or any\npublish-gate/ref mutation. If the ref already points at a newer SHA, that older\nintent is no longer release authority: the workflow records the requested and\ncurrent SHAs, proves that the current target is ahead of the requested commit,\nand completes as a `target-ref-advanced` superseded no-op. Diverged or behind\ncomparisons are not superseded transactions and still fail closed.\nMissing refs, unreadable repository state, invalid channels, governance\nfailures, and artifact mismatches still fail closed. The promote action repeats\nthe target check at the mutation boundary, so a ref that advances after the\nworkflow preflight cannot receive a second set of publication side effects.\n\n## Version Lines\n\nKungfu uses Python-like version lines where a minor line can represent a\nlong-lived product train. A line such as `v2.0` can produce many production\npatch releases:\n\n```text\nv2.0.0\nv2.0.1\nv2.0.2\n...\nv2.0.1234\n```\n\nThis is why Buildchain maintains both exact and floating refs:\n\n- `v2.0.2` is immutable release evidence;\n- `v2.0` is the latest production release on the `2.0` line;\n- `v2` is the selected stable major-line entrypoint;\n- `v2.0.3-alpha.0` is immutable alpha evidence;\n- `v2.0-alpha` is the latest test channel for the `2.0` line.\n- `v3-alpha` is the latest test channel on the highest published alpha minor in major `2`.\n\nA release does not mean \"minor is complete.\" It means \"this patch on this minor\nline is now production.\"\n\nGitHub repository rules must preserve that distinction. Exact tags such as\n`v2.0.2` and `v2.0.3-alpha.0` should be immutable. Floating channel tags such as\n`v2`, `v2.0`, `v2.0-alpha`, and `v3-alpha` must remain movable by the Buildchain promotion\ntoken after governance checks and publish evidence pass. A tag ruleset that\nprotects every `refs/tags/v*` ref is too broad because it also locks the\nfloating channel tags that Buildchain is required to update. Prefer exact-tag\npatterns such as `refs/tags/v*.*.*` for immutable release evidence, while\nleaving floating channel tags under Buildchain automation control.\n\n## Alpha Semantics\n\nAn alpha merge is:\n\n```text\ndev/vX/vX.Y -> alpha/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Computes the next prerelease for the minor line.\n2. Writes version state such as `vX.Y.Z-alpha.N`.\n3. Verifies the generated version-state tree.\n4. Creates or reuses the exact alpha tag.\n5. Moves `alpha/vX/vX.Y` to the generated alpha commit.\n6. Moves `dev/vX/vX.Y` to the same generated alpha commit when this is a\n fast-forward update.\n7. Moves `vX.Y-alpha` to the same generated alpha commit.\n8. Moves `vX-alpha` only when `X.Y` is the highest minor in major `X` with a published alpha; older minor alpha work records a skip and cannot move the major channel backwards.\n\nThe npm channel follows the same ownership rule. The highest alpha minor publishes\nwith dist-tag `alpha`; maintenance alphas on an older minor publish with the\nline-specific dist-tag `vX.Y-alpha` so they cannot roll the global `alpha`\nchannel backward. Exact prerelease versions remain installable directly.\n\nThis keeps the test channel self-describing. If a consumer checks out\n`v2.0-alpha` or `v3-alpha`, the manifests and exact alpha tag agree. The major\nalpha ref removes routine consumer edits when Buildchain opens a newer minor,\nwhile exact tags and SHAs remain the reproducible audit choice.\n\n### Buildchain Alpha Self-Dogfood\n\nBuildchain continuously consumes its own current major alpha through\n`.github/workflows/buildchain-alpha-self-dogfood.yml`. Both lanes call the\nreleased channel router at `build.yml@v3-alpha`. The auto lane must resolve\n`v3-alpha`; the explicit stable lane must resolve `v2`. Both execute the same\ndeclared install, build, and verify fixture, proving that a single consumer\nsurface routes to distinct released runtimes without duplicating lifecycle\nconfiguration in the consumer.\n\nBuildchain's generic artifact-signing contract seals source-, tree-, runtime-,\nplatform-, and digest-bound requests from ordinary credential-free build jobs.\nProvider-specific authority jobs consume only those sealed payloads. Apple\nDeveloper ID, Windows Authenticode, and detached cryptographic signatures share\nthe request/receipt model, while each profile retains its honest platform\nsemantics and fail-closed verification requirements.\n\nThe central `buildchain-artifact-signing` environment reuses the established\nmacOS Credential Island names (`BUILDCHAIN_MACOS_CERTIFICATE_*`,\n`BUILDCHAIN_MACOS_NOTARY_API_*`, and\n`BUILDCHAIN_MACOS_EXPECTED_TEAM_ID`). Those authority-only values are never\ndeclared by or forwarded through a consumer repository. Windows and detached\nproviders follow the same central-environment boundary.\n\nThe reusable build trust gate reads `job.workflow_ref`, which identifies the\ncalled workflow and its selected ref. It does not infer the runtime from\n`github.workflow_ref`, because GitHub defines that context as the caller\nworkflow identity during a reusable call.\n\nThe router selects `.buildchain/alpha-contract-lock.json` for alpha and\n`.buildchain/contract-lock.json` for stable. The alpha lock records the exact\nreviewed alpha SHA and compatibility digest; it does not replace the stable\nconsumer lock. A later alpha with only compatible additive drift continues,\nwhile a changed breaking digest fails until the new alpha contract is reviewed.\n\nThe evidence job resolves `v3-alpha` and `v2` through the GitHub refs API,\ncompares those immutable SHAs with the reusable workflow outputs, verifies the\n`alpha` and `stable` classifications, and uploads a JSON evidence artifact.\nThe canary runs after successful Buildchain ref promotion, on a daily fallback\nschedule, and on manual dispatch. It shares the release-promotion concurrency\ngroup, so another promotion cannot move the floating refs between runtime\nresolution and evidence comparison.\n\nThis is a post-publication consumer canary, not a release bootstrap. Source\nverification still runs the current commit, and\n`buildchain-ref-promotion.yml` still passes the verified exact SHA into the\npromotion workflow. Patrol, dev-merge, repair, and promotion defaults remain on\nstable or exact refs so a broken alpha cannot prevent Buildchain from publishing\nits fix. When Buildchain opens a new major, inventory validation requires this\nworkflow's fixed GitHub `uses` refs to move from `vN`/`vN-alpha` to the new\nmajor; GitHub does not allow expressions in a reusable-workflow `uses` ref.\n\nIf `dev/vX/vX.Y` has already advanced before generated alpha version-state\nbookkeeping can sync back, Buildchain records `skipped-non-fast-forward` for the\ndev sync and still completes the exact and floating alpha tags for the reviewed\nalpha commit. Later dev changes must go through their own dev-to-alpha\npromotion instead of rewinding dev. The normal path is direct: after alpha\nmerges, Buildchain applies the generated version-state commit to alpha and then\nfast-forwards dev to the same commit without a human version-state PR.\n\nIf alpha finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain accepts the current alpha head as the generated\ncommit, or as a historical merge commit that contains the recorded release\nmaterial. An already-created exact alpha tag may point at the transaction\nrelease/material SHA or at the finalized alpha head; missing floating alpha\ntags are retried before the transaction becomes `complete`.\n\n## Release Semantics\n\nA release merge is:\n\n```text\nalpha/vX/vX.Y -> release/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Finds the same-patch alpha tag that was tested.\n2. Checks that the release source tree matches that alpha tag tree, excluding\n only generated version-state differences.\n3. Writes final release version state such as `vX.Y.Z`.\n4. Verifies the generated release tree.\n5. Creates or reuses the exact release tag `vX.Y.Z`.\n6. Moves `release/vX/vX.Y` to the exact release commit.\n7. Moves `vX.Y` to the exact release commit.\n8. Moves `vX` when this minor line should be the stable major entrypoint.\n9. Prepares the next alpha version-state commit, such as\n `vX.Y.(Z+1)-alpha.0`.\n10. Moves `alpha/vX/vX.Y`, `dev/vX/vX.Y`, and `vX.Y-alpha` to that next alpha\n commit.\n11. Moves `vX-alpha` to that next alpha only when this remains the highest published alpha minor.\n\nThe historical alpha tree comparison remains the default stable source gate.\nA promote-only stable run may accept a broader reviewed release PR only when\nthe downloaded RC passport proves that the PR's exact target tree is the tree\nthat completed the PR-stage build. Buildchain also requires the target commit\nto belong to a merged same-repository PR into the selected release branch and\nrecords the accepted commit, tree, RC source, alpha source, and PR as promotion\nevidence. A stale passport, a different target tree, a generated final release\ncommit, or an unreviewed target commit still falls through to the normal\nalpha-tree and declared version-state checks.\n\nThe production channel and the test channel therefore intentionally diverge\nafter release: production stays on the release commit, while alpha/dev continue\nat the next prerelease commit.\n\n### Stable Release Throttle And Canary Gate\n\nBuildchain's own stable channel has an additional pre-publication gate. It is\nevaluated after the PR-stage release candidate has been resolved and before the\npublish-gate ref, package registry, exact stable tag, or floating stable refs\nare mutated. Train refs and alpha promotion do not execute this gate.\n\nAll alpha and release promotions first run a metadata-only release-candidate\npreflight after queued-intent revalidation. The preflight uses the same resolver\nand exact target SHA as the publication job, but does not download payloads or\ninstall the candidate repository dependencies. Missing channel PRs, stale\nworkflow runs, expired passport pairs, and insufficient payload artifact sets\ntherefore fail before the full promotion job starts. The publication job still\ndownloads and validates the exact evidence again at the trust boundary; the\npreflight moves failure earlier without weakening the final check.\n\nThe policy is versioned in `.buildchain/stable-release-policy.json`. A stable\ncandidate is allowed only when all of these facts are true:\n\n- the candidate resolves to an immutable exact alpha tag and its GitHub\n prerelease timestamp;\n- at least 24 hours have passed since the preceding stable patch on the same\n minor line;\n- the preceding stable-to-alpha comparison contains a product or public\n contract path, not only version, test, evidence, or retrospective changes;\n- the version-bound impact record has a non-empty summary and at least one\n surface impact;\n- the `Build Surface Fixture` release-candidate run succeeded;\n- `site-libkungfu-dev` completed its no-apply `Buildchain Stable Canary` and an allowed\n maintainer attested that successful run on the exact alpha SHA through the\n `buildchain-canary/site-libkungfu-dev` commit-status context;\n- the canary runtime input is exactly the candidate alpha tag or the 40-character\n commit SHA resolved from that tag; a successful status pointing at another\n workflow, repository, tag, floating ref, or SHA is rejected as mismatched;\n- at least one hour has elapsed after the last required canary completed.\n\nThe machine report is written to\n`.buildchain/release-passport/stable-release-gate.json`. A passing report is\nuploaded with the stable release passport. A blocked run writes the same report\nbefore failing, so the missing or stale condition is inspectable without\nopening a publication transaction.\n\nGitHub's Actions run REST object does not expose `workflow_dispatch` inputs.\nBuildchain therefore verifies the run's `workflow_id` against the authoritative\nworkflow metadata, then reads the exact runtime from the workflow-owned\n`<workflow name> / <runtime ref>` run-name when no input field is available.\nAn explicit API input, when present, remains authoritative and cannot be\noverridden by the display name.\n\nThe cooldown is a minimum interval, not an instruction to release every day.\nCompatible work should still be batched until a stable release has a concrete\nconsumer need. Changing the interval, canary set, attestors, product path\nboundary, or soak time is a reviewed policy change.\n\nRepositories that want a predictable scheduled stable window can use\n[`Stable Candidate Patrol`](stable-candidate-patrol.md). It persists each exact\nalpha independently, qualifies it after repository-declared checks and soak,\nand selects the newest qualified non-revoked candidate. A newer soaking alpha\ndoes not invalidate an older qualified candidate. The selected tree enters the\nexisting strict `publish-gate/release/<line>/<version> -> release/<line>` PR\npath, so scheduled selection changes release intent timing without weakening\nsource locks, review, verification, publish transactions, or passports.\n\nIf release finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain applies the same recovery rule: the current\nrelease head may be the generated commit, or a historical merge commit that\ncontains the recorded release material, existing exact tags and alpha/dev refs\nare accepted when they match the transaction, and missing floating `vX.Y` or\n`vX` tags are retried idempotently before completion.\n\nOnce the durable release transaction is `complete` and the exact/floating\nstable refs have moved, next-alpha preparation is post-release bookkeeping. A\nfailure there records `deferred-post-release-bookkeeping` and\n`next-anchor-required` instead of retroactively reporting the stable release as\nfailed. Generated next-alpha merges use the current dev tree as their base and\noverlay only declared version-state paths, preserving concurrent dev changes.\n\n## Major Gate Semantics\n\nA major gate merge is:\n\n```text\nrelease/vX/vX.Y -> publish-gate/major\n```\n\n`publish-gate/major` is the explicit replacement for the older ABV `main`\nchannel. The name is intentionally operational: it is a gate for a rare\nadministrator decision, not the active trunk. Keeping this decision in the same\nPR UI as alpha and release promotion keeps the human workflow simple while\navoiding the misleading meaning of `main`. The older `major-gate` branch name is\na compatibility alias only.\n\nBuildchain then:\n\n1. Verifies the source is a merged same-repository PR from a protected release\n line into `publish-gate/major`.\n2. Writes the next major production version state, such as `v(X+1).0.0`.\n3. Creates or reuses the exact release tag `v(X+1).0.0`.\n4. Moves `publish-gate/major` and `release/v(X+1)/v(X+1).0` to that release commit.\n5. Moves `v(X+1).0` and `v(X+1)` to that release commit.\n6. Prepares the next alpha version-state commit, such as\n `v(X+1).0.1-alpha.0`.\n7. Moves `alpha/v(X+1)/v(X+1).0`, `dev/v(X+1)/v(X+1).0`, and\n `v(X+1).0-alpha` to that next alpha commit.\n\nChecking out `publish-gate/major` should therefore look like a frozen release\nstate, not like a branch where day-to-day source work continues. Day-to-day\nsource work continues on `dev/vX/vX.Y`.\n\n## Protected Dev Branches\n\n`dev/vX/vX.Y` is a protected development channel, not a scratch branch. Normal\nsource changes should be made on work branches such as `feature/*`, `fix/*`,\n`chore/*`, `docs/*`, `ci/*`, or `refactor/*`, then reviewed through a pull\nrequest into the target dev line.\n\nThis keeps the earliest development channel audit-friendly:\n\n- the version line being changed is visible in the PR base branch;\n- CI and required checks run before the channel moves;\n- branch protection can prevent direct pushes and stale merges;\n- later `dev -> alpha -> release` promotion inherits a reviewable source\n lineage instead of trying to reconstruct how the dev branch changed.\n\nWhen required checks take longer than the normal dev-channel commit interval,\nclassic strict up-to-date protection can become a non-converging retry loop:\neach base update invalidates a completed check set and rebasing restarts the\nsame slow checks. Buildchain supports GitHub merge queues for that channel\nshape. The queue validates the projected merged result and serializes the final\nref update, so concurrent channel movement no longer invalidates the candidate.\n\nEvery required workflow must handle both `pull_request` and `merge_group`\nbefore the queue is enabled. Queue runs do not provide\n`github.event.pull_request`; required workflows must use the checked-out\n`github.sha` or event-neutral source facts. The governance command is dry-run by\ndefault and refuses to enable a queue when a declared required workflow lacks\neither trigger or still reads the pull-request-only payload directly:\n\n```bash\nbuildchain dev merge-queue \\\n --repository owner/repository \\\n --branch dev/v4/v4.0 \\\n --workflow .github/workflows/source-acceptance.yml \\\n --workflow .github/workflows/affected-native-pr.yml \\\n --bypass-app dedicated-release-app\n```\n\nAfter reviewing the plan, repeat with `--apply`. Buildchain creates or updates\nan exact-branch `merge_queue` ruleset first, then changes only the classic\nrequired-status-check policy from strict to loose. Reviews, administrator\nenforcement, conversation resolution, required check identities, force-push\nprotection, and deletion protection remain owned by the existing branch\nprotection. The ruleset uses the first merge method that the repository itself\nallows, and fails closed when the repository has no enabled merge method.\nRe-running the command is idempotent.\n\nThe repository policy can be declared once instead of repeated as CLI flags:\n\n```toml\n[governance.dev.merge_queue]\nmode = \"inherit\"\nrequired_workflows = [\".github/workflows/verify.yml\"]\n```\n\n`enabled` explicitly requires Buildchain to create or update an exact-branch\nqueue; `inherit` copies queue parameters and bypass actors from the repository's\ncurrent default dev branch; `disabled` prevents automatic queue creation. An\nabsent declaration behaves as `inherit` during release-line bootstrap so a new\nmajor or minor line does not silently lose governance already active on the\nprevious line. Required status-check identities still come from the new\nbranch's own classic protection rather than being copied from the old branch.\n\nMerge-queue rules also reject generated post-publish version-state ref updates.\nWhen the sealed promotion workflow uses a dedicated GitHub App, user, or team\nalready declared by release governance, repeat `--bypass-app`, `--bypass-user`,\nor `--bypass-team` to project that exact actor into the ruleset. Bypass actors\nare never inferred and broad repository or organization roles are not accepted.\nThis keeps ordinary feature PRs on the predecessor-aligned queue path while the\nsealed publication authority can finish its machine-verified bookkeeping. The\ndry-run receipt exposes the exact actor IDs before `--apply` changes GitHub.\n\nBuildchain provides the reusable\n`.github/workflows/dev-pr-auto-merge.yml` workflow for repositories that want a\nscheduled or manual \"merge ready dev PRs\" pass. The consumer repository owns\nthe trigger schedule, but the merge decision is declared through workflow\ninputs: target dev branch, required status/check names, ready and block labels,\nallowed work-branch prefixes, review requirements, maximum merges per run,\nmerge method, and dry-run mode.\n\nThe workflow defaults are conservative. A PR is skipped unless it targets the\nconfigured dev line, is not a draft, has the ready label, has no block label,\ncomes from the same repository, uses an allowed work-branch prefix, has a\ncurrent approval, is mergeable, and has the configured required checks passing.\n`landing-mode: auto` reads the target branch's native merge-queue state. When a\nqueue exists, Buildchain never calls the direct merge endpoint: it admits at\nmost one PR against the observed target-branch SHA and immutable PR head, then\ncalls GraphQL `enqueuePullRequest` with `expectedHeadOid`. GitHub's\n`merge_group` checks remain the final authority for the projected merge.\n\nThe admission receipt records the expected and observed base/head SHAs, policy\nchecks, decision, reason, and active predecessor. Buildchain re-reads the base,\nhead, mergeability, and native queue immediately before enqueueing. Base or\nhead drift fails closed, an active queue entry blocks admission, and a rejected\nready predecessor leaves its PR open while later PRs receive\n`blocked-by-predecessor`. Workflow concurrency serializes Buildchain-owned\nadmission runs; GitHub still owns the atomic queue and protected-ref update.\nRepositories may explicitly select `landing-mode: direct` only when the target\nbranch has no native queue. Queue presence always disables the direct path.\n\nThe canonical consumer required check context is `check / check`, matching the\nreusable workflow call plus its `check` job. Buildchain's own `Verify` workflow\nemits the repository-local context `check`, so Buildchain self-promotion,\nrelease-line bootstrap, and dogfood patrol wrappers pass `check` explicitly.\nRelease governance preserves the exact context emitted by the repository and\nrecords branch-protection policy before/after facts; it does not rewrite one\ncontext shape into the other. Consumer repositories can keep their context\nstable while changing the actual verification command declaratively in\n`buildchain.toml`:\n\n```toml\n[lifecycle.install]\ncommand = \"cargo fetch --locked\"\n\n[lifecycle.verify]\ncommand = \"cargo test --workspace --locked\"\n```\n\nConsumers that want Buildchain to own the check wrapper can call\n`.github/workflows/check.yml@v3`. The wrapper runs the declared\n`lifecycle.install` and `lifecycle.verify` stages and fails the `check` job when\neither declaration is missing or the command exits non-zero.\n\nDevelopment pull requests that need source acceptance without product build or\nartifact verification can opt into `mode: source`. That mode runs only\n`lifecycle.install` and `lifecycle.check` on GitHub-hosted `ubuntu-24.04` while\npreserving the stable `check / check` required-check context. Existing callers\nremain on `mode: verify` by default, and callers may set\n`upload-artifacts: false` without weakening the job conclusion.\n\nTypical consumer wrapper:\n\n```yaml\nname: Merge Ready Dev PRs\n\non:\n schedule:\n - cron: \"17 * * * *\"\n workflow_dispatch:\n inputs:\n dry-run:\n type: boolean\n default: true\n\njobs:\n merge-dev:\n uses: kungfu-systems/buildchain/.github/workflows/dev-pr-auto-merge.yml@v3\n permissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n with:\n target-branch: dev/v2/v2.6\n required-status-checks: check / check\n ready-label: ready\n block-labels: blocked,do-not-merge\n max-merges: 1\n landing-mode: auto\n dry-run: ${{ inputs.dry-run || false }}\n```\n\n## Buildchain Patrol\n\n`dev-pr-auto-merge.yml` remains the focused merge primitive. For repositories\nthat want a stable day-to-day operations contract, Buildchain also exposes a\npatrol workflow family:\n\n| Workflow | Intended cadence | Default intent |\n| --- | --- | --- |\n| `.github/workflows/patrol-daily.yml` | daily | lightweight inspection plus ready dev PR maintenance |\n| `.github/workflows/patrol-weekly.yml` | weekly | release-state, passport, gate, and stale-state health checks as they are added |\n| `.github/workflows/patrol-monthly.yml` | monthly | governance, permission, branch-protection, and workflow drift checks as they are added |\n| `.github/workflows/patrol-observed-evidence.yml` | caller-selected schedule | validated immutable observation plus atomic last-known-good publication; no per-refresh PR |\n| `.github/workflows/stable-candidate-patrol.yml` | repository-selected release window | qualify immutable alpha candidates and open the exact source-lock stable PR |\n\nThe cadence names describe patrol intensity, not release cadence:\n\n- daily patrol can run every day without implying a daily release;\n- weekly patrol is for medium-cost maintenance and audit checks;\n- monthly patrol is for structural drift checks that should not block ordinary\n development velocity.\n\nStable Candidate Patrol is separate from those maintenance cadences because its\ncaller-owned cron is a release-intent window. Its candidate ledger and selection\nremain generic; registry-specific side effects still run through the normal\nrepository `lifecycle.publish` transaction. See\n[`stable-candidate-patrol.md`](stable-candidate-patrol.md).\n\nObserved data that is mechanically regenerated and path-scoped uses the\nseparate [`Observed Evidence Patrol`](observed-evidence-patrol.md) contract.\nIts one-time mechanism changes remain reviewed, while steady-state snapshot\nrefreshes publish directly from trusted default-branch schedule/manual callers.\n\nConsumers should schedule thin callers and keep their YAML declarative. For\nexample:\n\n```yaml\nname: Buildchain Daily Patrol\n\non:\n schedule:\n - cron: \"17 2 * * *\"\n workflow_dispatch:\n\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-daily.yml@v3\n with:\n dry-run: false\n max-actions: 1\n```\n\nWeekly and monthly callers use the matching wrapper:\n\n```yaml\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-weekly.yml@v3\n with:\n dry-run: true\n```\n\nAll three wrappers default to the `v2` floating Buildchain runtime. When\n`target-branch` is omitted, the caller's current/default branch selects the\nactive semver dev line, so consumers do not pin patrol to a stale minor branch.\nThe separate workflow names keep consumer schedules readable and stable while\nBuildchain adds new checks behind the cadence wrappers.\n\n## Package-Manager Adapters\n\nOld ABV assumed JavaScript repositories with root version state and often\nLerna. Buildchain keeps the version-state contract but does not assume every\nrepository is yarn/Lerna.\n\nThe promotion action discovers and updates:\n\n- root `package.json`;\n- `lerna.json`;\n- package manifests from `package.json` workspaces;\n- package manifests from `lerna.json` packages;\n- package manifests from `pnpm-workspace.yaml`.\n\nIt then runs the repository's detected package manager semantics where needed:\n\n- pnpm repositories use pnpm-oriented workspace discovery;\n- npm repositories use npm/package-lock semantics where present;\n- yarn repositories use yarn-style metadata where present.\n\nFor Buildchain itself, version state is required. For a consumer repository that\nhas no package manifest, the same action can degrade to ref-only behavior only\nwhen that is explicitly allowed by the caller.\n\n## Lifecycle Configuration\n\n`buildchain.toml` is the v2 user configuration format. It lets a repository\ndeclare version-state files and lifecycle commands without pretending every\nproject is a Node workspace. Supported version files include JSON, TOML, and\nregex-based files such as `CMakeLists.txt` or `conanfile.py`.\n\nThe promotion action consumes `version.files`, optional anchored/manual\n`version.derived_files`, and `lifecycle.verify`.\nThe verify stage runs after generated version-state changes are applied locally\nand before any release refs move. If `verification-command` is passed directly\nto the action, that explicit command overrides `lifecycle.verify`.\n\nAnchored/manual repositories may use `version.derived_files` for committed\nversion witnesses that are regenerated by `lifecycle.version-state`. The\nrelease-candidate build verifies those witnesses before heavy builds and records\ntheir digests with the exact alpha and release tree identities. Promotion then\naccepts only declared version files, the anchor manifest, and those derived\nfiles as differences from the tested alpha tree; release passports preserve the\nsame material binding.\n\nProtected release-line branches keep their normal human review gate. Managed\n`dev/vN/vN.M`, `alpha/vN/vN.M`, and `release/vN/vN.M` branches are configured\nwith one required approving review, required GitHub Actions checks, administrator\nenforcement, conversation resolution, no force pushes, and no deletions. Each\ntarget uses the exact check set, GitHub App identity, and strictness declared by\nthe governance authority descriptor. The\nreusable `release-candidate-promote.yml` wrapper defaults\n`branch-protection-bypass-apps` to `github-actions`, which lets the workflow's\nautomation identity apply generated version-state or post-publish channel\nbookkeeping after the reviewed channel PR has merged. Direct\n`promote-buildchain-ref` callers may opt into that one controlled bypass with\n`branch-protection-bypass-apps: github-actions`; every other App slug and all\nuser or team bypass actors are rejected. Before\npatching a protected generated bookkeeping ref, the action creates the\nfull configured required-check set on the exact generated version-state commit, so strict\nstatus checks are satisfied by machine-verifiable Buildchain evidence rather\nthan a human PR. The protected ref PATCH itself uses the generated ref update\ntoken; the reusable wrapper binds it to the run-scoped `github.token`. If direct generated\nrelease finalization bookkeeping is still rejected, Buildchain creates or\nreuses a same-repository `buildchain/version-state/*` PR and records\n`finalization-needed=true` in the durable transaction output. Strict alpha\nfollows the same provider-enforced PR path for its alpha and dev bookkeeping.\nThe PR remains subject to the declared review, required checks, and merge-queue\npolicy; publication resumes idempotently after that protected transaction\nlands.\n\nFor a stable release, the wrapper also checks out the exact current development\nchannel into `.buildchain/reconciliation/dev`. When the prepared next-alpha\ncommit cannot fast-forward dev because reviewed work landed concurrently, the\npromotion action applies the next version to that checkout, regenerates every\ndeclared derived version-state file, reruns the verification lifecycle, and\nonly then creates the two-parent reconciliation commit. A checkout/current-ref\nSHA mismatch blocks reconciliation instead of committing stale projections.\nBuildchain's own promotion workflow accepts only\n`BUILDCHAIN_PROMOTION_BYPASS_APPS=github-actions`, defaulting to that exact App\nwhen the variable is absent. Buildchain's release-line bootstrap uses the\nadministrator-scoped promotion token only to configure protection; branch\ncreation and generated ref updates use the run-scoped token. New channel\nprotection binds required checks to GitHub Actions App id `15368`, enables Code\nOwner, stale-review, and latest-push review gates, and admits no user or team\nbypass actor.\n\n## What This Guarantees\n\nWhen the loop succeeds, maintainers and consumers can rely on these facts:\n\n- every production release has an exact tag such as `v2.0.2`;\n- every production minor line has a floating tag such as `v2.0`;\n- every selected stable major has a floating tag such as `v2`;\n- every next-major release is driven by a reviewed `release -> publish-gate/major` PR,\n not a hidden manual button;\n- every test channel has an exact alpha tag such as `v2.0.3-alpha.0`;\n- every alpha minor line has a floating tag such as `v2.0-alpha`;\n- every major with a published alpha has a cross-minor floating tag such as `v3-alpha`, owned by its highest published alpha minor;\n- version manifests match the tag visible from the same commit;\n- production releases are derived from the alpha tree that was tested;\n- manual non-dry-run promotion cannot bypass PR review and verification;\n- flow-internal automation bypasses apply only to declared GitHub Apps, users,\n or teams on Buildchain-managed channel branch protection, while one-review\n protection remains enforced for humans;\n- admin users cannot make a channel promotion valid by temporarily bypassing\n branch protection.\n\nThis is the practical meaning of \"governance closed loop\" in Buildchain: the\ndecision, code, version state, and Git refs close over the same evidence chain.\n\n## What This Does Not Do\n\nBuildchain release promotion does not embed registry clients or product-specific\npublish logic. When publish transactions are enabled, `promote-buildchain-ref`\ncan run the consumer's `lifecycle.publish` command and own the transaction,\nevidence validation, durable recovery state, and ref finalization order. The\nconsumer repository still owns registry truth: npm, PyPI, OCI, S3, Conan, CMake\npackaging, download pages, dist-tags, and similar side effects must be\nimplemented by project lifecycle commands that emit Buildchain publish evidence.\n\nDurable transaction recovery is also bound to the exact publication version\nplanned for the current run. An unfinished transaction may be resumed from the\ncurrent source or its history only when its recorded version matches that plan;\nan older failed transaction that happens to be an ancestor cannot reserve its\nold exact tag for a newer package publication.\n\nThe exact tag is also part of the durable transaction identity. If an anchored\npackage publication completed registry side effects under a stale internal tag\nselection, a retry may rebind the unfinished `published` or `finalizing`\ntransaction to the newly planned internal tag only when the package version,\nsource, release material, target, complete artifact set, and evidence all still\nmatch; the stale tag must not point at the transaction, and the requested tag\nmust be absent or already point at accepted release material. This preserves an\nimmutable tag that represents a completed transaction while allowing a tag\ncollision discovered after registry publication to recover without republishing.\n\nEvery Buildchain publish model that can run registry side effects must bind the\npublish entrypoint to an immutable `publish-gate/*` source lock. The reusable\n`release-candidate-promote.yml@v3` wrapper creates or updates that gate ref and\npasses `require-publish-source-lock`, `publish-source-ref`,\n`publish-source-sha`, and `publish-source-locked` to\n`promote-buildchain-ref`. Direct action callers must pass the same four inputs\nfrom the reusable build outputs. Workflows that only collect passports or run\ndry-run package checks do not move publish refs and are not publish-gate\npublication models. A dry-run of `release-candidate-promote.yml` computes and\nreports the exact `publish-gate/*` source lock that a real promotion would use,\nbut does not read, create, or move that ref.\n\nSemver GitHub Release publication is owned by `promote-buildchain-ref`, not by\nconsumer shell glue. Consumers normally use the `release-candidate-promote.yml`\ngenerated channel router, where GitHub Release publication is enabled by default and can be\ndisabled with `github-release: false`; the wrapper passes that declaration to\nthe action. After the publish transaction reaches `complete`, Buildchain creates\nor updates the public GitHub Release and uploads the generated\n`buildchain.release.json`, release-passport assets, and publish evidence. The\nauthoritative publication channel controls GitHub metadata: alpha is marked\n`prerelease=true` and `make_latest=false`; release/stable/major is marked latest.\nSemver tag syntax remains the fallback for ordinary callers without explicit\npublication intent. For anchored/manual package releases, the public\nrelease tag defaults to `v<publishedVersion>` while the internal exact\ntransaction tag remains in the release passport and release-state ref. This is\nthe supported path for downstream\n`release.published` propagation across semver, major, and promote-only release\ncandidate publication models.\n\nPublished GitHub Release assets are immutable evidence. A repeated promotion\npreserves an existing asset when its SHA-256 digest matches the regenerated\nbytes, uploads only missing assets, and fails with an immutable-release\ncollision when a same-name asset has different bytes. It never deletes and\nreplaces an existing asset during retry or duplicate workflow delivery.\n\nProduct payloads are included only through the explicit\n`github-release-payload-patterns` input. Patterns match basenames inside the\ndownloaded PR-stage RC payload bundle; zero matches or duplicate public\nbasenames fail closed. This preserves the exact PR-built bytes instead of\nrebuilding archives during promotion.\n\nConsumers with a signed well-known discovery document can additionally provide\n`publication-commit-command`. The advanced promotion workflow validates its\ntopology before any publish-gate or release mutation, then runs it only after\nthe GitHub Release and its immutable payload/passport assets exist. The command\nmust publicly read back the exact new payload root and emit\n`kungfu-buildchain-publication-commit-evidence/v1`; that evidence is copied\ninto the controller artifact and exposed as workflow outputs. The previous\nauthority must remain valid on every failure. Deferred standalone binary\ndistribution is incompatible with this mode because the discovery authority\nmust be the final product mutation.\n\nBuildchain also does not maintain bare exact tags such as `1.0.0`. The supported\nexact release and alpha refs are v-prefixed:\n\n```text\nv2.0.0\nv2.0.1-alpha.0\n```\n\n## Operational Reading Order\n\nWhen debugging or extending release behavior, read in this order:\n\n1. `docs/release-flow.md`\n2. `.github/workflows/release-verify.yml`\n3. `.github/workflows/buildchain-ref-promotion.yml`\n4. `.github/workflows/release-candidate-promote.yml`\n5. `.github/workflows/.release-candidate-promote.yml`\n6. `actions/promote-buildchain-ref/README.md`\n7. `actions/promote-buildchain-ref/src/`\n8. `docs/migration-inventory.md`\n\nThat path gives the policy first, the workflow trigger second, and the action\nimplementation last."
1811
+ "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: buildchain-release-governance\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-27\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-27\n invisible_context: not asserted\n---\n\n# Release Governance\n\nBuildchain v2 preserves the release semantics of the older ABV workflow while\nmoving the implementation into one modern repository.\n\nThe central idea is simple: a reviewed merge into a release channel is the\nrelease intent. Automation must then create the version-state commit, exact tag,\nfloating tag, and next alpha state that make that intent true in Git.\n\n## Design Problem\n\nKungfu release automation has to keep four facts aligned:\n\n1. The source tree that was reviewed.\n2. The package version recorded in manifests such as `package.json` or\n `lerna.json`.\n3. The exact immutable release or prerelease tag.\n4. The floating channel refs that consumers actually use.\n\nIf any one of these facts is updated by hand, the system can split:\n\n- a consumer can fetch `v2.0` and receive a tree whose package version still\n says the previous release;\n- a maintainer can move `v2` without producing an exact `v2.0.N` audit tag;\n- an alpha can be promoted to production even though the release tree is not the\n same tree that was tested;\n- a protected branch merge can succeed while the follow-up version commit is\n missing, or a flow-internal generated `dev`/`alpha`/`release` ref update can\n fail after publish because the automation identity was not declared in the\n branch-protection review bypass allowance.\n\nThe older ABV workflow addressed this by letting GitHub PRs drive release\nstate. Buildchain keeps that choice because it makes release intent reviewable,\nobservable, and recoverable from Git history.\n\n## What ABV Contributed\n\nThe old ABV model was not just \"bump a version number.\" It encoded a governance\nloop:\n\n- release branches are named as channels: `dev`, `alpha`, `release`, and the\n administrative `publish-gate/major`;\n- a PR from one channel to the next is the release request;\n- verify jobs check that the branch pair is valid before merge;\n- a maintainer review is required before the branch moves;\n- after merge, automation writes the version change and moves tags;\n- exact tags and floating refs are aligned with the resulting commit;\n- the next development channel is prepared automatically.\n\nABV also kept the version-state mutation in the repository. For JavaScript\nrepositories that usually meant changing `lerna.json` and/or `package.json`.\nThat commit is important because the tag alone is not enough evidence: the\nsource tree should also declare the version that the tag advertises.\n\nBuildchain v2 treats that as a hard semantic requirement for its own release\nline.\n\n## Buildchain Implementation\n\nBuildchain implements the same governance loop with:\n\n- `.github/workflows/release-verify.yml` for PR verification;\n- `.github/workflows/buildchain-ref-promotion.yml` for post-verify ref\n promotion; this workflow dogfoods the declarative\n `release-candidate-promote.yml` wrapper and does not hand-wire resolver,\n artifact download, publish-gate, or promote action steps;\n- Buildchain self promotion enables `release-passport-buildchain-self-kfd`, so\n the promote action generates KFD-1 witnesses, KFD-2 public claim JSON, and\n KFD-3 collaboration-interface witnesses from the final version-state workspace\n before release passport finalization. The witness hashes therefore bind to the\n exact published package and site facts from\n `packages/core/buildchain-kfd-claims.js` instead of relying on prose release\n notes;\n- `actions/promote-buildchain-ref` for branch, tag, version-state, and\n governance checks;\n- package-manager adapters that can update version state for pnpm, npm, and\n yarn style repositories;\n- `buildchain.toml` lifecycle configuration for repositories whose version\n state or verification commands are not Node package-manager defaults.\n\nThe implementation is intentionally stricter than a local release script:\n\n- manual workflow dispatch can only do dry-run promotion;\n- non-dry-run promotion must be driven by a completed `Verify` workflow;\n- target branch protection details must be readable, and branch protection must\n apply to administrators as well as regular contributors;\n- `alpha/vX/vX.Y` and `release/vX/vX.Y` branch protection must require both the\n general `check` context and the Release Verify aggregate `verify` context, so\n an invalid channel pair cannot merge even when repository checks pass;\n- release targets keep those checks non-strict with respect to source-branch\n ancestry: generated channel bookkeeping intentionally makes the source and\n target histories diverge, while the pair-specific `verify` context validates\n the legal channel transition;\n- alpha promotion must come from a merged same-repository PR from\n `dev/vX/vX.Y` to `alpha/vX/vX.Y`;\n- release promotion must come from a merged same-repository PR from\n `alpha/vX/vX.Y` to `release/vX/vX.Y`;\n- major promotion must come from a merged same-repository PR from\n `release/vX/vX.Y` to `publish-gate/major`;\n- release promotion requires an existing same-patch alpha tag and checks the\n release source tree against that tested alpha tree;\n- generated version-state commits are verified before refs move.\n\n## Reconciling a protected line without rebuilding\n\nThe public `build.yml` channel router ends with a top-level job named\n`Summarize build contract`. Keeping this aggregate at the public router boundary\nprevents its required check context from changing when the internal reusable\nbuild workflow gains another nesting layer.\n\nFor an already-tested pull request whose protected target still requires an\nolder Buildchain aggregate context, inspect the exact candidate SHA first:\n\n```bash\nGH_TOKEN=\"$(gh auth token)\" npx @kungfu-tech/buildchain@latest \\\n release-governance reconcile \\\n --repository kungfu-systems/example \\\n --branch release/v2/v2.14 \\\n --candidate-sha <tested-pr-head-sha> \\\n --json\n```\n\nThe dry run reads the successful checks emitted for that SHA and reports the\nexact expected/actual context pair. It chooses the shallowest successful\n`Summarize build contract` context, so a new top-level router aggregate wins\nover the nested internal build summary. To apply the plan, rerun the same\ncommand with `--apply` using a token that can update branch protection.\n\nReconciliation changes only the required-status-check subresource. It replaces\nstale Buildchain aggregate contexts, preserves unrelated checks and strictness,\nand does not modify review requirements, administrator enforcement,\nconversation resolution, force-push policy, or deletion policy. The candidate\nmust still be the head of a pull request targeting the named managed branch;\nthe command fails closed otherwise. This lets a previously successful candidate\ncontinue from the same SHA without another native build or an administrator\nmerge bypass.\n\nRepositories may also expose a small caller workflow around\n`.github/workflows/release-governance-reconcile.yml@v3`. Pass `branch`,\n`candidate-sha`, and `apply`, and provide `governance-token` through the caller's\nsecrets. The reusable workflow uploads the JSON reconciliation receipt.\n\nExact publication planning installs the checked-out promotion source's declared\ndependencies before version-state verification. This keeps the pre-authority\nversion plan on the same package-manager boundary as the later promotion job,\nincluding repositories whose verification commands import production packages.\nThe planning pass may materialize and verify declared derived files locally,\nbut dry-run never creates Git blobs, trees, commits, refs, or tags.\n\nPromotion intents are serialized globally per caller repository with\n`cancel-in-progress: false`. A queued intent re-reads its protected target ref\nbefore checkout, dependency installation, release-candidate resolution, or any\npublish-gate/ref mutation. If the ref already points at a newer SHA, that older\nintent is no longer release authority: the workflow records the requested and\ncurrent SHAs, proves that the current target is ahead of the requested commit,\nand completes as a `target-ref-advanced` superseded no-op. Diverged or behind\ncomparisons are not superseded transactions and still fail closed.\nMissing refs, unreadable repository state, invalid channels, governance\nfailures, and artifact mismatches still fail closed. The promote action repeats\nthe target check at the mutation boundary, so a ref that advances after the\nworkflow preflight cannot receive a second set of publication side effects.\n\n## Version Lines\n\nKungfu uses Python-like version lines where a minor line can represent a\nlong-lived product train. A line such as `v2.0` can produce many production\npatch releases:\n\n```text\nv2.0.0\nv2.0.1\nv2.0.2\n...\nv2.0.1234\n```\n\nThis is why Buildchain maintains both exact and floating refs:\n\n- `v2.0.2` is immutable release evidence;\n- `v2.0` is the latest production release on the `2.0` line;\n- `v2` is the selected stable major-line entrypoint;\n- `v2.0.3-alpha.0` is immutable alpha evidence;\n- `v2.0-alpha` is the latest test channel for the `2.0` line.\n- `v3-alpha` is the latest test channel on the highest published alpha minor in major `2`.\n\nA release does not mean \"minor is complete.\" It means \"this patch on this minor\nline is now production.\"\n\nGitHub repository rules must preserve that distinction. Exact tags such as\n`v2.0.2` and `v2.0.3-alpha.0` should be immutable. Floating channel tags such as\n`v2`, `v2.0`, `v2.0-alpha`, and `v3-alpha` must remain movable by the Buildchain promotion\ntoken after governance checks and publish evidence pass. A tag ruleset that\nprotects every `refs/tags/v*` ref is too broad because it also locks the\nfloating channel tags that Buildchain is required to update. Prefer exact-tag\npatterns such as `refs/tags/v*.*.*` for immutable release evidence, while\nleaving floating channel tags under Buildchain automation control.\n\n## Alpha Semantics\n\nAn alpha merge is:\n\n```text\ndev/vX/vX.Y -> alpha/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Computes the next prerelease for the minor line.\n2. Writes version state such as `vX.Y.Z-alpha.N`.\n3. Verifies the generated version-state tree.\n4. Creates or reuses the exact alpha tag.\n5. Moves `alpha/vX/vX.Y` to the generated alpha commit.\n6. Moves `dev/vX/vX.Y` to the same generated alpha commit when this is a\n fast-forward update.\n7. Moves `vX.Y-alpha` to the same generated alpha commit.\n8. Moves `vX-alpha` only when `X.Y` is the highest minor in major `X` with a published alpha; older minor alpha work records a skip and cannot move the major channel backwards.\n\nThe npm channel follows the same ownership rule. The highest alpha minor publishes\nwith dist-tag `alpha`; maintenance alphas on an older minor publish with the\nline-specific dist-tag `vX.Y-alpha` so they cannot roll the global `alpha`\nchannel backward. Exact prerelease versions remain installable directly.\n\nThis keeps the test channel self-describing. If a consumer checks out\n`v2.0-alpha` or `v3-alpha`, the manifests and exact alpha tag agree. The major\nalpha ref removes routine consumer edits when Buildchain opens a newer minor,\nwhile exact tags and SHAs remain the reproducible audit choice.\n\n### Buildchain Alpha Self-Dogfood\n\nBuildchain continuously consumes its own current major alpha through\n`.github/workflows/buildchain-alpha-self-dogfood.yml`. Both lanes call the\nreleased channel router at `build.yml@v3-alpha`. The auto lane must resolve\n`v3-alpha`; the explicit stable lane must resolve `v2`. Both execute the same\ndeclared install, build, and verify fixture, proving that a single consumer\nsurface routes to distinct released runtimes without duplicating lifecycle\nconfiguration in the consumer.\n\nBuildchain's generic artifact-signing contract seals source-, tree-, runtime-,\nplatform-, and digest-bound requests from ordinary credential-free build jobs.\nProvider-specific authority jobs consume only those sealed payloads. Apple\nDeveloper ID, Windows Authenticode, and detached cryptographic signatures share\nthe request/receipt model, while each profile retains its honest platform\nsemantics and fail-closed verification requirements.\n\nThe authority verifies the complete result set on GitHub-hosted infrastructure\nbefore delivery. The consumer controller also performs final result verification,\nexact-byte import, manifest recomputation, and deterministic-artifact replacement\non a GitHub-hosted lane. Self-hosted build runners do not download authority\nresult payloads, and aggregate/release evidence fails closed until this\nfinalization succeeds.\n\nThe central `buildchain-artifact-signing` environment reuses the established\nmacOS Credential Island names (`BUILDCHAIN_MACOS_CERTIFICATE_*`,\n`BUILDCHAIN_MACOS_NOTARY_API_*`, and\n`BUILDCHAIN_MACOS_EXPECTED_TEAM_ID`). Those authority-only values are never\ndeclared by or forwarded through a consumer repository. Windows and detached\nproviders follow the same central-environment boundary.\n\nThe reusable build trust gate reads `job.workflow_ref`, which identifies the\ncalled workflow and its selected ref. It does not infer the runtime from\n`github.workflow_ref`, because GitHub defines that context as the caller\nworkflow identity during a reusable call.\n\nThe router selects `.buildchain/alpha-contract-lock.json` for alpha and\n`.buildchain/contract-lock.json` for stable. The alpha lock records the exact\nreviewed alpha SHA and compatibility digest; it does not replace the stable\nconsumer lock. A later alpha with only compatible additive drift continues,\nwhile a changed breaking digest fails until the new alpha contract is reviewed.\n\nThe evidence job resolves `v3-alpha` and `v2` through the GitHub refs API,\ncompares those immutable SHAs with the reusable workflow outputs, verifies the\n`alpha` and `stable` classifications, and uploads a JSON evidence artifact.\nThe canary runs after successful Buildchain ref promotion, on a daily fallback\nschedule, and on manual dispatch. It shares the release-promotion concurrency\ngroup, so another promotion cannot move the floating refs between runtime\nresolution and evidence comparison.\n\nThis is a post-publication consumer canary, not a release bootstrap. Source\nverification still runs the current commit, and\n`buildchain-ref-promotion.yml` still passes the verified exact SHA into the\npromotion workflow. Patrol, dev-merge, repair, and promotion defaults remain on\nstable or exact refs so a broken alpha cannot prevent Buildchain from publishing\nits fix. When Buildchain opens a new major, inventory validation requires this\nworkflow's fixed GitHub `uses` refs to move from `vN`/`vN-alpha` to the new\nmajor; GitHub does not allow expressions in a reusable-workflow `uses` ref.\n\nIf `dev/vX/vX.Y` has already advanced before generated alpha version-state\nbookkeeping can sync back, Buildchain records `skipped-non-fast-forward` for the\ndev sync and still completes the exact and floating alpha tags for the reviewed\nalpha commit. Later dev changes must go through their own dev-to-alpha\npromotion instead of rewinding dev. The normal path is direct: after alpha\nmerges, Buildchain applies the generated version-state commit to alpha and then\nfast-forwards dev to the same commit without a human version-state PR.\n\nIf alpha finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain accepts the current alpha head as the generated\ncommit, or as a historical merge commit that contains the recorded release\nmaterial. An already-created exact alpha tag may point at the transaction\nrelease/material SHA or at the finalized alpha head; missing floating alpha\ntags are retried before the transaction becomes `complete`.\n\n## Release Semantics\n\nA release merge is:\n\n```text\nalpha/vX/vX.Y -> release/vX/vX.Y\n```\n\nBuildchain then:\n\n1. Finds the same-patch alpha tag that was tested.\n2. Checks that the release source tree matches that alpha tag tree, excluding\n only generated version-state differences.\n3. Writes final release version state such as `vX.Y.Z`.\n4. Verifies the generated release tree.\n5. Creates or reuses the exact release tag `vX.Y.Z`.\n6. Moves `release/vX/vX.Y` to the exact release commit.\n7. Moves `vX.Y` to the exact release commit.\n8. Moves `vX` when this minor line should be the stable major entrypoint.\n9. Prepares the next alpha version-state commit, such as\n `vX.Y.(Z+1)-alpha.0`.\n10. Moves `alpha/vX/vX.Y`, `dev/vX/vX.Y`, and `vX.Y-alpha` to that next alpha\n commit.\n11. Moves `vX-alpha` to that next alpha only when this remains the highest published alpha minor.\n\nThe historical alpha tree comparison remains the default stable source gate.\nA promote-only stable run may accept a broader reviewed release PR only when\nthe downloaded RC passport proves that the PR's exact target tree is the tree\nthat completed the PR-stage build. Buildchain also requires the target commit\nto belong to a merged same-repository PR into the selected release branch and\nrecords the accepted commit, tree, RC source, alpha source, and PR as promotion\nevidence. A stale passport, a different target tree, a generated final release\ncommit, or an unreviewed target commit still falls through to the normal\nalpha-tree and declared version-state checks.\n\nThe production channel and the test channel therefore intentionally diverge\nafter release: production stays on the release commit, while alpha/dev continue\nat the next prerelease commit.\n\n### Stable Release Throttle And Canary Gate\n\nBuildchain's own stable channel has an additional pre-publication gate. It is\nevaluated after the PR-stage release candidate has been resolved and before the\npublish-gate ref, package registry, exact stable tag, or floating stable refs\nare mutated. Train refs and alpha promotion do not execute this gate.\n\nAll alpha and release promotions first run a metadata-only release-candidate\npreflight after queued-intent revalidation. The preflight uses the same resolver\nand exact target SHA as the publication job, but does not download payloads or\ninstall the candidate repository dependencies. Missing channel PRs, stale\nworkflow runs, expired passport pairs, and insufficient payload artifact sets\ntherefore fail before the full promotion job starts. The publication job still\ndownloads and validates the exact evidence again at the trust boundary; the\npreflight moves failure earlier without weakening the final check.\n\nThe policy is versioned in `.buildchain/stable-release-policy.json`. A stable\ncandidate is allowed only when all of these facts are true:\n\n- the candidate resolves to an immutable exact alpha tag and its GitHub\n prerelease timestamp;\n- at least 24 hours have passed since the preceding stable patch on the same\n minor line;\n- the preceding stable-to-alpha comparison contains a product or public\n contract path, not only version, test, evidence, or retrospective changes;\n- the version-bound impact record has a non-empty summary and at least one\n surface impact;\n- the `Build Surface Fixture` release-candidate run succeeded;\n- `site-libkungfu-dev` completed its no-apply `Buildchain Stable Canary` and an allowed\n maintainer attested that successful run on the exact alpha SHA through the\n `buildchain-canary/site-libkungfu-dev` commit-status context;\n- the canary runtime input is exactly the candidate alpha tag or the 40-character\n commit SHA resolved from that tag; a successful status pointing at another\n workflow, repository, tag, floating ref, or SHA is rejected as mismatched;\n- at least one hour has elapsed after the last required canary completed.\n\nThe machine report is written to\n`.buildchain/release-passport/stable-release-gate.json`. A passing report is\nuploaded with the stable release passport. A blocked run writes the same report\nbefore failing, so the missing or stale condition is inspectable without\nopening a publication transaction.\n\nGitHub's Actions run REST object does not expose `workflow_dispatch` inputs.\nBuildchain therefore verifies the run's `workflow_id` against the authoritative\nworkflow metadata, then reads the exact runtime from the workflow-owned\n`<workflow name> / <runtime ref>` run-name when no input field is available.\nAn explicit API input, when present, remains authoritative and cannot be\noverridden by the display name.\n\nThe cooldown is a minimum interval, not an instruction to release every day.\nCompatible work should still be batched until a stable release has a concrete\nconsumer need. Changing the interval, canary set, attestors, product path\nboundary, or soak time is a reviewed policy change.\n\nRepositories that want a predictable scheduled stable window can use\n[`Stable Candidate Patrol`](stable-candidate-patrol.md). It persists each exact\nalpha independently, qualifies it after repository-declared checks and soak,\nand selects the newest qualified non-revoked candidate. A newer soaking alpha\ndoes not invalidate an older qualified candidate. The selected tree enters the\nexisting strict `publish-gate/release/<line>/<version> -> release/<line>` PR\npath, so scheduled selection changes release intent timing without weakening\nsource locks, review, verification, publish transactions, or passports.\n\nIf release finalization is resumed after generated version-state bookkeeping was\npartially applied, Buildchain applies the same recovery rule: the current\nrelease head may be the generated commit, or a historical merge commit that\ncontains the recorded release material, existing exact tags and alpha/dev refs\nare accepted when they match the transaction, and missing floating `vX.Y` or\n`vX` tags are retried idempotently before completion.\n\nOnce the durable release transaction is `complete` and the exact/floating\nstable refs have moved, next-alpha preparation is post-release bookkeeping. A\nfailure there records `deferred-post-release-bookkeeping` and\n`next-anchor-required` instead of retroactively reporting the stable release as\nfailed. Generated next-alpha merges use the current dev tree as their base and\noverlay only declared version-state paths, preserving concurrent dev changes.\n\n## Major Gate Semantics\n\nA major gate merge is:\n\n```text\nrelease/vX/vX.Y -> publish-gate/major\n```\n\n`publish-gate/major` is the explicit replacement for the older ABV `main`\nchannel. The name is intentionally operational: it is a gate for a rare\nadministrator decision, not the active trunk. Keeping this decision in the same\nPR UI as alpha and release promotion keeps the human workflow simple while\navoiding the misleading meaning of `main`. The older `major-gate` branch name is\na compatibility alias only.\n\nBuildchain then:\n\n1. Verifies the source is a merged same-repository PR from a protected release\n line into `publish-gate/major`.\n2. Writes the next major production version state, such as `v(X+1).0.0`.\n3. Creates or reuses the exact release tag `v(X+1).0.0`.\n4. Moves `publish-gate/major` and `release/v(X+1)/v(X+1).0` to that release commit.\n5. Moves `v(X+1).0` and `v(X+1)` to that release commit.\n6. Prepares the next alpha version-state commit, such as\n `v(X+1).0.1-alpha.0`.\n7. Moves `alpha/v(X+1)/v(X+1).0`, `dev/v(X+1)/v(X+1).0`, and\n `v(X+1).0-alpha` to that next alpha commit.\n\nChecking out `publish-gate/major` should therefore look like a frozen release\nstate, not like a branch where day-to-day source work continues. Day-to-day\nsource work continues on `dev/vX/vX.Y`.\n\n## Protected Dev Branches\n\n`dev/vX/vX.Y` is a protected development channel, not a scratch branch. Normal\nsource changes should be made on work branches such as `feature/*`, `fix/*`,\n`chore/*`, `docs/*`, `ci/*`, or `refactor/*`, then reviewed through a pull\nrequest into the target dev line.\n\nThis keeps the earliest development channel audit-friendly:\n\n- the version line being changed is visible in the PR base branch;\n- CI and required checks run before the channel moves;\n- branch protection can prevent direct pushes and stale merges;\n- later `dev -> alpha -> release` promotion inherits a reviewable source\n lineage instead of trying to reconstruct how the dev branch changed.\n\nWhen required checks take longer than the normal dev-channel commit interval,\nclassic strict up-to-date protection can become a non-converging retry loop:\neach base update invalidates a completed check set and rebasing restarts the\nsame slow checks. Buildchain supports GitHub merge queues for that channel\nshape. The queue validates the projected merged result and serializes the final\nref update, so concurrent channel movement no longer invalidates the candidate.\n\nEvery required workflow must handle both `pull_request` and `merge_group`\nbefore the queue is enabled. Queue runs do not provide\n`github.event.pull_request`; required workflows must use the checked-out\n`github.sha` or event-neutral source facts. The governance command is dry-run by\ndefault and refuses to enable a queue when a declared required workflow lacks\neither trigger or still reads the pull-request-only payload directly:\n\n```bash\nbuildchain dev merge-queue \\\n --repository owner/repository \\\n --branch dev/v4/v4.0 \\\n --workflow .github/workflows/source-acceptance.yml \\\n --workflow .github/workflows/affected-native-pr.yml \\\n --bypass-app dedicated-release-app\n```\n\nAfter reviewing the plan, repeat with `--apply`. Buildchain creates or updates\nan exact-branch `merge_queue` ruleset first, then changes only the classic\nrequired-status-check policy from strict to loose. Reviews, administrator\nenforcement, conversation resolution, required check identities, force-push\nprotection, and deletion protection remain owned by the existing branch\nprotection. The ruleset uses the first merge method that the repository itself\nallows, and fails closed when the repository has no enabled merge method.\nRe-running the command is idempotent.\n\nThe repository policy can be declared once instead of repeated as CLI flags:\n\n```toml\n[governance.dev.merge_queue]\nmode = \"inherit\"\nrequired_workflows = [\".github/workflows/verify.yml\"]\n```\n\n`enabled` explicitly requires Buildchain to create or update an exact-branch\nqueue; `inherit` copies queue parameters and bypass actors from the repository's\ncurrent default dev branch; `disabled` prevents automatic queue creation. An\nabsent declaration behaves as `inherit` during release-line bootstrap so a new\nmajor or minor line does not silently lose governance already active on the\nprevious line. Required status-check identities still come from the new\nbranch's own classic protection rather than being copied from the old branch.\n\nMerge-queue rules also reject generated post-publish version-state ref updates.\nWhen the sealed promotion workflow uses a dedicated GitHub App, user, or team\nalready declared by release governance, repeat `--bypass-app`, `--bypass-user`,\nor `--bypass-team` to project that exact actor into the ruleset. Bypass actors\nare never inferred and broad repository or organization roles are not accepted.\nThis keeps ordinary feature PRs on the predecessor-aligned queue path while the\nsealed publication authority can finish its machine-verified bookkeeping. The\ndry-run receipt exposes the exact actor IDs before `--apply` changes GitHub.\n\nBuildchain provides the reusable\n`.github/workflows/dev-pr-auto-merge.yml` workflow for repositories that want a\nscheduled or manual \"merge ready dev PRs\" pass. The consumer repository owns\nthe trigger schedule, but the merge decision is declared through workflow\ninputs: target dev branch, required status/check names, ready and block labels,\nallowed work-branch prefixes, review requirements, maximum merges per run,\nmerge method, and dry-run mode.\n\nThe workflow defaults are conservative. A PR is skipped unless it targets the\nconfigured dev line, is not a draft, has the ready label, has no block label,\ncomes from the same repository, uses an allowed work-branch prefix, has a\ncurrent approval, is mergeable, and has the configured required checks passing.\n`landing-mode: auto` reads the target branch's native merge-queue state. When a\nqueue exists, Buildchain never calls the direct merge endpoint: it admits at\nmost one PR against the observed target-branch SHA and immutable PR head, then\ncalls GraphQL `enqueuePullRequest` with `expectedHeadOid`. GitHub's\n`merge_group` checks remain the final authority for the projected merge.\n\nThe admission receipt records the expected and observed base/head SHAs, policy\nchecks, decision, reason, and active predecessor. Buildchain re-reads the base,\nhead, mergeability, and native queue immediately before enqueueing. Base or\nhead drift fails closed, an active queue entry blocks admission, and a rejected\nready predecessor leaves its PR open while later PRs receive\n`blocked-by-predecessor`. Workflow concurrency serializes Buildchain-owned\nadmission runs; GitHub still owns the atomic queue and protected-ref update.\nRepositories may explicitly select `landing-mode: direct` only when the target\nbranch has no native queue. Queue presence always disables the direct path.\n\nThe canonical consumer required check context is `check / check`, matching the\nreusable workflow call plus its `check` job. Buildchain's own `Verify` workflow\nemits the repository-local context `check`, so Buildchain self-promotion,\nrelease-line bootstrap, and dogfood patrol wrappers pass `check` explicitly.\nRelease governance preserves the exact context emitted by the repository and\nrecords branch-protection policy before/after facts; it does not rewrite one\ncontext shape into the other. Consumer repositories can keep their context\nstable while changing the actual verification command declaratively in\n`buildchain.toml`:\n\n```toml\n[lifecycle.install]\ncommand = \"cargo fetch --locked\"\n\n[lifecycle.verify]\ncommand = \"cargo test --workspace --locked\"\n```\n\nConsumers that want Buildchain to own the check wrapper can call\n`.github/workflows/check.yml@v3`. The wrapper runs the declared\n`lifecycle.install` and `lifecycle.verify` stages and fails the `check` job when\neither declaration is missing or the command exits non-zero.\n\nDevelopment pull requests that need source acceptance without product build or\nartifact verification can opt into `mode: source`. That mode runs only\n`lifecycle.install` and `lifecycle.check` on GitHub-hosted `ubuntu-24.04` while\npreserving the stable `check / check` required-check context. Existing callers\nremain on `mode: verify` by default, and callers may set\n`upload-artifacts: false` without weakening the job conclusion.\n\nTypical consumer wrapper:\n\n```yaml\nname: Merge Ready Dev PRs\n\non:\n schedule:\n - cron: \"17 * * * *\"\n workflow_dispatch:\n inputs:\n dry-run:\n type: boolean\n default: true\n\njobs:\n merge-dev:\n uses: kungfu-systems/buildchain/.github/workflows/dev-pr-auto-merge.yml@v3\n permissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n with:\n target-branch: dev/v2/v2.6\n required-status-checks: check / check\n ready-label: ready\n block-labels: blocked,do-not-merge\n max-merges: 1\n landing-mode: auto\n dry-run: ${{ inputs.dry-run || false }}\n```\n\n## Buildchain Patrol\n\n`dev-pr-auto-merge.yml` remains the focused merge primitive. For repositories\nthat want a stable day-to-day operations contract, Buildchain also exposes a\npatrol workflow family:\n\n| Workflow | Intended cadence | Default intent |\n| --- | --- | --- |\n| `.github/workflows/patrol-daily.yml` | daily | lightweight inspection plus ready dev PR maintenance |\n| `.github/workflows/patrol-weekly.yml` | weekly | release-state, passport, gate, and stale-state health checks as they are added |\n| `.github/workflows/patrol-monthly.yml` | monthly | governance, permission, branch-protection, and workflow drift checks as they are added |\n| `.github/workflows/patrol-observed-evidence.yml` | caller-selected schedule | validated immutable observation plus atomic last-known-good publication; no per-refresh PR |\n| `.github/workflows/stable-candidate-patrol.yml` | repository-selected release window | qualify immutable alpha candidates and open the exact source-lock stable PR |\n\nThe cadence names describe patrol intensity, not release cadence:\n\n- daily patrol can run every day without implying a daily release;\n- weekly patrol is for medium-cost maintenance and audit checks;\n- monthly patrol is for structural drift checks that should not block ordinary\n development velocity.\n\nStable Candidate Patrol is separate from those maintenance cadences because its\ncaller-owned cron is a release-intent window. Its candidate ledger and selection\nremain generic; registry-specific side effects still run through the normal\nrepository `lifecycle.publish` transaction. See\n[`stable-candidate-patrol.md`](stable-candidate-patrol.md).\n\nObserved data that is mechanically regenerated and path-scoped uses the\nseparate [`Observed Evidence Patrol`](observed-evidence-patrol.md) contract.\nIts one-time mechanism changes remain reviewed, while steady-state snapshot\nrefreshes publish directly from trusted default-branch schedule/manual callers.\n\nConsumers should schedule thin callers and keep their YAML declarative. For\nexample:\n\n```yaml\nname: Buildchain Daily Patrol\n\non:\n schedule:\n - cron: \"17 2 * * *\"\n workflow_dispatch:\n\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-daily.yml@v3\n with:\n dry-run: false\n max-actions: 1\n```\n\nWeekly and monthly callers use the matching wrapper:\n\n```yaml\njobs:\n patrol:\n uses: kungfu-systems/buildchain/.github/workflows/patrol-weekly.yml@v3\n with:\n dry-run: true\n```\n\nAll three wrappers default to the `v3` floating Buildchain runtime. When\n`target-branch` is omitted, the caller's current/default branch selects the\nactive semver dev line, so consumers do not pin patrol to a stale minor branch.\nThe separate workflow names keep consumer schedules readable and stable while\nBuildchain adds new checks behind the cadence wrappers.\n\n## Package-Manager Adapters\n\nOld ABV assumed JavaScript repositories with root version state and often\nLerna. Buildchain keeps the version-state contract but does not assume every\nrepository is yarn/Lerna.\n\nThe promotion action discovers and updates:\n\n- root `package.json`;\n- `lerna.json`;\n- package manifests from `package.json` workspaces;\n- package manifests from `lerna.json` packages;\n- package manifests from `pnpm-workspace.yaml`.\n\nIt then runs the repository's detected package manager semantics where needed:\n\n- pnpm repositories use pnpm-oriented workspace discovery;\n- npm repositories use npm/package-lock semantics where present;\n- yarn repositories use yarn-style metadata where present.\n\nFor Buildchain itself, version state is required. For a consumer repository that\nhas no package manifest, the same action can degrade to ref-only behavior only\nwhen that is explicitly allowed by the caller.\n\n## Lifecycle Configuration\n\n`buildchain.toml` is the v2 user configuration format. It lets a repository\ndeclare version-state files and lifecycle commands without pretending every\nproject is a Node workspace. Supported version files include JSON, TOML, and\nregex-based files such as `CMakeLists.txt` or `conanfile.py`.\n\nThe promotion action consumes `version.files`, optional anchored/manual\n`version.derived_files`, and `lifecycle.verify`.\nThe verify stage runs after generated version-state changes are applied locally\nand before any release refs move. If `verification-command` is passed directly\nto the action, that explicit command overrides `lifecycle.verify`.\n\nAnchored/manual repositories may use `version.derived_files` for committed\nversion witnesses that are regenerated by `lifecycle.version-state`. The\nrelease-candidate build verifies those witnesses before heavy builds and records\ntheir digests with the exact alpha and release tree identities. Promotion then\naccepts only declared version files, the anchor manifest, and those derived\nfiles as differences from the tested alpha tree; release passports preserve the\nsame material binding.\n\nProtected release-line branches keep their normal human review gate. Managed\n`dev/vN/vN.M`, `alpha/vN/vN.M`, and `release/vN/vN.M` branches are configured\nwith one required approving review, required GitHub Actions checks, administrator\nenforcement, conversation resolution, no force pushes, and no deletions. Each\ntarget uses the exact check set, GitHub App identity, and strictness declared by\nthe governance authority descriptor. The\nreusable `release-candidate-promote.yml` wrapper defaults\n`branch-protection-bypass-apps` to `github-actions`, which lets the workflow's\nautomation identity apply generated version-state or post-publish channel\nbookkeeping after the reviewed channel PR has merged. Direct\n`promote-buildchain-ref` callers may opt into that one controlled bypass with\n`branch-protection-bypass-apps: github-actions`; every other App slug and all\nuser or team bypass actors are rejected. Before\npatching a protected generated bookkeeping ref, the action creates the\nfull configured required-check set on the exact generated version-state commit, so strict\nstatus checks are satisfied by machine-verifiable Buildchain evidence rather\nthan a human PR. The protected ref PATCH itself uses the generated ref update\ntoken; the reusable wrapper binds it to the run-scoped `github.token`. If direct generated\nrelease finalization bookkeeping is still rejected, Buildchain creates or\nreuses a same-repository `buildchain/version-state/*` PR and records\n`finalization-needed=true` in the durable transaction output. Strict alpha\nfollows the same provider-enforced PR path for its alpha and dev bookkeeping.\nThe PR remains subject to the declared review, required checks, and merge-queue\npolicy; publication resumes idempotently after that protected transaction\nlands.\n\nFor a stable release, the wrapper also checks out the exact current development\nchannel into `.buildchain/reconciliation/dev`. When the prepared next-alpha\ncommit cannot fast-forward dev because reviewed work landed concurrently, the\npromotion action applies the next version to that checkout, regenerates every\ndeclared derived version-state file, reruns the verification lifecycle, and\nonly then creates the two-parent reconciliation commit. A checkout/current-ref\nSHA mismatch blocks reconciliation instead of committing stale projections.\nBuildchain's own promotion workflow accepts only\n`BUILDCHAIN_PROMOTION_BYPASS_APPS=github-actions`, defaulting to that exact App\nwhen the variable is absent. Buildchain's release-line bootstrap uses the\nadministrator-scoped promotion token only to configure protection; branch\ncreation and generated ref updates use the run-scoped token. New channel\nprotection binds required checks to GitHub Actions App id `15368`, enables Code\nOwner, stale-review, and latest-push review gates, and admits no user or team\nbypass actor.\n\n## What This Guarantees\n\nWhen the loop succeeds, maintainers and consumers can rely on these facts:\n\n- every production release has an exact tag such as `v2.0.2`;\n- every production minor line has a floating tag such as `v2.0`;\n- every selected stable major has a floating tag such as `v2`;\n- every next-major release is driven by a reviewed `release -> publish-gate/major` PR,\n not a hidden manual button;\n- every test channel has an exact alpha tag such as `v2.0.3-alpha.0`;\n- every alpha minor line has a floating tag such as `v2.0-alpha`;\n- every major with a published alpha has a cross-minor floating tag such as `v3-alpha`, owned by its highest published alpha minor;\n- version manifests match the tag visible from the same commit;\n- production releases are derived from the alpha tree that was tested;\n- manual non-dry-run promotion cannot bypass PR review and verification;\n- flow-internal automation bypasses apply only to declared GitHub Apps, users,\n or teams on Buildchain-managed channel branch protection, while one-review\n protection remains enforced for humans;\n- admin users cannot make a channel promotion valid by temporarily bypassing\n branch protection.\n\nThis is the practical meaning of \"governance closed loop\" in Buildchain: the\ndecision, code, version state, and Git refs close over the same evidence chain.\n\n## What This Does Not Do\n\nBuildchain release promotion does not embed registry clients or product-specific\npublish logic. When publish transactions are enabled, `promote-buildchain-ref`\ncan run the consumer's `lifecycle.publish` command and own the transaction,\nevidence validation, durable recovery state, and ref finalization order. The\nconsumer repository still owns registry truth: npm, PyPI, OCI, S3, Conan, CMake\npackaging, download pages, dist-tags, and similar side effects must be\nimplemented by project lifecycle commands that emit Buildchain publish evidence.\n\nDurable transaction recovery is also bound to the exact publication version\nplanned for the current run. An unfinished transaction may be resumed from the\ncurrent source or its history only when its recorded version matches that plan;\nan older failed transaction that happens to be an ancestor cannot reserve its\nold exact tag for a newer package publication.\n\nThe exact tag is also part of the durable transaction identity. If an anchored\npackage publication completed registry side effects under a stale internal tag\nselection, a retry may rebind the unfinished `published` or `finalizing`\ntransaction to the newly planned internal tag only when the package version,\nsource, release material, target, complete artifact set, and evidence all still\nmatch; the stale tag must not point at the transaction, and the requested tag\nmust be absent or already point at accepted release material. This preserves an\nimmutable tag that represents a completed transaction while allowing a tag\ncollision discovered after registry publication to recover without republishing.\n\nEvery Buildchain publish model that can run registry side effects must bind the\npublish entrypoint to an immutable `publish-gate/*` source lock. The reusable\n`release-candidate-promote.yml@v3` wrapper creates or updates that gate ref and\npasses `require-publish-source-lock`, `publish-source-ref`,\n`publish-source-sha`, and `publish-source-locked` to\n`promote-buildchain-ref`. Direct action callers must pass the same four inputs\nfrom the reusable build outputs. Workflows that only collect passports or run\ndry-run package checks do not move publish refs and are not publish-gate\npublication models. A dry-run of `release-candidate-promote.yml` computes and\nreports the exact `publish-gate/*` source lock that a real promotion would use,\nbut does not read, create, or move that ref.\n\nSemver GitHub Release publication is owned by `promote-buildchain-ref`, not by\nconsumer shell glue. Consumers normally use the `release-candidate-promote.yml`\ngenerated channel router, where GitHub Release publication is enabled by default and can be\ndisabled with `github-release: false`; the wrapper passes that declaration to\nthe action. After the publish transaction reaches `complete`, Buildchain creates\nor updates the public GitHub Release and uploads the generated\n`buildchain.release.json`, release-passport assets, and publish evidence. The\nauthoritative publication channel controls GitHub metadata: alpha is marked\n`prerelease=true` and `make_latest=false`; release/stable/major is marked latest.\nSemver tag syntax remains the fallback for ordinary callers without explicit\npublication intent. For anchored/manual package releases, the public\nrelease tag defaults to `v<publishedVersion>` while the internal exact\ntransaction tag remains in the release passport and release-state ref. This is\nthe supported path for downstream\n`release.published` propagation across semver, major, and promote-only release\ncandidate publication models.\n\nPublished GitHub Release assets are immutable evidence. A repeated promotion\npreserves an existing asset when its SHA-256 digest matches the regenerated\nbytes, uploads only missing assets, and fails with an immutable-release\ncollision when a same-name asset has different bytes. It never deletes and\nreplaces an existing asset during retry or duplicate workflow delivery.\n\nProduct payloads are included only through the explicit\n`github-release-payload-patterns` input. Patterns match basenames inside the\ndownloaded PR-stage RC payload bundle; zero matches or duplicate public\nbasenames fail closed. This preserves the exact PR-built bytes instead of\nrebuilding archives during promotion.\n\nConsumers with a signed well-known discovery document can additionally provide\n`publication-commit-command`. The advanced promotion workflow validates its\ntopology before any publish-gate or release mutation, then runs it only after\nthe GitHub Release and its immutable payload/passport assets exist. The command\nmust publicly read back the exact new payload root and emit\n`kungfu-buildchain-publication-commit-evidence/v1`; that evidence is copied\ninto the controller artifact and exposed as workflow outputs. The previous\nauthority must remain valid on every failure. Deferred standalone binary\ndistribution is incompatible with this mode because the discovery authority\nmust be the final product mutation.\n\nBuildchain also does not maintain bare exact tags such as `1.0.0`. The supported\nexact release and alpha refs are v-prefixed:\n\n```text\nv2.0.0\nv2.0.1-alpha.0\n```\n\n## Operational Reading Order\n\nWhen debugging or extending release behavior, read in this order:\n\n1. `docs/release-flow.md`\n2. `.github/workflows/release-verify.yml`\n3. `.github/workflows/buildchain-ref-promotion.yml`\n4. `.github/workflows/release-candidate-promote.yml`\n5. `.github/workflows/.release-candidate-promote.yml`\n6. `actions/promote-buildchain-ref/README.md`\n7. `actions/promote-buildchain-ref/src/`\n8. `docs/migration-inventory.md`\n\nThat path gives the policy first, the workflow trigger second, and the action\nimplementation last."
1812
1812
  },
1813
1813
  {
1814
1814
  "id": "manual:release-passport",
@@ -1936,7 +1936,7 @@
1936
1936
  ],
1937
1937
  "maturity": "stable",
1938
1938
  "sourcePath": "docs/reusable-build-surface.md",
1939
- "digest": "sha256:ea36ec722f811f5156735aa7471ed080cdc7e87d71c20c300243b226e233e675",
1939
+ "digest": "sha256:4409c8259c3613b51a1db809493e73d9b85a0d16d292a491046fd36770d98fb0",
1940
1940
  "headings": [
1941
1941
  {
1942
1942
  "level": 1,
@@ -2039,7 +2039,7 @@
2039
2039
  "anchor": "fixture"
2040
2040
  }
2041
2041
  ],
2042
- "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: buildchain-reusable-build\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-27\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-27\n invisible_context: not asserted\n---\n\n# Reusable Build Surface\n\nBuildchain v2 provides a reusable build workflow for repositories that need\nBuildchain's release semantics but cannot be described as a simple Node package.\nThe first target shape is `libnode`: expensive native builds, multiple operating\nsystems, self-hosted runner labels, and release artifacts that must be auditable.\n\nFor Linux release artifacts, the build workflow can hand the sealed artifact,\nplatform manifest, and Release Passport to the separate GitHub-hosted keyless\nattester. The compiler runner remains the recorded build identity; the attester\nonly signs and verifies immutable data. See\n[`github-artifact-attestation.md`](github-artifact-attestation.md).\n\n## Automatic Channel Router\n\nThe preferred consumer surface is one reusable workflow call. After v2.12\nreaches the stable major ref, consumers keep this configuration for both alpha\ndevelopment and stable release work:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/build.yml@v3\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n working-directory: .\n artifact-name: libnode\n runner-preset: kungfu-v4-self-hosted\n publish-channel: none\n secrets: inherit\n```\n\n`buildchain-channel` defaults to `auto`. Selection uses this precedence:\n\n1. an explicit `buildchain-ref` protected authority, train, SHA, or official channel;\n2. an explicit `buildchain-channel: alpha|stable`;\n3. `publish-channel: alpha|release|major`;\n4. GitHub release prerelease metadata;\n5. a canonical semver tag;\n6. non-release PR, push, dispatch, schedule, and workflow-run events default to\n alpha.\n\nThe resolved runtime is `vN-alpha` for development and prerelease intent and\n`vN` for stable release intent. Unknown custom publish channels, malformed\nrelease events, and non-semver release-like tags fail before the build matrix;\nthey never guess alpha for a stable release.\n\nThe router automatically selects `.buildchain/alpha-contract-lock.json` for\nalpha and `.buildchain/contract-lock.json` for stable. A repository can override\nthe common path with `buildchain-contract-lock-path`, or override one channel\nwith `buildchain-alpha-contract-lock-path` /\n`buildchain-stable-contract-lock-path`.\n\nOnly repositories changing the default policy need extra routing input:\n\n```yaml\nwith:\n buildchain-channel: stable\n```\n\nDuring the v2.12 prerelease evaluation window, canaries use\n`build.yml@v3-alpha`. The same router then selects `v3-alpha` or stable `v2` as\nthe runtime. Production consumers should adopt `build.yml@v3` after the router\nhas reached stable; this keeps the routing shell itself on a stable ref.\n\nThe router is generated from `.build.yml`'s input/output surface. Run\n`node scripts/generate-channel-build-workflow.mjs` after changing the advanced\nbuild workflow; inventory and unit tests reject a stale generated router.\n\n## Advanced Workflow\n\nConsumers that need direct workflow-shell or runtime control call the advanced\nsurface:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n working-directory: .\n artifact-name: libnode\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n artifact-name-template: \"{artifact}-{platform}-{sha}\"\n artifact-paths: |\n dist\n build/stage\n expected-artifacts-json: >-\n {\"minFiles\":2,\"requiredPaths\":[\"dist/libnode.tar.gz\",\"dist/checksums.txt\"]}\n process-summary-path: .buildchain/diagnostics/process-summary.json\n release-candidate: true\n publish-channel: release\n publish-source-ref: publish-gate/release/v22/v22.22/22.22.3-kf.0\n```\n\n`runner-preset` is the stable first-class surface for known runner fleets:\n\n| Preset | Platforms |\n| ----------------------- | ------------------------------------------------------------------------ |\n| `github-hosted` | `ubuntu-24.04`, `macos-latest`, `windows-2022` |\n| `kungfu-v4-self-hosted` | Kungfu Linux x64, macOS ARM64, and Windows x64 self-hosted runner labels |\n| `kungfu-v4-native` | Kungfu Linux x64, Linux ARM64, macOS ARM64, and Windows x64; Linux ARM64 uses GitHub-hosted `ubuntu-24.04-arm` |\n| `custom` | Requires `platforms-json` |\n\nCallers can still provide a custom matrix with `platforms-json`. Each platform\nobject has:\n\n| Field | Meaning |\n| -------- | ------------------------------------------------- |\n| `id` | Stable artifact/platform key, such as `linux-x64` |\n| `name` | Human-readable job name |\n| `runner` | JSON string passed to `runs-on` after `fromJSON` |\n\nThe runner field is intentionally a JSON string so callers can pass either\nGitHub-hosted runners or multi-label self-hosted runners without Buildchain\nguessing the labels.\n\nOnly include platforms that should run. GitHub schedules matrix jobs before\nsteps execute, so a disabled entry with unavailable runner labels can still\nblock the workflow queue.\n\n`fail-fast` defaults to `false`, preserving the diagnostic behavior that\ncollects every platform result. Required promotion callers can set it to `true`\nto cancel sibling native, container, and relay matrix lanes after the first\nfailure. This input changes scheduling only: it does not reduce the declared\nplatform matrix, turn cancellation into a pass, or alter artifact and release\nadmission.\n\n## Linux Job Containers\n\nLinux build platforms can run inside a digest-pinned job container while macOS\nand Windows keep using native runners. This is the recommended way to remove\nmoving Linux runner prerequisites from Buildchain consumers: the Linux host only\nneeds a GitHub Actions runner, Docker, and network access; common verification\ntools come from the image contract.\n\nUse the Kungfu verification image for lifecycle stages that need Git, jq,\nPython, uv, and fnm, but do not need native compilation:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n```\n\n`kungfu-verify` resolves to:\n\n```text\nghcr.io/kungfu-systems/build-images/kungfu-verify@sha256:11f0ba64267ce88174a4f73a9bf833ff4e9c59cd16ec3d08a6432a06c2be6fb1\n```\n\nCallers that own a different Linux image can pass it explicitly:\n\n```yaml\nwith:\n linux-container-preset: custom\n linux-container-image: ghcr.io/example/project-build@sha256:<digest>\n```\n\n`linux-container-image` should be pinned by digest. A floating tag makes the\nrunner surface mutable and weakens Buildchain's release evidence.\n\nThe workflow splits the matrix into two build jobs:\n\n- Linux platforms go to `build-linux-container` when a Linux container is\n configured.\n- All other platforms go to `build-native`.\n\nArtifact names, manifest paths, expected artifact checks, publish-source locks,\nand aggregate summaries are the same in both jobs. The split is an execution\ndetail, not a different artifact contract.\n\n## Native Rust Toolchains\n\nNative lifecycle jobs can request an isolated Rust installation instead of\ndepending on a self-hosted runner user's PATH:\n\n```yaml\nwith:\n setup-rust: true\n rust-toolchain: \"1.96.0\"\n rustup-dist-server: \"https://rsproxy.cn\"\n rustup-update-root: \"https://rsproxy.cn/rustup\"\n cargo-registry-index: ${{ vars.BUILDCHAIN_CARGO_REGISTRY_INDEX }}\n```\n\n`setup-rust` defaults to `false`, so existing consumers are unchanged. When it\nis enabled, Buildchain installs `rust-toolchain` before the install, build, and\nverify lifecycle stages on every native matrix platform. Windows uses the\nofficial rustup bootstrap through `cmd.exe` and `curl.exe` into runner-temporary\nCargo and rustup homes, so it works under a restrictive PowerShell execution\npolicy and the service account does not depend on another user's PATH or mutate\nhost toolchain state. Pin an exact toolchain for release builds. Linux container jobs continue\nto obtain Rust from their digest-pinned image contract; Buildchain does not\nmutate that container surface.\n\nThe rustup server inputs are optional and default to Rust's official servers.\nConsumers behind a slow cross-border link may select a trusted transport mirror;\nrustup still verifies the selected toolchain's distribution metadata and\ncomponent checksums.\n\n`cargo-registry-index` is also optional. When set, Buildchain exposes it to\nCargo as `CARGO_REGISTRIES_CRATES_IO_INDEX` for every native lifecycle stage,\nso a self-hosted runner can use a repository or organization variable without\ncommitting private LAN topology to public workflow YAML. The endpoint must be a\ncrates.io-compatible index whose `config.json` download contract serves the\nmatching checksum-verified crate archives. An empty value preserves Cargo's\nnormal crates.io behavior.\n\nThe container image provides `fnm` but does not preinstall Node. Buildchain uses\n`fnm` inside the container to install the requested `node-version` before it\nruns Buildchain runtime scripts or lifecycle actions.\n\nDo not use `kungfu-verify` for stages that need CMake, Ninja, ccache, Conan, or\nDocker image publishing. Those should use a heavier native-build image or remain\non a host runner until their image contract is explicit.\n\n## Buildchain Runtime Override\n\nStable consumers should keep the reusable workflow pinned to stable refs such as\n`@v3`. The optional `buildchain-ref` input is empty by default; empty means\nBuildchain resolves and executes the stable runtime selected by the workflow\nshell. The full train validation protocol is documented in\n[`runtime-train-validation.md`](runtime-train-validation.md).\n\nFor one-off manual validation, a trusted maintainer can run the caller workflow\nwith a temporary runtime override:\n\n```yaml\non:\n workflow_dispatch:\n inputs:\n buildchain-ref:\n description: \"Temporary Buildchain runtime ref for trusted manual validation\"\n required: false\n default: \"\"\n\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n buildchain-ref: ${{ inputs.buildchain-ref || '' }}\n```\n\nAllowed override refs are deliberately narrow:\n\n| Ref form | Meaning |\n| --- | --- |\n| `train/v3/v3.0/<capability>` | Temporary capability train under the active minor line |\n| `refs/heads/train/v3/v3.0/<capability>` | Explicit branch ref for the same train |\n| `<40-character SHA>` | Exact immutable Buildchain runtime commit |\n\nOverride requests fail closed unless the event is `workflow_dispatch` and the\nactor has write, maintain, or admin permission on the caller repository.\nPull requests, including same-repository pull requests and fork-originated pull\nrequests, cannot use `buildchain-ref` override. This keeps automated PR builds on\nthe stable runtime surface.\n\nEvery run resolves the runtime ref to an immutable SHA before checkout. The job\nsummary and aggregate build summary record the workflow shell ref, requested\nruntime ref, resolved runtime ref, runtime SHA, stability class, trust decision,\nand rollback ref. Train refs are development validation refs: they do not move\n`v2`, `vX.Y`, `vX.Y-alpha`, npm dist-tags, or production release refs, and they\nmust not be pinned as long-term production dependencies.\n\nRuntime override validates Buildchain runtime scripts, CLI code, local actions,\nconfig parsing, and lifecycle behavior. It cannot validate changes that require\nthe outer reusable workflow YAML itself to change, such as new jobs,\npermissions, workflow outputs, or matrix topology. Those changes need a canary\nworkflow path or a temporary explicit workflow ref.\n\n## Floating Ref Contract Lock\n\nStable consumers should use floating major refs such as `@v3`, but a floating\nref is not blind trust. Each released Buildchain ref carries a package-owned\nruntime contract world in `dist/site/buildchain-contract.json`. Consumers may\nkeep a small lock file, `.buildchain/contract-lock.json`, recording the\nBuildchain ref, resolved SHA, contract digest, compatibility digest, accepted\nmajor line, and compatibility policy they reviewed.\n\nThe reusable build trust gate checks this lock before any heavy matrix job:\n\n1. resolve the Buildchain runtime ref, for example `v2`, to an immutable SHA;\n2. read `dist/site/buildchain-contract.json` from that checked-out Buildchain\n ref;\n3. read the consumer's `.buildchain/contract-lock.json`;\n4. compare the accepted contract with the current contract.\n\nSHA drift alone is not a failure. `v2` is expected to advance. Buildchain only\nfails fast when the accepted contract is no longer compatible, for example a\nrequired input is removed, a required output disappears, a protected behavior\npromise changes, or the major line changes. Additive changes such as optional\ninputs, optional outputs, diagnostics, or documentation updates continue under\nthe default `major-compatible` policy.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n buildchain-contract-lock-path: .buildchain/contract-lock.json\n buildchain-contract-compatibility-policy: major-compatible\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\nWhen compatible drift is detected, the build continues and Buildchain opens or\nupdates a low-priority issue in the consumer repository. The issue records the\nold SHA/digest, new SHA/digest, compatibility result, workflow run, and the next\naction: review the Buildchain release notes and update the lock. When breaking\ndrift is detected, the same issue path is used, but the trust gate fails before\nmatrix build or publish work starts. If the workflow token cannot write issues,\nBuildchain writes a copyable issue body into the job summary.\n\nThe lock is intentionally small. It does not copy the full contract. The full\ncontract remains in the Buildchain ref and package; the consumer records only\nwhat it accepted and the policy used to compare future floating-ref movement.\n\nAdvanced alpha-channel consumers select the matching workflow shell:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3-alpha\n with:\n buildchain-contract-lock-path: .buildchain/contract-lock.json\n```\n\nThe runtime follows the called workflow through `job.workflow_ref`. Callers may\nalso pass `buildchain-ref: v3-alpha` explicitly; official floating refs are\nordinary channel selections and are allowed on pull requests and pushes. Train\nrefs and exact SHAs remain trusted manual overrides.\n\n## Shifu Cache Profile Passthrough\n\nBuildchain can carry one trusted Shifu cache-profile reference and its exact\ndigest into lifecycle execution. Its contract is an opaque reference and digest\nonly. This surface is deliberately opaque:\nBuildchain does not fetch the profile, parse JSON, select cache services,\nrewrite bindings, decide fallback, or emit Shifu resolution evidence. Those\nsemantics remain owned by the consumer's pinned Shifu implementation.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/build.yml@v3\n with:\n shifu-cache-profile-ref: ${{ vars.SHIFU_CACHE_PROFILE_REF }}\n shifu-cache-profile-digest: ${{ vars.SHIFU_CACHE_PROFILE_DIGEST }}\n```\n\nThe reusable workflow passes the pair as `SHIFU_CACHE_PROFILE_REF` and\n`SHIFU_CACHE_PROFILE_DIGEST` to install, build, and verify lifecycle commands.\nThe consumer must invoke its Shifu cache-aware execution surface. An empty pair\npreserves existing behavior; a consumer Shifu should fail closed when exactly\none value is present or the resolved bytes do not match the expected digest.\n\nUse trusted repository or organization variables rather than PR-controlled\nfiles for private/LAN references. The variables must remain secret-free; any\ncredentials use a separate provider-approved secret surface and must not be\nembedded in the profile reference. This passthrough is separate from\nBuildchain's locked source checkout cache below: Buildchain owns checkout\ntransport and source identity, while Shifu owns post-checkout execution cache\nbindings and receipts.\n\n## Locked Source Checkout Cache\n\nSelf-hosted runners that build large repositories can opt into a locked checkout\ncache for both the consumer source and the Buildchain runtime. This changes only\nthe Git object transport. Buildchain still resolves `publish-source-sha` and the\nruntime SHA before any build runner starts, checks out those exact commits, and\nverifies each final `HEAD` plus the resolved consumer source tree SHA before\nlifecycle commands run.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n runner-preset: kungfu-v4-self-hosted\n checkout-cache-mode: auto\n checkout-cache-mirror-url-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE }}\n checkout-cache-reference-repository-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE }}\n checkout-cache-fallback: github\n checkout-cache-timeout-seconds: 60\n checkout-cache-github-timeout-seconds: 600\n checkout-cache-fetch-attempts: 3\n```\n\n`checkout-cache-mode` accepts:\n\n| Mode | Behavior |\n| --- | --- |\n| `off` | Default. Buildchain fetches the locked commit from GitHub. |\n| `auto` | Try the trusted cache first; on miss, record the miss and fall back according to `checkout-cache-fallback`. |\n| `require` | Require the cache to provide the locked commit and fail before lifecycle work if unavailable. |\n\nThe cache can be a local/LAN mirror URL template or a runner-local bare\nreference repository template. Templates support `{owner}`, `{repo}`,\n`{repository}`, `{repositorySlug}`, and `{sha}`. The workflow also reads\nrepository or organization variables named\n`BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE` and\n`BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE`, so consumers can keep\nprivate LAN topology out of repository YAML.\n\nThe GitHub-hosted trust gate resolves the reusable workflow shell to an exact\ncommit and uploads that shell's small checkout bootstrap script. Native and\nLinux-container build jobs download the bootstrap, then use the same cache\npolicy to obtain both the selected Buildchain runtime and consumer source at\ntheir already resolved immutable SHAs. Keeping the bootstrap owned by the\nworkflow shell is important when `@vN-alpha` routes a stable release to an older\n`vN` runtime: the stable runtime does not need to already contain the newest\ncheckout transport implementation. This also prevents a large direct\n`actions/checkout` runtime clone from becoming a separate timeout path on\nconstrained self-hosted uplinks. The bootstrap artifact does not contain the\nruntime repository and cannot move either selected ref.\n\nDo not read cache URLs or reference paths from PR-controlled files such as\n`.buildchain/buildchain.toml`. These values are trusted workflow inputs or repo/org\nvariables. Buildchain does not pass GitHub credentials to cache mirrors or\nreference repositories. If it must fall back to GitHub, the workflow token is\nused only for the GitHub fetch path. Cache attempts use\n`checkout-cache-timeout-seconds`; the potentially larger GitHub fallback uses\nthe independent `checkout-cache-github-timeout-seconds` budget (600 seconds by\ndefault). Buildchain fetches the advertised source ref before trying an exact\nSHA, so a cache hit or stale-cache seed can contribute objects and the fallback\ndoes not first waste a full timeout on an unadvertised SHA. Retryable timeout\nand transient network failures use the bounded `checkout-cache-fetch-attempts`\nbudget; permanent failures stop immediately. Diagnostics record both timeout\nbudgets and the actual GitHub fetch attempts before exact HEAD/tree\nverification.\n\nEach platform diagnostics artifact includes `source-checkout.json` and embeds a\ncompact `sourceCheckout` summary in `diagnostics.json`: mode, transport,\nhit/miss, fallback reason, duration, final HEAD verification, and tree\nverification. Remote URLs are sanitized and local reference paths are represented\nby a short display name plus fingerprint, not by secret-bearing credentials.\nRuntime checkout evidence is uploaded separately as `runtime-checkout.json`,\nincluding cache transport, fallback attempts, and exact runtime `HEAD`\nverification, even when a later lifecycle step fails.\n\nWhen a Buildchain maintainer asks for downstream validation, the expected\nrequest is:\n\n```text\nBuildchain train ready: buildchain-ref=train/v3/v3.0/<capability>.\nKeep uses: ...@v3; run workflow_dispatch with that buildchain-ref and report the runtime evidence summary.\n```\n\nAfter validation succeeds, the Buildchain change should continue through the\nnormal mainline and release path. Do not treat the train as a pending merge\nitem; it is only a temporary fast-use, diagnostic, and rollback channel. It may\nremain for a retention window after release, with old trains handled by a\nseparate periodic cleanup task.\n\n## Workflow Outputs\n\nThe reusable workflow exposes the resolved contract:\n\n| Output | Meaning |\n| --------------------------------- | ------------------------------------------------------------------------------- |\n| `runner-preset` | Resolved preset, or `custom` when `platforms-json` was provided |\n| `platforms-json` | Exact matrix JSON used by the build job |\n| `platform-count` | Number of matrix platforms |\n| `linux-container-enabled` | `true` when Linux platforms are routed through a job container |\n| `linux-container-image` | Resolved digest-pinned Linux job container image |\n| `build-summary-artifact` | Uploaded aggregate summary artifact name |\n| `build-diagnostics-summary-artifact` | Uploaded aggregate diagnostics summary artifact name |\n| `release-candidate-passport-artifact` | Uploaded PR-stage release-candidate passport artifact name when `release-candidate` is enabled |\n| `release-candidate-passport-json` | Compact release-candidate passport JSON when `release-candidate` is enabled |\n| `build-summary-json` | Compact aggregate JSON with platform count, file count, and byte total |\n| `build-diagnostics-summary-json` | Compact aggregate diagnostics JSON with platform, lifecycle warning/error, diagnostics contract warning, and sidecar manifest warning totals |\n| `trusted-event` | `true` when the event is trusted enough to reach build runners |\n| `buildchain-runtime-ref` | Runtime ref selected after applying the empty-default or override policy |\n| `buildchain-runtime-sha` | Immutable Buildchain runtime commit used by all runtime checkouts |\n| `buildchain-runtime-class` | `stable`, `alpha`, `authority`, `train`, `exact-sha`, or `development` |\n| `buildchain-runtime-override` | `true` when an authority, train, or exact-SHA `buildchain-ref` override was accepted |\n| `buildchain-runtime-trust-decision` | Runtime override trust decision |\n| `buildchain-contract-lock-status` | `unchanged`, `compatible-drift`, `breaking-drift`, `missing-lock`, `non-floating-runtime`, or first-release `runtime-contract-unavailable` |\n| `buildchain-contract-lock-drift` | `true` when the floating runtime SHA or contract digest changed |\n| `buildchain-contract-digest` | Current Buildchain runtime contract digest |\n| `publish-channel` | Resolved publish channel requested by the caller |\n| `publish-allowed` | `true` only when this event/ref may publish after verification |\n| `publish-reason` | Human-readable reason for the publish gate decision |\n| `publish-source-ref` | Gate source ref that was resolved before checkout |\n| `publish-source-sha` | Exact source commit used by checkout, build, verify, and artifacts |\n| `publish-source-locked` | `true` when a `publish-gate/*` source ref was explicitly locked |\n| `publish-source-channel` | `alpha`, `release`, `anchor`, or `major` parsed from the source ref |\n| `publish-source-line` | Product line parsed from source refs such as `v22/v22.22` |\n| `publish-source-consumer-version` | Consumer package version parsed from source refs |\n| `release-manifest-json` | Resolved release manifest including source lock, version state, and anchor data |\n\nThe aggregate summaries are intentionally artifacts as well as outputs. GitHub\nActions matrix outputs are not a reliable place to carry every platform's full\nmanifest, so Buildchain uploads each platform manifest and then emits one\naggregate build summary artifact after the matrix completes. Buildchain uploads\n`diagnostics-summary.json` as a separate aggregate diagnostics summary artifact,\na compact rollup of each platform's small diagnostics upload. The rollup keeps\nper-platform runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler-cache availability, lifecycle timing,\nprocess sampler context, and links back to the exact platform artifacts. Each\nplatform diagnostics upload includes `diagnostics.json`,\n`diagnostics-manifest.json`, the lifecycle `events.jsonl`, and process sampler\nsidecars when enabled, so slow-build diagnosis does not require downloading the\nbinary platform artifact or the aggregate build summary. The sidecar manifest\nrecords the uploaded diagnostics files with bytes and sha256 hashes. Each\n`diagnostics.json` also records the related binary artifact name, manifest\nartifact name, diagnostics artifact name, diagnostics sidecar manifest path, and\nplatform id in `links`, so a reviewer can navigate from the small diagnostics\nartifact back to the exact platform outputs when deeper inspection is needed.\nThe workflow output `build-diagnostics-summary-json` includes\n`diagnosticsContractWarningCount` and `diagnosticsManifestWarningCount` so\nrelease jobs can detect drifting diagnostics JSON contracts and missing or\ndrifting diagnostics sidecar manifests without downloading the per-platform\ndiagnostics artifacts first.\n\n## Artifact Signing Authority\n\nArtifact signing is a Buildchain capability, not a macOS application workflow.\nConsumers declare desired signature state next to their artifact facts; they do\nnot configure certificates, Team IDs, notary credentials, protected\nenvironments, authority roles, or signing jobs:\n\n```toml\n[[signing.artifacts]]\nid = \"native-engine\"\npath = \"dist/kungfu-engine\"\nprofile = \"auto\"\nkind = \"mach-o\"\nplatforms = [\"macos-arm64\", \"macos-x64\"]\n```\n\nEvery native and container build lane reads this declaration after the build\nlifecycle and before verification. Buildchain binds the exact artifact bytes or directory tree to\nthe caller repository, source commit, source tree, immutable runtime, platform,\nand requested signature semantics, then publishes a deterministic\n`<artifact>-signing-request-<platform>-<source-sha>` request. No consumer\nworkflow step is required.\n\n`profile = \"auto\"` resolves signable Apple artifacts such as Mach-O files,\n`.dylib`, `.framework`, `.app`, `.xpc`, `.plugin`, `.pkg`, and `.dmg` to the\nnative `apple-developer-id` provider. Windows `pe` and `binary` artifacts\nresolve to timestamped native `windows-authenticode`; Windows PE never falls\nback to a detached signature. Linux and other non-native binary files,\narchives, blobs, and directories resolve to `detached-signature-v1`. Buildchain records that as a\ndetached cryptographic signature and never misrepresents it as an operating\nsystem code signature. Explicit incompatible provider/kind/platform\ncombinations fail closed.\n\nThe request schema rejects credential and authority-infrastructure fields. The\nBuildchain-owned signing authority is responsible for credential selection,\nnative signing, notarization where applicable, immutable result delivery, and a\nreceipt bound to the request digest, runtime SHA, output digest, and signature\nevidence. Consumer repositories neither receive nor duplicate credential-island\nmaterial. The reusable workflow dispatches the sealed request to the\nBuildchain repository, waits for its protected authority workflow, verifies the\nimmutable result, replaces only the declared artifact with the returned final\nbytes, and then runs the consumer's normal verification. Platform manifests,\nKFD evidence, checksums, and Release Passport inputs therefore observe the\nfinal signed artifact rather than the pre-signing build output.\n\nThe durable v3 authority runtime is\n`authority/v3/v3.0/artifact-signing`. It is channel-neutral: alpha and stable\nrelease work use the same protected `buildchain-artifact-signing` environment\nand provider identities. The authority ref is protected independently from\nrelease channels and can advance only through reviewed, checked changes; the\ntemporary `train/v3/v3.0/artifact-signing-authority` ref is retained only as a\nbounded migration rollback.\n\nThe older `credential-island-macos-*` reusable-workflow inputs remain a\ncompatibility surface while existing callers migrate. They are not the target\nconsumer contract and must not be used to design new integrations.\n\n## Artifact Transfer Relay\n\nBy default, platform jobs upload payloads, manifests, and diagnostics directly\nto GitHub artifacts:\n\n```yaml\nwith:\n artifact-transfer-mode: github-artifacts\n```\n\nLarge self-hosted native builds can opt into the first-class S3 relay path:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n runner-preset: kungfu-v4-self-hosted\n artifact-transfer-mode: s3-to-github-artifacts\n artifact-relay-s3-bucket: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET }}\n artifact-relay-s3-region: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_REGION }}\n artifact-relay-s3-prefix: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX }}\n```\n\nIn relay mode, each self-hosted platform job uploads the heavy payload files to\nS3 and uploads only a small `relay-manifest.json` to GitHub. A GitHub-hosted\n`relay-artifacts` job then assumes the configured download role, downloads the\npayloads from S3, verifies every file by SHA256, and re-uploads the normal\nGitHub artifacts under the same artifact names that direct mode uses. Downstream\nsummary, release-candidate, and promote-only workflows therefore continue to\nconsume GitHub artifacts and do not need custom S3 logic.\nThe relay implementation uses Node.js plus the standard AWS environment\ncredentials from GitHub OIDC; runner images and build containers do not need the\nAWS CLI installed.\n\nAfter the GitHub artifact uploads succeed, Buildchain deletes the S3 objects\nlisted in the relay manifest for that platform. If any download, verification,\nor GitHub artifact upload fails, cleanup is skipped so maintainers can inspect\nthe retained S3 payload. Configure a short bucket lifecycle expiration as a\ncost and cleanup backstop.\n\nThe relay configuration is intentionally generic. Buildchain does not hard-code\norganization buckets, regions, or role ARNs. Callers may pass explicit inputs,\nor set repository/organization variables and secrets using these names:\n\n| Variable or secret | Meaning |\n| --- | --- |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET` | Relay bucket name |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_REGION` | Relay bucket region |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX` | Relay object prefix; defaults to `buildchain-artifacts` |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_ROLE_ARN` | Shared OIDC role ARN for upload and download |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_UPLOAD_ROLE_ARN` | Upload OIDC role ARN for self-hosted build jobs |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_DOWNLOAD_ROLE_ARN` | Download OIDC role ARN for the GitHub-hosted relay job |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_OIDC_AUDIENCE` | Optional OIDC audience override |\n\nFor AWS China regions, Buildchain defaults the OIDC audience to\n`sts.amazonaws.com.cn`; other regions default to `sts.amazonaws.com`. The caller\nworkflow must allow `id-token: write`, and the target role trust policy should\nrestrict GitHub OIDC claims to the expected organization, repository, workflow,\nand branch/ref. The S3 permissions should be scoped to the relay bucket/prefix\nused by the repository.\nUpload roles need write/delete access under the relay prefix; download roles\nneed read access plus delete access for successful cleanup.\n\nRelay mode is opt-in and does not affect forks or open-source users that do not\nconfigure S3. Missing bucket, region, upload role, or download role values fail\nbefore the heavy build matrix is scheduled. Buildchain treats S3 as a transport\ncache, not as the final release evidence store; the final audit entry remains\nthe GitHub artifact set plus the Buildchain build summary and release-candidate\npassport.\n\nSet `release-candidate: true` when the successful reusable build is meant to be\nthe artifact source promoted later. Buildchain then uploads\n`release-candidate-passport.json` under the\n`<artifact-name>-release-candidate-<publish-source-sha>` artifact name. Promotion\njobs can pass that passport to `promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` so source, channel, platforms, and the\naggregate build-summary hash are checked before publish-gate side effects. The\npassport records the locked commit's Git tree SHA, so a post-merge channel HEAD\ncan be accepted only when it is tree-equivalent to the PR-stage build evidence.\n\n## Publish Gate\n\nBuildchain separates \"may build/verify\" from \"may publish.\" A same-repository\npull request may be trusted enough to run the build matrix, but it still must\nnot publish packages, S3 objects, release pages, or preview aliases. Publishing\nis allowed only when the caller explicitly requests a channel and the current\nevent/ref matches that channel.\n\nUse `publish-channel` to request a channel:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n publish-channel: release\n\n publish:\n needs: build\n if: ${{ needs.build.outputs.publish-allowed == 'true' }}\n runs-on: ubuntu-24.04\n steps:\n - run: ./scripts/publish.sh\n```\n\nDefault channels are:\n\n| Channel | Allowed refs |\n| --------- | ---------------------------------------------------------------------------------------------------- |\n| `none` | Never publishes; this is the default |\n| `alpha` | `alpha/vN/vN.M` branches or exact `vN.M.P-alpha.K` tags |\n| `release` | `release/vN/vN.M` branches or release tags such as `vN.M.P`, `vN.M`, `vN` |\n| `major` | `publish-gate/major`, legacy `major-gate`, or next-major release tags such as `vN.0.0`, `vN.0`, `vN` |\n\nPull request events always produce `publish-allowed=false`, even when the PR is\nfrom the same repository. Untrusted fork events also produce\n`publish-allowed=false`; with the default `untrusted-policy: fail`, the workflow\nthen fails before any build runner starts.\n\nProjects with their own channel names can pass `publish-refs-json`:\n\n```yaml\nwith:\n publish-channel: nightly\n publish-refs-json: >-\n {\"nightly\":[\"^refs/heads/nightly/v\\\\d+$\"]}\n```\n\nThe aggregate build summary includes the same publish gate decision under\n`publishGate`, so a downloaded artifact summary explains both what was built and\nwhy it was or was not eligible to publish.\n\n## Publish Source Lock\n\n`publish-channel` answers \"may this event publish?\" Source lock answers \"which\nsource tree is the publish decision about?\" A caller can pass `publish-source-ref`\nto bind a publish run to a reviewed gate branch before any checkout happens:\n\n| Ref | Meaning |\n| ------------------------------------------------ | --------------------------------------------------------------------------- |\n| `publish-gate/alpha/<line>/<consumer-version>` | Build and publish an alpha candidate for a consumer line |\n| `publish-gate/release/<line>/<consumer-version>` | Build and publish a production candidate for a consumer line |\n| `publish-gate/anchor` | Resolve an explicit anchor request; it does not publish artifacts by itself |\n| `publish-gate/major` | Gate the next major source state |\n| `major-gate` | Legacy compatibility alias for the major gate |\n\nFor alpha and release refs, `<line>` is intentionally allowed to contain `/`, so\nKungfu-style lines such as `v22/v22.22` stay readable. The final path segment is\nthe consumer-visible version, for example `22.22.3-kf.0`.\n\nThe reusable workflow resolves the branch tip to `publish-source-sha`, checks out\nthat SHA in every build job, and uses the same SHA in artifact names, manifests,\nand aggregate summaries. Reruns therefore rebuild the same source tree even if a\ngate branch moves later.\n\nBefore any heavy build matrix is scheduled, the workflow also verifies that the\ntarget channel ref implied by the source lock already points at\n`publish-source-sha` and that the target channel HEAD came from the required\nmerged same-repository channel PR. `publish-gate/alpha/<line>/<version>` must\nmatch `alpha/<line>` and have PR lineage `dev/<line> -> alpha/<line>`;\n`publish-gate/release/<line>/<version>` must match `release/<line>` and have PR\nlineage `alpha/<line> -> release/<line>`. If either check fails, the run fails\nfast with a diagnostic telling maintainers to merge the source commit through\nthe channel PR first. This keeps verify from spending runner time on a source\ntree that cannot legally enter the requested publish channel.\n\nThe resolved release manifest is uploaded as an artifact and emitted as\n`release-manifest-json`. It records:\n\n- source ref, source SHA, channel, line, and consumer version;\n- configured version strategy and configured version-state files;\n- each version file's value, with release gates failing closed if the configured\n files do not equal the consumer version;\n- anchor manifest summary for anchored/manual projects;\n- explicit anchor request JSON for `publish-gate/anchor`;\n- publish registry, dist-tag, and gate visibility metadata.\n\nPublish side-effect jobs should verify the lock immediately before publishing:\n\n```yaml\n- name: Verify publish gate did not move\n run: node .buildchain/runtime/scripts/verify-publish-source-lock.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nIf the branch tip no longer matches the manifest SHA, the publish job must fail\nclosed. Moving a gate branch creates a new publish decision and should produce a\nnew build run.\n\n## Release Candidate Promote-Only\n\nFor native package sets, the PR build is the only heavy build. When a PR targets\n`alpha/<line>` or `release/<line>`, `.build.yml` uploads a release-candidate\nbundle next to the platform artifacts. The bundle contains:\n\n- `release-candidate.passport.json`;\n- the aggregate `build-summary.json`;\n- copied platform manifest evidence for the built platforms.\n\nThe passport records two separate source identities:\n\n- `builtSourceSha` / `builtSourceTreeSha`: the PR-stage source that produced the\n artifacts, usually the PR merge ref;\n- `promotionChannelSha` / `promotionChannelTreeSha`: the post-merge channel\n commit used for publish authority.\n\nThe reusable promote wrapper resolves the merged PR, finds exactly one matching\nPR-stage release-candidate artifact, downloads it with the build summary and\npayload artifacts from the same PR-stage run, validates the payload count,\ncompares the built tree with the promotion channel tree, locks\n`publish-gate/{alpha,release,major}` to the promotion channel commit, and then\ncalls `actions/promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` and\n`require-publish-source-lock: \"true\"`. The wrapper passes the created\n`publish-gate/*` ref, target SHA, and `locked=true` into the promote action, so\nfloating `@v3` consumers receive publish-side source-lock drift protection by\ndefault. It also defaults `branch-protection-bypass-apps` to `github-actions`\nso the workflow automation can apply generated version-state and channel\nbookkeeping on protected `dev`/`alpha`/`release` branches after the reviewed\nchannel PR has merged. Other App slugs and all user or team bypass actors are\nrejected. The wrapper uses the run-scoped `github.token` as the generated ref\nupdate token for protected bookkeeping PATCH calls, so the exact GitHub Actions\nApp authority can sync dev immediately after alpha/release publish without a\npost-publish PR.\nIt does not call `.build.yml`, does not create a matrix, and must fail before\npublish if the RC evidence, payload set, or source-lock ref is missing or\nambiguous.\n\nThe public `release-candidate-promote.yml` is a generated channel router. It\nderives the publication lane from `target-ref`, then selects the matching\nadvanced workflow shell, runtime, and consumer lock before the advanced\npromotion starts:\n\n- alpha targets use `.release-candidate-promote.yml@vN-alpha`, runtime\n `vN-alpha`, and `buildchain-alpha-contract-lock-path`;\n- release and major targets use `.release-candidate-promote.yml@vN`, runtime\n `vN`, and `buildchain-stable-contract-lock-path`.\n\nThe generated router also owns the stable-shell layout transition through\n`.buildchain/promotion-shell-routing.json`. Stable `v2.14.13` contains the hidden\nadvanced workflow, so the stable lane calls that workflow at the exact immutable\nSHA behind the released `v2` state and forwards the complete internal promotion\nidentity surface. The logical shell identity remains `vN`, and the router\nretains it in the public audit outputs. The internal advanced-shell call receives\nthe exact call ref selected by the routing configuration, so its called-workflow\nref check and checkout SHA both bind to the same immutable identity. Updating the\nrouting pin after a stable release does not require any consumer declaration\nchange.\n\nThe router resolves immutable SHAs and the selected lock digest before candidate\ndownload. The advanced shell verifies the same router, shell, runtime, lock,\nchannel, and target binding again. Train and exact-SHA runtime overrides remain\nrestricted to trusted `workflow_dispatch` actors with write, maintain, or admin\npermission. Promotion controller evidence, the promotion copy of the release\ncandidate passport, and the final release passport record these identities.\n\n```yaml\njobs:\n promote:\n uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v3\n secrets:\n buildchain-issue-app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n buildchain-issue-app-private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n with:\n buildchain-channel: auto\n buildchain-alpha-contract-lock-path: .buildchain/alpha-contract-lock.json\n buildchain-stable-contract-lock-path: .buildchain/contract-lock.json\n channel: alpha\n target-ref: alpha/v22/v22.22\n artifact-name: libnode\n # Defaults to build.yml / Build. Override only when the PR-stage build\n # workflow uses a different file or display name.\n release-candidate-workflow-file: build.yml\n release-candidate-workflow-name: Build\n package-manager: npm\n publish-target: npm\n runner-preset: github-hosted\n trusted-publishing: true\n github-release: true\n required-status-check: check / check\n required-artifact-count: 3\n publish-dist-tag: alpha\n publish-package-set-order: platforms-first-main-last\n publish-package-main: \"@kungfu-tech/libnode\"\n release-passport-product-name: Libnode\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\nExisting callers may keep `buildchain-contract-lock-path`; a non-empty explicit\npath overrides channel-specific selection for compatibility. Migration only\nrequires adding the two channel lock inputs and may retain the remaining common\npromotion declaration unchanged. Consumers must not call the dot-prefixed\nadvanced workflow directly.\n\n`buildchain-issue-app-id` and `buildchain-issue-app-private-key` are optional\nbut recommended for cross-repository consumers. They should identify a GitHub\nApp installation with `issues: write` on `kungfu-systems/buildchain`; the\nwrapper mints the installation token before calling\n`actions/report-buildchain-issue`. Consumers can also pass a pre-minted\n`buildchain-issue-token`. If both are omitted, the wrapper falls back to\n`BUILDCHAIN_ISSUE_TOKEN`, `BUILDCHAIN_PROMOTION_TOKEN`, and then the consumer\nworkflow's `github.token`; the last fallback can only report issues when it has\nwrite access to the target Buildchain repository.\n\n`publish-required-artifacts-json` can still be passed explicitly for custom\npublish targets. Custom OCI requirements may omit pre-publish refs and digests;\nthe action resolves the exact version ref and validates final digests and any\nbuilt/reused provenance after `lifecycle.publish`. For the default\n`publish-artifact-kind: npm` path, consumers do\nnot download artifacts or run repository scripts to build publish evidence. The\nwrapper downloads the PR-stage payload artifacts, finds the downloaded `.tgz`\npackages, reads each tarball's `package/package.json` for the real scoped\npackage name and version, computes the npm `sha512-...` integrity from the\ntarball bytes, marks the package matching `publish-package-main` as `role:\nmain`, marks the rest as `role: platform`, and passes the generated\n`publish-required-artifacts-json` to `promote-buildchain-ref` before any publish\nside effect. Downloaded platform manifests are still passed into the release\npassport unless `release-passport-platform-manifest-paths` is set explicitly.\nThe same Buildchain contract lock check runs before release-candidate\nresolution and before publish. A compatible `v2` drift leaves an issue in the\nconsumer repository but does not trigger a second heavy build; an incompatible\ndrift fails before publish side effects.\n\nThe wrapper publishes the public release tag as a GitHub Release by default.\nAfter `promote-buildchain-ref` reports a complete release transaction, the\nwrapper creates or updates the public release, marks semver prerelease tags\nsuch as `v1.2.3-alpha.0`, `v1.2.3-rc.1`, or `v22.22.3-kf.3-alpha.7` as\n`prerelease=true` and `make_latest=false`, marks stable semver tags as latest,\nand uploads the publish evidence file plus every file in the generated release\npassport directory, including `buildchain.release.json` and `check-report.json`.\nFor anchored/manual package releases, the public release tag is derived from the\npublished package version and the internal exact transaction tag remains\navailable in the release passport.\nConsumers do not need to hand-write `gh release` logic to trigger\n`release.published` propagation. Set `github-release: false` only for\nrepositories that intentionally do not maintain GitHub Releases.\nIf the transaction still needs protected-ref finalization, the wrapper defers\nGitHub Release creation until the later run that reaches `state=complete`.\n\nCustom publish jobs can also repeat the channel-ref preflight:\n\n```yaml\n- name: Verify publish channel ref still matches\n run: node .buildchain/runtime/scripts/verify-publish-channel-ref.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nAnchored/manual package release jobs should also make the Buildchain promotion\naction validate that publication is entering through the same\n`publish-gate/{alpha,release,major}` source-lock contract before any package\npublish side effect:\n\n```yaml\n- name: Promote release ref and publish npm package set\n uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v3\n with:\n sha: ${{ needs.build.outputs.publish-source-sha }}\n target-ref: release/v22/v22.22\n require-publish-source-lock: 'true'\n publish-source-ref: ${{ needs.build.outputs.publish-source-ref }}\n publish-source-sha: ${{ needs.build.outputs.publish-source-sha }}\n publish-source-locked: ${{ needs.build.outputs.publish-source-locked }}\n```\n\n`target-ref` stays the Buildchain channel promotion target, such as\n`alpha/v22/v22.22`, `release/v22/v22.22`, or `publish-gate/major`.\n`publish-source-ref` is the reviewed source-lock branch that authorized this\nspecific package publication. For alpha and release package publications, the\nsource-lock branch must point at the exact channel-line commit that promotion is\nvalidating; it is not a replacement for `target-ref`.\n\nThis keeps the version bump commit, publish authorization, and auditable publish\nentrypoint on the Buildchain source-lock protocol. The CLI form\n`buildchain publish-source validate-anchored-release --json` is still useful for\ncustom publish scripts, but the preferred GitHub Actions gate is the promotion\naction input above. A publish job that still runs directly from `alpha/*` or\n`release/*` channel branches fails this check because those refs are channel\nstate, not publish-gate decisions.\n\n## Package-Set Publish Plan\n\nProjects that publish multiple packages should treat package publication as a\npackage-set operation. Buildchain's package-set planner uses these rules:\n\n- platform packages publish first;\n- the main package publishes last;\n- the dist-tag move happens only after the full package set is present;\n- reruns accept already-published packages only when package name, version, and\n integrity match;\n- an existing package with different integrity is a hard failure.\n\nThis keeps a consumer from observing a floating dist-tag that points to a main\npackage before all platform artifacts for the same source SHA are available.\n\n## Command Sources\n\nThe workflow runs `.buildchain/buildchain.toml` lifecycle stages by default:\n\n```toml\n[lifecycle.install]\ncommand = \"corepack yarn install --immutable\"\n\n[lifecycle.build]\ncommands = [\n \"corepack yarn make\",\n \"corepack yarn build\",\n]\n\n[lifecycle.verify]\ncommand = \"corepack yarn test\"\n```\n\nCallers can override any stage for one invocation:\n\n```yaml\nwith:\n build-command: cmake --build build --config Release\n verify-command: ctest --test-dir build --output-on-failure\n```\n\nEvery native and container matrix job is bounded by\n`lifecycle-timeout-minutes`, which defaults to 120 minutes. The same input is\nthe fallback deadline for each install, build, and verify action, so a hung\ncommand fails with the lifecycle name and matrix platform before it can occupy\na self-hosted runner indefinitely. A stage-level `timeout_minutes` in\n`buildchain.toml` remains the more specific override for that stage.\n\n```yaml\nwith:\n lifecycle-timeout-minutes: 90\n```\n\nThe reusable build workflow samples the build lifecycle by default and carries\nthe generated summary into the final verify diagnostics. Callers can override\nthe sidecar path or disable sampling:\n\n```yaml\nwith:\n sample-process-tree: true\n process-summary-path: .buildchain/diagnostics/process-summary.json\n process-sample-interval-ms: 15000\n requested-parallelism: 20\n```\n\nWhen `sample-process-tree` is true, Buildchain wraps either `build-command` or\nthe configured `lifecycle.build` stage with `buildchain sample process-tree`.\nThe path is relative to the checked-out workspace and is read again during the\nfinal verify lifecycle. Custom workflows can still write their own sampler\nsummary and pass `process-summary-path`; Buildchain reads the file after the\nlifecycle command finishes, so it may be produced during the same invocation.\nWhen the build stage is optional, the reusable workflow treats the default\nsampler path as optional during verify; an explicitly supplied\n`process-summary-path` remains required.\n\nFor custom workflows, use the action directly:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/run-lifecycle@v3\n with:\n stage: build\n required: \"true\"\n timeout-minutes: \"90\"\n artifact-name: libnode-linux-x64-${{ github.sha }}\n artifact-paths: |\n dist\n build/stage\n```\n\n## Artifact Contract\n\nEach platform upload uses `artifact-name-template`. The default is:\n\n```text\n{artifact}-{platform}-{sha}\n```\n\nSupported placeholders are `{artifact}`, `{artifactName}`, `{platform}`,\n`{platformId}`, `{platformName}`, `{sha}`, `{shortSha}`, `{ref}`, `{runId}`,\nand `{runAttempt}`. Invalid GitHub artifact name characters are normalized to\n`-`, so `{ref}` remains deterministic even for refs such as\n`refs/heads/dev/v2/v2.0`.\n\nEach platform also writes and uploads:\n\n```text\n.buildchain/artifacts/<platform-id>/manifest.json\n.buildchain/artifacts/<platform-id>/summary.json\n```\n\nThe manifest schema is:\n\n```json\n{\n \"schemaVersion\": 1,\n \"contract\": \"kungfu-buildchain-artifact\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"platform\": {\n \"id\": \"linux-x64\",\n \"name\": \"Linux x64\",\n \"os\": \"Linux\",\n \"arch\": \"X64\"\n },\n \"git\": {\n \"repository\": \"kungfu-systems/libnode\",\n \"sha\": \"<sha>\",\n \"ref\": \"<ref>\",\n \"runId\": \"<run id>\",\n \"runAttempt\": \"<attempt>\"\n },\n \"lifecycle\": {\n \"stage\": \"verify\",\n \"commandSource\": \"buildchain.toml\",\n \"executed\": true\n },\n \"summary\": {\n \"contract\": \"kungfu-buildchain-artifact-summary\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"fileCount\": 1,\n \"totalBytes\": 1234,\n \"digest\": \"<hex>\"\n },\n \"expectedArtifacts\": {\n \"ok\": true,\n \"source\": \"expected-artifacts-json\",\n \"checks\": []\n },\n \"files\": [\n {\n \"path\": \"dist/example.zip\",\n \"size\": 1234,\n \"sha256\": \"<hex>\"\n }\n ]\n}\n```\n\nArtifact names do not include actor names, timestamps, or retry counters. Reruns\nproduce a new GitHub Actions run but keep the same source SHA/platform contract.\n\n`expected-artifacts-json` fails the build before upload when the artifact does\nnot match the caller's declared contract. Supported checks are:\n\n| Field | Meaning |\n| --------------- | ------------------------------------ |\n| `minFiles` | Minimum number of manifest files |\n| `maxFiles` | Maximum number of manifest files |\n| `minTotalBytes` | Minimum total byte count |\n| `requiredPaths` | Exact manifest paths that must exist |\n\n## Trusted Event Gate\n\nThe workflow has an explicit `trust-gate` job. By default, pull requests from\nforks fail before any build job can reach self-hosted runners, secrets,\npublishing credentials, or heavyweight build commands. Same-repository PRs,\nworkflow dispatches, and protected branch events can proceed.\n\nIf a repository wants fork PRs to skip rather than fail, it can set:\n\n```yaml\nwith:\n untrusted-policy: skip\n```\n\nDo not set `require-trusted-event: false` for workflows that use self-hosted\nrunners or secrets.\n\nThe build matrix and the workflow control plane are routed independently. The\nmatrix continues to use `runner-preset` and `platforms-json`. Consumers with a\ngoverned runner may also move channel resolution, trust evaluation, contract\nresolution, controller evidence, artifact transfer, and aggregation off the\ndefault GitHub-hosted runner:\n\n```yaml\nwith:\n control-runner-json: '[\"self-hosted\",\"agent-120\"]'\n runner-preset: custom\n platforms-json: '[{\"id\":\"linux-x64\",\"name\":\"Linux x64\",\"runner\":\"[\\\"self-hosted\\\",\\\"agent-120\\\"]\"}]'\n```\n\n`control-runner-json` is additive and defaults to `[\"ubuntu-24.04\"]`. Keep\n`require-trusted-event: true` whenever either runner input selects\n`self-hosted`; a self-hosted control plane must not be exposed to untrusted fork\nevents or arbitrary caller-controlled workflow code.\n\n`require-trusted-event` controls access to build runners. It does not override\nthe publish gate: pull requests remain non-publishing events.\n\n## Fixture\n\n`fixtures/libnode-shaped` is the contract fixture. It has:\n\n- `package.json` version state;\n- `.buildchain/buildchain.toml` with `install`, `build`, `verify`, and `publish`;\n- cross-platform Node scripts that create small `dist/` outputs;\n- `Build Surface Fixture` workflow coverage.\n\nThe fixture proves the reusable surface without running the real libnode native\nbuild."
2042
+ "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: buildchain-reusable-build\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-30\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-30\n invisible_context: not asserted\n---\n\n# Reusable Build Surface\n\nBuildchain v2 provides a reusable build workflow for repositories that need\nBuildchain's release semantics but cannot be described as a simple Node package.\nThe first target shape is `libnode`: expensive native builds, multiple operating\nsystems, self-hosted runner labels, and release artifacts that must be auditable.\n\nFor Linux release artifacts, the build workflow can hand the sealed artifact,\nplatform manifest, and Release Passport to the separate GitHub-hosted keyless\nattester. The compiler runner remains the recorded build identity; the attester\nonly signs and verifies immutable data. See\n[`github-artifact-attestation.md`](github-artifact-attestation.md).\n\n## Automatic Channel Router\n\nThe preferred consumer surface is one reusable workflow call. After v2.12\nreaches the stable major ref, consumers keep this configuration for both alpha\ndevelopment and stable release work:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/build.yml@v3\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n working-directory: .\n artifact-name: libnode\n runner-preset: kungfu-v4-self-hosted\n publish-channel: none\n secrets: inherit\n```\n\n`buildchain-channel` defaults to `auto`. Selection uses this precedence:\n\n1. an explicit `buildchain-ref` protected authority, train, SHA, or official channel;\n2. an explicit `buildchain-channel: alpha|stable`;\n3. `publish-channel: alpha|release|major`;\n4. GitHub release prerelease metadata;\n5. a canonical semver tag;\n6. non-release PR, push, dispatch, schedule, and workflow-run events default to\n alpha.\n\nThe resolved runtime is `vN-alpha` for development and prerelease intent and\n`vN` for stable release intent. Unknown custom publish channels, malformed\nrelease events, and non-semver release-like tags fail before the build matrix;\nthey never guess alpha for a stable release.\n\nThe router automatically selects `.buildchain/alpha-contract-lock.json` for\nalpha and `.buildchain/contract-lock.json` for stable. A repository can override\nthe common path with `buildchain-contract-lock-path`, or override one channel\nwith `buildchain-alpha-contract-lock-path` /\n`buildchain-stable-contract-lock-path`.\n\nOnly repositories changing the default policy need extra routing input:\n\n```yaml\nwith:\n buildchain-channel: stable\n```\n\nDuring the v2.12 prerelease evaluation window, canaries use\n`build.yml@v3-alpha`. The same router then selects `v3-alpha` or stable `v2` as\nthe runtime. Production consumers should adopt `build.yml@v3` after the router\nhas reached stable; this keeps the routing shell itself on a stable ref.\n\nThe router is generated from `.build.yml`'s input/output surface. Run\n`node scripts/generate-channel-build-workflow.mjs` after changing the advanced\nbuild workflow; inventory and unit tests reject a stale generated router.\n\n## Advanced Workflow\n\nConsumers that need direct workflow-shell or runtime control call the advanced\nsurface:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n working-directory: .\n artifact-name: libnode\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n artifact-name-template: \"{artifact}-{platform}-{sha}\"\n artifact-paths: |\n dist\n build/stage\n expected-artifacts-json: >-\n {\"minFiles\":2,\"requiredPaths\":[\"dist/libnode.tar.gz\",\"dist/checksums.txt\"]}\n process-summary-path: .buildchain/diagnostics/process-summary.json\n release-candidate: true\n publish-channel: release\n publish-source-ref: publish-gate/release/v22/v22.22/22.22.3-kf.0\n```\n\n`runner-preset` is the stable first-class surface for known runner fleets:\n\n| Preset | Platforms |\n| ----------------------- | -------------------------------------------------------------------------------------------------------------- |\n| `github-hosted` | `ubuntu-24.04`, `macos-latest`, `windows-2022` |\n| `kungfu-v4-self-hosted` | Kungfu Linux x64, macOS ARM64, and Windows x64 self-hosted runner labels |\n| `kungfu-v4-native` | Kungfu Linux x64, Linux ARM64, macOS ARM64, and Windows x64; Linux ARM64 uses GitHub-hosted `ubuntu-24.04-arm` |\n| `custom` | Requires `platforms-json` |\n\nCallers can still provide a custom matrix with `platforms-json`. Each platform\nobject has:\n\n| Field | Meaning |\n| -------- | ------------------------------------------------- |\n| `id` | Stable artifact/platform key, such as `linux-x64` |\n| `name` | Human-readable job name |\n| `runner` | JSON string passed to `runs-on` after `fromJSON` |\n\nThe runner field is intentionally a JSON string so callers can pass either\nGitHub-hosted runners or multi-label self-hosted runners without Buildchain\nguessing the labels.\n\nOnly include platforms that should run. GitHub schedules matrix jobs before\nsteps execute, so a disabled entry with unavailable runner labels can still\nblock the workflow queue.\n\n`fail-fast` defaults to `false`, preserving the diagnostic behavior that\ncollects every platform result. Required promotion callers can set it to `true`\nto cancel sibling native, container, and relay matrix lanes after the first\nfailure. This input changes scheduling only: it does not reduce the declared\nplatform matrix, turn cancellation into a pass, or alter artifact and release\nadmission.\n\n## Linux Job Containers\n\nLinux build platforms can run inside a digest-pinned job container while macOS\nand Windows keep using native runners. This is the recommended way to remove\nmoving Linux runner prerequisites from Buildchain consumers: the Linux host only\nneeds a GitHub Actions runner, Docker, and network access; common verification\ntools come from the image contract.\n\nUse the Kungfu verification image for lifecycle stages that need Git, jq,\nPython, uv, and fnm, but do not need native compilation:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n runner-preset: kungfu-v4-self-hosted\n linux-container-preset: kungfu-verify\n```\n\n`kungfu-verify` resolves to:\n\n```text\nghcr.io/kungfu-systems/build-images/kungfu-verify@sha256:11f0ba64267ce88174a4f73a9bf833ff4e9c59cd16ec3d08a6432a06c2be6fb1\n```\n\nCallers that own a different Linux image can pass it explicitly:\n\n```yaml\nwith:\n linux-container-preset: custom\n linux-container-image: ghcr.io/example/project-build@sha256:<digest>\n```\n\n`linux-container-image` should be pinned by digest. A floating tag makes the\nrunner surface mutable and weakens Buildchain's release evidence.\n\nThe workflow splits the matrix into two build jobs:\n\n- Linux platforms go to `build-linux-container` when a Linux container is\n configured.\n- All other platforms go to `build-native`.\n\nArtifact names, manifest paths, expected artifact checks, publish-source locks,\nand aggregate summaries are the same in both jobs. The split is an execution\ndetail, not a different artifact contract.\n\n## Native Rust Toolchains\n\nNative lifecycle jobs can request an isolated Rust installation instead of\ndepending on a self-hosted runner user's PATH:\n\n```yaml\nwith:\n setup-rust: true\n rust-toolchain: \"1.96.0\"\n rustup-dist-server: \"https://rsproxy.cn\"\n rustup-update-root: \"https://rsproxy.cn/rustup\"\n cargo-registry-index: ${{ vars.BUILDCHAIN_CARGO_REGISTRY_INDEX }}\n```\n\n`setup-rust` defaults to `false`, so existing consumers are unchanged. When it\nis enabled, Buildchain installs `rust-toolchain` before the install, build, and\nverify lifecycle stages on every native matrix platform. Windows uses the\nofficial rustup bootstrap through `cmd.exe` and `curl.exe` into runner-temporary\nCargo and rustup homes, so it works under a restrictive PowerShell execution\npolicy and the service account does not depend on another user's PATH or mutate\nhost toolchain state. Pin an exact toolchain for release builds. Linux container jobs continue\nto obtain Rust from their digest-pinned image contract; Buildchain does not\nmutate that container surface.\n\nThe rustup server inputs are optional and default to Rust's official servers.\nConsumers behind a slow cross-border link may select a trusted transport mirror;\nrustup still verifies the selected toolchain's distribution metadata and\ncomponent checksums.\n\n`cargo-registry-index` is also optional. When set, Buildchain exposes it to\nCargo as `CARGO_REGISTRIES_CRATES_IO_INDEX` for every native lifecycle stage,\nso a self-hosted runner can use a repository or organization variable without\ncommitting private LAN topology to public workflow YAML. The endpoint must be a\ncrates.io-compatible index whose `config.json` download contract serves the\nmatching checksum-verified crate archives. An empty value preserves Cargo's\nnormal crates.io behavior.\n\nThe container image provides `fnm` but does not preinstall Node. Buildchain uses\n`fnm` inside the container to install the requested `node-version` before it\nruns Buildchain runtime scripts or lifecycle actions.\n\nDo not use `kungfu-verify` for stages that need CMake, Ninja, ccache, Conan, or\nDocker image publishing. Those should use a heavier native-build image or remain\non a host runner until their image contract is explicit.\n\n## Buildchain Runtime Override\n\nStable consumers should keep the reusable workflow pinned to stable refs such as\n`@v3`. The optional `buildchain-ref` input is empty by default; empty means\nBuildchain resolves and executes the stable runtime selected by the workflow\nshell. The full train validation protocol is documented in\n[`runtime-train-validation.md`](runtime-train-validation.md).\n\nFor one-off manual validation, a trusted maintainer can run the caller workflow\nwith a temporary runtime override:\n\n```yaml\non:\n workflow_dispatch:\n inputs:\n buildchain-ref:\n description: \"Temporary Buildchain runtime ref for trusted manual validation\"\n required: false\n default: \"\"\n\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n buildchain-ref: ${{ inputs.buildchain-ref || '' }}\n```\n\nAllowed override refs are deliberately narrow:\n\n| Ref form | Meaning |\n| --------------------------------------- | ------------------------------------------------------ |\n| `train/v3/v3.0/<capability>` | Temporary capability train under the active minor line |\n| `refs/heads/train/v3/v3.0/<capability>` | Explicit branch ref for the same train |\n| `<40-character SHA>` | Exact immutable Buildchain runtime commit |\n\nOverride requests fail closed unless the event is `workflow_dispatch` and the\nactor has write, maintain, or admin permission on the caller repository.\nPull requests, including same-repository pull requests and fork-originated pull\nrequests, cannot use `buildchain-ref` override. This keeps automated PR builds on\nthe stable runtime surface.\n\nEvery run resolves the runtime ref to an immutable SHA before checkout. The job\nsummary and aggregate build summary record the workflow shell ref, requested\nruntime ref, resolved runtime ref, runtime SHA, stability class, trust decision,\nand rollback ref. Train refs are development validation refs: they do not move\n`v2`, `vX.Y`, `vX.Y-alpha`, npm dist-tags, or production release refs, and they\nmust not be pinned as long-term production dependencies.\n\nRuntime override validates Buildchain runtime scripts, CLI code, local actions,\nconfig parsing, and lifecycle behavior. It cannot validate changes that require\nthe outer reusable workflow YAML itself to change, such as new jobs,\npermissions, workflow outputs, or matrix topology. Those changes need a canary\nworkflow path or a temporary explicit workflow ref.\n\n## Floating Ref Contract Lock\n\nStable consumers should use floating major refs such as `@v3`, but a floating\nref is not blind trust. Each released Buildchain ref carries a package-owned\nruntime contract world in `dist/site/buildchain-contract.json`. Consumers may\nkeep a small lock file, `.buildchain/contract-lock.json`, recording the\nBuildchain ref, resolved SHA, contract digest, compatibility digest, accepted\nmajor line, and compatibility policy they reviewed.\n\nThe reusable build trust gate checks this lock before any heavy matrix job:\n\n1. resolve the Buildchain runtime ref, for example `v2`, to an immutable SHA;\n2. read `dist/site/buildchain-contract.json` from that checked-out Buildchain\n ref;\n3. read the consumer's `.buildchain/contract-lock.json`;\n4. compare the accepted contract with the current contract.\n\nSHA drift alone is not a failure. `v2` is expected to advance. Buildchain only\nfails fast when the accepted contract is no longer compatible, for example a\nrequired input is removed, a required output disappears, a protected behavior\npromise changes, or the major line changes. Additive changes such as optional\ninputs, optional outputs, diagnostics, or documentation updates continue under\nthe default `major-compatible` policy.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n buildchain-contract-lock-path: .buildchain/contract-lock.json\n buildchain-contract-compatibility-policy: major-compatible\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\nWhen compatible drift is detected, the build continues and Buildchain opens or\nupdates a low-priority issue in the consumer repository. The issue records the\nold SHA/digest, new SHA/digest, compatibility result, workflow run, and the next\naction: review the Buildchain release notes and update the lock. When breaking\ndrift is detected, the same issue path is used, but the trust gate fails before\nmatrix build or publish work starts. If the workflow token cannot write issues,\nBuildchain writes a copyable issue body into the job summary.\n\nThe lock is intentionally small. It does not copy the full contract. The full\ncontract remains in the Buildchain ref and package; the consumer records only\nwhat it accepted and the policy used to compare future floating-ref movement.\n\nAdvanced alpha-channel consumers select the matching workflow shell:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3-alpha\n with:\n buildchain-contract-lock-path: .buildchain/contract-lock.json\n```\n\nThe runtime follows the called workflow through `job.workflow_ref`. Callers may\nalso pass `buildchain-ref: v3-alpha` explicitly; official floating refs are\nordinary channel selections and are allowed on pull requests and pushes. Train\nrefs and exact SHAs remain trusted manual overrides.\n\n## Shifu Cache Profile Passthrough\n\nBuildchain can carry one trusted Shifu cache-profile reference and its exact\ndigest into lifecycle execution. Its contract is an opaque reference and digest\nonly. This surface is deliberately opaque:\nBuildchain does not fetch the profile, parse JSON, select cache services,\nrewrite bindings, decide fallback, or emit Shifu resolution evidence. Those\nsemantics remain owned by the consumer's pinned Shifu implementation.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/build.yml@v3\n with:\n shifu-cache-profile-ref: ${{ vars.SHIFU_CACHE_PROFILE_REF }}\n shifu-cache-profile-digest: ${{ vars.SHIFU_CACHE_PROFILE_DIGEST }}\n```\n\nThe reusable workflow passes the pair as `SHIFU_CACHE_PROFILE_REF` and\n`SHIFU_CACHE_PROFILE_DIGEST` to install, build, and verify lifecycle commands.\nThe consumer must invoke its Shifu cache-aware execution surface. An empty pair\npreserves existing behavior; a consumer Shifu should fail closed when exactly\none value is present or the resolved bytes do not match the expected digest.\n\nUse trusted repository or organization variables rather than PR-controlled\nfiles for private/LAN references. The variables must remain secret-free; any\ncredentials use a separate provider-approved secret surface and must not be\nembedded in the profile reference. This passthrough is separate from\nBuildchain's locked source checkout cache below: Buildchain owns checkout\ntransport and source identity, while Shifu owns post-checkout execution cache\nbindings and receipts.\n\n## Locked Source Checkout Cache\n\nSelf-hosted runners that build large repositories can opt into a locked checkout\ncache for both the consumer source and the Buildchain runtime. This changes only\nthe Git object transport. Buildchain still resolves `publish-source-sha` and the\nruntime SHA before any build runner starts, checks out those exact commits, and\nverifies each final `HEAD` plus the resolved consumer source tree SHA before\nlifecycle commands run.\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n runner-preset: kungfu-v4-self-hosted\n checkout-cache-mode: auto\n checkout-cache-mirror-url-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE }}\n checkout-cache-reference-repository-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE }}\n checkout-cache-fallback: github\n checkout-cache-timeout-seconds: 60\n checkout-cache-github-timeout-seconds: 600\n checkout-cache-fetch-attempts: 3\n```\n\n`checkout-cache-mode` accepts:\n\n| Mode | Behavior |\n| --------- | ----------------------------------------------------------------------------------------------------------- |\n| `off` | Default. Buildchain fetches the locked commit from GitHub. |\n| `auto` | Try the trusted cache first; on miss, record the miss and fall back according to `checkout-cache-fallback`. |\n| `require` | Require the cache to provide the locked commit and fail before lifecycle work if unavailable. |\n\nThe cache can be a local/LAN mirror URL template or a runner-local bare\nreference repository template. Templates support `{owner}`, `{repo}`,\n`{repository}`, `{repositorySlug}`, and `{sha}`. The workflow also reads\nrepository or organization variables named\n`BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE` and\n`BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE`, so consumers can keep\nprivate LAN topology out of repository YAML.\n\nThe GitHub-hosted trust gate resolves the reusable workflow shell to an exact\ncommit and uploads that shell's small checkout bootstrap script. Native and\nLinux-container build jobs download the bootstrap, then use the same cache\npolicy to obtain both the selected Buildchain runtime and consumer source at\ntheir already resolved immutable SHAs. Keeping the bootstrap owned by the\nworkflow shell is important when `@vN-alpha` routes a stable release to an older\n`vN` runtime: the stable runtime does not need to already contain the newest\ncheckout transport implementation. This also prevents a large direct\n`actions/checkout` runtime clone from becoming a separate timeout path on\nconstrained self-hosted uplinks. The bootstrap artifact does not contain the\nruntime repository and cannot move either selected ref.\n\nDo not read cache URLs or reference paths from PR-controlled files such as\n`.buildchain/buildchain.toml`. These values are trusted workflow inputs or repo/org\nvariables. Buildchain does not pass GitHub credentials to cache mirrors or\nreference repositories. If it must fall back to GitHub, the workflow token is\nused only for the GitHub fetch path. Cache attempts use\n`checkout-cache-timeout-seconds`; the potentially larger GitHub fallback uses\nthe independent `checkout-cache-github-timeout-seconds` budget (600 seconds by\ndefault). Buildchain fetches the advertised source ref before trying an exact\nSHA, so a cache hit or stale-cache seed can contribute objects and the fallback\ndoes not first waste a full timeout on an unadvertised SHA. Retryable timeout\nand transient network failures use the bounded `checkout-cache-fetch-attempts`\nbudget; permanent failures stop immediately. Diagnostics record both timeout\nbudgets and the actual GitHub fetch attempts before exact HEAD/tree\nverification.\n\nEach platform diagnostics artifact includes `source-checkout.json` and embeds a\ncompact `sourceCheckout` summary in `diagnostics.json`: mode, transport,\nhit/miss, fallback reason, duration, final HEAD verification, and tree\nverification. Remote URLs are sanitized and local reference paths are represented\nby a short display name plus fingerprint, not by secret-bearing credentials.\nRuntime checkout evidence is uploaded separately as `runtime-checkout.json`,\nincluding cache transport, fallback attempts, and exact runtime `HEAD`\nverification, even when a later lifecycle step fails.\n\nWhen a Buildchain maintainer asks for downstream validation, the expected\nrequest is:\n\n```text\nBuildchain train ready: buildchain-ref=train/v3/v3.0/<capability>.\nKeep uses: ...@v3; run workflow_dispatch with that buildchain-ref and report the runtime evidence summary.\n```\n\nAfter validation succeeds, the Buildchain change should continue through the\nnormal mainline and release path. Do not treat the train as a pending merge\nitem; it is only a temporary fast-use, diagnostic, and rollback channel. It may\nremain for a retention window after release, with old trains handled by a\nseparate periodic cleanup task.\n\n## Workflow Outputs\n\nThe reusable workflow exposes the resolved contract:\n\n| Output | Meaning |\n| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |\n| `runner-preset` | Resolved preset, or `custom` when `platforms-json` was provided |\n| `platforms-json` | Exact matrix JSON used by the build job |\n| `platform-count` | Number of matrix platforms |\n| `linux-container-enabled` | `true` when Linux platforms are routed through a job container |\n| `linux-container-image` | Resolved digest-pinned Linux job container image |\n| `build-summary-artifact` | Uploaded aggregate summary artifact name |\n| `build-diagnostics-summary-artifact` | Uploaded aggregate diagnostics summary artifact name |\n| `release-candidate-passport-artifact` | Uploaded PR-stage release-candidate passport artifact name when `release-candidate` is enabled |\n| `release-candidate-passport-json` | Compact release-candidate passport JSON when `release-candidate` is enabled |\n| `build-summary-json` | Compact aggregate JSON with platform count, file count, and byte total |\n| `build-diagnostics-summary-json` | Compact aggregate diagnostics JSON with platform, lifecycle warning/error, diagnostics contract warning, and sidecar manifest warning totals |\n| `trusted-event` | `true` when the event is trusted enough to reach build runners |\n| `buildchain-runtime-ref` | Runtime ref selected after applying the empty-default or override policy |\n| `buildchain-runtime-sha` | Immutable Buildchain runtime commit used by all runtime checkouts |\n| `buildchain-runtime-class` | `stable`, `alpha`, `authority`, `train`, `exact-sha`, or `development` |\n| `buildchain-runtime-override` | `true` when an authority, train, or exact-SHA `buildchain-ref` override was accepted |\n| `buildchain-runtime-trust-decision` | Runtime override trust decision |\n| `buildchain-contract-lock-status` | `unchanged`, `compatible-drift`, `breaking-drift`, `missing-lock`, `non-floating-runtime`, or first-release `runtime-contract-unavailable` |\n| `buildchain-contract-lock-drift` | `true` when the floating runtime SHA or contract digest changed |\n| `buildchain-contract-digest` | Current Buildchain runtime contract digest |\n| `publish-channel` | Resolved publish channel requested by the caller |\n| `publish-allowed` | `true` only when this event/ref may publish after verification |\n| `publish-reason` | Human-readable reason for the publish gate decision |\n| `publish-source-ref` | Gate source ref that was resolved before checkout |\n| `publish-source-sha` | Exact source commit used by checkout, build, verify, and artifacts |\n| `publish-source-locked` | `true` when a `publish-gate/*` source ref was explicitly locked |\n| `publish-source-channel` | `alpha`, `release`, `anchor`, or `major` parsed from the source ref |\n| `publish-source-line` | Product line parsed from source refs such as `v22/v22.22` |\n| `publish-source-consumer-version` | Consumer package version parsed from source refs |\n| `release-manifest-json` | Resolved release manifest including source lock, version state, and anchor data |\n\nThe aggregate summaries are intentionally artifacts as well as outputs. GitHub\nActions matrix outputs are not a reliable place to carry every platform's full\nmanifest, so Buildchain uploads each platform manifest and then emits one\naggregate build summary artifact after the matrix completes. Buildchain uploads\n`diagnostics-summary.json` as a separate aggregate diagnostics summary artifact,\na compact rollup of each platform's small diagnostics upload. The rollup keeps\nper-platform runner facts, checked tool versions/missing tools, package\nmanager/cache directory details, compiler-cache availability, lifecycle timing,\nprocess sampler context, and links back to the exact platform artifacts. Each\nplatform diagnostics upload includes `diagnostics.json`,\n`diagnostics-manifest.json`, the lifecycle `events.jsonl`, and process sampler\nsidecars when enabled, so slow-build diagnosis does not require downloading the\nbinary platform artifact or the aggregate build summary. The sidecar manifest\nrecords the uploaded diagnostics files with bytes and sha256 hashes. Each\n`diagnostics.json` also records the related binary artifact name, manifest\nartifact name, diagnostics artifact name, diagnostics sidecar manifest path, and\nplatform id in `links`, so a reviewer can navigate from the small diagnostics\nartifact back to the exact platform outputs when deeper inspection is needed.\nThe workflow output `build-diagnostics-summary-json` includes\n`diagnosticsContractWarningCount` and `diagnosticsManifestWarningCount` so\nrelease jobs can detect drifting diagnostics JSON contracts and missing or\ndrifting diagnostics sidecar manifests without downloading the per-platform\ndiagnostics artifacts first.\n\n## Artifact Signing Authority\n\nArtifact signing is a Buildchain capability, not a macOS application workflow.\nConsumers declare desired signature state next to their artifact facts; they do\nnot configure certificates, Team IDs, notary credentials, protected\nenvironments, authority roles, or signing jobs:\n\n```toml\n[[signing.artifacts]]\nid = \"native-engine\"\npath = \"dist/kungfu-engine\"\nprofile = \"auto\"\nkind = \"mach-o\"\nplatforms = [\"macos-arm64\", \"macos-x64\"]\n```\n\nEvery native and container build lane reads this declaration after the build\nlifecycle and before verification. Buildchain binds the exact artifact bytes or directory tree to\nthe caller repository, source commit, source tree, immutable runtime, platform,\nand requested signature semantics, then publishes a deterministic\n`<artifact>-signing-request-<platform>-<source-sha>` request. No consumer\nworkflow step is required.\n\nThe request root is a Buildchain-owned generated output. After the declaration,\nlifecycle manifest, and source paths pass validation, sealing replaces that root\nbefore materializing the current request set. This keeps repeated jobs on a\nself-hosted runner idempotent and prevents stale requests from an earlier run\nfrom entering the uploaded request artifact. An output root that contains the\nworkspace, working directory, lifecycle manifest, or any declared subject is\nrejected before cleanup.\n\nSelf-hosted runners whose network requires different routes for Artifact upload\nand download can scope an upload-only proxy bypass to the sealed signing request:\n\n```yaml\nwith:\n artifact-signing-request-upload-no-proxy: \".blob.core.windows.net\"\n```\n\nThe caller repository variable\n`BUILDCHAIN_ARTIFACT_SIGNING_REQUEST_UPLOAD_NO_PROXY` provides the same value\nwithout changing a consumer workflow; an explicit workflow input takes\nprecedence. When neither is set, Buildchain preserves the runner's existing\n`NO_PROXY` and `no_proxy` values. The resolved value applies only to the\nBuildchain-owned signing-request upload. Authority dispatch and immutable\nsigned-result download keep the runner's original proxy route. This is a\ntransport control only: it does not change request bytes, signing authority,\nartifact identity, or verification policy.\n\n`profile = \"auto\"` resolves signable Apple artifacts such as Mach-O files,\n`.dylib`, `.framework`, `.app`, `.xpc`, `.plugin`, `.pkg`, `.dmg`, and macOS\narchives containing native code to the native `apple-developer-id` provider.\nFor a declared macOS `archive`, the authority safely extracts the sealed\ncontainer, signs and verifies every Mach-O payload, signs Mach-O payloads inside\nembedded Python wheels, rebuilds each affected wheel's PEP 427 `RECORD`, and\nrecreates the original zip or tar.gz before returning the exact final bytes.\nWindows `pe` and `binary` artifacts\nresolve to timestamped native `windows-authenticode`; Windows PE never falls\nback to a detached signature. Linux and other non-native binary files,\narchives, blobs, and directories resolve to `detached-signature-v1`. Buildchain records that as a\ndetached cryptographic signature and never misrepresents it as an operating\nsystem code signature. Explicit incompatible provider/kind/platform\ncombinations fail closed.\n\nThe request schema rejects credential and authority-infrastructure fields. The\nBuildchain-owned signing authority is responsible for credential selection,\nnative signing, notarization where applicable, immutable result delivery, and a\nreceipt bound to the request digest, runtime SHA, output digest, and signature\nevidence. Consumer repositories neither receive nor duplicate credential-island\nmaterial. The reusable workflow dispatches the sealed request to the\nBuildchain repository, waits for its protected authority workflow, verifies the\nimmutable result, replaces only the declared artifact with the returned final\nbytes. The ordinary platform lane completes the consumer's functional\nverification before delegation. A GitHub-hosted finalization lane then verifies\nthe authority result against the sealed request, imports the exact signed bytes,\nand recomputes the final manifest before replacing the deterministic artifact.\nThe signing result is never downloaded back to a self-hosted native runner.\nPlatform manifests, KFD evidence, checksums, and Release Passport inputs\ntherefore observe the final signed artifact rather than the pre-signing build\noutput.\n\nFor a standalone Mach-O request, the authority requires strict Developer ID\nverification, the declared Team ID, hardened runtime, and an `Accepted`\n`notarytool` result for the exact submission. Apple creates the notarization\nticket for that binary and publishes it online, but\n[standalone binaries do not support stapling](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow).\nBuildchain therefore records\n`standalone-notary-ticket-online` and does not misapply app-bundle\n`spctl --assess --type execute` semantics to the raw executable.\n\nFor a compound archive request, the authority notarizes the complete extracted\nsigned product tree and records `compound-notary-ticket-online`. A generic\narchive container cannot carry a stapled ticket and is not itself a Gatekeeper\nexecution target; Gatekeeper evaluates the extracted signed code. Archive path\nand symlink validation fail closed before any payload is signed.\n\nThe durable v3 authority runtime is\n`authority/v3/v3.0/artifact-signing`. It is channel-neutral: alpha and stable\nrelease work use the same protected `buildchain-artifact-signing` environment\nand provider identities. The authority ref is protected independently from\nrelease channels and can advance only through reviewed, checked changes; the\ntemporary `train/v3/v3.0/artifact-signing-authority` ref is retained only as a\nbounded migration rollback.\n\nThe older `credential-island-macos-*` reusable-workflow inputs remain a\ncompatibility surface while existing callers migrate. They are not the target\nconsumer contract and must not be used to design new integrations.\n\n## Artifact Transfer Relay\n\nBy default, platform jobs upload payloads, manifests, and diagnostics directly\nto GitHub artifacts:\n\n```yaml\nwith:\n artifact-transfer-mode: github-artifacts\n artifact-compression-level: 0\n```\n\nDirect GitHub Artifact payloads default to compression level `0`. Buildchain\nartifacts are commonly already-compressed archives; storing them without a\nsecond compression pass shortens the upload window while preserving the same\nartifact name, run/id/digest binding, retention, and no-overwrite behavior.\nCallers may select `1` through `9` for payloads that materially benefit from\ncompression. Manifests and diagnostics retain their existing small-artifact\nbehavior.\n\nLarge self-hosted native builds can opt into the first-class S3 relay path:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n runner-preset: kungfu-v4-self-hosted\n artifact-transfer-mode: s3-to-github-artifacts\n artifact-relay-s3-bucket: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET }}\n artifact-relay-s3-region: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_REGION }}\n artifact-relay-s3-prefix: ${{ vars.BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX }}\n```\n\nIn relay mode, each self-hosted platform job uploads the heavy payload files to\nS3 and uploads only a small `relay-manifest.json` to GitHub. A GitHub-hosted\n`relay-artifacts` job then assumes the configured download role, downloads the\npayloads from S3, verifies every file by SHA256, and re-uploads the normal\nGitHub artifacts under the same artifact names that direct mode uses. Downstream\nsummary, release-candidate, and promote-only workflows therefore continue to\nconsume GitHub artifacts and do not need custom S3 logic.\nThe relay implementation uses Node.js plus the standard AWS environment\ncredentials from GitHub OIDC; runner images and build containers do not need the\nAWS CLI installed.\n\nAfter the GitHub artifact uploads succeed, Buildchain deletes the S3 objects\nlisted in the relay manifest for that platform. If any download, verification,\nor GitHub artifact upload fails, cleanup is skipped so maintainers can inspect\nthe retained S3 payload. Configure a short bucket lifecycle expiration as a\ncost and cleanup backstop.\n\nThe relay configuration is intentionally generic. Buildchain does not hard-code\norganization buckets, regions, or role ARNs. Callers may pass explicit inputs,\nor set repository/organization variables and secrets using these names:\n\n| Variable or secret | Meaning |\n| ------------------------------------------------ | ------------------------------------------------------- |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_BUCKET` | Relay bucket name |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_REGION` | Relay bucket region |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_PREFIX` | Relay object prefix; defaults to `buildchain-artifacts` |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_ROLE_ARN` | Shared OIDC role ARN for upload and download |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_UPLOAD_ROLE_ARN` | Upload OIDC role ARN for self-hosted build jobs |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_DOWNLOAD_ROLE_ARN` | Download OIDC role ARN for the GitHub-hosted relay job |\n| `BUILDCHAIN_ARTIFACT_RELAY_S3_OIDC_AUDIENCE` | Optional OIDC audience override |\n\nFor AWS China regions, Buildchain defaults the OIDC audience to\n`sts.amazonaws.com.cn`; other regions default to `sts.amazonaws.com`. The caller\nworkflow must allow `id-token: write`, and the target role trust policy should\nrestrict GitHub OIDC claims to the expected organization, repository, workflow,\nand branch/ref. The S3 permissions should be scoped to the relay bucket/prefix\nused by the repository.\nUpload roles need write/delete access under the relay prefix; download roles\nneed read access plus delete access for successful cleanup.\n\nRelay mode is opt-in and does not affect forks or open-source users that do not\nconfigure S3. Missing bucket, region, upload role, or download role values fail\nbefore the heavy build matrix is scheduled. Buildchain treats S3 as a transport\ncache, not as the final release evidence store; the final audit entry remains\nthe GitHub artifact set plus the Buildchain build summary and release-candidate\npassport.\n\nSet `release-candidate: true` when the successful reusable build is meant to be\nthe artifact source promoted later. Buildchain then uploads\n`release-candidate-passport.json` under the\n`<artifact-name>-release-candidate-<publish-source-sha>` artifact name. Promotion\njobs can pass that passport to `promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` so source, channel, platforms, and the\naggregate build-summary hash are checked before publish-gate side effects. The\npassport records the locked commit's Git tree SHA, so a post-merge channel HEAD\ncan be accepted only when it is tree-equivalent to the PR-stage build evidence.\n\n## Publish Gate\n\nBuildchain separates \"may build/verify\" from \"may publish.\" A same-repository\npull request may be trusted enough to run the build matrix, but it still must\nnot publish packages, S3 objects, release pages, or preview aliases. Publishing\nis allowed only when the caller explicitly requests a channel and the current\nevent/ref matches that channel.\n\nUse `publish-channel` to request a channel:\n\n```yaml\njobs:\n build:\n uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3\n with:\n publish-channel: release\n\n publish:\n needs: build\n if: ${{ needs.build.outputs.publish-allowed == 'true' }}\n runs-on: ubuntu-24.04\n steps:\n - run: ./scripts/publish.sh\n```\n\nDefault channels are:\n\n| Channel | Allowed refs |\n| --------- | ---------------------------------------------------------------------------------------------------- |\n| `none` | Never publishes; this is the default |\n| `alpha` | `alpha/vN/vN.M` branches or exact `vN.M.P-alpha.K` tags |\n| `release` | `release/vN/vN.M` branches or release tags such as `vN.M.P`, `vN.M`, `vN` |\n| `major` | `publish-gate/major`, legacy `major-gate`, or next-major release tags such as `vN.0.0`, `vN.0`, `vN` |\n\nPull request events always produce `publish-allowed=false`, even when the PR is\nfrom the same repository. Untrusted fork events also produce\n`publish-allowed=false`; with the default `untrusted-policy: fail`, the workflow\nthen fails before any build runner starts.\n\nProjects with their own channel names can pass `publish-refs-json`:\n\n```yaml\nwith:\n publish-channel: nightly\n publish-refs-json: >-\n {\"nightly\":[\"^refs/heads/nightly/v\\\\d+$\"]}\n```\n\nThe aggregate build summary includes the same publish gate decision under\n`publishGate`, so a downloaded artifact summary explains both what was built and\nwhy it was or was not eligible to publish.\n\n## Publish Source Lock\n\n`publish-channel` answers \"may this event publish?\" Source lock answers \"which\nsource tree is the publish decision about?\" A caller can pass `publish-source-ref`\nto bind a publish run to a reviewed gate branch before any checkout happens:\n\n| Ref | Meaning |\n| ------------------------------------------------ | --------------------------------------------------------------------------- |\n| `publish-gate/alpha/<line>/<consumer-version>` | Build and publish an alpha candidate for a consumer line |\n| `publish-gate/release/<line>/<consumer-version>` | Build and publish a production candidate for a consumer line |\n| `publish-gate/anchor` | Resolve an explicit anchor request; it does not publish artifacts by itself |\n| `publish-gate/major` | Gate the next major source state |\n| `major-gate` | Legacy compatibility alias for the major gate |\n\nFor alpha and release refs, `<line>` is intentionally allowed to contain `/`, so\nKungfu-style lines such as `v22/v22.22` stay readable. The final path segment is\nthe consumer-visible version, for example `22.22.3-kf.0`.\n\nThe reusable workflow resolves the branch tip to `publish-source-sha`, checks out\nthat SHA in every build job, and uses the same SHA in artifact names, manifests,\nand aggregate summaries. Reruns therefore rebuild the same source tree even if a\ngate branch moves later.\n\nBefore any heavy build matrix is scheduled, the workflow also verifies that the\ntarget channel ref implied by the source lock already points at\n`publish-source-sha` and that the target channel HEAD came from the required\nmerged same-repository channel PR. `publish-gate/alpha/<line>/<version>` must\nmatch `alpha/<line>` and have PR lineage `dev/<line> -> alpha/<line>`;\n`publish-gate/release/<line>/<version>` must match `release/<line>` and have PR\nlineage `alpha/<line> -> release/<line>`. If either check fails, the run fails\nfast with a diagnostic telling maintainers to merge the source commit through\nthe channel PR first. This keeps verify from spending runner time on a source\ntree that cannot legally enter the requested publish channel.\n\nThe resolved release manifest is uploaded as an artifact and emitted as\n`release-manifest-json`. It records:\n\n- source ref, source SHA, channel, line, and consumer version;\n- configured version strategy and configured version-state files;\n- each version file's value, with release gates failing closed if the configured\n files do not equal the consumer version;\n- anchor manifest summary for anchored/manual projects;\n- explicit anchor request JSON for `publish-gate/anchor`;\n- publish registry, dist-tag, and gate visibility metadata.\n\nPublish side-effect jobs should verify the lock immediately before publishing:\n\n```yaml\n- name: Verify publish gate did not move\n run: node .buildchain/runtime/scripts/verify-publish-source-lock.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nIf the branch tip no longer matches the manifest SHA, the publish job must fail\nclosed. Moving a gate branch creates a new publish decision and should produce a\nnew build run.\n\n## Release Candidate Promote-Only\n\nFor native package sets, the PR build is the only heavy build. When a PR targets\n`alpha/<line>` or `release/<line>`, `.build.yml` uploads a release-candidate\nbundle next to the platform artifacts. The bundle contains:\n\n- `release-candidate.passport.json`;\n- the aggregate `build-summary.json`;\n- copied platform manifest evidence for the built platforms.\n\nThe passport records two separate source identities:\n\n- `builtSourceSha` / `builtSourceTreeSha`: the PR-stage source that produced the\n artifacts, usually the PR merge ref;\n- `promotionChannelSha` / `promotionChannelTreeSha`: the post-merge channel\n commit used for publish authority.\n\nThe reusable promote wrapper resolves the merged PR, finds exactly one matching\nPR-stage release-candidate artifact, downloads it with the build summary and\npayload artifacts from the same PR-stage run, validates the payload count,\ncompares the built tree with the promotion channel tree, locks\n`publish-gate/{alpha,release,major}` to the promotion channel commit, and then\ncalls `actions/promote-buildchain-ref` with\n`promote-only-release-candidate: \"true\"` and\n`require-publish-source-lock: \"true\"`. The wrapper passes the created\n`publish-gate/*` ref, target SHA, and `locked=true` into the promote action, so\nfloating `@v3` consumers receive publish-side source-lock drift protection by\ndefault. It also defaults `branch-protection-bypass-apps` to `github-actions`\nso the workflow automation can apply generated version-state and channel\nbookkeeping on protected `dev`/`alpha`/`release` branches after the reviewed\nchannel PR has merged. Other App slugs and all user or team bypass actors are\nrejected. The wrapper uses the run-scoped `github.token` as the generated ref\nupdate token for protected bookkeeping PATCH calls, so the exact GitHub Actions\nApp authority can sync dev immediately after alpha/release publish without a\npost-publish PR.\nIt does not call `.build.yml`, does not create a matrix, and must fail before\npublish if the RC evidence, payload set, or source-lock ref is missing or\nambiguous.\n\nThe public `release-candidate-promote.yml` is a generated channel router. It\nderives the publication lane from `target-ref`, then selects the matching\nadvanced workflow shell, runtime, and consumer lock before the advanced\npromotion starts:\n\n- alpha targets use `.release-candidate-promote.yml@vN-alpha`, runtime\n `vN-alpha`, and `buildchain-alpha-contract-lock-path`;\n- release and major targets use `.release-candidate-promote.yml@vN`, runtime\n `vN`, and `buildchain-stable-contract-lock-path`.\n\nThe generated router also owns the stable-shell layout transition through\n`.buildchain/promotion-shell-routing.json`. Stable `v2.14.13` contains the hidden\nadvanced workflow, so the stable lane calls that workflow at the exact immutable\nSHA behind the released `v2` state and forwards the complete internal promotion\nidentity surface. The logical shell identity remains `vN`, and the router\nretains it in the public audit outputs. The internal advanced-shell call receives\nthe exact call ref selected by the routing configuration, so its called-workflow\nref check and checkout SHA both bind to the same immutable identity. Updating the\nrouting pin after a stable release does not require any consumer declaration\nchange.\n\nThe router resolves immutable SHAs and the selected lock digest before candidate\ndownload. The advanced shell verifies the same router, shell, runtime, lock,\nchannel, and target binding again. Train and exact-SHA runtime overrides remain\nrestricted to trusted `workflow_dispatch` actors with write, maintain, or admin\npermission. Promotion controller evidence, the promotion copy of the release\ncandidate passport, and the final release passport record these identities.\n\n```yaml\njobs:\n promote:\n uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v3\n secrets:\n buildchain-issue-app-id: ${{ secrets.BUILDCHAIN_ISSUE_APP_ID }}\n buildchain-issue-app-private-key: ${{ secrets.BUILDCHAIN_ISSUE_APP_PRIVATE_KEY }}\n with:\n buildchain-channel: auto\n buildchain-alpha-contract-lock-path: .buildchain/alpha-contract-lock.json\n buildchain-stable-contract-lock-path: .buildchain/contract-lock.json\n channel: alpha\n target-ref: alpha/v22/v22.22\n artifact-name: libnode\n # Defaults to build.yml / Build. Override only when the PR-stage build\n # workflow uses a different file or display name.\n release-candidate-workflow-file: build.yml\n release-candidate-workflow-name: Build\n package-manager: npm\n publish-target: npm\n runner-preset: github-hosted\n trusted-publishing: true\n github-release: true\n required-status-check: check / check\n required-artifact-count: 3\n publish-dist-tag: alpha\n publish-package-set-order: platforms-first-main-last\n publish-package-main: \"@kungfu-tech/libnode\"\n release-passport-product-name: Libnode\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n```\n\nExisting callers may keep `buildchain-contract-lock-path`; a non-empty explicit\npath overrides channel-specific selection for compatibility. Migration only\nrequires adding the two channel lock inputs and may retain the remaining common\npromotion declaration unchanged. Consumers must not call the dot-prefixed\nadvanced workflow directly.\n\n`buildchain-issue-app-id` and `buildchain-issue-app-private-key` are optional\nbut recommended for cross-repository consumers. They should identify a GitHub\nApp installation with `issues: write` on `kungfu-systems/buildchain`; the\nwrapper mints the installation token before calling\n`actions/report-buildchain-issue`. Consumers can also pass a pre-minted\n`buildchain-issue-token`. If both are omitted, the wrapper falls back to\n`BUILDCHAIN_ISSUE_TOKEN`, `BUILDCHAIN_PROMOTION_TOKEN`, and then the consumer\nworkflow's `github.token`; the last fallback can only report issues when it has\nwrite access to the target Buildchain repository.\n\n`publish-required-artifacts-json` can still be passed explicitly for custom\npublish targets. Custom OCI requirements may omit pre-publish refs and digests;\nthe action resolves the exact version ref and validates final digests and any\nbuilt/reused provenance after `lifecycle.publish`. For the default\n`publish-artifact-kind: npm` path, consumers do\nnot download artifacts or run repository scripts to build publish evidence. The\nwrapper downloads the PR-stage payload artifacts, finds the downloaded `.tgz`\npackages, reads each tarball's `package/package.json` for the real scoped\npackage name and version, computes the npm `sha512-...` integrity from the\ntarball bytes, marks the package matching `publish-package-main` as `role:\nmain`, marks the rest as `role: platform`, and passes the generated\n`publish-required-artifacts-json` to `promote-buildchain-ref` before any publish\nside effect. Downloaded platform manifests are still passed into the release\npassport unless `release-passport-platform-manifest-paths` is set explicitly.\nThe same Buildchain contract lock check runs before release-candidate\nresolution and before publish. A compatible `v2` drift leaves an issue in the\nconsumer repository but does not trigger a second heavy build; an incompatible\ndrift fails before publish side effects.\n\nThe wrapper publishes the public release tag as a GitHub Release by default.\nAfter `promote-buildchain-ref` reports a complete release transaction, the\nwrapper creates or updates the public release, marks semver prerelease tags\nsuch as `v1.2.3-alpha.0`, `v1.2.3-rc.1`, or `v22.22.3-kf.3-alpha.7` as\n`prerelease=true` and `make_latest=false`, marks stable semver tags as latest,\nand uploads the publish evidence file plus every file in the generated release\npassport directory, including `buildchain.release.json` and `check-report.json`.\nFor anchored/manual package releases, the public release tag is derived from the\npublished package version and the internal exact transaction tag remains\navailable in the release passport.\nConsumers do not need to hand-write `gh release` logic to trigger\n`release.published` propagation. Set `github-release: false` only for\nrepositories that intentionally do not maintain GitHub Releases.\nIf the transaction still needs protected-ref finalization, the wrapper defers\nGitHub Release creation until the later run that reaches `state=complete`.\n\nCustom publish jobs can also repeat the channel-ref preflight:\n\n```yaml\n- name: Verify publish channel ref still matches\n run: node .buildchain/runtime/scripts/verify-publish-channel-ref.mjs\n env:\n BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}\n BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}\n BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}\n GITHUB_TOKEN: ${{ github.token }}\n```\n\nAnchored/manual package release jobs should also make the Buildchain promotion\naction validate that publication is entering through the same\n`publish-gate/{alpha,release,major}` source-lock contract before any package\npublish side effect:\n\n```yaml\n- name: Promote release ref and publish npm package set\n uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v3\n with:\n sha: ${{ needs.build.outputs.publish-source-sha }}\n target-ref: release/v22/v22.22\n require-publish-source-lock: \"true\"\n publish-source-ref: ${{ needs.build.outputs.publish-source-ref }}\n publish-source-sha: ${{ needs.build.outputs.publish-source-sha }}\n publish-source-locked: ${{ needs.build.outputs.publish-source-locked }}\n```\n\n`target-ref` stays the Buildchain channel promotion target, such as\n`alpha/v22/v22.22`, `release/v22/v22.22`, or `publish-gate/major`.\n`publish-source-ref` is the reviewed source-lock branch that authorized this\nspecific package publication. For alpha and release package publications, the\nsource-lock branch must point at the exact channel-line commit that promotion is\nvalidating; it is not a replacement for `target-ref`.\n\nThis keeps the version bump commit, publish authorization, and auditable publish\nentrypoint on the Buildchain source-lock protocol. The CLI form\n`buildchain publish-source validate-anchored-release --json` is still useful for\ncustom publish scripts, but the preferred GitHub Actions gate is the promotion\naction input above. A publish job that still runs directly from `alpha/*` or\n`release/*` channel branches fails this check because those refs are channel\nstate, not publish-gate decisions.\n\n## Package-Set Publish Plan\n\nProjects that publish multiple packages should treat package publication as a\npackage-set operation. Buildchain's package-set planner uses these rules:\n\n- platform packages publish first;\n- the main package publishes last;\n- the dist-tag move happens only after the full package set is present;\n- reruns accept already-published packages only when package name, version, and\n integrity match;\n- an existing package with different integrity is a hard failure.\n\nThis keeps a consumer from observing a floating dist-tag that points to a main\npackage before all platform artifacts for the same source SHA are available.\n\n## Command Sources\n\nThe workflow runs `.buildchain/buildchain.toml` lifecycle stages by default:\n\n```toml\n[lifecycle.install]\ncommand = \"corepack yarn install --immutable\"\n\n[lifecycle.build]\ncommands = [\n \"corepack yarn make\",\n \"corepack yarn build\",\n]\n\n[lifecycle.verify]\ncommand = \"corepack yarn test\"\n```\n\nCallers can override any stage for one invocation:\n\n```yaml\nwith:\n build-command: cmake --build build --config Release\n verify-command: ctest --test-dir build --output-on-failure\n```\n\nEvery native and container matrix job is bounded by\n`lifecycle-timeout-minutes`, which defaults to 120 minutes. The same input is\nthe fallback deadline for each install, build, and verify action, so a hung\ncommand fails with the lifecycle name and matrix platform before it can occupy\na self-hosted runner indefinitely. A stage-level `timeout_minutes` in\n`buildchain.toml` remains the more specific override for that stage.\n\n```yaml\nwith:\n lifecycle-timeout-minutes: 90\n```\n\nThe reusable build workflow samples the build lifecycle by default and carries\nthe generated summary into the final verify diagnostics. Callers can override\nthe sidecar path or disable sampling:\n\n```yaml\nwith:\n sample-process-tree: true\n process-summary-path: .buildchain/diagnostics/process-summary.json\n process-sample-interval-ms: 15000\n requested-parallelism: 20\n```\n\nWhen `sample-process-tree` is true, Buildchain wraps either `build-command` or\nthe configured `lifecycle.build` stage with `buildchain sample process-tree`.\nThe path is relative to the checked-out workspace and is read again during the\nfinal verify lifecycle. Custom workflows can still write their own sampler\nsummary and pass `process-summary-path`; Buildchain reads the file after the\nlifecycle command finishes, so it may be produced during the same invocation.\nWhen the build stage is optional, the reusable workflow treats the default\nsampler path as optional during verify; an explicitly supplied\n`process-summary-path` remains required.\n\nFor custom workflows, use the action directly:\n\n```yaml\n- uses: kungfu-systems/buildchain/actions/run-lifecycle@v3\n with:\n stage: build\n required: \"true\"\n timeout-minutes: \"90\"\n artifact-name: libnode-linux-x64-${{ github.sha }}\n artifact-paths: |\n dist\n build/stage\n```\n\n## Artifact Contract\n\nEach platform upload uses `artifact-name-template`. The default is:\n\n```text\n{artifact}-{platform}-{sha}\n```\n\nSupported placeholders are `{artifact}`, `{artifactName}`, `{platform}`,\n`{platformId}`, `{platformName}`, `{sha}`, `{shortSha}`, `{ref}`, `{runId}`,\nand `{runAttempt}`. Invalid GitHub artifact name characters are normalized to\n`-`, so `{ref}` remains deterministic even for refs such as\n`refs/heads/dev/v2/v2.0`.\n\nEach platform also writes and uploads:\n\n```text\n.buildchain/artifacts/<platform-id>/manifest.json\n.buildchain/artifacts/<platform-id>/summary.json\n```\n\nThe manifest schema is:\n\n```json\n{\n \"schemaVersion\": 1,\n \"contract\": \"kungfu-buildchain-artifact\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"platform\": {\n \"id\": \"linux-x64\",\n \"name\": \"Linux x64\",\n \"os\": \"Linux\",\n \"arch\": \"X64\"\n },\n \"git\": {\n \"repository\": \"kungfu-systems/libnode\",\n \"sha\": \"<sha>\",\n \"ref\": \"<ref>\",\n \"runId\": \"<run id>\",\n \"runAttempt\": \"<attempt>\"\n },\n \"lifecycle\": {\n \"stage\": \"verify\",\n \"commandSource\": \"buildchain.toml\",\n \"executed\": true\n },\n \"summary\": {\n \"contract\": \"kungfu-buildchain-artifact-summary\",\n \"artifactName\": \"libnode-linux-x64-<sha>\",\n \"fileCount\": 1,\n \"totalBytes\": 1234,\n \"digest\": \"<hex>\"\n },\n \"expectedArtifacts\": {\n \"ok\": true,\n \"source\": \"expected-artifacts-json\",\n \"checks\": []\n },\n \"files\": [\n {\n \"path\": \"dist/example.zip\",\n \"size\": 1234,\n \"sha256\": \"<hex>\"\n }\n ]\n}\n```\n\nArtifact names do not include actor names, timestamps, or retry counters. Reruns\nproduce a new GitHub Actions run but keep the same source SHA/platform contract.\n\n`expected-artifacts-json` fails the build before upload when the artifact does\nnot match the caller's declared contract. Supported checks are:\n\n| Field | Meaning |\n| --------------- | ------------------------------------ |\n| `minFiles` | Minimum number of manifest files |\n| `maxFiles` | Maximum number of manifest files |\n| `minTotalBytes` | Minimum total byte count |\n| `requiredPaths` | Exact manifest paths that must exist |\n\n## Trusted Event Gate\n\nThe workflow has an explicit `trust-gate` job. By default, pull requests from\nforks fail before any build job can reach self-hosted runners, secrets,\npublishing credentials, or heavyweight build commands. Same-repository PRs,\nworkflow dispatches, and protected branch events can proceed.\n\nIf a repository wants fork PRs to skip rather than fail, it can set:\n\n```yaml\nwith:\n untrusted-policy: skip\n```\n\nDo not set `require-trusted-event: false` for workflows that use self-hosted\nrunners or secrets.\n\nThe build matrix and the workflow control plane are routed independently. The\nmatrix continues to use `runner-preset` and `platforms-json`. Consumers with a\ngoverned runner may also move channel resolution, trust evaluation, contract\nresolution, controller evidence, artifact transfer, and aggregation off the\ndefault GitHub-hosted runner:\n\n```yaml\nwith:\n control-runner-json: '[\"self-hosted\",\"agent-120\"]'\n runner-preset: custom\n platforms-json: '[{\"id\":\"linux-x64\",\"name\":\"Linux x64\",\"runner\":\"[\\\"self-hosted\\\",\\\"agent-120\\\"]\"}]'\n```\n\n`control-runner-json` is additive and defaults to `[\"ubuntu-24.04\"]`. Keep\n`require-trusted-event: true` whenever either runner input selects\n`self-hosted`; a self-hosted control plane must not be exposed to untrusted fork\nevents or arbitrary caller-controlled workflow code.\n\n`require-trusted-event` controls access to build runners. It does not override\nthe publish gate: pull requests remain non-publishing events.\n\n## Fixture\n\n`fixtures/libnode-shaped` is the contract fixture. It has:\n\n- `package.json` version state;\n- `.buildchain/buildchain.toml` with `install`, `build`, `verify`, and `publish`;\n- cross-platform Node scripts that create small `dist/` outputs;\n- `Build Surface Fixture` workflow coverage.\n\nThe fixture proves the reusable surface without running the real libnode native\nbuild."
2043
2043
  },
2044
2044
  {
2045
2045
  "id": "manual:runtime-train-validation",
@@ -2104,7 +2104,7 @@
2104
2104
  ],
2105
2105
  "maturity": "stable",
2106
2106
  "sourcePath": "docs/shifu-gate-profiles.md",
2107
- "digest": "sha256:155bf431ebbad8ca10b2ae8d200b3b89747d603bddf9f7105ed9319ec030cead",
2107
+ "digest": "sha256:bba20085d84ea5dce9a63c0b3ba967d1fd7c3dc8003256557560375f0875f8ce",
2108
2108
  "headings": [
2109
2109
  {
2110
2110
  "level": 1,
@@ -2142,7 +2142,7 @@
2142
2142
  "anchor": "validation-boundary"
2143
2143
  }
2144
2144
  ],
2145
- "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: buildchain-shifu-gate-orchestration\ndoc_type: technical-manual\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: B\nreview_state: self-reviewed\nlast_reviewed: 2026-07-13\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-13\n invisible_context_boundary: No private runner configuration, credentials, or unpublished Shifu implementation state was used.\n---\n\n# Shifu Gate profile orchestration\n\nBuildchain can schedule and aggregate a project-owned Shifu Gate profile without\nowning that project's gate ids, commands, dependencies, or dev/alpha/release\npolicy. The reusable workflow is\n`.github/workflows/.gate-profile.yml`.\n\n## Ownership boundary\n\n| Concern | Owner | Enforced surface |\n| ----------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------- |\n| Gate schema, profile planning, execution, receipt qualification | Shifu | `shifu gate plan`, `shifu gate run --profile`, `shifu gate receipt validate` |\n| Concrete gate catalog and profile decisions | Consumer project | project Gate registry and detailed Gate docs |\n| Runner labels and declared capabilities | Consumer workflow / Buildchain preset | `runner-preset` or `platforms-json` |\n| Deterministic runner matrix, immutable checkout, receipt transport, aggregate check | Buildchain | `.gate-profile.yml` and `shifu-gate-profile.mjs` |\n| Whether a profile aggregate is required for dev, alpha, or release | Consumer project | protected-branch required-check policy and caller workflow |\n\nBuildchain treats the Shifu plan and receipt as versioned input contracts. It\ndoes not reimplement policy selection, execute raw shell strings, convert an\nexplicit diagnostic gate run into qualification, or mint missing evidence.\n\n## Runner matrix\n\nThe plan job asks the consumer's Shifu entrypoint for one plan per configured\nplatform. A platform is dispatchable only when:\n\n- the Shifu plan is qualifying;\n- every required selection is supported on that platform;\n- the runner declares every capability requested by the selected gates; and\n- all platform plans carry the same project id and registry digest.\n\nConfigured platforms are required by default. A required platform that cannot\nhost the profile fails before runner dispatch. A platform with\n`\"required\": false` may be omitted, but the omission and reasons remain in the\nmatrix and aggregate. Matrix entries retain the Shifu plan digest, ordered gate\ngroups, required/advisory modes, action ids, definition digests, skips, and\nunsupported selections.\n\n`github-hosted` declares only the inherent `node` capability. Projects that\nneed a native compiler, product artifacts, devices, or other facilities must\nuse a suitable preset or declare a custom matrix. Capabilities are scheduling\nclaims, not installation instructions.\n\n```json\n[\n {\n \"id\": \"linux-native\",\n \"name\": \"Linux native\",\n \"platform\": \"linux\",\n \"runner\": \"[\\\"self-hosted\\\",\\\"Linux\\\",\\\"X64\\\",\\\"product-build\\\"]\",\n \"capabilities\": [\"node\", \"native-toolchain\", \"product-artifacts\"]\n }\n]\n```\n\n## Execution and receipts\n\nEvery matrix job checks out the exact source SHA planned by Buildchain, invokes\n`shifu gate run --profile`, writes the receipt outside the source checkout, and\nthen invokes `shifu gate receipt validate`. Buildchain uploads the original\nreceipt and validation result even when the run fails.\n\nBefore invoking the project-owned command, the workflow adds the runner\naccount's `~/.local/bin` directory to `PATH` on Windows, Linux, and macOS. This\nkeeps user-scoped tools such as `uv` available to strict Shifu cache profiles\nwithout assuming an administrator-managed system installation. The consumer or\nrunner owner remains responsible for provisioning the declared tools.\n\nThe fixed `Gate profile / aggregate` job fails closed for missing receipts,\ninvalid or stale Shifu validation, dirty or mismatched source SHA, registry or\nplan drift, missing required results, required failures/skips, or gate action\nand definition digest drift. Advisory failures remain visible but do not turn a\nShifu-qualifying receipt into a required failure. Buildchain's aggregate is\n`buildchain.shifu-gate-aggregate/v1`; its digest covers the matrix, receipts,\nper-gate evidence pointers, omissions, and issues.\n\n## Consumer workflow\n\n```yaml\njobs:\n gates:\n uses: kungfu-systems/buildchain/.github/workflows/.gate-profile.yml@v3\n with:\n gate-profile: alpha-pr\n runner-preset: kungfu-v4-self-hosted\n include-advisory: true\n\n build:\n needs: gates\n uses: kungfu-systems/buildchain/.github/workflows/build.yml@v3\n with:\n release-candidate: true\n gate-profile-aggregate-json: ${{ needs.gates.outputs.gate-aggregate-json }}\n```\n\nThe command input is an argv map, not a shell string. The default supports the\nordinary Shifu launcher names on all three platforms. A project with a\ndifferent launcher can override it without teaching Buildchain project tasks:\n\n```yaml\ngate-command-json: >-\n {\"linux\":[\"./tools/shifu\"],\"macos\":[\"./tools/shifu\"],\"windows\":[\"./tools/shifu.cmd\"]}\n```\n\n`gate-command-json` is the execution command. If execution needs a cache,\ncontainer, or other project-owned wrapper that should not make the read-only\nplan depend on that service, pass a separate lightweight argv map through\n`gate-plan-command-json`. It defaults to the execution command for backward\ncompatibility; Buildchain still treats both inputs as argv and never evaluates\na shell string.\n\nProjects may also pass non-sensitive scalar environment through\n`gate-environment-json`; Buildchain validates the JSON shape and forwards it\nwithout interpreting names or values. Cache profile references use the same\nopaque `shifu-cache-profile-ref` and `shifu-cache-profile-digest` inputs as the\nreusable build. Do not place tokens, credentials, or other secrets in workflow\ninputs or Gate receipts.\n\nWhen a qualifying aggregate is passed to the build workflow, the\nrelease-candidate passport binds its profile, source SHA, registry digest,\nmatrix digest, aggregate digest, receipt count, and result count. A failed,\nnon-qualifying, or source-mismatched aggregate cannot produce a valid passport.\nPromote-only release validation preserves that same Gate evidence summary in\nthe final Release Passport release identity, so promotion cannot silently drop\nthe qualified profile provenance.\n\n## Failure diagnosis and rollback\n\nStart with the aggregate artifact, then the platform receipt named in its\nissues. Reproduce the exact project decision with Shifu, for example:\n\n```bash\n./shifu gate explain <gate-id> --profile <profile>\n./shifu gate plan <profile> --platform <platform> --json\n./shifu gate receipt validate <receipt.json> --json\n```\n\nThe existing reusable build workflow remains usable without Gate inputs. To\nroll back Gate orchestration, remove the caller's `gates` job and\n`gate-profile-aggregate-json` handoff; this does not alter the consumer's Shifu\nregistry or its direct diagnostic commands.\n\n## Validation boundary\n\nUnit fixtures prove deterministic matrix generation and required/advisory,\ncapability, unsupported, missing, stale, failure, and definition-drift\npropagation. Because a train ref changes runtime scripts but not the outer\nreusable workflow topology, an unreleased `.gate-profile.yml` must also be\nvalidated through a trusted `workflow_dispatch` canary that references the\ntemporary workflow ref or exact SHA. See\n[`runtime-train-validation.md`](runtime-train-validation.md)."
2145
+ "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: buildchain-shifu-gate-orchestration\ndoc_type: technical-manual\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: B\nreview_state: self-reviewed\nlast_reviewed: 2026-07-29\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-13\n invisible_context_boundary: No private runner configuration, credentials, or unpublished Shifu implementation state was used.\n---\n\n# Shifu Gate profile orchestration\n\nBuildchain can schedule and aggregate a project-owned Shifu Gate profile without\nowning that project's gate ids, commands, dependencies, or dev/alpha/release\npolicy. The reusable workflow is\n`.github/workflows/.gate-profile.yml`.\n\n## Ownership boundary\n\n| Concern | Owner | Enforced surface |\n| ----------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------- |\n| Gate schema, profile planning, execution, receipt qualification | Shifu | `shifu gate plan`, `shifu gate run --profile`, `shifu gate receipt validate` |\n| Concrete gate catalog and profile decisions | Consumer project | project Gate registry and detailed Gate docs |\n| Runner labels and declared capabilities | Consumer workflow / Buildchain preset | `runner-preset` or `platforms-json` |\n| Deterministic runner matrix, immutable checkout, receipt transport, aggregate check | Buildchain | `.gate-profile.yml` and `shifu-gate-profile.mjs` |\n| Whether a profile aggregate is required for dev, alpha, or release | Consumer project | protected-branch required-check policy and caller workflow |\n\nBuildchain treats the Shifu plan and receipt as versioned input contracts. It\ndoes not reimplement policy selection, execute raw shell strings, convert an\nexplicit diagnostic gate run into qualification, or mint missing evidence.\n\n## Runner matrix\n\nThe plan job asks the consumer's Shifu entrypoint for one plan per configured\nplatform. A platform is dispatchable only when:\n\n- the Shifu plan is qualifying;\n- every required selection is supported on that platform;\n- the runner declares every capability requested by the selected gates; and\n- all platform plans carry the same project id and registry digest.\n\nConfigured platforms are required by default. A required platform that cannot\nhost the profile fails before runner dispatch. A platform with\n`\"required\": false` may be omitted, but the omission and reasons remain in the\nmatrix and aggregate. Matrix entries retain the Shifu plan digest, ordered gate\ngroups, required/advisory modes, action ids, definition digests, skips, and\nunsupported selections.\n\nEach matrix job timeout reserves the sum of the selected Gate action budgets\nplus 30 minutes for Buildchain-owned checkout, toolchain setup, plan download,\nreceipt validation, and artifact upload. The total remains capped at GitHub's\nsix-hour job limit. This control-plane allowance does not enlarge any Shifu\nGate's own declared action budget or change its definition digest.\n\n`github-hosted` declares only the inherent `node` capability. Projects that\nneed a native compiler, product artifacts, devices, or other facilities must\nuse a suitable preset or declare a custom matrix. Capabilities are scheduling\nclaims, not installation instructions.\n\n```json\n[\n {\n \"id\": \"linux-native\",\n \"name\": \"Linux native\",\n \"platform\": \"linux\",\n \"runner\": \"[\\\"self-hosted\\\",\\\"Linux\\\",\\\"X64\\\",\\\"product-build\\\"]\",\n \"capabilities\": [\"node\", \"native-toolchain\", \"product-artifacts\"]\n }\n]\n```\n\n## Execution and receipts\n\nEvery matrix job checks out the exact source SHA planned by Buildchain, invokes\n`shifu gate run --profile`, writes the receipt outside the source checkout, and\nthen invokes `shifu gate receipt validate`. Buildchain uploads the original\nreceipt and validation result even when the run fails.\n\nBefore invoking the project-owned command, the workflow adds the runner\naccount's `~/.local/bin` directory to `PATH` on Windows, Linux, and macOS. This\nkeeps user-scoped tools such as `uv` available to strict Shifu cache profiles\nwithout assuming an administrator-managed system installation. The consumer or\nrunner owner remains responsible for provisioning the declared tools.\n\nThe fixed `Gate profile / aggregate` job fails closed for missing receipts,\ninvalid or stale Shifu validation, dirty or mismatched source SHA, registry or\nplan drift, missing required results, required failures/skips, or gate action\nand definition digest drift. Advisory failures remain visible but do not turn a\nShifu-qualifying receipt into a required failure. Buildchain's aggregate is\n`buildchain.shifu-gate-aggregate/v1`; its digest covers the matrix, receipts,\nper-gate evidence pointers, omissions, and issues.\n\n## Consumer workflow\n\n```yaml\njobs:\n gates:\n uses: kungfu-systems/buildchain/.github/workflows/.gate-profile.yml@v3\n with:\n gate-profile: alpha-pr\n runner-preset: kungfu-v4-self-hosted\n include-advisory: true\n\n build:\n needs: gates\n uses: kungfu-systems/buildchain/.github/workflows/build.yml@v3\n with:\n release-candidate: true\n gate-profile-aggregate-json: ${{ needs.gates.outputs.gate-aggregate-json }}\n```\n\nThe command input is an argv map, not a shell string. The default supports the\nordinary Shifu launcher names on all three platforms. A project with a\ndifferent launcher can override it without teaching Buildchain project tasks:\n\n```yaml\ngate-command-json: >-\n {\"linux\":[\"./tools/shifu\"],\"macos\":[\"./tools/shifu\"],\"windows\":[\"./tools/shifu.cmd\"]}\n```\n\n`gate-command-json` is the execution command. If execution needs a cache,\ncontainer, or other project-owned wrapper that should not make the read-only\nplan depend on that service, pass a separate lightweight argv map through\n`gate-plan-command-json`. It defaults to the execution command for backward\ncompatibility; Buildchain still treats both inputs as argv and never evaluates\na shell string.\n\nProjects may also pass non-sensitive scalar environment through\n`gate-environment-json`; Buildchain validates the JSON shape and forwards it\nwithout interpreting names or values. Cache profile references use the same\nopaque `shifu-cache-profile-ref` and `shifu-cache-profile-digest` inputs as the\nreusable build. Do not place tokens, credentials, or other secrets in workflow\ninputs or Gate receipts.\n\nWhen a qualifying aggregate is passed to the build workflow, the\nrelease-candidate passport binds its profile, source SHA, registry digest,\nmatrix digest, aggregate digest, receipt count, and result count. A failed,\nnon-qualifying, or source-mismatched aggregate cannot produce a valid passport.\nPromote-only release validation preserves that same Gate evidence summary in\nthe final Release Passport release identity, so promotion cannot silently drop\nthe qualified profile provenance.\n\n## Failure diagnosis and rollback\n\nStart with the aggregate artifact, then the platform receipt named in its\nissues. Reproduce the exact project decision with Shifu, for example:\n\n```bash\n./shifu gate explain <gate-id> --profile <profile>\n./shifu gate plan <profile> --platform <platform> --json\n./shifu gate receipt validate <receipt.json> --json\n```\n\nThe existing reusable build workflow remains usable without Gate inputs. To\nroll back Gate orchestration, remove the caller's `gates` job and\n`gate-profile-aggregate-json` handoff; this does not alter the consumer's Shifu\nregistry or its direct diagnostic commands.\n\n## Validation boundary\n\nUnit fixtures prove deterministic matrix generation and required/advisory,\ncapability, unsupported, missing, stale, failure, and definition-drift\npropagation. Because a train ref changes runtime scripts but not the outer\nreusable workflow topology, an unreleased `.gate-profile.yml` must also be\nvalidated through a trusted `workflow_dispatch` canary that references the\ntemporary workflow ref or exact SHA. See\n[`runtime-train-validation.md`](runtime-train-validation.md)."
2146
2146
  },
2147
2147
  {
2148
2148
  "id": "manual:site-bundle-contract",
@@ -2305,7 +2305,7 @@
2305
2305
  ],
2306
2306
  "maturity": "stable",
2307
2307
  "sourcePath": "docs/versioning.md",
2308
- "digest": "sha256:4b40cb8675c111b7588297102a61cb241630f2b707a67a0384a6df7527b2c64c",
2308
+ "digest": "sha256:52f58a1a69ea924cb1fae11bb6a8def2b0c66cc4ab08416553cf680c213b5ded",
2309
2309
  "headings": [
2310
2310
  {
2311
2311
  "level": 1,
@@ -2333,7 +2333,7 @@
2333
2333
  "anchor": "runner-policy"
2334
2334
  }
2335
2335
  ],
2336
- "markdown": "# Buildchain Versioning\n\nBuildchain uses semantic version lines to describe public contracts, not only\ncode size. A release can be small in diff size and still open a new minor line\nwhen it adds a durable surface that consumers, workflows, or agents can depend\non.\n\n## Lines\n\n| Line | Meaning |\n| --- | --- |\n| Patch | Compatible fix, hardening, documentation correction, or implementation repair inside an existing surface. |\n| Minor | New compatible welded surface: reusable workflow output, CLI command family, config protocol, published subpath, evidence file, runner contract, or agent-readable artifact. |\n| Major | Breaking semantic change, removed stable surface, changed branch/tag governance, or incompatible protocol rewrite. |\n\nKungfu minor lines are long-lived trains. `v2.0`, `v2.1`, and `v2.2` can each\nreceive many patch releases. The major ref, such as `v2`, points at the\nselected stable major entrypoint; the minor ref, such as `v2.2`, points at the\nlatest stable production patch for that minor line.\n\n## Welded Surfaces\n\nThese surfaces are classified independently; the final release impact is the\nhighest impact across the affected registered surfaces:\n\n- reusable workflow inputs, outputs, and artifact contracts;\n- public CLI command families and their machine-readable JSON shapes;\n- public npm exports such as `@kungfu-tech/buildchain/logging`;\n- config protocols such as `buildchain.toml`;\n- release governance state machines and protected ref semantics;\n- release evidence contracts such as passport, artifact evidence, impact\n ledger, and agent index files;\n- binary distribution shapes that users can install or automate against.\n\nFor each surface:\n\n- content, documentation, or implementation-only work that does not touch a\n registered surface is patch;\n- additive fields, new commands, new exports, new evidence sections, or new\n registered surfaces are minor;\n- removals, incompatible renames, changed meanings, newly required fields,\n weakened trust gates, or changed ref flow are major.\n\nThe release passport records this as `surfaceImpacts[]` plus\n`versionImpact.final`. The final impact must equal the highest surface impact,\nso an agent cannot silently label a release patch when one machine surface needs\nminor review.\n\n`surfaceImpacts[]` is mandatory for production release passports (`release/*`)\nand major publish-gate passports. Alpha, local, and legacy passport contexts\nkeep the field optional so temporary validation can proceed without pretending\nto be a production release decision.\n\nExample: a KFD document such as KFD-2 is content and remains patch, but adding a\n`kind` field to the machine-consumed KFD `registry.json` is an additive change\nto the `kfd-registry-schema` surface and therefore requires minor-impact\nreview. This avoids both false shortcuts: \"new KFD means minor\" and \"all KFD\nrepository changes are patch\".\n\n## Decision Log\n\n| Date | Action | Line | Faces | Class | Rationale | PR |\n| --- | --- | --- | --- | --- | --- | --- |\n| 2026-07-28 | extend-minor | `v3.0` | auditable-demo-workflow, auditable-demo-media-profile, auditable-demo-media-receipt | additive | Add opt-in archive, web-delivery, and site-hero profiles; independently bind codec, container, audio, layout, byte-budget, role, and fast-start facts into a v2 media receipt while preserving the existing archive default. | |\n| 2026-07-26 | extend-minor | `v3.0` | auditable-demo-workflow, auditable-demo-evidence | additive | Forward-port the consumer-neutral reusable Gate that binds exact same-run GitHub Artifacts to checked-in adapters and immutable renderer evidence, with optional media rendering only from the exact passing Gate bundle. | #1862 |\n| 2026-07-23 | extend-minor | `v2.14` | credential-island-macos-input, protected-signer-job, macos-signing-evidence, action-subpaths | additive | The reusable build surface can seal an exact source-bound macOS app and hand it to a protected caller environment, where an immutable Buildchain action signs, notarizes, staples, Gatekeeper-assesses, and returns an auditable additional release-candidate platform without exposing credentials to consumer lifecycle jobs. | |\n| 2026-07-20 | extend-minor | `v2.14` | anchored-derived-version-material, build-controller-evidence, release-passport, package-subpaths, release-propagation-controller | additive | Anchored/manual consumers can declare derived version witnesses that Buildchain regenerates and verifies before heavy builds, binds to exact alpha/release trees and passports, and admits during protected promotion; propagation receipts now model their existing optional consumer stages. | |\n| 2026-07-17 | extend-minor | `v2.14` | merge-queue-config, release-line-governance-inheritance | additive | Buildchain config can explicitly enable, inherit, or disable exact dev-channel merge queues, and release-line bootstrap reconciles the declared or inherited policy before moving the repository default branch. | |\n| 2026-07-16 | open-minor | `v2.14` | dev-merge-queue-governance | additive | The public CLI adds a dry-run-first, idempotent merge-queue governance command that verifies required workflow event compatibility before applying an exact dev-channel ruleset and removing the strict up-to-date race. | |\n| 2026-07-15 | open-minor | `v2.13` | artifact-verification-envelope, package-subpaths | additive | The public envelope seals exact artifact, provenance, identity, lifecycle, revocation, and existing KFD assessment roots into one consumer-ready KFX admission input, with a dedicated Node API export and fail-closed verifier. | |\n| 2026-07-11 | open-minor | `v2.12` | channel-build-router, channel-selection-protocol | additive | The public `build.yml` reusable workflow lets consumers declare one build job while Buildchain selects generic major alpha for development/prerelease intent and stable major for production release intent, with explicit overrides, separate locks, and fail-closed ambiguity handling. | |\n| 2026-07-08 | open-minor | `v2.9` | build-facts-contract | additive | Build Facts add a public CLI command family, Node API export, config protocol, module/product fact contracts, release-passport evidence section, and Kungfu legacy buildinfo projection from the same source facts. | |\n| 2026-07-06 | open-minor | `v2.8` | kfd-1-contract-world-release-gate, kfd-2-release-trust-passport-audit, kfd-3-collaboration-interface-trust-proof, publish-source-lock-enforcement, required-check-protection | additive | KFD release gates add KFD-1 self contract verification, KFD-2 public release trust claim audit, KFD-3 collaboration-interface trust proofs, publish-side source-lock enforcement for promote-only wrappers, and protected channel required checks repaired to bind GitHub Actions check runs instead of legacy commit status contexts. | |\n| 2026-07-04 | open-minor | `v2.5` | scheduled-integration-governance | additive | Scheduled integration governance adds scheduled feature-branch discovery, conflict-free integration, reporting, and agent-visible governance automation for dev-line maintenance. | |\n| 2026-07-03 | open-minor | `v2.4` | infra-contract-lifecycle | additive | Infra contract lifecycle adds the provider-neutral `infra-contract` CLI command family, project type, adapter capability contract, lifecycle evidence bundle, propagation evidence, CI evidence mode, and consumer-facing contract artifacts. | |\n| 2026-07-02 | open-minor | `v2.3` | web-surface-host-mapping | additive | Web surface host mapping adds first-class multi-host surface bindings, reusable workflow URL outputs, per-surface deployment overrides, and an agent-readable fixture contract. | |\n| 2026-07-02 | open-minor | `v2.2` | release-passport, binary-distribution | additive | Release passport and binary distribution add agent-readable release passport files, artifact evidence, impact ledger, agent index, GitHub Release collection and verification commands, and standalone binary assets. | |\n| 2026-07-02 | open-minor | `v2.1` | logging-sdk, cli-observability, package-subpaths | additive | Buildchain toolkit observability adds the public logging SDK, CLI observability commands, and package subpaths that consumers can import. | |\n\n## Runner Policy\n\nThe `v2.2` binary distribution lane uses GitHub-hosted runners for production\nassets because that is the easiest release path for external users to reproduce:\n\n- `ubuntu-24.04`\n- `macos-latest`\n- `windows-2022`\n\nSelf-hosted runners remain compatibility fixtures. They prove Buildchain's\nprotocol does not depend on GitHub-hosted images, but they do not define the\npublic binary distribution path."
2336
+ "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: buildchain-versioning\ndoc_type: policy\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-30\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-30\n invisible_context_boundary: Live release and provider state must be verified independently.\n---\n\n# Buildchain Versioning\n\nBuildchain uses semantic version lines to describe public contracts, not only\ncode size. A release can be small in diff size and still open a new minor line\nwhen it adds a durable surface that consumers, workflows, or agents can depend\non.\n\n## Lines\n\n| Line | Meaning |\n| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Patch | Compatible fix, hardening, documentation correction, or implementation repair inside an existing surface. |\n| Minor | New compatible welded surface: reusable workflow output, CLI command family, config protocol, published subpath, evidence file, runner contract, or agent-readable artifact. |\n| Major | Breaking semantic change, removed stable surface, changed branch/tag governance, or incompatible protocol rewrite. |\n\nKungfu minor lines are long-lived trains. `v2.0`, `v2.1`, and `v2.2` can each\nreceive many patch releases. The major ref, such as `v2`, points at the\nselected stable major entrypoint; the minor ref, such as `v2.2`, points at the\nlatest stable production patch for that minor line.\n\n## Welded Surfaces\n\nThese surfaces are classified independently; the final release impact is the\nhighest impact across the affected registered surfaces:\n\n- reusable workflow inputs, outputs, and artifact contracts;\n- public CLI command families and their machine-readable JSON shapes;\n- public npm exports such as `@kungfu-tech/buildchain/logging`;\n- config protocols such as `buildchain.toml`;\n- release governance state machines and protected ref semantics;\n- release evidence contracts such as passport, artifact evidence, impact\n ledger, and agent index files;\n- binary distribution shapes that users can install or automate against.\n\nFor each surface:\n\n- content, documentation, or implementation-only work that does not touch a\n registered surface is patch;\n- additive fields, new commands, new exports, new evidence sections, or new\n registered surfaces are minor;\n- removals, incompatible renames, changed meanings, newly required fields,\n weakened trust gates, or changed ref flow are major.\n\nThe release passport records this as `surfaceImpacts[]` plus\n`versionImpact.final`. The final impact must equal the highest surface impact,\nso an agent cannot silently label a release patch when one machine surface needs\nminor review.\n\n`surfaceImpacts[]` is mandatory for production release passports (`release/*`)\nand major publish-gate passports. Alpha, local, and legacy passport contexts\nkeep the field optional so temporary validation can proceed without pretending\nto be a production release decision.\n\nExample: a KFD document such as KFD-2 is content and remains patch, but adding a\n`kind` field to the machine-consumed KFD `registry.json` is an additive change\nto the `kfd-registry-schema` surface and therefore requires minor-impact\nreview. This avoids both false shortcuts: \"new KFD means minor\" and \"all KFD\nrepository changes are patch\".\n\n## Decision Log\n\n| Date | Action | Line | Faces | Class | Rationale | PR |\n| ---------- | ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |\n| 2026-07-30 | extend-minor | `v3.0` | reusable-build-workflow, observed-evidence-bundle, release-passport-json-reader, web-surface-release-governance | additive | Add bounded artifact compression and remote-read controls plus transactional derived evidence projections while repairing release-PR runtime handoff so production remains protected-main-only. | |\n| 2026-07-29 | extend-minor | `v3.0` | artifact-signing-config, apple-developer-id-authority, artifact-signing-evidence | additive | Extend the consumer-neutral signing declaration to macOS compound archives, including nested wheel Mach-O signing, PEP 427 RECORD repair, safe archive reconstruction, and whole-product notarization under the same protected authority. | |\n| 2026-07-28 | extend-minor | `v3.0` | auditable-demo-workflow, auditable-demo-media-profile, auditable-demo-media-receipt | additive | Add opt-in archive, web-delivery, and site-hero profiles; independently bind codec, container, audio, layout, byte-budget, role, and fast-start facts into a v2 media receipt while preserving the existing archive default. | |\n| 2026-07-26 | extend-minor | `v3.0` | auditable-demo-workflow, auditable-demo-evidence | additive | Forward-port the consumer-neutral reusable Gate that binds exact same-run GitHub Artifacts to checked-in adapters and immutable renderer evidence, with optional media rendering only from the exact passing Gate bundle. | #1862 |\n| 2026-07-23 | extend-minor | `v2.14` | credential-island-macos-input, protected-signer-job, macos-signing-evidence, action-subpaths | additive | The reusable build surface can seal an exact source-bound macOS app and hand it to a protected caller environment, where an immutable Buildchain action signs, notarizes, staples, Gatekeeper-assesses, and returns an auditable additional release-candidate platform without exposing credentials to consumer lifecycle jobs. | |\n| 2026-07-20 | extend-minor | `v2.14` | anchored-derived-version-material, build-controller-evidence, release-passport, package-subpaths, release-propagation-controller | additive | Anchored/manual consumers can declare derived version witnesses that Buildchain regenerates and verifies before heavy builds, binds to exact alpha/release trees and passports, and admits during protected promotion; propagation receipts now model their existing optional consumer stages. | |\n| 2026-07-17 | extend-minor | `v2.14` | merge-queue-config, release-line-governance-inheritance | additive | Buildchain config can explicitly enable, inherit, or disable exact dev-channel merge queues, and release-line bootstrap reconciles the declared or inherited policy before moving the repository default branch. | |\n| 2026-07-16 | open-minor | `v2.14` | dev-merge-queue-governance | additive | The public CLI adds a dry-run-first, idempotent merge-queue governance command that verifies required workflow event compatibility before applying an exact dev-channel ruleset and removing the strict up-to-date race. | |\n| 2026-07-15 | open-minor | `v2.13` | artifact-verification-envelope, package-subpaths | additive | The public envelope seals exact artifact, provenance, identity, lifecycle, revocation, and existing KFD assessment roots into one consumer-ready KFX admission input, with a dedicated Node API export and fail-closed verifier. | |\n| 2026-07-11 | open-minor | `v2.12` | channel-build-router, channel-selection-protocol | additive | The public `build.yml` reusable workflow lets consumers declare one build job while Buildchain selects generic major alpha for development/prerelease intent and stable major for production release intent, with explicit overrides, separate locks, and fail-closed ambiguity handling. | |\n| 2026-07-08 | open-minor | `v2.9` | build-facts-contract | additive | Build Facts add a public CLI command family, Node API export, config protocol, module/product fact contracts, release-passport evidence section, and Kungfu legacy buildinfo projection from the same source facts. | |\n| 2026-07-06 | open-minor | `v2.8` | kfd-1-contract-world-release-gate, kfd-2-release-trust-passport-audit, kfd-3-collaboration-interface-trust-proof, publish-source-lock-enforcement, required-check-protection | additive | KFD release gates add KFD-1 self contract verification, KFD-2 public release trust claim audit, KFD-3 collaboration-interface trust proofs, publish-side source-lock enforcement for promote-only wrappers, and protected channel required checks repaired to bind GitHub Actions check runs instead of legacy commit status contexts. | |\n| 2026-07-04 | open-minor | `v2.5` | scheduled-integration-governance | additive | Scheduled integration governance adds scheduled feature-branch discovery, conflict-free integration, reporting, and agent-visible governance automation for dev-line maintenance. | |\n| 2026-07-03 | open-minor | `v2.4` | infra-contract-lifecycle | additive | Infra contract lifecycle adds the provider-neutral `infra-contract` CLI command family, project type, adapter capability contract, lifecycle evidence bundle, propagation evidence, CI evidence mode, and consumer-facing contract artifacts. | |\n| 2026-07-02 | open-minor | `v2.3` | web-surface-host-mapping | additive | Web surface host mapping adds first-class multi-host surface bindings, reusable workflow URL outputs, per-surface deployment overrides, and an agent-readable fixture contract. | |\n| 2026-07-02 | open-minor | `v2.2` | release-passport, binary-distribution | additive | Release passport and binary distribution add agent-readable release passport files, artifact evidence, impact ledger, agent index, GitHub Release collection and verification commands, and standalone binary assets. | |\n| 2026-07-02 | open-minor | `v2.1` | logging-sdk, cli-observability, package-subpaths | additive | Buildchain toolkit observability adds the public logging SDK, CLI observability commands, and package subpaths that consumers can import. | |\n\n## Runner Policy\n\nThe `v2.2` binary distribution lane uses GitHub-hosted runners for production\nassets because that is the easiest release path for external users to reproduce:\n\n- `ubuntu-24.04`\n- `macos-latest`\n- `windows-2022`\n\nSelf-hosted runners remain compatibility fixtures. They prove Buildchain's\nprotocol does not depend on GitHub-hosted images, but they do not define the\npublic binary distribution path."
2337
2337
  },
2338
2338
  {
2339
2339
  "id": "manual:web-surface-deployments",
@@ -2347,7 +2347,7 @@
2347
2347
  ],
2348
2348
  "maturity": "stable",
2349
2349
  "sourcePath": "docs/web-surface-deployments.md",
2350
- "digest": "sha256:ed4ff02dcb6f66118a29084784f25d8008ca472f59005b2e014133524706a9c5",
2350
+ "digest": "sha256:6b907af93c55ffd8274cd83ad87a45dd48eb9a01938cf419aafebb5557be5821",
2351
2351
  "headings": [
2352
2352
  {
2353
2353
  "level": 1,
@@ -2460,7 +2460,7 @@
2460
2460
  "anchor": "boundaries"
2461
2461
  }
2462
2462
  ],
2463
- "markdown": "# Web-Surface Deployment Contract\n\nBuildchain supports `project.type = \"web-surface\"` for repositories that publish\nsites, docs, product pages, operator consoles, or browser apps. These projects\nneed auditable deployment semantics, but they are not package release lines and\nshould not be forced into `dev/alpha/release` version-state automation.\n\nThe release object for a web surface is:\n\n```text\nsource commit + build artifact + deploy target + channel + deployment manifest\n```\n\nThis keeps the evidence chain clear:\n\n- the source SHA explains what code was built;\n- the artifact hash explains exactly what was deployed;\n- the channel explains who can see it and whether it is promotable;\n- the deploy target and adapter explain where it would be published;\n- the deployment manifest records retention, rollback, security, and secret\n reference metadata.\n\n## Configuration\n\n`.buildchain/buildchain.toml` is the source of truth. Web-surface projects must declare\npreview, staging, and production channels plus a deploy adapter for each.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-kungfu-tech\"\nsite = \"kungfu-tech\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.kungfu.tech\"\nvisibility = \"ephemeral\"\nrequires_auth = false\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.kungfu.tech\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://kungfu.tech\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[deploy.preview]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"kungfu-tech-preview\"\ncloudfront_distribution = \"E-PREVIEW\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n# Optional. Defaults to \"buildchain\".\n# Use \"external\" when an existing viewer-request CloudFront Function already\n# owns preview alias, surface-prefix, and directory-index routing.\ndirectory_index_rewrite = \"buildchain\"\n# Optional. Defaults to HTTP for public channels and S3 object evidence for\n# managed-network channels. Use \"s3-object\" when CI should verify uploaded\n# objects and manifests instead of waiting for public edge convergence.\nhealth_strategy = \"http\"\n```\n\n### Multi-Surface Host Mapping\n\nSome site repositories publish more than one first-class web surface from the\nsame artifact. For example, `site-libkungfu-dev` has a hub plus separate\nhostnames for core, Buildchain, and Kung Fu Decisions. These are not just\nnavigation paths; staging, production preflight, and post-deploy health checks\nmust verify host-level behavior for each surface.\n\nDeclare named surfaces with per-channel URLs:\n\n```toml\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[surfaces.buildchain]\npath = \"/buildchain/\"\nproduction_url = \"https://buildchain.libkungfu.dev\"\nstaging_url = \"https://buildchain.staging.libkungfu.dev\"\npreview_url_pattern = \"https://buildchain-{alias}.preview.libkungfu.dev\"\n\n[surfaces.kfd]\npath = \"/kfd/\"\nproduction_url = \"https://kfd.libkungfu.dev\"\nstaging_url = \"https://kfd.staging.libkungfu.dev\"\npreview_url_pattern = \"https://kfd-{alias}.preview.libkungfu.dev\"\n```\n\nBuildchain resolves every `(channel, surface)` pair. A preview alias such as\n`pr-12` becomes:\n\n```text\nhub: https://pr-12.preview.libkungfu.dev\ncore: https://core-pr-12.preview.libkungfu.dev\nbuildchain: https://buildchain-pr-12.preview.libkungfu.dev\nkfd: https://kfd-pr-12.preview.libkungfu.dev\n```\n\nWhen `surfaces` is omitted, Buildchain preserves the legacy single-surface\ncontract by creating an implicit `default` surface from the channel URL. When a\nsurface is intentionally path-only, declare it explicitly:\n\n```toml\n[surfaces.docs]\npath = \"/docs/\"\npath_only = true\n```\n\n`path_only = true` is an exception, not the default. Without it, every named\nsurface must declare `preview_url_pattern`, `staging_url`, and\n`production_url`. This makes staging/production mismatches fail during\nvalidation instead of becoming invisible deploy drift.\n\nAdapter strategy remains explicit. The default `aws-s3-cloudfront` plan uses the\nchannel deploy target for every surface, and each binding records its own\nbucket, distribution id, object prefix, manifest key, source path, and URL. A\nchannel can override target details per surface:\n\n```toml\n[deploy.staging.surfaces.core]\nbucket = \"libkungfu-dev-core-staging\"\ncloudfront_distribution = \"E-CORE-STAGING\"\norigin_path = \"/core\"\n```\n\nBuildchain validates these hard constraints:\n\n- `channels.preview.url_pattern` is required and must contain the alias shape\n used by preview deployments.\n- `channels.staging.access_control` must protect staging. Supported modes are\n `managed-network`, `edge-basic-auth`, `oidc`, and `app-auth`.\n- `channels.staging.edge_auth` records whether the edge layer owns auth. Use\n `edge_auth = \"none\"` when staging is protected by managed network controls\n such as WAF/IP allowlists or VPN access.\n- `channels.staging.noindex = true` is required.\n- `channels.production.url` is required.\n- deploy adapters must be declared per channel.\n- named surfaces must declare first-class URLs for every channel unless\n `path_only = true` is explicitly set.\n- secret material must be declared as reference names, such as\n `secret_refs = [\"AWS_ROLE_ARN\"]`; inline secret-like deploy keys are rejected.\n\n### Floating Runtime Contract Lock\n\nWeb-surface repositories can consume the stable Buildchain workflow shell with a\nfloating ref, such as:\n\n```yaml\njobs:\n web:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n buildchain-contract-lock-path: .buildchain/contract-lock.json\n buildchain-contract-compatibility-policy: major-compatible\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n build-command: pnpm build\n artifact-path: dist\n```\n\nThe caller repository commits `.buildchain/contract-lock.json` after reviewing an\naccepted Buildchain runtime SHA and contract digest. The reusable workflow then\nresolves the floating runtime to an immutable SHA, checks the lock before the\ncaller build command, and applies these rules:\n\n- unchanged lock: continue without feedback;\n- compatible drift: continue, write the drift summary, and open or update a\n caller-repository issue when permissions allow;\n- breaking drift: fail closed before rendering, deployment planning, deploy\n apply, or release publication.\n\nThe caller no longer needs to run `scripts/buildchain-contract-lock.mjs` inside\nits own build command. That check belongs to Buildchain because the actual\ncontract world is stored in the Buildchain runtime ref being used.\n\nSupported adapter names are:\n\n| Adapter | Initial use |\n| --- | --- |\n| `aws-s3-cloudfront` | Static site artifact sync plus CDN invalidation plan |\n| `aws-elastic-beanstalk` | Future dynamic app environment adapter |\n| `aws-ecs-service` | Future dynamic service adapter |\n\nThe channel ontology is independent of the adapter. A future dynamic staging\nenvironment still remains `channel = \"staging\"` with protected/noindex/security\nrequirements.\n\n## Preview Aliases\n\nPreview uses subdomains, not path prefixes:\n\n```text\nhttps://pr-123.preview.kungfu.tech\nhttps://sha-abcdef123456.preview.kungfu.tech\n```\n\nAlias semantics are explicit:\n\n| Alias | Meaning | Mutable | Retention |\n| --- | --- | --- | --- |\n| `pr-123` | Current preview for a pull request | yes | short-lived |\n| `sha-abcdef123456` | Immutable preview for one source SHA | no | longer-lived |\n\nThis allows PR comments to stay stable while preserving immutable evidence for a\nspecific source commit.\n\n## Deployment Manifest\n\nBuildchain emits a manifest with the deployment facts that matter for audit and\nrollback:\n\n```json\n{\n \"schemaVersion\": 1,\n \"contract\": \"kungfu-buildchain-web-surface-deployment\",\n \"site\": \"libkungfu-dev\",\n \"channel\": \"preview\",\n \"alias\": \"sha-abcdef123456\",\n \"url\": \"https://sha-abcdef123456.preview.libkungfu.dev\",\n \"generatedAt\": \"2026-07-01T00:00:00.000Z\",\n \"publishedAt\": \"2026-07-01T00:00:00.000Z\",\n \"reproducible\": true,\n \"timestampPolicy\": \"ci-injected\",\n \"deterministicInputs\": [\n \"web-surface artifact content\",\n \"buildchain.toml web-surface channels/deploy/surfaces\",\n \"sourceSha\",\n \"artifactHash\",\n \"deployment channel\",\n \"deployment alias\"\n ],\n \"sourceRevision\": \"...\",\n \"timestampPolicyDetails\": {\n \"contract\": \"kungfu-buildchain-surface-timestamp-policy\",\n \"timestampFields\": [\"generatedAt\", \"publishedAt\", \"deployedAt\"],\n \"timestampFieldsParticipateInArtifactDigest\": false,\n \"artifactDigestScope\": \"web-surface artifactHash excludes deployment manifest timestamps\"\n },\n \"sourceSha\": \"...\",\n \"artifactHash\": \"...\",\n \"deployTarget\": \"libkungfu-dev-preview\",\n \"adapter\": \"aws-s3-cloudfront\",\n \"deployedAt\": \"2026-07-01T00:00:00.000Z\",\n \"retentionClass\": \"preview-sha-immutable\",\n \"expiresAt\": \"2026-09-29T00:00:00.000Z\",\n \"accessControl\": \"none\",\n \"edgeAuth\": \"none\",\n \"noindex\": true,\n \"secretRefs\": [\"AWS_ROLE_ARN\"],\n \"surfaceBindings\": [\n {\n \"surface\": \"hub\",\n \"channel\": \"preview\",\n \"alias\": \"sha-abcdef123456\",\n \"url\": \"https://sha-abcdef123456.preview.libkungfu.dev\",\n \"sourcePath\": \"/\",\n \"artifactPathPrefix\": \"\",\n \"viewerPathPrefix\": \"/\",\n \"directoryIndex\": \"index.html\",\n \"directoryIndexResolution\": true,\n \"canonicalUrl\": \"https://libkungfu.dev\",\n \"bucket\": \"libkungfu-dev-preview\",\n \"distributionId\": \"E-PREVIEW\",\n \"originPath\": \"\",\n \"objectPrefix\": \"sha-abcdef123456\",\n \"manifestKey\": \".buildchain/deployments/sha-abcdef123456/hub.json\",\n \"routing\": {\n \"contract\": \"kungfu-buildchain-web-surface-path-prefix-rewrite\",\n \"viewerPathPrefix\": \"/\",\n \"artifactPathPrefix\": \"\",\n \"objectPrefix\": \"sha-abcdef123456\",\n \"directoryIndex\": \"index.html\",\n \"directoryIndexResolution\": true\n },\n \"smokeUrls\": [\n {\n \"kind\": \"root\",\n \"requestPath\": \"/\",\n \"url\": \"https://sha-abcdef123456.preview.libkungfu.dev/\",\n \"required\": true\n }\n ],\n \"noindex\": true,\n \"accessControl\": \"none\"\n }\n ]\n}\n```\n\nDynamic adapters can also fill `runtimeId`, `configFingerprint`,\n`healthCheck`, `migrationState`, `rollbackPointer`, and\n`rollbackLimitations`. Buildchain records secret reference names only, never\nsecret values.\n\nThe timestamp policy is shared with package site bundles. Public deployment\nmanifests should expose real workflow generation/publication times while\nseparately declaring why the deployed artifact remains reproducible. For\nweb-surface deployment manifests, `artifactHash` is the static site artifact\ndigest and does not include deployment timestamp fields; the manifest itself\nstill records those fields for human and agent audit.\n\n## Deploy Plans\n\nDeploy planning is the default behavior. It plans the adapter steps and writes\nmanifest JSON, but it does not touch AWS, DNS, CloudFront, or deployment\ncredentials.\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode deploy-plan \\\n --cwd fixtures/web-surface-shaped \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --alias sha-aaaaaaaaaaaa\n```\n\nFor manifest-only output:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode manifest \\\n --cwd fixtures/web-surface-shaped \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --alias pr-123 \\\n --output .buildchain/web-surface-manifest.json\n```\n\nThe CLI emits GitHub outputs when `GITHUB_OUTPUT` is present:\n\n- `web-surface-channel`\n- `web-surface-alias`\n- `web-surface-url`\n- `web-surface-urls-json`\n- `web-surface-artifact-hash`\n- `web-surface-manifest-json`\n\nThe reusable workflow resolves that same effective channel before running the\ncaller build or verify command. Both steps receive\n`BUILDCHAIN_WEB_SURFACE_CHANNEL` (`preview`, `staging`, or `production`) and\n`BUILDCHAIN_PREVIEW_ALIAS` for previews. Compatibility aliases\n`BUILDCHAIN_SURFACE_CHANNEL` and `BUILDCHAIN_WEB_SURFACE_ALIAS` are also\nprovided. Callers should consume these variables instead of reconstructing the\nrelease-intent state machine from raw GitHub events.\nAn unapproved manual canary resolves to `staging`; a trusted manual dispatch\nwith `production-approved=true` resolves to `production` through the same path.\n\n## Explicit Apply\n\n`deploy-apply` and `cleanup-apply` are explicit execution modes for the\n`aws-s3-cloudfront` static-site adapter. They still default to `--dry-run true`;\nlive AWS mutation requires `--dry-run false`.\n\nDeploy apply syncs the artifact, writes the deployment manifest, and invalidates\nCloudFront when a distribution id is configured:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode deploy-apply \\\n --cwd fixtures/web-surface-shaped \\\n --channel staging \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --artifact-path dist \\\n --dry-run false \\\n --output .buildchain/web-surface-staging-apply.json\n```\n\n### Explicit cache classes\n\nThe S3/CloudFront adapter can declare cache metadata per deploy channel or\nsurface override:\n\n```toml\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\ncache_control_default = \"public,max-age=3600\"\ncache_control_mutable = \"public,max-age=300,must-revalidate\"\ncache_control_immutable = \"public,max-age=31536000,immutable\"\n```\n\n`cache_control_default` applies to the ordinary artifact sync.\n`cache_control_mutable` is then applied to HTML, JSON, XML, generated directory\nindex aliases, and the deployment manifest. `cache_control_immutable` applies\nto append-only publication roots discovered through the archive policy below.\nMutable metadata updates exclude those append-only roots, so HTML or JSON inside\nan immutable version archive keeps the immutable class.\nThe deploy plan, surface binding, apply operations, and deployment manifest all\nrecord the effective values. Existing consumers that omit these fields retain\ntheir prior upload behavior.\n\nCache metadata complements, rather than replaces, invalidation. Every deploy\nstill records and creates the exact surface wildcard and deployment-manifest\ninvalidation paths.\n\n### Immutable publication paths\n\nWhen a surface artifact contains `manifest.json` with\n`archivePolicy.contract = \"kungfu-buildchain-publication-archive-policy\"`,\nBuildchain treats every declared `publications[].versions[].immutablePath` as\nan append-only publication boundary. This applies identically to preview,\nstaging, and production adapters.\n\nThe adapter derives the protected archive root from those declared version\npaths and applies four ordered safeguards:\n\n1. every local immutable file is checked against an existing S3 object;\n2. missing files are uploaded with `aws s3 sync --no-overwrite` and a SHA-256\n checksum;\n3. every immutable file is checked again after upload, closing the race between\n the first check and the no-overwrite transfer;\n4. mutable site content keeps normal `sync --delete` behavior, but every parent\n or owning surface sync excludes the protected archive root from deletion.\n\nAn existing object with a different SHA-256 digest fails apply before mutable\ncontent is changed. Older objects without a stored S3 SHA-256 checksum are read\nand byte-hashed for compatibility. Directory-index alias writes are skipped\nunder protected roots so they cannot overwrite immutable route objects; viewer\nrequest rewriting remains the directory-index authority.\n\nThe deploy plan and manifest record `immutablePublication`,\n`mutableDeleteExcludes`, and the parent-surface coverage. Apply output records\n`immutablePreservation` plus every pre-check, no-overwrite sync, post-check, and\nmutable sync operation. Health output adds an `__immutable__` check proving that\nthe owning and parent surface syncs carried their required delete exclusions.\nThe runner must provide an AWS CLI version whose `s3 sync` supports\n`--no-overwrite`.\n\nFor multi-surface sites, each surface host is treated as a root-relative view\nof that surface's artifact path prefix. For example, a `buildchain` surface with\n`path = \"/buildchain/\"` and preview URL\n`https://buildchain-pr-29.preview.libkungfu.dev` syncs the artifact subtree\n`dist/buildchain/` to the preview object prefix `pr-29/buildchain`. A viewer\nrequest for `https://buildchain-pr-29.preview.libkungfu.dev/docs/` therefore\nresolves against the artifact's `dist/buildchain/docs/index.html`, not\n`dist/docs/index.html` and not the hub surface root. The deployment manifest\nrecords this as `routing.contract =\n\"kungfu-buildchain-web-surface-path-prefix-rewrite\"` with\n`viewerPathPrefix = \"/\"`, `artifactPathPrefix = \"buildchain\"`, and\n`directoryIndexResolution = true`.\n\nWhen a surface uses an S3 object prefix, directory-index routing must be handled\nat the viewer-request layer. By default, `directory_index_rewrite =\n\"buildchain\"` makes Buildchain install or update one CloudFront Function per\ndistribution before uploading payloads. The function rewrites any request path\nending in `/` to the corresponding `index.html`, so\n`https://buildchain-pr-29.preview.libkungfu.dev/` resolves to\n`pr-29/buildchain/index.html` and\n`https://buildchain-pr-29.preview.libkungfu.dev/docs/` resolves to\n`pr-29/buildchain/docs/index.html`. This keeps multi-host preview roots\ncompatible with S3 REST origins, where copying alias objects such as\n`pr-29/buildchain` or `pr-29/buildchain/` is not a reliable substitute for an\nedge rewrite.\n\nIf the distribution already has a viewer-request function that owns preview\nalias routing and surface-prefix routing, set `directory_index_rewrite =\n\"external\"` on the deploy channel or surface override. In that mode Buildchain\ndoes not create, update, or attach a generic directory-index function. Instead,\nthe deployment manifest records `directoryIndexRewrite = \"external\"` and\n`directoryIndexStrategy = \"external-viewer-request-function\"`, then the normal\nhealth check still verifies every required root and nested surface URL. This is\nthe correct contract for shared preview distributions such as\n`site-libkungfu-dev`, where a generic function cannot replace the existing\nprefix router.\n\nBuildchain still writes directory-index alias objects during apply as\ncompatibility evidence, but root correctness comes from the viewer-request\nrewrite contract, not from extensionless S3 keys. If Buildchain-managed mode\nfinds a distribution with a different viewer-request function, apply fails\nclosed and records that conflict in the apply result instead of silently serving\n403s. If CloudFront rejects `UpdateDistribution` because its optimistic-lock\nETag became stale, Buildchain re-reads the distribution and retries with the\nnew ETag at most twice. A concurrent update that already attached the intended\nfunction is accepted as converged; a different viewer-request function or any\nnon-ETag AWS error still fails immediately. The reusable workflow uploads\n`buildchain-web-surface-*-diagnostics`\nartifacts containing the apply and health JSON so the failing AWS operation or\nHTTP check is visible from the consumer run.\n\nIt can also execute a previously saved deploy plan. In that mode Buildchain\nrecomputes the local artifact hash before running AWS commands and fails closed\nif the artifact no longer matches the saved plan. Before any adapter operation,\nit also checks channel-aware JSON manifests in the artifact: top-level\n`canonicalHost` and declared `pages[].host` facts must belong to the plan's\nsurface hosts. A production plan therefore rejects staging/preview host facts\nbefore AWS apply:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode deploy-apply \\\n --cwd fixtures/web-surface-shaped \\\n --plan .buildchain/web-surface-staging-plan.json \\\n --dry-run false \\\n --output .buildchain/web-surface-staging-apply.json\n```\n\nCleanup apply deletes preview content, deletes the preview manifest, and\ninvalidates CloudFront:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode cleanup-apply \\\n --cwd fixtures/web-surface-shaped \\\n --event pull-request-closed \\\n --pull-number 123 \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --dry-run false \\\n --output .buildchain/web-surface-cleanup-apply.json\n```\n\nCleanup apply can also execute a saved cleanup plan:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode cleanup-apply \\\n --cwd fixtures/web-surface-shaped \\\n --plan .buildchain/web-surface-cleanup-plan.json \\\n --dry-run false \\\n --output .buildchain/web-surface-cleanup-apply.json\n```\n\nApply output records the channel, alias, source SHA, artifact hash, target\nbucket, object prefix, manifest key, all surface URLs, all surface bindings, CDN\ninvalidation paths, actor/run metadata, immutable preservation evidence, and\nevery adapter operation with\n`executed`, `exitCode`, `stdout`, and `stderr`. If an operation fails,\nBuildchain records the failed operation, stops subsequent adapter operations,\nand exits non-zero after writing the result JSON. Buildchain records secret\nreference names only; the runner must provide the AWS CLI and credentials\noutside Buildchain, typically through OIDC and the declared `secret_refs`.\n\n## Production Preflight And Health\n\nProduction promotion is not just `deploy-apply --channel production`. Before a\nlive production apply, the reusable workflow runs:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode production-preflight \\\n --cwd fixtures/web-surface-shaped \\\n --plan .buildchain/web-surface-production-plan.json \\\n --execute true \\\n --output .buildchain/web-surface-production-preflight.json\n```\n\nThe production preflight checks that:\n\n- `channels.production` is canonical and indexable;\n- every surface has concrete production bucket and CloudFront targets;\n- every production surface URL is HTTPS;\n- the production AWS role can inspect the declared bucket and distribution;\n- CloudFront aliases cover every surface host, including product hosts such as\n `kfd.libkungfu.dev`;\n- DNS resolves for every surface host.\n\nAfter preview, staging, and production apply, the workflow runs:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode health-check \\\n --cwd fixtures/web-surface-shaped \\\n --result .buildchain/web-surface-production-apply.json \\\n --output .buildchain/web-surface-production-health.json\n```\n\nWhen the apply result includes CloudFront invalidations from the\n`aws-s3-cloudfront` adapter, health check waits for those invalidations to reach\n`Completed` before fetching public smoke URLs. This avoids reporting stale\nCloudFront 403 responses as deployment failures immediately after a successful\nS3 sync and invalidation request.\n\nAfter the invalidation wait, HTTP smoke checks also retry transient 403, 404, and\n5xx responses before recording failure. This keeps the public health signal\nstrict while absorbing short CloudFront edge propagation windows that can remain\nvisible after the invalidation waiter returns. By default, Buildchain attempts\neach HTTP smoke URL 12 times with a 10 second interval. Consumers can override\nthat window with `BUILDCHAIN_WEB_SURFACE_HEALTH_HTTP_RETRY_ATTEMPTS` and\n`BUILDCHAIN_WEB_SURFACE_HEALTH_HTTP_RETRY_INTERVAL_MS` when they need a\nsite-specific health policy.\n\nThe health check fetches every surface root URL and any nested smoke URLs\nrecorded in each surface binding. Nested smoke URLs are derived from nested HTML\nartifact files under the surface path prefix, with directory index resolution\nsuch as `dist/buildchain/docs/index.html` becoming `/docs/` on the buildchain\npreview host. If a surface has no nested HTML route, Buildchain records only\nthe root smoke URL; absence of nested HTML is not a deployment failure. When a\nnested route is present, the check fails closed if a deploy reports success but\nthat child page returns 403 or another unexpected status. Surface root checks\nexpect the apply result to have installed the directory-index rewrite, so a\nmulti-host preview root such as `https://buildchain-pr-29.preview.libkungfu.dev/`\nmust resolve to the surface `index.html`, not the bare prefix directory.\nProduction additionally fails if a response is unreachable, returns an\nunexpected status, or still sends `x-robots-tag: noindex`. The health check also\nverifies that each surface binding recorded a deployment manifest pointer. The\nproduction release passport embeds the deploy plan, apply result, production\npreflight, and health check so a reviewer or agent can audit why the production\nsite changed and whether every declared host and every existing nested route was\nactually covered.\n\nPublic channels can opt into object-level health with\n`deploy.<channel>.health_strategy = \"s3-object\"` when the deployment contract is\nalready covered by S3 manifest/object writes and public edge convergence is not\nthe right CI gate. This is useful for preview distributions whose viewer-request\nrouting is owned by an external CloudFront Function. The channel remains public;\nonly the CI health evidence changes from HTTP fetches to S3 `head-object`\nchecks.\n\nChannels declared with `access_control = \"managed-network\"` use a different\nhealth strategy by default. Buildchain does not require a GitHub-hosted runner\nto fetch a URL that is intentionally reachable only from an approved network.\nInstead, after a live apply the health check uses the deploy role to run S3\n`head-object` checks for each surface manifest and the smoke target object, such\nas the surface `index.html` or a nested `docs/index.html`. The check records\n`healthStrategy = \"s3-object\"` and skips the public HTTP fetch. Dry-run and\nplan-only checks fall back to deployment evidence: each surface must have a\nmanifest key, bucket, object prefix, `sync-static-artifact`, and\n`write-deployment-manifest` evidence, recorded as\n`healthStrategy = \"deployment-evidence\"`. If the workflow is running on a runner\nthat is allowed to reach the managed network, set\n`BUILDCHAIN_WEB_SURFACE_HEALTH_ALLOWED_RUNNER=true` or pass\n`--allowed-managed-network-runner true` to keep the normal HTTP smoke checks.\nSet `BUILDCHAIN_WEB_SURFACE_HEALTH_S3_OBJECTS=false` or pass\n`--managed-network-s3-object-verification false` only when an external channel\npolicy owns managed-network object verification.\n\n## Cleanup Plans\n\nPreview cleanup is an auditable cleanup contract. It can run as a dry-run plan,\nan apply-mode plan, or the explicit `cleanup-apply` executor with preview-only\ncredentials:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode cleanup-plan \\\n --cwd fixtures/web-surface-shaped \\\n --event pull-request-closed \\\n --pull-number 123 \\\n --aliases pr-123,sha-abcdef123456\n```\n\nThe plan and apply result keep mutable PR aliases and immutable SHA aliases\ndistinct so a caller can expire them with different retention windows. Closed-PR\ncleanup can derive `pr-N` from `--pull-number`, records the event, source SHA,\nactor, run id, preview bucket/prefix, manifest key, and adapter steps, and is an\nauditable no-op when no aliases are requested.\n\n## Reusable Workflow Shape\n\nBuildchain ships `.github/workflows/.web-surface.yml` for repositories that want\nthe standard PR review and promotion flow without copying bespoke glue:\n\n```yaml\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n build-command: npm run build\n verify-command: npm run check\n artifact-path: dist\n```\n\nThe reusable workflow maps GitHub events to Buildchain web-surface semantics:\n\n| Event | Buildchain behavior |\n| --- | --- |\n| `pull_request` opened / synchronized / reopened | validate, build, verify, and plan `preview` for `pr-N` |\n| `pull_request` closed | plan apply-mode cleanup for the `pr-N` preview alias and manifest |\n| `push` to `main` | validate, build, verify, plan and apply `staging` from the merged `main` SHA, then optionally open a production release PR |\n| `push` to `main` from a matching release PR merge | validate the associated release PR, plan `production`, and enter the configured GitHub Environment gate |\n| `workflow_dispatch` with `production-approved = true` | plan `production` and enter the configured GitHub Environment gate |\n\nThe optional `buildchain-ref` input is empty by default. Empty keeps the\nweb-surface run on the stable Buildchain runtime selected by the reusable\nworkflow ref, normally `@v3`. A trusted maintainer can expose a\n`workflow_dispatch` input and pass it through for one-off train validation.\nSee [`runtime-train-validation.md`](runtime-train-validation.md) for the shared\ntrain protocol and notification template:\n\n```yaml\non:\n workflow_dispatch:\n inputs:\n buildchain-ref:\n description: \"Temporary Buildchain runtime ref for trusted manual validation\"\n required: false\n default: \"\"\n\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n buildchain-ref: ${{ inputs.buildchain-ref || '' }}\n build-command: pnpm run build\n verify-command: pnpm run check\n artifact-path: dist\n```\n\nOnly trusted `workflow_dispatch` runs by repository actors with write,\nmaintain, or admin permission may use a non-empty runtime override. Train refs\nsuch as `train/v3/v3.0/site-source-of-truth` are temporary validation refs, not\nstable production dependencies or pending merge targets. They may remain for a\nretention window after release as a fast-use and rollback channel, with old\ntrains handled by periodic Buildchain cleanup. The web-surface deployment\nmanifest records the resolved runtime SHA as `runtimeId` and the stable\nrollback ref as `rollbackPointer`.\n\nThe workflow deliberately plans and emits manifests by default. Live mutation is\nopt-in per channel:\n\n```yaml\npermissions:\n contents: read\n id-token: write\n pull-requests: write\n\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n build-command: pnpm run build\n verify-command: pnpm run check\n artifact-path: dist\n preview-apply: true\n preview-cleanup-apply: true\n preview-aws-role-arn: arn:aws:iam::123456789012:role/site-preview-github-actions\n staging-apply: true\n staging-aws-role-arn: arn:aws:iam::123456789012:role/site-staging-github-actions\n production-apply: false\n production-release-on-main: false\n production-aws-role-arn: arn:aws:iam::123456789012:role/site-production-github-actions\n production-environment: production\n release-feedback-actor-privacy: public\n```\n\nWhen enabled, Buildchain owns the full release apply state machine:\n\n- PR preview deploys run `deploy-apply --dry-run false` with the preview role\n and update a single idempotent PR comment.\n- Closed PR cleanup runs `cleanup-apply --dry-run false` with the preview role\n only.\n- Pushes to `main` run staging `deploy-apply --dry-run false` with the staging\n role, then write a staging release feedback passport artifact and comment the\n associated merged PR with the staging URL, source SHA, artifact identity, run\n URL, and failure context when apply did not complete.\n- When `production-release-on-main=true`, successful staging applies open or\n update a Buildchain-owned release PR from\n `release/<channel>-<short-sha>` to `main`, unless the current push already\n came from a matching release PR merge. The release PR contains one empty\n release-intent commit, carries `production-release-label`, and includes the\n staging URLs, source SHA, artifact hash, and staging release-passport artifact\n link in the PR body.\n- Production release PR handoff is permission-aware. Staging apply and staging\n health remain successful even when the repository or organization has\n GitHub Actions workflow permissions set to read-only. In that case Buildchain\n records `release-pr-status=permission-denied`, uploads the release PR handoff\n summary/body plus staging release passport artifacts, and writes an exact\n manual `gh pr create` command to the step summary. Set\n `fail-on-release-pr-error=true` only when PR creation failure should fail the\n whole workflow.\n- Release pull requests that match the configured production gate get a\n Buildchain review comment with the staging URL and production target, so the\n operator can verify staging from the PR page and use merge as the approval\n action. Consumers do not need to hand-write `gh pr create` or production\n release-intent glue.\n- `production-apply=true` enables the production capability; it does not request\n production for every event. Ordinary `main` pushes remain staging-only and\n can create or update a release PR. Production runs only when the capability\n is enabled and either:\n - a `workflow_dispatch` passes `production-approved=true` and the triggering\n actor currently has `write`, `maintain`, or `admin` permission; or\n - `production-release-on-main=true` and the `main` push commit is associated\n with exactly one same-repository, merged release pull request matching\n `production-release-label` and `production-release-head-prefix`.\n The production job is then gated by the configured GitHub Environment.\n- Before production artifact download, Buildchain assembles a managed sealed\n publication capability from the exact source/runtime SHAs, production plan\n and artifact hash, a qualifying pre-publication controller receipt, the\n trusted manual or reviewed-release-PR decision, production Environment and\n AWS role target, an ephemeral-runner receipt, and a fresh nonce. Production\n revalidates that capability against the downloaded plan before it downloads\n product bytes. The later AWS OIDC exchange remains the provider's final\n transaction-time authorization decision and fails closed before deploy apply.\n- The `publication-*-json` inputs are an advanced external-evidence\n compatibility path, not a prerequisite for the standard release-PR or trusted\n manual mechanisms. External evidence must now include\n `publication-gate-aggregate-json`; supplying only a partial set still fails\n closed.\n- Production apply writes a production release feedback passport artifact and\n comments the release PR with the production URL, source SHA, artifact\n identity, run URL, rollback pointer, and failure context when apply did not\n complete.\n\nThe feedback passport records the release responsibility chain:\n\n- human decision actor;\n- trigger actor;\n- runner/execution actor;\n- OIDC/deploy identity reference;\n- decision type and time;\n- source event, PR number, merge commit, and required gate label/head-prefix.\n\n`release-feedback-actor-privacy` controls actor values in the passport and\ncomments. `public` records GitHub actor names, `redacted` records only the actor\nrole, and `private-ref` records a stable private reference hash without exposing\nthe actor name.\n\nFor release-PR publishing, callers opt in explicitly:\n\n```yaml\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n build-command: npm run build\n verify-command: npm run check\n artifact-path: dist\n production-apply: true\n production-release-on-main: true\n production-release-label: buildchain-release\n production-release-head-prefix: release/\n production-release-branch-channel: production\n production-release-pr-mode: auto\n production-aws-role-arn: arn:aws:iam::123456789012:role/site-production-github-actions\n production-environment: production\n```\n\nKeep `production-apply` enabled in the caller when the repository supports\nproduction. Buildchain derives whether the current event may use that\ncapability: an ordinary `main` push plans and applies staging, a matching\nreviewed release PR merge authorizes production, and an approved trusted manual\ndispatch authorizes production. Inputs from an untrusted event cannot turn that\ndecision on.\n\n`production-release-pr-mode` controls the post-staging handoff:\n\n| Mode | Behavior |\n| --- | --- |\n| `auto` | Generate release PR facts, create/update the empty release-intent branch and PR, and label it when token permissions allow. This is the default. |\n| `summary-only` | Generate and upload release PR facts, body, passport evidence, and manual command, but do not call the GitHub PR API. |\n| `disabled` | Record a disabled handoff and skip release PR API calls. |\n\nAutomatic release PR creation normally uses the workflow `github.token`.\nConsumers that cannot enable \"GitHub Actions can create and approve pull\nrequests\" globally should prefer the first-class GitHub App path. Pass the App\nclient id as an input and the private key as a reusable workflow secret; Buildchain\ncreates an installation token inside the release PR job and uses it only for the\nrelease-intent branch, PR, and label operations:\n\n```yaml\nwith:\n production-release-app-client-id: ${{ vars.KUNGFU_RELEASE_APP_CLIENT_ID }}\nsecrets:\n production-release-app-private-key: ${{ secrets.KUNGFU_RELEASE_APP_PRIVATE_KEY }}\n```\n\nWhen either side of the App configuration is missing, Buildchain does not hide\nthat behind the fallback `github.token`. The handoff JSON and job summary report\n`status: \"app-token-unavailable\"` with an `appTokenStatus` such as\n`missing-client-id`, `missing-private-key`, or `create-failed`, and still include\nthe manual PR creation command. If the repository intentionally uses another\nnarrow token, pass it through `production-release-pr-token`.\n\n`production-release-app-id` remains accepted as a deprecated alias for the input\nname, but the value should be the GitHub App client id. GitHub App numeric App\nIDs and client IDs are distinct, and Buildchain passes the value to\n`actions/create-github-app-token` through its non-deprecated `client-id` input so\nnew runs do not emit the deprecated `app-id` warning.\n\nIf a repository already generates its own narrow token or PAT, it can still pass\nthat through `production-release-pr-token`:\n\n```yaml\nwith:\n production-release-pr-token: ${{ secrets.BUILDCHAIN_RELEASE_PR_TOKEN }}\n```\n\nToken priority is: generated GitHub App installation token,\n`production-release-pr-token`, then `github.token`.\n\nThe merge button becomes the production approval only for a PR that carries the\nrelease label and comes from the configured source-branch prefix. Ordinary pull\nrequests merged into `main` deploy staging and open a release-intent PR; merging\nthat release PR triggers production. A release PR merge push does not open\nanother release PR.\n\nApply-only inputs are validated before the caller build or verification command\nruns. If the current event would run preview, staging, or production apply,\nmissing role inputs, a production apply without `production-approved=true` on\nmanual dispatch, or a manual actor without repository write authority fail\nimmediately instead of spending the build and plan jobs first.\n\nCallers must grant `id-token: write` for OIDC role assumption. Preview comments\nneed `pull-requests: write`. Automatic release PR creation also needs\n`contents: write`, `pull-requests: write`, and `issues: write` so Buildchain can\ncreate the release branch, write the empty release-intent commit, open or update\nthe PR, and apply the release label. If these permissions are unavailable,\nBuildchain degrades the release handoff instead of marking a successful staging\ndeployment as failed, unless `fail-on-release-pr-error=true`. The AWS roles remain caller-owned and\nshould be scoped by channel: preview can mutate only preview resources, staging\ncan mutate only staging resources, and production can mutate only production\nresources.\n\nApply mode fails closed when the deploy config still contains placeholder AWS\ntargets such as `pending-preview-distribution`. Planning can use placeholders\nfor dry-run-only design work, but live apply requires concrete bucket and\nCloudFront distribution identifiers.\n\n## Site Repository Shape\n\nA site repository can start with:\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-kungfu-tech\"\nsite = \"kungfu-tech\"\n\n[lifecycle.build]\ncommand = \"pnpm run build\"\n\n[lifecycle.verify]\ncommand = \"pnpm run check\"\n```\n\nThen add the channel, deploy, retention, and security declarations shown above.\nThe project may use pnpm, npm, yarn, Vite, Astro, Next static export, Sphinx,\nMkDocs, CMake-generated docs, or another lifecycle command source. Buildchain\nonly needs a deterministic artifact path and the manifest facts.\n\n## Signed bootstrap installer publications\n\nA web-surface artifact that contains `installer-publication.json` opts into the\n`kungfu.bootstrap-installer-publication/v1` seam. During planning, Buildchain\nfails closed unless the manifest binds:\n\n- one signed-channel payload root and exact channel-file digest;\n- one source SHA and Release Passport;\n- unique platform/architecture entries with manifest, artifact, and archive\n digest roots; and\n- byte-identical friendly and immutable `install.sh` / `install.ps1` assets.\n\nThe resulting `kungfu-buildchain-installer-publication-evidence/v1` object and\nroot are welded into the deployment manifest. This does not make Buildchain the\nproduct installer authority: Kungfu generates the installer from its signed\nrelease channel, while the site owns only routes and presentation.\n\nThe site artifact should also declare the existing\n`kungfu-buildchain-publication-archive-policy` in its root `manifest.json`, with\nthe versioned installer directory as `immutablePath`. Buildchain then performs\npre-upload object digest checks, `--no-overwrite` upload, post-upload checks, and\nexcludes the immutable root from mutable deletion.\n\nLocal verification:\n\n```sh\nnode scripts/installer-publication.mjs \\\n --manifest dist/installer-publication.json \\\n --artifact-root dist\n```\n\nAfter preview, staging, or production apply, public read-back verifies exact\nbytes plus route semantics. Friendly routes require a revalidated cache policy\nwith `max-age` no greater than 300 seconds; immutable routes require at least\none year and the `immutable` directive:\n\n```sh\nnode scripts/installer-publication.mjs \\\n --manifest dist/installer-publication.json \\\n --public-readback\n```\n\nRedirects are not accepted as successful read-back. The evidence retains\ncontent type, cache control, ETag, object version id when exposed, size, digest,\nURL, channel root, source SHA, and Release Passport coordinates.\n\n## Boundaries\n\nBuildchain only performs live AWS mutations in explicit apply modes with\n`--dry-run false`. Production deploys must still be gated by a human-controlled\nworkflow, release, or GitHub Environment. DNS changes, staging auth\nimplementation, CloudFront distribution creation, and credential provisioning\nremain explicitly authorized infrastructure operations outside the web-surface\nartifact apply contract."
2463
+ "markdown": "---\nstatus: draft\nperiod: ongoing\ntheme: web-surface-deployments\ndoc_type: contract\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-30\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-30\n invisible_context_boundary: No credentials, private logs, or unpublished deployment values are included.\n---\n\n# Web-Surface Deployment Contract\n\nBuildchain supports `project.type = \"web-surface\"` for repositories that publish\nsites, docs, product pages, operator consoles, or browser apps. These projects\nneed auditable deployment semantics, but they are not package release lines and\nshould not be forced into `dev/alpha/release` version-state automation.\n\nThe release object for a web surface is:\n\n```text\nsource commit + build artifact + deploy target + channel + deployment manifest\n```\n\nThis keeps the evidence chain clear:\n\n- the source SHA explains what code was built;\n- the artifact hash explains exactly what was deployed;\n- the channel explains who can see it and whether it is promotable;\n- the deploy target and adapter explain where it would be published;\n- the deployment manifest records retention, rollback, security, and secret\n reference metadata.\n\n## Configuration\n\n`.buildchain/buildchain.toml` is the source of truth. Web-surface projects must declare\npreview, staging, and production channels plus a deploy adapter for each.\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-kungfu-tech\"\nsite = \"kungfu-tech\"\n\n[channels.preview]\nurl_pattern = \"https://{alias}.preview.kungfu.tech\"\nvisibility = \"ephemeral\"\nrequires_auth = false\nnoindex = true\n\n[channels.staging]\nurl = \"https://staging.kungfu.tech\"\nvisibility = \"protected\"\naccess_control = \"managed-network\"\nedge_auth = \"none\"\nnoindex = true\npromotable = true\n\n[channels.production]\nurl = \"https://kungfu.tech\"\nvisibility = \"public\"\ncanonical = true\nnoindex = false\n\n[deploy.preview]\nadapter = \"aws-s3-cloudfront\"\nbucket = \"kungfu-tech-preview\"\ncloudfront_distribution = \"E-PREVIEW\"\nartifact_path = \"dist\"\nsecret_refs = [\"AWS_ROLE_ARN\"]\n# Optional. Defaults to \"buildchain\".\n# Use \"external\" when an existing viewer-request CloudFront Function already\n# owns preview alias, surface-prefix, and directory-index routing.\ndirectory_index_rewrite = \"buildchain\"\n# Optional. Defaults to HTTP for public channels and S3 object evidence for\n# managed-network channels. Use \"s3-object\" when CI should verify uploaded\n# objects and manifests instead of waiting for public edge convergence.\nhealth_strategy = \"http\"\n```\n\n### Multi-Surface Host Mapping\n\nSome site repositories publish more than one first-class web surface from the\nsame artifact. For example, `site-libkungfu-dev` has a hub plus separate\nhostnames for core, Buildchain, and Kung Fu Decisions. These are not just\nnavigation paths; staging, production preflight, and post-deploy health checks\nmust verify host-level behavior for each surface.\n\nDeclare named surfaces with per-channel URLs:\n\n```toml\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[surfaces.buildchain]\npath = \"/buildchain/\"\nproduction_url = \"https://buildchain.libkungfu.dev\"\nstaging_url = \"https://buildchain.staging.libkungfu.dev\"\npreview_url_pattern = \"https://buildchain-{alias}.preview.libkungfu.dev\"\n\n[surfaces.kfd]\npath = \"/kfd/\"\nproduction_url = \"https://kfd.libkungfu.dev\"\nstaging_url = \"https://kfd.staging.libkungfu.dev\"\npreview_url_pattern = \"https://kfd-{alias}.preview.libkungfu.dev\"\n```\n\nBuildchain resolves every `(channel, surface)` pair. A preview alias such as\n`pr-12` becomes:\n\n```text\nhub: https://pr-12.preview.libkungfu.dev\ncore: https://core-pr-12.preview.libkungfu.dev\nbuildchain: https://buildchain-pr-12.preview.libkungfu.dev\nkfd: https://kfd-pr-12.preview.libkungfu.dev\n```\n\nWhen `surfaces` is omitted, Buildchain preserves the legacy single-surface\ncontract by creating an implicit `default` surface from the channel URL. When a\nsurface is intentionally path-only, declare it explicitly:\n\n```toml\n[surfaces.docs]\npath = \"/docs/\"\npath_only = true\n```\n\n`path_only = true` is an exception, not the default. Without it, every named\nsurface must declare `preview_url_pattern`, `staging_url`, and\n`production_url`. This makes staging/production mismatches fail during\nvalidation instead of becoming invisible deploy drift.\n\nAdapter strategy remains explicit. The default `aws-s3-cloudfront` plan uses the\nchannel deploy target for every surface, and each binding records its own\nbucket, distribution id, object prefix, manifest key, source path, and URL. A\nchannel can override target details per surface:\n\n```toml\n[deploy.staging.surfaces.core]\nbucket = \"libkungfu-dev-core-staging\"\ncloudfront_distribution = \"E-CORE-STAGING\"\norigin_path = \"/core\"\n```\n\nBuildchain validates these hard constraints:\n\n- `channels.preview.url_pattern` is required and must contain the alias shape\n used by preview deployments.\n- `channels.staging.access_control` must protect staging. Supported modes are\n `managed-network`, `edge-basic-auth`, `oidc`, and `app-auth`.\n- `channels.staging.edge_auth` records whether the edge layer owns auth. Use\n `edge_auth = \"none\"` when staging is protected by managed network controls\n such as WAF/IP allowlists or VPN access.\n- `channels.staging.noindex = true` is required.\n- `channels.production.url` is required.\n- deploy adapters must be declared per channel.\n- named surfaces must declare first-class URLs for every channel unless\n `path_only = true` is explicitly set.\n- secret material must be declared as reference names, such as\n `secret_refs = [\"AWS_ROLE_ARN\"]`; inline secret-like deploy keys are rejected.\n\n### Floating Runtime Contract Lock\n\nWeb-surface repositories can consume the stable Buildchain workflow shell with a\nfloating ref, such as:\n\n```yaml\njobs:\n web:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n buildchain-contract-lock-path: .buildchain/contract-lock.json\n buildchain-contract-compatibility-policy: major-compatible\n buildchain-contract-drift-issue-mode: compatible-and-breaking\n build-command: pnpm build\n artifact-path: dist\n```\n\nThe caller repository commits `.buildchain/contract-lock.json` after reviewing an\naccepted Buildchain runtime SHA and contract digest. The reusable workflow then\nresolves the floating runtime to an immutable SHA, checks the lock before the\ncaller build command, and applies these rules:\n\n- unchanged lock: continue without feedback;\n- compatible drift: continue, write the drift summary, and open or update a\n caller-repository issue when permissions allow;\n- breaking drift: fail closed before rendering, deployment planning, deploy\n apply, or release publication.\n\nThe caller no longer needs to run `scripts/buildchain-contract-lock.mjs` inside\nits own build command. That check belongs to Buildchain because the actual\ncontract world is stored in the Buildchain runtime ref being used.\n\nSupported adapter names are:\n\n| Adapter | Initial use |\n| --- | --- |\n| `aws-s3-cloudfront` | Static site artifact sync plus CDN invalidation plan |\n| `aws-elastic-beanstalk` | Future dynamic app environment adapter |\n| `aws-ecs-service` | Future dynamic service adapter |\n\nThe channel ontology is independent of the adapter. A future dynamic staging\nenvironment still remains `channel = \"staging\"` with protected/noindex/security\nrequirements.\n\n## Preview Aliases\n\nPreview uses subdomains, not path prefixes:\n\n```text\nhttps://pr-123.preview.kungfu.tech\nhttps://sha-abcdef123456.preview.kungfu.tech\n```\n\nAlias semantics are explicit:\n\n| Alias | Meaning | Mutable | Retention |\n| --- | --- | --- | --- |\n| `pr-123` | Current preview for a pull request | yes | short-lived |\n| `sha-abcdef123456` | Immutable preview for one source SHA | no | longer-lived |\n\nThis allows PR comments to stay stable while preserving immutable evidence for a\nspecific source commit.\n\n## Deployment Manifest\n\nBuildchain emits a manifest with the deployment facts that matter for audit and\nrollback:\n\n```json\n{\n \"schemaVersion\": 1,\n \"contract\": \"kungfu-buildchain-web-surface-deployment\",\n \"site\": \"libkungfu-dev\",\n \"channel\": \"preview\",\n \"alias\": \"sha-abcdef123456\",\n \"url\": \"https://sha-abcdef123456.preview.libkungfu.dev\",\n \"generatedAt\": \"2026-07-01T00:00:00.000Z\",\n \"publishedAt\": \"2026-07-01T00:00:00.000Z\",\n \"reproducible\": true,\n \"timestampPolicy\": \"ci-injected\",\n \"deterministicInputs\": [\n \"web-surface artifact content\",\n \"buildchain.toml web-surface channels/deploy/surfaces\",\n \"sourceSha\",\n \"artifactHash\",\n \"deployment channel\",\n \"deployment alias\"\n ],\n \"sourceRevision\": \"...\",\n \"timestampPolicyDetails\": {\n \"contract\": \"kungfu-buildchain-surface-timestamp-policy\",\n \"timestampFields\": [\"generatedAt\", \"publishedAt\", \"deployedAt\"],\n \"timestampFieldsParticipateInArtifactDigest\": false,\n \"artifactDigestScope\": \"web-surface artifactHash excludes deployment manifest timestamps\"\n },\n \"sourceSha\": \"...\",\n \"artifactHash\": \"...\",\n \"deployTarget\": \"libkungfu-dev-preview\",\n \"adapter\": \"aws-s3-cloudfront\",\n \"deployedAt\": \"2026-07-01T00:00:00.000Z\",\n \"retentionClass\": \"preview-sha-immutable\",\n \"expiresAt\": \"2026-09-29T00:00:00.000Z\",\n \"accessControl\": \"none\",\n \"edgeAuth\": \"none\",\n \"noindex\": true,\n \"secretRefs\": [\"AWS_ROLE_ARN\"],\n \"surfaceBindings\": [\n {\n \"surface\": \"hub\",\n \"channel\": \"preview\",\n \"alias\": \"sha-abcdef123456\",\n \"url\": \"https://sha-abcdef123456.preview.libkungfu.dev\",\n \"sourcePath\": \"/\",\n \"artifactPathPrefix\": \"\",\n \"viewerPathPrefix\": \"/\",\n \"directoryIndex\": \"index.html\",\n \"directoryIndexResolution\": true,\n \"canonicalUrl\": \"https://libkungfu.dev\",\n \"bucket\": \"libkungfu-dev-preview\",\n \"distributionId\": \"E-PREVIEW\",\n \"originPath\": \"\",\n \"objectPrefix\": \"sha-abcdef123456\",\n \"manifestKey\": \".buildchain/deployments/sha-abcdef123456/hub.json\",\n \"routing\": {\n \"contract\": \"kungfu-buildchain-web-surface-path-prefix-rewrite\",\n \"viewerPathPrefix\": \"/\",\n \"artifactPathPrefix\": \"\",\n \"objectPrefix\": \"sha-abcdef123456\",\n \"directoryIndex\": \"index.html\",\n \"directoryIndexResolution\": true\n },\n \"smokeUrls\": [\n {\n \"kind\": \"root\",\n \"requestPath\": \"/\",\n \"url\": \"https://sha-abcdef123456.preview.libkungfu.dev/\",\n \"required\": true\n }\n ],\n \"noindex\": true,\n \"accessControl\": \"none\"\n }\n ]\n}\n```\n\nDynamic adapters can also fill `runtimeId`, `configFingerprint`,\n`healthCheck`, `migrationState`, `rollbackPointer`, and\n`rollbackLimitations`. Buildchain records secret reference names only, never\nsecret values.\n\nThe timestamp policy is shared with package site bundles. Public deployment\nmanifests should expose real workflow generation/publication times while\nseparately declaring why the deployed artifact remains reproducible. For\nweb-surface deployment manifests, `artifactHash` is the static site artifact\ndigest and does not include deployment timestamp fields; the manifest itself\nstill records those fields for human and agent audit.\n\n## Deploy Plans\n\nDeploy planning is the default behavior. It plans the adapter steps and writes\nmanifest JSON, but it does not touch AWS, DNS, CloudFront, or deployment\ncredentials.\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode deploy-plan \\\n --cwd fixtures/web-surface-shaped \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --alias sha-aaaaaaaaaaaa\n```\n\nFor manifest-only output:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode manifest \\\n --cwd fixtures/web-surface-shaped \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --alias pr-123 \\\n --output .buildchain/web-surface-manifest.json\n```\n\nThe CLI emits GitHub outputs when `GITHUB_OUTPUT` is present:\n\n- `web-surface-channel`\n- `web-surface-alias`\n- `web-surface-url`\n- `web-surface-urls-json`\n- `web-surface-artifact-hash`\n- `web-surface-manifest-json`\n\nThe reusable workflow resolves that same effective channel before running the\ncaller build or verify command. Both steps receive\n`BUILDCHAIN_WEB_SURFACE_CHANNEL` (`preview`, `staging`, or `production`) and\n`BUILDCHAIN_PREVIEW_ALIAS` for previews. Compatibility aliases\n`BUILDCHAIN_SURFACE_CHANNEL` and `BUILDCHAIN_WEB_SURFACE_ALIAS` are also\nprovided. Callers should consume these variables instead of reconstructing the\nrelease-intent state machine from raw GitHub events.\nAn unapproved manual canary resolves to `staging`; a trusted manual dispatch\nwith `production-approved=true` resolves to `production` through the same path.\n\n## Explicit Apply\n\n`deploy-apply` and `cleanup-apply` are explicit execution modes for the\n`aws-s3-cloudfront` static-site adapter. They still default to `--dry-run true`;\nlive AWS mutation requires `--dry-run false`.\n\nDeploy apply syncs the artifact, writes the deployment manifest, and invalidates\nCloudFront when a distribution id is configured:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode deploy-apply \\\n --cwd fixtures/web-surface-shaped \\\n --channel staging \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --artifact-path dist \\\n --dry-run false \\\n --output .buildchain/web-surface-staging-apply.json\n```\n\n### Explicit cache classes\n\nThe S3/CloudFront adapter can declare cache metadata per deploy channel or\nsurface override:\n\n```toml\n[deploy.production]\nadapter = \"aws-s3-cloudfront\"\ncache_control_default = \"public,max-age=3600\"\ncache_control_mutable = \"public,max-age=300,must-revalidate\"\ncache_control_immutable = \"public,max-age=31536000,immutable\"\n```\n\n`cache_control_default` applies to the ordinary artifact sync.\n`cache_control_mutable` is then applied to HTML, JSON, XML, generated directory\nindex aliases, and the deployment manifest. `cache_control_immutable` applies\nto append-only publication roots discovered through the archive policy below.\nMutable metadata updates exclude those append-only roots, so HTML or JSON inside\nan immutable version archive keeps the immutable class.\nThe deploy plan, surface binding, apply operations, and deployment manifest all\nrecord the effective values. Existing consumers that omit these fields retain\ntheir prior upload behavior.\n\nCache metadata complements, rather than replaces, invalidation. Every deploy\nstill records and creates the exact surface wildcard and deployment-manifest\ninvalidation paths.\n\n### Immutable publication paths\n\nWhen a surface artifact contains `manifest.json` with\n`archivePolicy.contract = \"kungfu-buildchain-publication-archive-policy\"`,\nBuildchain treats every declared `publications[].versions[].immutablePath` as\nan append-only publication boundary. This applies identically to preview,\nstaging, and production adapters.\n\nThe adapter derives the protected archive root from those declared version\npaths and applies four ordered safeguards:\n\n1. every local immutable file is checked against an existing S3 object;\n2. missing files are uploaded with `aws s3 sync --no-overwrite` and a SHA-256\n checksum;\n3. every immutable file is checked again after upload, closing the race between\n the first check and the no-overwrite transfer;\n4. mutable site content keeps normal `sync --delete` behavior, but every parent\n or owning surface sync excludes the protected archive root from deletion.\n\nAn existing object with a different SHA-256 digest fails apply before mutable\ncontent is changed. Older objects without a stored S3 SHA-256 checksum are read\nand byte-hashed for compatibility. Directory-index alias writes are skipped\nunder protected roots so they cannot overwrite immutable route objects; viewer\nrequest rewriting remains the directory-index authority.\n\nThe deploy plan and manifest record `immutablePublication`,\n`mutableDeleteExcludes`, and the parent-surface coverage. Apply output records\n`immutablePreservation` plus every pre-check, no-overwrite sync, post-check, and\nmutable sync operation. Health output adds an `__immutable__` check proving that\nthe owning and parent surface syncs carried their required delete exclusions.\nThe runner must provide an AWS CLI version whose `s3 sync` supports\n`--no-overwrite`.\n\nFor multi-surface sites, each surface host is treated as a root-relative view\nof that surface's artifact path prefix. For example, a `buildchain` surface with\n`path = \"/buildchain/\"` and preview URL\n`https://buildchain-pr-29.preview.libkungfu.dev` syncs the artifact subtree\n`dist/buildchain/` to the preview object prefix `pr-29/buildchain`. A viewer\nrequest for `https://buildchain-pr-29.preview.libkungfu.dev/docs/` therefore\nresolves against the artifact's `dist/buildchain/docs/index.html`, not\n`dist/docs/index.html` and not the hub surface root. The deployment manifest\nrecords this as `routing.contract =\n\"kungfu-buildchain-web-surface-path-prefix-rewrite\"` with\n`viewerPathPrefix = \"/\"`, `artifactPathPrefix = \"buildchain\"`, and\n`directoryIndexResolution = true`.\n\nWhen a surface uses an S3 object prefix, directory-index routing must be handled\nat the viewer-request layer. By default, `directory_index_rewrite =\n\"buildchain\"` makes Buildchain install or update one CloudFront Function per\ndistribution before uploading payloads. The function rewrites any request path\nending in `/` to the corresponding `index.html`, so\n`https://buildchain-pr-29.preview.libkungfu.dev/` resolves to\n`pr-29/buildchain/index.html` and\n`https://buildchain-pr-29.preview.libkungfu.dev/docs/` resolves to\n`pr-29/buildchain/docs/index.html`. This keeps multi-host preview roots\ncompatible with S3 REST origins, where copying alias objects such as\n`pr-29/buildchain` or `pr-29/buildchain/` is not a reliable substitute for an\nedge rewrite.\n\nIf the distribution already has a viewer-request function that owns preview\nalias routing and surface-prefix routing, set `directory_index_rewrite =\n\"external\"` on the deploy channel or surface override. In that mode Buildchain\ndoes not create, update, or attach a generic directory-index function. Instead,\nthe deployment manifest records `directoryIndexRewrite = \"external\"` and\n`directoryIndexStrategy = \"external-viewer-request-function\"`, then the normal\nhealth check still verifies every required root and nested surface URL. This is\nthe correct contract for shared preview distributions such as\n`site-libkungfu-dev`, where a generic function cannot replace the existing\nprefix router.\n\nBuildchain still writes directory-index alias objects during apply as\ncompatibility evidence, but root correctness comes from the viewer-request\nrewrite contract, not from extensionless S3 keys. If Buildchain-managed mode\nfinds a distribution with a different viewer-request function, apply fails\nclosed and records that conflict in the apply result instead of silently serving\n403s. If CloudFront rejects `UpdateDistribution` because its optimistic-lock\nETag became stale, Buildchain re-reads the distribution and retries with the\nnew ETag at most twice. A concurrent update that already attached the intended\nfunction is accepted as converged; a different viewer-request function or any\nnon-ETag AWS error still fails immediately. The reusable workflow uploads\n`buildchain-web-surface-*-diagnostics`\nartifacts containing the apply and health JSON so the failing AWS operation or\nHTTP check is visible from the consumer run.\n\nIt can also execute a previously saved deploy plan. In that mode Buildchain\nrecomputes the local artifact hash before running AWS commands and fails closed\nif the artifact no longer matches the saved plan. Before any adapter operation,\nit also checks channel-aware JSON manifests in the artifact: top-level\n`canonicalHost` and declared `pages[].host` facts must belong to the plan's\nsurface hosts. A production plan therefore rejects staging/preview host facts\nbefore AWS apply:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode deploy-apply \\\n --cwd fixtures/web-surface-shaped \\\n --plan .buildchain/web-surface-staging-plan.json \\\n --dry-run false \\\n --output .buildchain/web-surface-staging-apply.json\n```\n\nCleanup apply deletes preview content, deletes the preview manifest, and\ninvalidates CloudFront:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode cleanup-apply \\\n --cwd fixtures/web-surface-shaped \\\n --event pull-request-closed \\\n --pull-number 123 \\\n --source-sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \\\n --dry-run false \\\n --output .buildchain/web-surface-cleanup-apply.json\n```\n\nCleanup apply can also execute a saved cleanup plan:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode cleanup-apply \\\n --cwd fixtures/web-surface-shaped \\\n --plan .buildchain/web-surface-cleanup-plan.json \\\n --dry-run false \\\n --output .buildchain/web-surface-cleanup-apply.json\n```\n\nApply output records the channel, alias, source SHA, artifact hash, target\nbucket, object prefix, manifest key, all surface URLs, all surface bindings, CDN\ninvalidation paths, actor/run metadata, immutable preservation evidence, and\nevery adapter operation with\n`executed`, `exitCode`, `stdout`, and `stderr`. If an operation fails,\nBuildchain records the failed operation, stops subsequent adapter operations,\nand exits non-zero after writing the result JSON. Buildchain records secret\nreference names only; the runner must provide the AWS CLI and credentials\noutside Buildchain, typically through OIDC and the declared `secret_refs`.\n\n## Production Preflight And Health\n\nProduction promotion is not just `deploy-apply --channel production`. Before a\nlive production apply, the reusable workflow runs:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode production-preflight \\\n --cwd fixtures/web-surface-shaped \\\n --plan .buildchain/web-surface-production-plan.json \\\n --execute true \\\n --output .buildchain/web-surface-production-preflight.json\n```\n\nThe production preflight checks that:\n\n- `channels.production` is canonical and indexable;\n- every surface has concrete production bucket and CloudFront targets;\n- every production surface URL is HTTPS;\n- the production AWS role can inspect the declared bucket and distribution;\n- CloudFront aliases cover every surface host, including product hosts such as\n `kfd.libkungfu.dev`;\n- DNS resolves for every surface host.\n\nAfter preview, staging, and production apply, the workflow runs:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode health-check \\\n --cwd fixtures/web-surface-shaped \\\n --result .buildchain/web-surface-production-apply.json \\\n --output .buildchain/web-surface-production-health.json\n```\n\nWhen the apply result includes CloudFront invalidations from the\n`aws-s3-cloudfront` adapter, health check waits for those invalidations to reach\n`Completed` before fetching public smoke URLs. This avoids reporting stale\nCloudFront 403 responses as deployment failures immediately after a successful\nS3 sync and invalidation request.\n\nAfter the invalidation wait, HTTP smoke checks also retry transient 403, 404, and\n5xx responses before recording failure. This keeps the public health signal\nstrict while absorbing short CloudFront edge propagation windows that can remain\nvisible after the invalidation waiter returns. By default, Buildchain attempts\neach HTTP smoke URL 12 times with a 10 second interval. Consumers can override\nthat window with `BUILDCHAIN_WEB_SURFACE_HEALTH_HTTP_RETRY_ATTEMPTS` and\n`BUILDCHAIN_WEB_SURFACE_HEALTH_HTTP_RETRY_INTERVAL_MS` when they need a\nsite-specific health policy.\n\nThe health check fetches every surface root URL and any nested smoke URLs\nrecorded in each surface binding. Nested smoke URLs are derived from nested HTML\nartifact files under the surface path prefix, with directory index resolution\nsuch as `dist/buildchain/docs/index.html` becoming `/docs/` on the buildchain\npreview host. If a surface has no nested HTML route, Buildchain records only\nthe root smoke URL; absence of nested HTML is not a deployment failure. When a\nnested route is present, the check fails closed if a deploy reports success but\nthat child page returns 403 or another unexpected status. Surface root checks\nexpect the apply result to have installed the directory-index rewrite, so a\nmulti-host preview root such as `https://buildchain-pr-29.preview.libkungfu.dev/`\nmust resolve to the surface `index.html`, not the bare prefix directory.\nProduction additionally fails if a response is unreachable, returns an\nunexpected status, or still sends `x-robots-tag: noindex`. The health check also\nverifies that each surface binding recorded a deployment manifest pointer. The\nproduction release passport embeds the deploy plan, apply result, production\npreflight, and health check so a reviewer or agent can audit why the production\nsite changed and whether every declared host and every existing nested route was\nactually covered.\n\nPublic channels can opt into object-level health with\n`deploy.<channel>.health_strategy = \"s3-object\"` when the deployment contract is\nalready covered by S3 manifest/object writes and public edge convergence is not\nthe right CI gate. This is useful for preview distributions whose viewer-request\nrouting is owned by an external CloudFront Function. The channel remains public;\nonly the CI health evidence changes from HTTP fetches to S3 `head-object`\nchecks.\n\nChannels declared with `access_control = \"managed-network\"` use a different\nhealth strategy by default. Buildchain does not require a GitHub-hosted runner\nto fetch a URL that is intentionally reachable only from an approved network.\nInstead, after a live apply the health check uses the deploy role to run S3\n`head-object` checks for each surface manifest and the smoke target object, such\nas the surface `index.html` or a nested `docs/index.html`. The check records\n`healthStrategy = \"s3-object\"` and skips the public HTTP fetch. Dry-run and\nplan-only checks fall back to deployment evidence: each surface must have a\nmanifest key, bucket, object prefix, `sync-static-artifact`, and\n`write-deployment-manifest` evidence, recorded as\n`healthStrategy = \"deployment-evidence\"`. If the workflow is running on a runner\nthat is allowed to reach the managed network, set\n`BUILDCHAIN_WEB_SURFACE_HEALTH_ALLOWED_RUNNER=true` or pass\n`--allowed-managed-network-runner true` to keep the normal HTTP smoke checks.\nSet `BUILDCHAIN_WEB_SURFACE_HEALTH_S3_OBJECTS=false` or pass\n`--managed-network-s3-object-verification false` only when an external channel\npolicy owns managed-network object verification.\n\n## Cleanup Plans\n\nPreview cleanup is an auditable cleanup contract. It can run as a dry-run plan,\nan apply-mode plan, or the explicit `cleanup-apply` executor with preview-only\ncredentials:\n\n```bash\nnode scripts/web-surface.mjs \\\n --mode cleanup-plan \\\n --cwd fixtures/web-surface-shaped \\\n --event pull-request-closed \\\n --pull-number 123 \\\n --aliases pr-123,sha-abcdef123456\n```\n\nThe plan and apply result keep mutable PR aliases and immutable SHA aliases\ndistinct so a caller can expire them with different retention windows. Closed-PR\ncleanup can derive `pr-N` from `--pull-number`, records the event, source SHA,\nactor, run id, preview bucket/prefix, manifest key, and adapter steps, and is an\nauditable no-op when no aliases are requested.\n\n## Reusable Workflow Shape\n\nBuildchain ships `.github/workflows/.web-surface.yml` for repositories that want\nthe standard PR review and promotion flow without copying bespoke glue:\n\n```yaml\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n build-command: npm run build\n verify-command: npm run check\n artifact-path: dist\n```\n\nThe reusable workflow maps GitHub events to Buildchain web-surface semantics:\n\n| Event | Buildchain behavior |\n| --- | --- |\n| `pull_request` opened / synchronized / reopened | validate, build, verify, and plan `preview` for `pr-N` |\n| `pull_request` closed | plan apply-mode cleanup for the `pr-N` preview alias and manifest |\n| `pull_request` closed for a matching release PR | verify the release intent and exact workflow-shell runtime, then wait for the protected `main` push; do not plan or apply production from `refs/pull/*/merge` |\n| `push` to `main` | validate, build, verify, plan and apply `staging` from the merged `main` SHA, then optionally open a production release PR |\n| `push` to `main` from a matching release PR merge | validate the associated release PR, plan `production`, and enter the configured GitHub Environment gate from the protected mainline ref |\n| `workflow_dispatch` with `production-approved = true` | plan `production` and enter the configured GitHub Environment gate |\n\nThe optional `buildchain-ref` input is empty by default. Empty keeps the\nweb-surface run on the stable Buildchain runtime selected by the reusable\nworkflow ref, normally `@v3`. A trusted maintainer can expose a\n`workflow_dispatch` input and pass it through for one-off train validation.\nSee [`runtime-train-validation.md`](runtime-train-validation.md) for the shared\ntrain protocol and notification template:\n\n```yaml\non:\n workflow_dispatch:\n inputs:\n buildchain-ref:\n description: \"Temporary Buildchain runtime ref for trusted manual validation\"\n required: false\n default: \"\"\n\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n buildchain-ref: ${{ inputs.buildchain-ref || '' }}\n build-command: pnpm run build\n verify-command: pnpm run check\n artifact-path: dist\n```\n\nOnly trusted `workflow_dispatch` runs by repository actors with write,\nmaintain, or admin permission may use a non-empty runtime override. Train refs\nsuch as `train/v3/v3.0/site-source-of-truth` are temporary validation refs, not\nstable production dependencies or pending merge targets. They may remain for a\nretention window after release as a fast-use and rollback channel, with old\ntrains handled by periodic Buildchain cleanup. The web-surface deployment\nmanifest records the resolved runtime SHA as `runtimeId` and the stable\nrollback ref as `rollbackPointer`.\n\nThe workflow deliberately plans and emits manifests by default. Live mutation is\nopt-in per channel:\n\n```yaml\npermissions:\n contents: read\n id-token: write\n pull-requests: write\n\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n build-command: pnpm run build\n verify-command: pnpm run check\n artifact-path: dist\n preview-apply: true\n preview-cleanup-apply: true\n preview-aws-role-arn: arn:aws:iam::123456789012:role/site-preview-github-actions\n staging-apply: true\n staging-aws-role-arn: arn:aws:iam::123456789012:role/site-staging-github-actions\n production-apply: false\n production-release-on-main: false\n production-aws-role-arn: arn:aws:iam::123456789012:role/site-production-github-actions\n production-environment: production\n release-feedback-actor-privacy: public\n```\n\nWhen enabled, Buildchain owns the full release apply state machine:\n\n- PR preview deploys run `deploy-apply --dry-run false` with the preview role\n and update a single idempotent PR comment.\n- Closed PR cleanup runs `cleanup-apply --dry-run false` with the preview role\n only.\n- Pushes to `main` run staging `deploy-apply --dry-run false` with the staging\n role, then write a staging release feedback passport artifact and comment the\n associated merged PR with the staging URL, source SHA, artifact identity, run\n URL, and failure context when apply did not complete.\n- When `production-release-on-main=true`, successful staging applies open or\n update a Buildchain-owned release PR from\n `release/<channel>-<short-sha>` to `main`, unless the current push already\n came from a matching release PR merge. The release PR contains one empty\n release-intent commit, carries `production-release-label`, and includes the\n staging URLs, source SHA, artifact hash, and staging release-passport artifact\n link in the PR body.\n- Production release PR handoff is permission-aware. Staging apply and staging\n health remain successful even when the repository or organization has\n GitHub Actions workflow permissions set to read-only. In that case Buildchain\n records `release-pr-status=permission-denied`, uploads the release PR handoff\n summary/body plus staging release passport artifacts, and writes an exact\n manual `gh pr create` command to the step summary. Set\n `fail-on-release-pr-error=true` only when PR creation failure should fail the\n whole workflow.\n- Release pull requests that match the configured production gate get a\n Buildchain review comment with the staging URL and production target, so the\n operator can verify staging from the PR page and use merge as the approval\n action. Consumers do not need to hand-write `gh pr create` or production\n release-intent glue.\n- `production-apply=true` enables the production capability; it does not request\n production for every event. Ordinary `main` pushes remain staging-only and\n can create or update a release PR. Production runs only when the capability\n is enabled and either:\n - a `workflow_dispatch` passes `production-approved=true` and the triggering\n actor currently has `write`, `maintain`, or `admin` permission; or\n - `production-release-on-main=true` and the `main` push commit is associated\n with exactly one same-repository, merged release pull request matching\n `production-release-label` and `production-release-head-prefix`.\n The production job is then gated by the configured GitHub Environment.\n- Before production artifact download, Buildchain assembles a managed sealed\n publication capability from the exact source/runtime SHAs, production plan\n and artifact hash, a qualifying pre-publication controller receipt, the\n trusted manual or reviewed-release-PR decision, production Environment and\n AWS role target, an ephemeral-runner receipt, and a fresh nonce. Production\n revalidates that capability against the downloaded plan before it downloads\n product bytes. The later AWS OIDC exchange remains the provider's final\n transaction-time authorization decision and fails closed before deploy apply.\n- The `publication-*-json` inputs are an advanced external-evidence\n compatibility path, not a prerequisite for the standard release-PR or trusted\n manual mechanisms. External evidence must now include\n `publication-gate-aggregate-json`; supplying only a partial set still fails\n closed.\n- Production apply writes a production release feedback passport artifact and\n comments the release PR with the production URL, source SHA, artifact\n identity, run URL, rollback pointer, and failure context when apply did not\n complete.\n\nThe feedback passport records the release responsibility chain:\n\n- human decision actor;\n- trigger actor;\n- runner/execution actor;\n- OIDC/deploy identity reference;\n- decision type and time;\n- source event, PR number, merge commit, and required gate label/head-prefix.\n\n`release-feedback-actor-privacy` controls actor values in the passport and\ncomments. `public` records GitHub actor names, `redacted` records only the actor\nrole, and `private-ref` records a stable private reference hash without exposing\nthe actor name.\n\nFor release-PR publishing, callers opt in explicitly:\n\n```yaml\njobs:\n web-surface:\n uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v3\n with:\n build-command: npm run build\n verify-command: npm run check\n artifact-path: dist\n production-apply: true\n production-release-on-main: true\n production-release-label: buildchain-release\n production-release-head-prefix: release/\n production-release-branch-channel: production\n production-release-pr-mode: auto\n production-aws-role-arn: arn:aws:iam::123456789012:role/site-production-github-actions\n production-environment: production\n```\n\nKeep `production-apply` enabled in the caller when the repository supports\nproduction. Buildchain derives whether the current event may use that\ncapability: an ordinary `main` push plans and applies staging, a matching\nreviewed release PR merge authorizes production, and an approved trusted manual\ndispatch authorizes production. Inputs from an untrusted event cannot turn that\ndecision on.\n\n`production-release-pr-mode` controls the post-staging handoff:\n\n| Mode | Behavior |\n| --- | --- |\n| `auto` | Generate release PR facts, create/update the empty release-intent branch and PR, and label it when token permissions allow. This is the default. |\n| `summary-only` | Generate and upload release PR facts, body, passport evidence, and manual command, but do not call the GitHub PR API. |\n| `disabled` | Record a disabled handoff and skip release PR API calls. |\n\nAutomatic release PR creation normally uses the workflow `github.token`.\nConsumers that cannot enable \"GitHub Actions can create and approve pull\nrequests\" globally should prefer the first-class GitHub App path. Pass the App\nclient id as an input and the private key as a reusable workflow secret; Buildchain\ncreates an installation token inside the release PR job and uses it only for the\nrelease-intent branch, PR, and label operations:\n\n```yaml\nwith:\n production-release-app-client-id: ${{ vars.KUNGFU_RELEASE_APP_CLIENT_ID }}\nsecrets:\n production-release-app-private-key: ${{ secrets.KUNGFU_RELEASE_APP_PRIVATE_KEY }}\n```\n\nWhen either side of the App configuration is missing, Buildchain does not hide\nthat behind the fallback `github.token`. The handoff JSON and job summary report\n`status: \"app-token-unavailable\"` with an `appTokenStatus` such as\n`missing-client-id`, `missing-private-key`, or `create-failed`, and still include\nthe manual PR creation command. If the repository intentionally uses another\nnarrow token, pass it through `production-release-pr-token`.\n\n`production-release-app-id` remains accepted as a deprecated alias for the input\nname, but the value should be the GitHub App client id. GitHub App numeric App\nIDs and client IDs are distinct, and Buildchain passes the value to\n`actions/create-github-app-token` through its non-deprecated `client-id` input so\nnew runs do not emit the deprecated `app-id` warning.\n\nIf a repository already generates its own narrow token or PAT, it can still pass\nthat through `production-release-pr-token`:\n\n```yaml\nwith:\n production-release-pr-token: ${{ secrets.BUILDCHAIN_RELEASE_PR_TOKEN }}\n```\n\nToken priority is: generated GitHub App installation token,\n`production-release-pr-token`, then `github.token`.\n\nThe merge button becomes the production approval only for a PR that carries the\nrelease label and comes from the configured source-branch prefix. Ordinary pull\nrequests merged into `main` deploy staging and open a release-intent PR; merging\nthat release PR triggers production. A release PR merge push does not open\nanother release PR.\n\nApply-only inputs are validated before the caller build or verification command\nruns. If the current event would run preview, staging, or production apply,\nmissing role inputs, a production apply without `production-approved=true` on\nmanual dispatch, or a manual actor without repository write authority fail\nimmediately instead of spending the build and plan jobs first.\n\nCallers must grant `id-token: write` for OIDC role assumption. Preview comments\nneed `pull-requests: write`. Automatic release PR creation also needs\n`contents: write`, `pull-requests: write`, and `issues: write` so Buildchain can\ncreate the release branch, write the empty release-intent commit, open or update\nthe PR, and apply the release label. If these permissions are unavailable,\nBuildchain degrades the release handoff instead of marking a successful staging\ndeployment as failed, unless `fail-on-release-pr-error=true`. The AWS roles remain caller-owned and\nshould be scoped by channel: preview can mutate only preview resources, staging\ncan mutate only staging resources, and production can mutate only production\nresources.\n\nApply mode fails closed when the deploy config still contains placeholder AWS\ntargets such as `pending-preview-distribution`. Planning can use placeholders\nfor dry-run-only design work, but live apply requires concrete bucket and\nCloudFront distribution identifiers.\n\n## Site Repository Shape\n\nA site repository can start with:\n\n```toml\nschema = 1\n\n[project]\ntype = \"web-surface\"\nname = \"site-kungfu-tech\"\nsite = \"kungfu-tech\"\n\n[lifecycle.build]\ncommand = \"pnpm run build\"\n\n[lifecycle.verify]\ncommand = \"pnpm run check\"\n```\n\nThen add the channel, deploy, retention, and security declarations shown above.\nThe project may use pnpm, npm, yarn, Vite, Astro, Next static export, Sphinx,\nMkDocs, CMake-generated docs, or another lifecycle command source. Buildchain\nonly needs a deterministic artifact path and the manifest facts.\n\n## Signed bootstrap installer publications\n\nA web-surface artifact that contains `installer-publication.json` opts into the\n`kungfu.bootstrap-installer-publication/v1` seam. During planning, Buildchain\nfails closed unless the manifest binds:\n\n- one signed-channel payload root and exact channel-file digest;\n- one source SHA and Release Passport;\n- unique platform/architecture entries with manifest, artifact, and archive\n digest roots; and\n- byte-identical friendly and immutable `install.sh` / `install.ps1` assets.\n\nThe resulting `kungfu-buildchain-installer-publication-evidence/v1` object and\nroot are welded into the deployment manifest. This does not make Buildchain the\nproduct installer authority: Kungfu generates the installer from its signed\nrelease channel, while the site owns only routes and presentation.\n\nThe site artifact should also declare the existing\n`kungfu-buildchain-publication-archive-policy` in its root `manifest.json`, with\nthe versioned installer directory as `immutablePath`. Buildchain then performs\npre-upload object digest checks, `--no-overwrite` upload, post-upload checks, and\nexcludes the immutable root from mutable deletion.\n\nLocal verification:\n\n```sh\nnode scripts/installer-publication.mjs \\\n --manifest dist/installer-publication.json \\\n --artifact-root dist\n```\n\nAfter preview, staging, or production apply, public read-back verifies exact\nbytes plus route semantics. Friendly routes require a revalidated cache policy\nwith `max-age` no greater than 300 seconds; immutable routes require at least\none year and the `immutable` directive:\n\n```sh\nnode scripts/installer-publication.mjs \\\n --manifest dist/installer-publication.json \\\n --public-readback\n```\n\nRedirects are not accepted as successful read-back. The evidence retains\ncontent type, cache control, ETag, object version id when exposed, size, digest,\nURL, channel root, source SHA, and Release Passport coordinates.\n\n## Boundaries\n\nBuildchain only performs live AWS mutations in explicit apply modes with\n`--dry-run false`. Production deploys must still be gated by a human-controlled\nworkflow, release, or GitHub Environment. DNS changes, staging auth\nimplementation, CloudFront distribution creation, and credential provisioning\nremain explicitly authorized infrastructure operations outside the web-surface\nartifact apply contract."
2464
2464
  },
2465
2465
  {
2466
2466
  "id": "fixture:infra-contract-aws-cdk-shaped",
@@ -2857,7 +2857,7 @@
2857
2857
  "path": "docs/auditable-demo.md",
2858
2858
  "plane": "verify",
2859
2859
  "exists": true,
2860
- "digest": "sha256:0e8cf2162c2e1f83cfe3a6171e3055f494cab5f03fdcbec3854e1793678e6908"
2860
+ "digest": "sha256:6af21e489bc3d2e9e2d01838926f1fbab6d74d92446d1f9cfc5eb08e090dd3a5"
2861
2861
  },
2862
2862
  {
2863
2863
  "id": "install",
@@ -2905,7 +2905,7 @@
2905
2905
  "path": "docs/github-governance-authority.md",
2906
2906
  "plane": "verify",
2907
2907
  "exists": true,
2908
- "digest": "sha256:b8c3d9ad53c2509bbd95804ccc31f6aa89989c3aadf3a2b75d9c73aef168b7e8"
2908
+ "digest": "sha256:304d0246d21bdd0f6a77183ac6839a5d1234d08d98bd5da9728c85a65b1ff7b7"
2909
2909
  },
2910
2910
  {
2911
2911
  "id": "release-candidate",
@@ -2953,7 +2953,7 @@
2953
2953
  "path": "docs/consumer-issue-reporting.md",
2954
2954
  "plane": "use",
2955
2955
  "exists": true,
2956
- "digest": "sha256:96804310360c02f408fee9d540385d875a40d09bef046c3df369228d311a1cdd"
2956
+ "digest": "sha256:20c33ab68e91227837626d1289d6c5540a318c8ff484d9a1f0ebe7dbe8626d02"
2957
2957
  },
2958
2958
  {
2959
2959
  "id": "infra-contract",
@@ -3049,7 +3049,7 @@
3049
3049
  "path": "docs/reusable-build-surface.md",
3050
3050
  "plane": "use",
3051
3051
  "exists": true,
3052
- "digest": "sha256:ea36ec722f811f5156735aa7471ed080cdc7e87d71c20c300243b226e233e675"
3052
+ "digest": "sha256:4409c8259c3613b51a1db809493e73d9b85a0d16d292a491046fd36770d98fb0"
3053
3053
  },
3054
3054
  {
3055
3055
  "id": "publish-transaction",
@@ -3065,7 +3065,7 @@
3065
3065
  "path": "docs/release-governance.md",
3066
3066
  "plane": "why",
3067
3067
  "exists": true,
3068
- "digest": "sha256:42a45f51db7ea11658431718835e1a430959db2a5cc80fc4651681502fe6fb7c"
3068
+ "digest": "sha256:8c31e6daccd40ea4e7cb08e8fa3eb2f238006e331621aed45eddcb80aee5532f"
3069
3069
  },
3070
3070
  {
3071
3071
  "id": "release-flow",
@@ -3089,7 +3089,7 @@
3089
3089
  "path": "docs/versioning.md",
3090
3090
  "plane": "why",
3091
3091
  "exists": true,
3092
- "digest": "sha256:4b40cb8675c111b7588297102a61cb241630f2b707a67a0384a6df7527b2c64c"
3092
+ "digest": "sha256:52f58a1a69ea924cb1fae11bb6a8def2b0c66cc4ab08416553cf680c213b5ded"
3093
3093
  },
3094
3094
  {
3095
3095
  "id": "web-surface-deployments",
@@ -3097,7 +3097,7 @@
3097
3097
  "path": "docs/web-surface-deployments.md",
3098
3098
  "plane": "use",
3099
3099
  "exists": true,
3100
- "digest": "sha256:ed4ff02dcb6f66118a29084784f25d8008ca472f59005b2e014133524706a9c5"
3100
+ "digest": "sha256:6b907af93c55ffd8274cd83ad87a45dd48eb9a01938cf419aafebb5557be5821"
3101
3101
  }
3102
3102
  ],
3103
3103
  "releaseModel": {