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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-bundle",
4
- "generatedAt": "2026-08-02T01:14:59.679Z",
5
- "publishedAt": "2026-08-02T01:14:59.679Z",
4
+ "generatedAt": "2026-08-02T03:31:24.457Z",
5
+ "publishedAt": "2026-08-02T03:31:24.457Z",
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": "99ce7a8de4cd35f81d743b63d723bee2d4c26f80",
22
+ "sourceRevision": "1d2c6f6f9571f0417063c9814e5ad0e6fd61bb81",
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.5-alpha.0",
40
+ "version": "3.0.5-alpha.2",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "source": {
@@ -446,7 +446,7 @@
446
446
  ],
447
447
  "maturity": "stable",
448
448
  "sourcePath": "docs/aws-us-elastic-runner-burst-plane.md",
449
- "digest": "sha256:13e8e212499e7af51884cff37d644998d2e0795f8c1bc28ac6566c4ddd2885e8",
449
+ "digest": "sha256:098bebc4d0d3d661fe8e0a32a14b153e8383ab8f8490d45913823f8b130f4c5d",
450
450
  "headings": [
451
451
  {
452
452
  "level": 1,
@@ -478,11 +478,21 @@
478
478
  "title": "Phase 2 contract",
479
479
  "anchor": "phase-2-contract"
480
480
  },
481
+ {
482
+ "level": 3,
483
+ "title": "Phase 2 campaign controller",
484
+ "anchor": "phase-2-campaign-controller"
485
+ },
481
486
  {
482
487
  "level": 2,
483
488
  "title": "Phase 3 contract",
484
489
  "anchor": "phase-3-contract"
485
490
  },
491
+ {
492
+ "level": 3,
493
+ "title": "Phase 3 lifecycle controller",
494
+ "anchor": "phase-3-lifecycle-controller"
495
+ },
486
496
  {
487
497
  "level": 2,
488
498
  "title": "Provider lifecycle",
@@ -494,7 +504,7 @@
494
504
  "anchor": "source-boundaries"
495
505
  }
496
506
  ],
497
- "markdown": "---\nstatus: draft\nperiod: 2026-07-28\ntheme: aws-us-elastic-runner-burst-plane\ndoc_type: design\nsource_level: local-files-and-provider-docs\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-30\n---\n\n# AWS US elastic runner burst plane\n\nThe local runner fleet remains the normal Kungfu build plane. This AWS US plane\nis an explicit, temporary overflow mechanism with sequential qualification:\n\n1. Linux CodeBuild proof of concept under USD 50.\n2. Windows EC2 one-job JIT runners.\n3. One bounded 24-hour EC2 Mac campaign.\n\nNo later phase can start from design intent alone. The preceding phase must\nproduce a qualifying source-bound receipt, actual cost, and zero-resource\ncleanup proof.\n\n## Phase 1 contract\n\n`aws-us-codebuild-linux` is a Linux-only runner preset. It requires the exact\nCodeBuild project name and resolves the runner label at workflow evaluation\ntime:\n\n```text\ncodebuild-<project>-<github.run_id>-<github.run_attempt>\n```\n\nThe GitHub-hosted `trust-gate` remains ahead of the matrix job. A fork pull\nrequest therefore fails or skips before the CodeBuild `runs-on` label exists as\na queued job. The dedicated consumer workflow is manual-only and does not add\nthe preset to dev, alpha, release, signing, notarization, deployment, or\npublication workflows.\n\nThe CodeBuild project is:\n\n- repository-scoped through an AWS CodeConnections GitHub App;\n- one ephemeral runner and one GitHub job per CodeBuild build;\n- outside a VPC, with no idle VM, NAT gateway, public ingress, SSH, or persistent\n workspace;\n- limited to two concurrent builds, 15 queued minutes, and 40 execution\n minutes;\n- allowed to write only its dedicated CloudWatch log group and request a token\n from its dedicated GitHub App connection;\n- forbidden from receiving signing, notarization, package publication, release,\n deploy, static AWS, long-lived GitHub, or SSH credentials.\n\nThe AWS-managed Ubuntu 24.04 standard image is the immutable base. Before a\nnative lifecycle starts, Buildchain installs the distribution's `gcc-14` and\n`g++-14` packages, exposes only per-job `gcc`/`g++` aliases, and downloads the\npinned Kitware CMake 3.31.6 archive after verifying its reviewed SHA256. The\nresolved package manager, versions, and CMake source digest are retained as\n`aws-native-toolchain.json`; no toolchain state survives the ephemeral\nCodeBuild execution. The toolchain adapter also retains the reviewed Amazon\nLinux 2023 `gcc14` path for compatible projects.\n\n## Cost and kill-switch envelope\n\nThe 2026-07-28 AWS Price List entry for\n`BUILD_GENERAL1_XLARGE` Linux in `us-east-1` is USD 0.0798 per build minute.\nThe contract rounds that rate up to USD 0.08. Twelve fully timed-out accepted\nbuilds reserve at most USD 38.40. At project concurrency two, the fail-closed\ncontroller can see at most two over-cap builds. The envelope conservatively\ncharges both race builds for their complete 40-minute timeout rather than\nassuming fast EventBridge delivery. The bounded CodeBuild maximum is therefore\nUSD 44.80, below the dedicated USD 49 budget and leaving USD 4.20 for the small\ncontroller, state, notification, and log charges.\n\nThe controller stores an idempotent build-id ledger, an atomic accepted-build\ncounter, and worst-case reservation in DynamoDB. Duplicate EventBridge delivery\ndoes not consume the bounded build allowance. It deletes the CodeBuild webhook\nand stops the triggering build when:\n\n- the accepted-build or reserved-cost cap is reached;\n- actual-cost telemetry is missing or more than six hours old;\n- actual CodeBuild spend reaches the budget;\n- AWS Budgets sends the 80% or 95% actual-spend notification;\n- the kill switch was already set.\n\nThe stack starts fail closed: it has no cost telemetry item and CloudFormation\ndoes not create the webhook. Before arming the webhook, the operator must write\na current Cost Explorer observation to the `COST` item, clear only the dedicated\ncontroller's killed state, and create the exact workflow-filtered webhook.\nRe-arming after any kill is a separate provider mutation and requires a new\nexplicit approval.\n\n## Qualification evidence\n\nEach successful job uploads `aws-runner-burst.json`, binding:\n\n- consumer repository, exact source SHA and ref;\n- GitHub run id, attempt and job;\n- CodeBuild project, build id, build ARN and initiator;\n- observation timestamp and canonical digest.\n\nLinux qualification requires:\n\n- at least 10 trusted exact-source successful jobs;\n- observed concurrency of at least two;\n- p95 queue-to-start of at most five minutes;\n- actual incremental AWS spend below USD 49;\n- no idle build and no active cloud residue.\n\n`node scripts/aws-runner-burst.mjs verify-linux --input <snapshot.json>` fails\nclosed when cost telemetry is missing/stale or any acceptance predicate is\nfalse.\n\n### Phase 1 recorded outcome\n\nThe Linux phase passed on 2026-07-29. Ten trusted exact-source Kungfu jobs\ncompleted successfully, including four overlapping two-job waves. The observed\nCodeBuild queue-to-start p95 was 0.696 seconds. All 16 paid executions,\nincluding six diagnostic runs, produced a conservative incremental compute\nupper bound of USD 25.798 by rounding every execution up to a whole minute at\nthe live AWS Price List rate.\n\nThe global webhook kill switch was exercised after the tenth qualifying job.\nThe project then reported no webhook or in-progress build, and the card-owned\nEC2 inventory was empty. AWS Billing and Cost Explorer still reported an\nestimated zero during their provider ingestion delay; the retained\nexecution-derived upper bound is therefore the immediate cost proof and must be\nreconciled with the eventual AWS line item in the final campaign report.\n\nThe source-bound evidence and deterministic phase receipt are:\n\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-input.json`\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-receipt.json`\n\n## Phase 2 contract\n\nThe Windows phase uses the explicit `aws-us-ec2-windows-jit` runner preset.\nIts caller supplies one bounded label under\n`aws-us-ec2-windows-jit-<qualification-id>`, and Buildchain resolves exactly\none Windows x64 native lane. The reusable trust gate still runs on a\nGitHub-hosted runner before the JIT label can select EC2.\n\nThe provider creates repository-level GitHub JIT configuration for\n`kungfu-systems/kungfu`. Its `labels` request must contain all four scheduling\nlabels: `self-hosted`, `Windows`, `X64`, and the card-scoped\n`aws-us-ec2-windows-jit-<qualification-id>` label. GitHub's JIT endpoint does\nnot infer the default OS and architecture labels when they are omitted. The\nencoded configuration is never placed in EC2 user data, a tag, a command log,\nor an artifact. The operator writes it to a card-scoped SSM SecureString under\n`/kungfu/burst/windows/`; the instance role can read and delete only that\nprefix. Bootstrap reads the value once, deletes the parameter immediately, and\npasses it only to the pinned runner process.\n\nEach runner uses:\n\n- Amazon's current Windows Server 2025 Full Base AMI, resolved through the\n public SSM AMI parameter and retained by exact AMI id and name;\n- `c7i.4xlarge`, one instance and one JIT runner per job;\n- GitHub Actions Runner 2.336.0 with the official Windows x64 SHA256;\n- PowerShell 7.6.4 with the official Windows x64 MSI SHA256 and Microsoft\n Authenticode verification;\n- pinned PortableGit 2.55.0.3 with its GitHub release SHA256, exposing only its\n `cmd` directory so POSIX compatibility tools cannot shadow Windows tools;\n- a Microsoft Authenticode-verified Visual Studio 2022 Build Tools bootstrap;\n- IMDSv2, an encrypted root volume with delete-on-termination, no inbound\n security-group rule, no key pair, and no warm Auto Scaling capacity.\n\nRunner diagnostics and a redacted lifecycle record are uploaded to the\nprovider's encrypted, private evidence bucket. The runner process exits after\none job, Windows shuts down, and EC2's instance-initiated shutdown behavior is\nset to `terminate`. A five-minute reaper terminates card-owned stopped or\nthree-hour-old instances and deletes only their dedicated JIT parameter.\n\nAt the 2026-07-29 AWS Price List rate of USD 1.45 per Windows\n`c7i.4xlarge` hour, six accepted three-hour instances reserve USD 26.10. The\ntwo-instance race envelope reserves another USD 8.70, producing a USD 34.80\nworst case below the dedicated USD 40 budget. Budget notifications at 80% and\n95% invoke the same card-scoped global kill switch.\n\nQualification requires one runner-profile smoke, three trusted exact-source\nfull Windows jobs, independent cancellation and timeout cleanup exercises, and\nzero repository runner, EC2 instance, disposable volume, min capacity, and\ndesired capacity within 15 minutes of the final job.\n\n## Phase 3 contract\n\nThe macOS phase uses the explicit `aws-us-ec2-macos-jit` runner preset. Its\ncaller supplies one unique label under\n`aws-us-ec2-macos-jit-<qualification-id>`, and Buildchain resolves exactly one\nnative macOS ARM64 lane with `self-hosted`, `macOS`, `ARM64`, and the unique\ncampaign label. The reusable trust gate remains ahead of the JIT runner.\n\nUnlike Windows, the Mac campaign deliberately reuses one instance on one\n`mac2.metal` Dedicated Host. The operator allocates exactly one tagged host,\nlaunches exactly one tagged instance, and sends three sequential SSM bootstrap\ncommands. Each command consumes and immediately deletes a distinct repository\nJIT SecureString under `/kungfu/burst/macos/`, then runs GitHub Actions Runner\n2.336.0 for exactly one job. The runner archive is pinned to the official\nmacOS ARM64 SHA256. No GitHub, signing, notarization, publication, SSH, or\nstatic AWS credential is admitted to the instance.\n\nThe instance uses the exact retained Amazon EC2 macOS AMI, IMDSv2, an encrypted\ndelete-on-termination root volume, no inbound security-group rule, and the\nAMI's preinstalled SSM Agent and AWS CLI v2. The three accepted jobs must bind\nto the same host id, instance id, AMI id, source SHA, and campaign. At least one\njob must exercise the full native lifecycle.\n\nAWS imposes a 24-hour minimum Dedicated Host allocation. The contract therefore\nkeeps the one host for at least 24 hours even if all three jobs finish earlier.\nAt the recorded USD 0.6498 hourly rate, the minimum commitment rounds to USD\n15.60. A 30-hour fail-closed ceiling rounds to USD 19.49, below the dedicated\nUSD 25 budget. A ten-minute reaper terminates an expired campaign instance and\nretries host release after the minimum allocation and Apple scrub constraints\nallow it. Budget notifications at 80% and 95% invoke the same card-scoped kill\nswitch.\n\nQualification requires three trusted exact-source one-job JIT runs on the one\nhost, including at least one full run, plus proof that:\n\n- the instance terminated and the encrypted disposable volume disappeared;\n- Apple host scrub completed;\n- the Dedicated Host was released between 24 and 30 hours after allocation;\n- the repository has no registered campaign runner;\n- AWS has no active campaign instance or allocated campaign host;\n- actual incremental spend remained below USD 25.\n\n## Provider lifecycle\n\nThe three infrastructure templates live under\n`infra/aws-us-elastic-runner-burst-plane/`. Creating a change set is the review\nboundary. Executing it, completing the GitHub App connection, creating or\nre-arming a webhook, allocating or releasing a Dedicated Host, writing cost\ntelemetry, dispatching paid jobs, operating a kill switch, and deleting a stack\nare all explicit provider mutations.\n\nThe reviewed Phase 1 provider sequence is below. It deliberately separates\nconnection creation, change-set inspection, stack execution, cost observation,\nand webhook arming:\n\n```bash\nburst_profile=us\nburst_region=us-east-1\nburst_stack=kungfu-buildchain-linux-burst-poc\nburst_project=kungfu-buildchain-linux-burst-poc\nburst_connection_name=kungfu-linux-burst-poc\nburst_change_set=phase1-linux-codebuild-poc\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections create-connection \\\n --provider-type GitHub \\\n --connection-name \"$burst_connection_name\" \\\n --tags Key=kungfu:owner,Value=buildchain \\\n Key=kungfu:plane,Value=aws-us-elastic-runner-burst\n```\n\nThe returned connection is `PENDING` until an operator completes the GitHub App\nhandshake in AWS. Read back `ConnectionStatus=AVAILABLE` before creating the\nchange set. Do not put an OAuth token or GitHub token in the shell:\n\nAWS CodeConnections connection names are limited to 32 characters, so keep the\nshorter connection name even when the stack and project use the longer\nBuildchain-specific name.\n\n```bash\nburst_connection_arn=REPLACE_WITH_AVAILABLE_CONNECTION_ARN\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections get-connection \\\n --connection-arn \"$burst_connection_arn\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation create-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\" \\\n --change-set-type CREATE \\\n --template-body \\\n file://infra/aws-us-elastic-runner-burst-plane/codebuild-poc.template.yml \\\n --capabilities CAPABILITY_IAM \\\n --parameters \\\n ParameterKey=GitHubConnectionArn,ParameterValue=\"$burst_connection_arn\" \\\n ParameterKey=ProjectName,ParameterValue=\"$burst_project\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait change-set-create-complete \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n```\n\nOnly after the change-set resource list and IAM diff are accepted:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation execute-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-create-complete \\\n --stack-name \"$burst_stack\"\n```\n\nArming requires a fresh, operator-observed CodeBuild cost value. `COST` is the\nonly mutable telemetry item and `CONTROL` is the only state cleared:\n\n```bash\nburst_table=$(\n aws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-stacks \\\n --stack-name \"$burst_stack\" \\\n --query \"Stacks[0].Outputs[?OutputKey=='StateTable'].OutputValue\" \\\n --output text\n)\nburst_observed_at=$(date -u +%s)\nburst_actual_usd=REPLACE_WITH_CURRENT_CODEBUILD_ACTUAL_USD\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb put-item \\\n --table-name \"$burst_table\" \\\n --item \"{\\\"pk\\\":{\\\"S\\\":\\\"COST\\\"},\\\"actual_usd\\\":{\\\"N\\\":\\\"$burst_actual_usd\\\"},\\\"observed_at\\\":{\\\"N\\\":\\\"$burst_observed_at\\\"}}\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb delete-item \\\n --table-name \"$burst_table\" \\\n --key '{\"pk\":{\"S\":\"CONTROL\"}}'\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild create-webhook \\\n --project-name \"$burst_project\" \\\n --filter-groups \\\n '[[{\"type\":\"EVENT\",\"pattern\":\"WORKFLOW_JOB_QUEUED\"},{\"type\":\"WORKFLOW_NAME\",\"pattern\":\"^AWS US Linux Burst Qualification$\"}]]'\n```\n\nThe immediate global kill is idempotent and targets only the dedicated project:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild delete-webhook \\\n --project-name \"$burst_project\"\n```\n\nAfter preserving the qualification evidence and proving no build is in\nprogress, rollback removes only the card-owned stack and connection:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation delete-stack \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-delete-complete \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections delete-connection \\\n --connection-arn \"$burst_connection_arn\"\n```\n\nPhase cleanup evidence must include:\n\n- CodeBuild batch/list results showing no in-progress build;\n- controller state and accepted-build ledger;\n- CodeBuild actual cost observation and its timestamp;\n- no EC2 instance, volume, launch template, Auto Scaling group, or dedicated\n host created by this phase;\n- the CodeBuild webhook deleted or the whole stack deleted.\n\n## Source boundaries\n\nThe design follows the current AWS CodeBuild GitHub Actions runner contract:\n`WORKFLOW_JOB_QUEUED` starts an ephemeral runner, the run id maps cancellation,\nand the build terminates after one job. It uses the current GitHub guidance to\nprefer ephemeral autoscaled self-hosted runners and to retain runner logs\nexternally. Provider documentation and the live AWS Price List query are the\nauthoritative external sources; this document is an auditable cache."
507
+ "markdown": "---\nstatus: draft\nperiod: 2026-07-28\ntheme: aws-us-elastic-runner-burst-plane\ndoc_type: design\nsource_level: local-files-and-provider-docs\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-08-02\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-08-01\n invisible_information: No hidden model checkpoint, parameters, or private training data were available.\n---\n\n# AWS US elastic runner burst plane\n\nThe local runner fleet remains the normal Kungfu build plane. This AWS US plane\nis an explicit, temporary overflow mechanism with sequential qualification:\n\n1. Linux CodeBuild proof of concept under USD 50.\n2. Windows EC2 one-job JIT runners.\n3. One bounded 24-hour EC2 Mac campaign.\n\nNo later phase can start from design intent alone. The preceding phase must\nproduce a qualifying source-bound receipt, actual cost, and zero-resource\ncleanup proof.\n\n## Phase 1 contract\n\n`aws-us-codebuild-linux` is a Linux-only runner preset. It requires the exact\nCodeBuild project name and resolves the runner label at workflow evaluation\ntime:\n\n```text\ncodebuild-<project>-<github.run_id>-<github.run_attempt>\n```\n\nThe GitHub-hosted `trust-gate` remains ahead of the matrix job. A fork pull\nrequest therefore fails or skips before the CodeBuild `runs-on` label exists as\na queued job. The dedicated consumer workflow is manual-only and does not add\nthe preset to dev, alpha, release, signing, notarization, deployment, or\npublication workflows.\n\nThe CodeBuild project is:\n\n- repository-scoped through an AWS CodeConnections GitHub App;\n- one ephemeral runner and one GitHub job per CodeBuild build;\n- outside a VPC, with no idle VM, NAT gateway, public ingress, SSH, or persistent\n workspace;\n- limited to two concurrent builds, 15 queued minutes, and 40 execution\n minutes;\n- allowed to write only its dedicated CloudWatch log group and request a token\n from its dedicated GitHub App connection;\n- forbidden from receiving signing, notarization, package publication, release,\n deploy, static AWS, long-lived GitHub, or SSH credentials.\n\nThe AWS-managed Ubuntu 24.04 standard image is the immutable base. Before a\nnative lifecycle starts, Buildchain installs the distribution's `gcc-14` and\n`g++-14` packages, exposes only per-job `gcc`/`g++` aliases, and downloads the\npinned Kitware CMake 3.31.6 archive after verifying its reviewed SHA256. The\nresolved package manager, versions, and CMake source digest are retained as\n`aws-native-toolchain.json`; no toolchain state survives the ephemeral\nCodeBuild execution. The toolchain adapter also retains the reviewed Amazon\nLinux 2023 `gcc14` path for compatible projects.\n\n## Cost and kill-switch envelope\n\nThe 2026-07-28 AWS Price List entry for\n`BUILD_GENERAL1_XLARGE` Linux in `us-east-1` is USD 0.0798 per build minute.\nThe contract rounds that rate up to USD 0.08. Twelve fully timed-out accepted\nbuilds reserve at most USD 38.40. At project concurrency two, the fail-closed\ncontroller can see at most two over-cap builds. The envelope conservatively\ncharges both race builds for their complete 40-minute timeout rather than\nassuming fast EventBridge delivery. The bounded CodeBuild maximum is therefore\nUSD 44.80, below the dedicated USD 49 budget and leaving USD 4.20 for the small\ncontroller, state, notification, and log charges.\n\nThe controller stores an idempotent build-id ledger, an atomic accepted-build\ncounter, and worst-case reservation in DynamoDB. Duplicate EventBridge delivery\ndoes not consume the bounded build allowance. It deletes the CodeBuild webhook\nand stops the triggering build when:\n\n- the accepted-build or reserved-cost cap is reached;\n- actual-cost telemetry is missing or more than six hours old;\n- actual CodeBuild spend reaches the budget;\n- AWS Budgets sends the 80% or 95% actual-spend notification;\n- the kill switch was already set.\n\nThe stack starts fail closed: it has no cost telemetry item and CloudFormation\ndoes not create the webhook. Before arming the webhook, the operator must write\na current Cost Explorer observation to the `COST` item, clear only the dedicated\ncontroller's killed state, and create the exact workflow-filtered webhook.\nRe-arming after any kill is a separate provider mutation and requires a new\nexplicit approval.\n\n## Qualification evidence\n\nEach successful job uploads `aws-runner-burst.json`, binding:\n\n- consumer repository, exact source SHA and ref;\n- GitHub run id, attempt and job;\n- CodeBuild project, build id, build ARN and initiator;\n- observation timestamp and canonical digest.\n\nLinux qualification requires:\n\n- at least 10 trusted exact-source successful jobs;\n- observed concurrency of at least two;\n- p95 queue-to-start of at most five minutes;\n- actual incremental AWS spend below USD 49;\n- no idle build and no active cloud residue.\n\n`node scripts/aws-runner-burst.mjs verify-linux --input <snapshot.json>` fails\nclosed when cost telemetry is missing/stale or any acceptance predicate is\nfalse.\n\n### Phase 1 recorded outcome\n\nThe Linux phase passed on 2026-07-29. Ten trusted exact-source Kungfu jobs\ncompleted successfully, including four overlapping two-job waves. The observed\nCodeBuild queue-to-start p95 was 0.696 seconds. All 16 paid executions,\nincluding six diagnostic runs, produced a conservative incremental compute\nupper bound of USD 25.798 by rounding every execution up to a whole minute at\nthe live AWS Price List rate.\n\nThe global webhook kill switch was exercised after the tenth qualifying job.\nThe project then reported no webhook or in-progress build, and the card-owned\nEC2 inventory was empty. AWS Billing and Cost Explorer still reported an\nestimated zero during their provider ingestion delay; the retained\nexecution-derived upper bound is therefore the immediate cost proof and must be\nreconciled with the eventual AWS line item in the final campaign report.\n\nThe source-bound evidence and deterministic phase receipt are:\n\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-input.json`\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-receipt.json`\n\n## Phase 2 contract\n\nThe Windows phase uses the explicit `aws-us-ec2-windows-jit` runner preset.\nIts caller supplies one bounded label under\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>`, and Buildchain\nresolves exactly one Windows x64 native lane. The reusable trust gate still\nruns on a GitHub-hosted runner before the JIT label can select EC2.\n\nThe provider creates repository-level GitHub JIT configuration for\n`kungfu-systems/kungfu`. Its `labels` request must contain all four scheduling\nlabels: `self-hosted`, `Windows`, `X64`, and the card-scoped\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>` label. The workflow\ndisplay title also carries both identities, allowing the launch controller to\nverify the queued run against its campaign plan. GitHub's JIT endpoint does\nnot infer the default OS and architecture labels when they are omitted. The\nencoded configuration is never placed in EC2 user data, a tag, a command log,\nor an artifact. The operator writes it to a card-scoped SSM SecureString under\n`/kungfu/burst/windows/`; the instance role can read and delete only that\nprefix. Bootstrap reads the value once, deletes the parameter immediately, and\npasses it only to the pinned runner process.\n\nEach runner uses:\n\n- Amazon's current Windows Server 2025 Full Base AMI, resolved through the\n public SSM AMI parameter and retained by exact AMI id and name;\n- `c7i.4xlarge`, one instance and one JIT runner per job;\n- GitHub Actions Runner 2.336.0 with the official Windows x64 SHA256;\n- PowerShell 7.6.4 with the official Windows x64 MSI SHA256 and Microsoft\n Authenticode verification;\n- pinned PortableGit 2.55.0.3 with its GitHub release SHA256, exposing only its\n `cmd` directory so POSIX compatibility tools cannot shadow Windows tools;\n- a Microsoft Authenticode-verified Visual Studio 2022 Build Tools bootstrap;\n- IMDSv2, an encrypted root volume with delete-on-termination, no inbound\n security-group rule, no key pair, and no warm Auto Scaling capacity.\n\nRunner diagnostics and a redacted lifecycle record are uploaded to the\nprovider's encrypted, private evidence bucket under the exact campaign, run,\nattempt, and instance identity. The runner process exits after one job, Windows\nshuts down, and EC2's instance-initiated shutdown behavior is set to\n`terminate`. A five-minute reaper terminates card-owned stopped or\nthree-hour-old instances and deletes only their dedicated JIT parameter.\n\nAt the 2026-07-29 AWS Price List rate of USD 1.45 per Windows\n`c7i.4xlarge` hour, each accepted instance reserves its complete three-hour\nUSD 4.35 fail-closed lifetime before `RunInstances`. A DynamoDB transaction\nbinds the exact campaign and source, creates an idempotent run ledger entry,\nand atomically refuses a seventh accepted instance. Six accepted instances\ntherefore reserve at most USD 26.10 under the dedicated USD 40 decision.\n\nThe campaign starts unarmed and expires within 24 hours. Its `CONTROL` record\ncan be created only once: a killed or expired campaign cannot be re-armed by\nthe campaign tool. A budget notification or any instance lifetime violation\npersists `KILLED` before cleanup, so later workflow dispatches fail before a\npaid launch. Reservations are never refunded: a controller crash, ambiguous\nlaunch, or successful launch all remain charged to the campaign, favoring a\nfalse stop over an accidental budget overrun.\n\nThe tag-filtered AWS Budget is defense in depth, not the authoritative launch\ngate. It is disabled by default because a linked account cannot activate a\ncost-allocation tag. Set `EnableTagFilteredBudget=true` only after the AWS\nOrganizations management account has activated `kungfu:provider` and a Cost\nExplorer readback proves that `windows-ec2-jit` spend is visible. The DynamoDB\nreservation cap remains mandatory in either mode.\n\nQualification requires one runner-profile smoke and three trusted exact-source\nfull Windows jobs all bound to the same campaign, independent cancellation and\ntimeout cleanup exercises, and zero repository runner, EC2 instance,\ndisposable volume, min capacity, and desired capacity within 15 minutes of the\nfinal job.\n\n### Phase 2 campaign controller\n\n`scripts/aws-windows-jit-campaign.mjs` is the one-shot operator boundary.\nWithout a mutation mode it emits the arm plan. `arm-campaign` requires the\ncampaign id, exact source SHA, and state table to be repeated as confirmations,\nthen creates `CONTROL` and `CAMPAIGN#<id>` with `attribute_not_exists`\nconditions. DynamoDB therefore refuses a second campaign in the same retained\nstate table. The operator can always use `kill-campaign`; there is deliberately\nno clear or re-arm operation.\n\nEvery `scripts/aws-windows-jit-controller.mjs --execute` call must provide the\nsame `--campaign-id`, `--confirm-campaign-id`, `--state-table`, and\n`--confirm-state-table`. After the GitHub, AMI, active-instance, SSM, and EC2\nDryRun checks pass, the controller\natomically reserves one run. Duplicate run-attempt-qualification identities,\nsource mismatch, expiry, `KILLED`, the seventh accepted instance, or a\nreservation over the USD 40 ceiling all fail closed before `RunInstances`.\n\nExample dry-run and arm boundary (do not execute without a new campaign budget\ndecision):\n\n```bash\nwindows_campaign=win-REPLACE_WITH_CAMPAIGN_ID\nwindows_source=REPLACE_WITH_EXACT_40_CHARACTER_SHA\nwindows_state_table=REPLACE_WITH_CAMPAIGN_STATE_TABLE\nwindows_expires_at=REPLACE_WITH_ISO_TIMESTAMP_WITHIN_24_HOURS\n\nnode scripts/aws-windows-jit-campaign.mjs plan-arm \\\n --campaign-id \"$windows_campaign\" \\\n --source-sha \"$windows_source\" \\\n --state-table \"$windows_state_table\" \\\n --expires-at \"$windows_expires_at\"\n\nnode scripts/aws-windows-jit-campaign.mjs arm-campaign \\\n --campaign-id \"$windows_campaign\" \\\n --confirm-campaign-id \"$windows_campaign\" \\\n --source-sha \"$windows_source\" \\\n --confirm-source-sha \"$windows_source\" \\\n --state-table \"$windows_state_table\" \\\n --confirm-state-table \"$windows_state_table\" \\\n --expires-at \"$windows_expires_at\"\n```\n\nArming creates a permanent one-shot control record and admits up to six paid\ninstances. Its rollback is fail-closed, not deletion: `kill-campaign` first\npersists `KILLED`, then publishes to the dedicated SNS topic so the reaper\nterminates active card-owned instances and removes their JIT parameters. The\ncommand is idempotent, but the operator must read back DynamoDB, EC2, SSM, and\nGitHub runners before treating cleanup as complete:\n\n```bash\nwindows_kill_topic=REPLACE_WITH_DEDICATED_KILL_SWITCH_TOPIC_ARN\n\nnode scripts/aws-windows-jit-campaign.mjs kill-campaign \\\n --campaign-id \"$windows_campaign\" \\\n --confirm-campaign-id \"$windows_campaign\" \\\n --source-sha \"$windows_source\" \\\n --confirm-source-sha \"$windows_source\" \\\n --state-table \"$windows_state_table\" \\\n --confirm-state-table \"$windows_state_table\" \\\n --kill-switch-topic \"$windows_kill_topic\" \\\n --confirm-kill-switch-topic \"$windows_kill_topic\" \\\n --reason operator-kill\n```\n\n## Phase 3 contract\n\nThe macOS phase uses the explicit `aws-us-ec2-macos-jit` runner preset. Its\ncaller supplies one unique label under\n`aws-us-ec2-macos-jit-<qualification-id>`, and Buildchain resolves exactly one\nnative macOS ARM64 lane with `self-hosted`, `macOS`, `ARM64`, and the unique\ncampaign label. The reusable trust gate remains ahead of the JIT runner.\n\nUnlike Windows, the Mac campaign deliberately reuses one instance on one\n`mac2.metal` Dedicated Host. The operator allocates exactly one tagged host,\nlaunches exactly one tagged instance, and sends three sequential SSM bootstrap\ncommands. Each command consumes and immediately deletes a distinct repository\nJIT SecureString under `/kungfu/burst/macos/`, then runs GitHub Actions Runner\n2.336.0 for exactly one job. The runner archive is pinned to the official\nmacOS ARM64 SHA256. No GitHub, signing, notarization, publication, SSH, or\nstatic AWS credential is admitted to the instance.\n\nThe instance uses the exact retained Amazon EC2 macOS AMI, IMDSv2, an encrypted\ndelete-on-termination root volume, no inbound security-group rule, and the\nAMI's preinstalled SSM Agent and AWS CLI v2. The three accepted jobs must bind\nto the same host id, instance id, AMI id, source SHA, and campaign. At least one\njob must exercise the full native lifecycle.\n\nAWS imposes a 24-hour minimum Dedicated Host allocation. The contract therefore\nkeeps the one host for at least 24 hours even if all three jobs finish earlier.\nAt the recorded USD 0.6498 hourly rate, the minimum commitment rounds to USD\n15.60. A 30-hour fail-closed ceiling rounds to USD 19.49, below the dedicated\nUSD 25 budget. A ten-minute reaper terminates an expired campaign instance and\nretries host release after the minimum allocation and Apple scrub constraints\nallow it. Budget notifications at 80% and 95% invoke the same card-scoped kill\nswitch.\n\nQualification requires three trusted exact-source one-job JIT runs on the one\nhost, including at least one full run, plus proof that:\n\n- the instance terminated and the encrypted disposable volume disappeared;\n- Apple host scrub completed;\n- the Dedicated Host was released between 24 and 30 hours after allocation;\n- the repository has no registered campaign runner;\n- AWS has no active campaign instance or allocated campaign host;\n- actual incremental spend remained below USD 25.\n\n### Phase 3 lifecycle controller\n\n`scripts/aws-macos-jit-controller.mjs` is the operator boundary for the paid\ncampaign. It has three explicit mutation modes:\n\n- `launch-campaign` binds the exact repository source, AMI, availability zone,\n tagged Dedicated Host, and reusable instance. It rejects pre-existing Mac\n capacity and requires successful `AllocateHosts` and `RunInstances` DryRuns\n before either real call.\n- `run-job` binds one queued exact-source GitHub job to the existing campaign\n host and instance. It writes the repository JIT configuration through a\n mode-0600 temporary file into a distinct SSM SecureString, sends only the\n credential-free bootstrap through SSM, and removes the parameter plus runner\n registration if command delivery fails.\n- `close-campaign` refuses execution before the provider's 24-hour minimum,\n verifies the encrypted delete-on-termination root volume, removes scoped JIT\n residue, terminates the exact instance, and requires a `ReleaseHosts` DryRun\n before release. If Apple host scrubbing is still in progress, it reports\n `release-pending`; the ten-minute card-scoped reaper remains the bounded\n retry path.\n\nEvery execute mode requires the exact source SHA and campaign id to be repeated\nthrough `--confirm-source-sha` and `--confirm-campaign-id`. `run-job` also\nrequires `--confirm-run-id`. Omitting `--execute` emits a deterministic plan\nwithout changing AWS or GitHub state.\n\n## Provider lifecycle\n\nThe three infrastructure templates live under\n`infra/aws-us-elastic-runner-burst-plane/`. Creating a change set is the review\nboundary. Executing it, completing the GitHub App connection, creating or\nre-arming a webhook, allocating or releasing a Dedicated Host, writing cost\ntelemetry, dispatching paid jobs, operating a kill switch, and deleting a stack\nare all explicit provider mutations.\n\nThe reviewed Phase 1 provider sequence is below. It deliberately separates\nconnection creation, change-set inspection, stack execution, cost observation,\nand webhook arming:\n\n```bash\nburst_profile=us\nburst_region=us-east-1\nburst_stack=kungfu-buildchain-linux-burst-poc\nburst_project=kungfu-buildchain-linux-burst-poc\nburst_connection_name=kungfu-linux-burst-poc\nburst_change_set=phase1-linux-codebuild-poc\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections create-connection \\\n --provider-type GitHub \\\n --connection-name \"$burst_connection_name\" \\\n --tags Key=kungfu:owner,Value=buildchain \\\n Key=kungfu:plane,Value=aws-us-elastic-runner-burst\n```\n\nThe returned connection is `PENDING` until an operator completes the GitHub App\nhandshake in AWS. Read back `ConnectionStatus=AVAILABLE` before creating the\nchange set. Do not put an OAuth token or GitHub token in the shell:\n\nAWS CodeConnections connection names are limited to 32 characters, so keep the\nshorter connection name even when the stack and project use the longer\nBuildchain-specific name.\n\n```bash\nburst_connection_arn=REPLACE_WITH_AVAILABLE_CONNECTION_ARN\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections get-connection \\\n --connection-arn \"$burst_connection_arn\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation create-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\" \\\n --change-set-type CREATE \\\n --template-body \\\n file://infra/aws-us-elastic-runner-burst-plane/codebuild-poc.template.yml \\\n --capabilities CAPABILITY_IAM \\\n --parameters \\\n ParameterKey=GitHubConnectionArn,ParameterValue=\"$burst_connection_arn\" \\\n ParameterKey=ProjectName,ParameterValue=\"$burst_project\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait change-set-create-complete \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n```\n\nOnly after the change-set resource list and IAM diff are accepted:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation execute-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-create-complete \\\n --stack-name \"$burst_stack\"\n```\n\nArming requires a fresh, operator-observed CodeBuild cost value. `COST` is the\nonly mutable telemetry item and `CONTROL` is the only state cleared:\n\n```bash\nburst_table=$(\n aws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-stacks \\\n --stack-name \"$burst_stack\" \\\n --query \"Stacks[0].Outputs[?OutputKey=='StateTable'].OutputValue\" \\\n --output text\n)\nburst_observed_at=$(date -u +%s)\nburst_actual_usd=REPLACE_WITH_CURRENT_CODEBUILD_ACTUAL_USD\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb put-item \\\n --table-name \"$burst_table\" \\\n --item \"{\\\"pk\\\":{\\\"S\\\":\\\"COST\\\"},\\\"actual_usd\\\":{\\\"N\\\":\\\"$burst_actual_usd\\\"},\\\"observed_at\\\":{\\\"N\\\":\\\"$burst_observed_at\\\"}}\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb delete-item \\\n --table-name \"$burst_table\" \\\n --key '{\"pk\":{\"S\":\"CONTROL\"}}'\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild create-webhook \\\n --project-name \"$burst_project\" \\\n --filter-groups \\\n '[[{\"type\":\"EVENT\",\"pattern\":\"WORKFLOW_JOB_QUEUED\"},{\"type\":\"WORKFLOW_NAME\",\"pattern\":\"^AWS US Linux Burst Qualification$\"}]]'\n```\n\nThe immediate global kill is idempotent and targets only the dedicated project:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild delete-webhook \\\n --project-name \"$burst_project\"\n```\n\nAfter preserving the qualification evidence and proving no build is in\nprogress, rollback removes only the card-owned stack and connection:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation delete-stack \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-delete-complete \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections delete-connection \\\n --connection-arn \"$burst_connection_arn\"\n```\n\nPhase cleanup evidence must include:\n\n- CodeBuild batch/list results showing no in-progress build;\n- controller state and accepted-build ledger;\n- CodeBuild actual cost observation and its timestamp;\n- no EC2 instance, volume, launch template, Auto Scaling group, or dedicated\n host created by this phase;\n- the CodeBuild webhook deleted or the whole stack deleted.\n\n## Source boundaries\n\nThe design follows the current AWS CodeBuild GitHub Actions runner contract:\n`WORKFLOW_JOB_QUEUED` starts an ephemeral runner, the run id maps cancellation,\nand the build terminates after one job. It uses the current GitHub guidance to\nprefer ephemeral autoscaled self-hosted runners and to retain runner logs\nexternally. Provider documentation and the live AWS Price List query are the\nauthoritative external sources; this document is an auditable cache."
498
508
  },
499
509
  {
500
510
  "id": "manual:binary-distribution",
@@ -2934,7 +2944,7 @@
2934
2944
  ],
2935
2945
  "maturity": "stable",
2936
2946
  "sourcePath": "docs/publish-transaction.md",
2937
- "digest": "sha256:9b830727e5b0d2192c937f19106c11ea5064dde77e30d290825f343df47f291b",
2947
+ "digest": "sha256:3a9ef099d5d93d9b558c7b6a0aa4403dfcce1479ce5d6fc36ecb3e34cdd5bb6d",
2938
2948
  "headings": [
2939
2949
  {
2940
2950
  "level": 1,
@@ -2997,7 +3007,7 @@
2997
3007
  "anchor": "build-images-follow-up"
2998
3008
  }
2999
3009
  ],
3000
- "markdown": "---\nstatus: active\nperiod: ongoing\ntheme: buildchain-publish-transaction\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-31\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-31\n invisible_context: not asserted\n---\n\n# Publish Transaction\n\nBuildchain release promotion is not just tag movement. A release can also publish\nexternal artifacts: npm packages, Python wheels, OCI images, binary archives,\nmetadata manifests, or site deployment records. Those side effects are harder\nthan Git refs because most registries are append-only: a failed rerun must know\nwhich artifacts already exist, which are still missing, and whether any existing\nartifact conflicts with the release material.\n\nBuildchain v3 models that work as a release transaction.\n\n## Why This Exists\n\nThe old ABV workflow made Git refs the visible release authority. That was\nenough when \"release\" meant \"create a version commit, move tags, and let\ndownstream jobs react.\" It is not enough when a single publish run must also\nupload packages and images.\n\nThe failure mode to avoid is:\n\n1. publish an external artifact;\n2. fail before moving the exact release tag or floating channel refs;\n3. rerun from a new job id with no memory of the artifact;\n4. either republish something different or move refs without proving the\n already-published artifact matches the release.\n\nThe transaction gives reruns a stable identity and a machine-readable state so\nBuildchain can resume safely. The identity is:\n\n```text\nrepository + version + source_sha + target_ref\n```\n\nIt is not the GitHub Actions run id.\n\n## Durable State\n\n`actions/promote-buildchain-ref` stores release transaction state in a\nmachine-managed Git branch:\n\n```text\nrefs/heads/buildchain/release-state/<version>\n```\n\nThe branch contains:\n\n```text\nstate.json\nevidence.json # present after publish evidence exists\nsealed-bundle/<candidate-root>/files/** # present for build-once publication\n```\n\nThe local `.buildchain/release-state/...` and\n`.buildchain/release-evidence/...` files are working copies. They are useful for\nlocal inspection and lifecycle commands, but they are not the durable truth for\nGitHub-hosted reruns. On action startup, Buildchain reads the durable state ref\nfirst, restores the local working copies, and only then decides whether to\npublish, repair, or finalize.\n\nEvery meaningful state transition is written to the durable ref before public\nrelease refs move. Release-state GitHub API reads and writes use retry/backoff\nfor transient service failures such as HTTP 5xx responses, connection resets,\ntimeouts, and \"other side closed\" socket failures. If the durable write still\ncannot be persisted after retries, the action fails closed.\n\nFor a sealed publication, `state.json` also carries the typed sealed-bundle\nmanifest, its candidate root, publication milestones, stable\n`publication_state`, and an exact resume command. The durable ref stores every\ndeclared bundle file as binary Git blobs before the publish lifecycle starts.\nA fresh runner restores those blobs into\n`.buildchain/recovered-publication/<version>/`, verifies every size and SHA-256\nagainst the manifest, and only then supplies the recovered paths to the publish\nlifecycle. A missing or changed tarball, PDF, source bundle, or manifest fails\nbefore registry publication.\n\nDurable release-state refs reserve their exact version even when the public exact\ntag was never created. If a later machine run sees a failed or repair-required\nstate for `vX.Y.Z-alpha.N` and cannot resume it with the same transaction\nidentity, alpha version selection must advance to the next prerelease instead\nof reusing or overwriting that failed transaction slot.\n\n## Lifecycle\n\nRepositories declare publish work in `.buildchain/buildchain.toml`:\n\n```toml\n[publish]\nmode = \"publish-final-version\"\nauth = \"trusted-publishing\"\ndist_tag = \"latest\"\npackage_set_order = \"platforms-first-main-last\"\nmain_package = \"@kungfu-tech/libnode\"\n\n[lifecycle.publish]\ncommands = [\n \"python scripts/publish_wheels.py\",\n \"node scripts/publish-images.mjs\",\n \"node scripts/write-publish-evidence.mjs\",\n]\n```\n\n`actions/promote-buildchain-ref` runs `lifecycle.publish` only when\n`publish-transaction: \"true\"` is set or when a `publish-command` input is\nprovided. The action sets:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_SEALED_BUNDLE_ROOT\nBUILDCHAIN_SEALED_NPM_TARBALL\nBUILDCHAIN_SEALED_NPM_INTEGRITY\nBUILDCHAIN_SEALED_NPM_SHA256\nBUILDCHAIN_REQUIRED_ARTIFACTS\nBUILDCHAIN_PUBLISH_MODE\nBUILDCHAIN_PUBLISH_AUTH\nBUILDCHAIN_NPM_DIST_TAG\nBUILDCHAIN_PACKAGE_SET_ORDER\nBUILDCHAIN_PACKAGE_SET_MAIN_PACKAGE\n```\n\nBuildchain itself uses this contract for npm publishing:\n\n```toml\n[lifecycle.publish]\ncommand = \"node scripts/npm-publish-transaction.mjs\"\n```\n\nThat script validates that `package.json` matches `BUILDCHAIN_VERSION`, runs\n`npm publish --access public --tag <BUILDCHAIN_NPM_DIST_TAG>` through npm Trusted\nPublishing, and writes npm artifact evidence before the promotion action moves\npublic refs. When the sealed npm variables are present, the script verifies and\npublishes that exact `.tgz` file. It does not run `npm pack` again.\n\n`BUILDCHAIN_RELEASE_MATERIAL_SHA` is the source material whose artifacts must\nmatch. `BUILDCHAIN_PUBLISH_TOOLING_SHA` identifies the publishing code. A repair\nrun may change tooling, but material drift fails closed.\n\n## Post-Publish Requirements And Artifact Provenance\n\n`publish-required-artifacts-json` is a pre-publish family declaration, not a\nrequest to guess registry digests. A descriptor must include `kind + name`; it\nmay omit `ref` and `digest`. The action resolves a missing `ref` to the exact\n`BUILDCHAIN_VERSION`. Registries whose exact refs add a stable prefix or suffix\nmay instead declare a `ref_template` containing exactly one `{version}`, such\nas `v{version}`. The template is expanded only after exact version selection,\nso a resumed alpha transaction receives the newly selected prerelease rather\nthan the checked-out version. Declaring both `ref` and `ref_template`, using\nanother placeholder, or leaving unmatched braces fails before\n`lifecycle.publish`. The action exports the normalized exact refs as\n`BUILDCHAIN_REQUIRED_ARTIFACTS`, runs `lifecycle.publish`, and then requires the\nfinal evidence to contain every exact member with a non-empty digest. Existing\ncallers may continue supplying exact refs and digests.\n\nOCI publishers can opt into strict per-artifact provenance by adding\n`action: built` or `action: reused`. Those artifacts carry two separate\ncoordinates:\n\n- `content`: the version, ref, source SHA, and material SHA that produced the\n immutable content;\n- `release`: the exact current version/ref, target ref, source SHA, and release\n material SHA that bind that content into this release.\n\nThis distinction permits truthful cross-version reuse without claiming that an\nold OCI config was rebuilt from current material. For an OCI artifact with an\naction, final evidence also requires `platform`, positive `contract_major`, and\n`verification` containing a public manifest result, exact ref and digest,\nplatform, contract major, optional parent digest, evidence location, and a\npassed named smoke policy. Buildchain cross-checks those values against the\nartifact and current transaction. Missing family members and ref, digest,\ncontent, release, or verification conflicts enter `repair_required` before\npublic refs move.\n\nExample reused OCI evidence entry (the pre-publish requirement may omit\n`ref`, `digest`, `release`, and the observed verification values):\n\n```json\n{\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/base-linux\",\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"action\": \"reused\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"content\": {\n \"version\": \"1.1.9\",\n \"ref\": \"1.1.9\",\n \"source_sha\": \"<source-sha>\",\n \"material_sha\": \"<material-sha>\"\n },\n \"release\": {\n \"version\": \"1.2.0-alpha.3\",\n \"ref\": \"1.2.0-alpha.3\",\n \"target_ref\": \"alpha/v1/v1.2\",\n \"source_sha\": \"<current-source-sha>\",\n \"material_sha\": \"<current-material-sha>\"\n },\n \"verification\": {\n \"public_manifest\": true,\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"evidence\": \"registry-inspect.json\",\n \"smoke\": {\n \"policy\": \"manifest-contract\",\n \"passed\": true,\n \"evidence\": \"smoke.json\"\n }\n }\n}\n```\n\n## Release Modes And Auth\n\nBuildchain distinguishes two npm release modes:\n\n| Mode | Use | Auth | npm operation |\n| --- | --- | --- | --- |\n| `publish-final-version` | normal alpha or stable publication | `trusted-publishing` | `npm publish --tag <alpha|vX.Y-alpha|latest>` |\n| `promote-existing-version` | same-version alpha-to-latest recovery | `npm-token` | `npm dist-tag add <pkg>@<version> latest` |\n\nThe normal libnode path is `publish-final-version`: publish an alpha package set\nsuch as `22.22.3-kf.3-alpha.0` with the `alpha` dist-tag, then publish a\ndistinct final package set such as `22.22.3-kf.3` with the `latest` dist-tag.\nGitHub-hosted npm Trusted Publishing can authorize those `npm publish` calls\nwhen the workflow grants `id-token: write`.\n\n`promote-existing-version` is deliberately separate. npm Trusted Publishing does\nnot authorize arbitrary registry-management operations such as `npm dist-tag\nadd`; it authorizes publish-time package provenance. Therefore same-version\npromotion must declare `auth = \"npm-token\"`. Buildchain runs an npm token\npreflight with `npm whoami` before it writes any release transaction state or\nmoves a dist-tag. Missing token auth fails early with a contract error instead\nof a late `E401` after publish evidence has started to move.\n\nFor package sets, `package_set_order = \"platforms-first-main-last\"` makes the\nmain package the visibility gate. Platform package side effects are planned or\nretried first, and the main package or main dist-tag move happens last.\n\nWhen the transaction reaches `complete`, `actions/promote-buildchain-ref`\ngenerates `.buildchain/release-passport/buildchain.release.json` and persists\nthe `release-passport/*` files into the durable `buildchain/release-state/...`\nref. The passport is the stable release artifact for agents and people: it\nlinks the package set, npm publish evidence, dist-tag evidence, build summary,\nplatform artifact manifests, trusted publishing metadata, release-state ref,\ndurable release-state SHA, and transaction result in one schema. Consumer\nrepositories can set `release-passport-product-name` so the passport names their\nproduct instead of the Buildchain default.\n\n## Evidence\n\nThe publish lifecycle must write JSON evidence. Buildchain validates common\nfields and required artifact identities before final refs move.\n\n```json\n{\n \"schema\": 1,\n \"version\": \"2.0.11\",\n \"channel\": \"release\",\n \"source_sha\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n \"release_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"target_ref\": \"release/v3/v3.0\",\n \"release_material_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"publish_tooling_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"artifacts\": [\n {\n \"group\": \"node\",\n \"kind\": \"npm\",\n \"name\": \"@kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n },\n {\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n }\n ]\n}\n```\n\nThe generic contract is intentionally small:\n\n- `version`, `channel`, `source_sha`, `release_sha`, and `target_ref` must match\n the promotion run;\n- dist-tag promotion evidence is written beside the publish evidence as\n `dist-tag-evidence.json` and is referenced from the generated passport;\n- required artifacts must appear in evidence;\n- evidence used by a GitHub-hosted rerun must either be stored in the durable\n state ref or be reconstructed by a machine-verifiable consumer command;\n- existing artifacts with the same identity and digest are accepted on rerun;\n- missing artifacts can be published by the next run;\n- an existing artifact with a different digest puts the transaction into\n `repair_required`.\n\nArtifact identity is `group + kind + name + ref`. A required artifact that omits\n`group` matches any group with the same `kind + name + ref`.\n\n## Registry Truth Contract\n\nBuildchain owns transaction orchestration, finalization ordering, durable state,\nand generic evidence validation. It does not embed registry clients for npm,\nPyPI, GHCR/OCI, GitHub Releases, S3, Conan, CMake packaging, or project-specific\ndownload pages.\n\nConsumer `lifecycle.publish` commands own registry truth. A valid consumer stage\nmust be idempotent and machine-verifiable:\n\n- inspect the target registry before publishing;\n- accept an existing exact artifact only when version, identity, digest, and\n release-source binding match;\n- publish missing required artifacts;\n- reject conflicting existing artifacts and write evidence that lets Buildchain\n move the transaction to `repair_required`;\n- write `BUILDCHAIN_PUBLISH_EVIDENCE` after every successful inspect/publish\n cycle;\n- leave floating aliases such as npm dist-tags, PyPI stable markers, OCI\n floating tags, GitHub Release \"published\" status, or download-page stable\n links to a finalization step after Buildchain evidence validation.\n\nThe first-class adapter surface is command-based. Projects may wrap npm, PyPI,\nGHCR/OCI, GitHub Release assets, archives, SBOMs, provenance, or checksums\nhowever they need, as long as they emit the common evidence contract.\n\n## States\n\nThe state machine is:\n\n```text\nprepared -> sealed -> publishing -> published -> finalizing -> complete\n | | |\n v v v\n publish_failed repair_required failed_permanently\n |\n v\n abandoned\n```\n\nSupported states:\n\n| State | Meaning |\n| -------------------- | -------------------------------------------------------------------------------------------------------- |\n| `prepared` | Transaction identity was created, but publish has not started. |\n| `sealed` | Exact candidate bytes and manifest are verified and durable; registry publication has not started. |\n| `publishing` | Publish lifecycle is running or may have been interrupted. |\n| `publish_failed` | Publish command failed before valid evidence was produced. |\n| `published` | Evidence is valid; refs have not necessarily finalized. |\n| `finalizing` | Buildchain is moving exact/floating refs or needs a later run to do it. |\n| `complete` | Required evidence is valid and refs have finalized. |\n| `repair_required` | Existing evidence or artifact state conflicts with expected release material. |\n| `abandoned` | A human or controlled process abandoned this transaction, usually because a newer version supersedes it. |\n| `failed_permanently` | Recovery should not continue without explicit override. |\n\n`repair_required`, `abandoned`, and `failed_permanently` fail closed unless the\noperator passes an explicit override. That override is for controlled repair\nruns, not normal retry behavior.\n\n`publication_state` is a stable operator-facing projection over the detailed\ntransaction state. Its successful progression is\n`prepared -> sealed -> package-published -> alpha-complete` for Alpha or\n`release-complete` for stable release. If npm succeeds but GitHub Release work\nis interrupted, the durable record remains `package-published`; the next run\nreuses the exact npm evidence and sealed release assets instead of rebuilding\nor republishing them.\n\n## Ref Ordering\n\nWhen publish transactions are enabled, promotion order is:\n\n1. verify target source and governance;\n2. create or reuse the version-state release commit;\n3. acquire or resume the release transaction;\n4. for build-once publication, verify and persist the complete sealed bundle;\n5. run `lifecycle.publish` from the exact sealed tarball or accept already-valid\n evidence;\n6. validate evidence and required artifacts;\n7. move exact release/prerelease tag;\n8. move floating tags and channel refs;\n9. mark the transaction `complete`;\n10. create or update the GitHub Release from restored sealed assets and record\n the `github_release` milestone.\n\nWhen a protected channel requires a generated version-state pull request, the\nfirst run can stop at `finalizing` after registry publication. If the reviewed\nmerge commit later contains that exact transaction release material but the\nexact tag is still absent, a retry performs finalization only: it reloads the\nsame durable source, release material, tooling, evidence, version, and target\nbindings; creates the exact and floating tags at the transaction release SHA;\nand completes the passport from the transaction source tree. It does not rerun\nthe provider mutation and does not authorize the newer composite channel tree\nas published material. A different source tree still requires a new version and\na fresh release candidate.\n\nDeferred binary dispatch, controller-evidence bundling, and any consumer\npublication commit are skipped while `finalization-needed=true`. They run only\nafter the exact public tag and complete release passport exist.\n\nConsumer products that expose a signed well-known channel can opt into one\nadditional, deliberately final step with `publication-commit-command`. Before\nthat command runs, Buildchain has already completed the transaction, created\nthe public GitHub Release, and uploaded every release-passport file plus the\nexplicit PR-stage payload files selected by\n`github-release-payload-patterns`. The command is therefore a commit point for\ndiscovery authority, not another artifact publisher.\n\nThe command receives the exact version, source SHA, release SHA, release tag,\nrelease passport path, and downloaded payload directory through\n`BUILDCHAIN_PUBLICATION_COMMIT_*`. Optional consumer-owned dispatch/API\ncredentials and private signing material are exposed separately as\n`BUILDCHAIN_PUBLICATION_COMMIT_TOKEN` and\n`BUILDCHAIN_PUBLICATION_COMMIT_SIGNING_KEY`; Buildchain never logs, persists,\nor interprets either value. The command must write\n`.buildchain/publication-commit/evidence.json` (or another declared path below\n`.buildchain/`) with this contract:\n\n```json\n{\n \"schema\": \"kungfu-buildchain-publication-commit-evidence/v1\",\n \"status\": \"passed\",\n \"identity\": {\n \"version\": \"4.0.0-alpha.2\",\n \"sourceSha\": \"<source-sha>\",\n \"releaseSha\": \"<release-sha>\",\n \"releaseTag\": \"v4.0.0-alpha.2\"\n },\n \"publication\": {\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<64-lowercase-hex>\"\n },\n \"readback\": {\n \"status\": \"passed\",\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<same-root>\"\n },\n \"recovery\": {\n \"previousAuthority\": \"preserved\",\n \"rollbackReference\": \"sha256:<previous-root>\"\n }\n}\n```\n\nBuildchain rejects stale evidence, identity drift, non-public or mutable URLs,\nread-back root drift, and missing recovery evidence. It also rejects\n`standalone-binary-distribution=true` with a final commit command because that\nwould queue product mutations after the authority moved. On any command or\nread-back failure, the consumer must leave the previous well-known document\nauthoritative; Buildchain does not retry the command behind a successful\nreceipt.\n\nIf protected branch finalization is interrupted after publish evidence is\nvalid, the transaction can stop in `finalizing` and output\n`finalization-needed=true`. A later run resumes from the same transaction state\nand completes ref movement without republishing matching artifacts. New\nBuildchain-managed promotions first try to finish generated version-state\nbookkeeping with the promotion token directly. Before patching a protected\ngenerated bookkeeping ref, Buildchain emits every configured required check on\nthe exact generated version-state commit so branch protection can\nvalidate the automation path without a second build, then uses the generated\nref update token for the protected ref PATCH. If release finalization\nbookkeeping is still rejected, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR and leaves the transaction resumable with\n`finalization-needed=true`. Strict alpha uses the same protected PR fallback\nfor both its target channel and subsequent dev reconciliation. A later\nidempotent run continues only after the provider shows that the PR reached the\nprotected branch. The reusable wrapper binds that token to the run-scoped\n`github.token` and rejects user, team, or alternate App bypass actors.\n\nIf finalization fails after an exact Git tag, a channel branch, or dev/alpha\nsync ref has already moved, the next run reads the durable `finalizing` state\nand continues from the recorded transaction. The current workflow SHA may be a\ngenerated version-state commit, or a historical version-state merge commit, that\ncontains or corresponds to the transaction's `release_material_sha`; it does not\nhave to equal the original `source_sha` or the transaction `release_sha`. Exact\ntags are accepted when they already point at the transaction release/material\nSHA or the finalized channel head. Floating\nchannel tags and dev/alpha refs are then retried idempotently, and the\ntransaction is marked `complete` only after those public refs are consistent.\nWriting `complete` clears any stale `failure` value from earlier attempts, so\nthe durable `state.json` represents the successful final state instead of the\nlast transient error seen before a rerun.\nAn exact tag at an unrelated SHA is still a material conflict and blocks\nrecovery.\n\nFor anchored package versions, the package version and internal line tag are\nseparate transaction coordinates. A retry can correct a stale internal tag on\nan unfinished `published` or `finalizing` transaction only when its validated\nevidence and complete artifact set match the same package version, source,\nrelease material, and target. Buildchain additionally requires that the stale\ntag does not already point at the transaction and that the newly selected tag\nis unclaimed or already points at accepted release material. No registry publish\ncommand is rerun during this exact-tag rebind.\n\nGoverned retries distinguish unrelated channel advancement from advancement\nmade by their own durable transaction. An unrelated descendant remains an\nauditable `superseded-promotion` no-op. When the target ref is exactly the\nrecorded `release_sha` for the requested source, target, and expected version,\nBuildchain resumes finalization, restores publish evidence, and emits the\nrelease-passport paths needed by downstream controller receipts.\n\nPublication authority planning applies the same occupied-version rule as the\nlater mutation step. If a current alpha transaction already contains published\nmaterial and regenerating version state would create new release material, the\nplanner advances to the next alpha before sealing authority. It never seals the\nold published version and then lets the publisher discover a different version\ninside the mutation boundary.\n\nIf finalization fails after an exact Git tag is created, the next run reads the\ndurable `finalizing` state, verifies the exact tag points at the recorded\nrelease SHA, and retries the remaining floating refs. An exact tag at a\ndifferent SHA is a material conflict and blocks recovery.\n\n## CLI Recovery\n\nLocal recovery commands operate on the same state/evidence files:\n\n```bash\nnode scripts/release-transaction.mjs inspect --version v3.0.2\nnode scripts/release-transaction.mjs recover --version v3.0.2\nnode scripts/release-transaction.mjs finalize --version v3.0.2\nnode scripts/release-transaction.mjs abort --version v3.0.2 --superseded-by v3.0.3\n```\n\nThe CLI is a diagnostic and local repair surface. It reports the durable\n`state_ref`, but remote durable-ref writes and public Git ref finalization are\nowned by `actions/promote-buildchain-ref`, because that action runs inside the\nsame governed GitHub permissions and branch-protection checks as release\npromotion. In other words, CLI `finalize` can mark the local transaction state\ncomplete after valid evidence; the machine-operated public finalization path is\nto rerun the promotion action.\n\nWhen no state file exists, creation commands also require:\n\n```bash\n--repository kungfu-systems/buildchain \\\n--source-sha <sha> \\\n--release-sha <sha> \\\n--target-ref release/v3/v3.0 \\\n--channel release\n```\n\n## Build-Images Follow-Up\n\n`build-images` should consume this contract rather than inventing a separate\nworkflow rule. The expected integration shape is:\n\n- image build writes OCI digests into publish evidence;\n- required image families are passed through `publish-required-artifacts-json`\n before their final digests are known;\n- mixed built/reused evidence preserves content provenance separately from the\n current release binding;\n- reruns check GHCR or the target registry and accept existing images only when\n tag and digest match;\n- preview or alpha image tags remain non-stable until the transaction evidence\n validates;\n- production image aliases move only after all required image artifacts are\n present and the Buildchain exact release tag has finalized."
3010
+ "markdown": "---\nstatus: active\nperiod: ongoing\ntheme: buildchain-publish-transaction\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-31\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-31\n invisible_context: not asserted\n---\n\n# Publish Transaction\n\nBuildchain release promotion is not just tag movement. A release can also publish\nexternal artifacts: npm packages, Python wheels, OCI images, binary archives,\nmetadata manifests, or site deployment records. Those side effects are harder\nthan Git refs because most registries are append-only: a failed rerun must know\nwhich artifacts already exist, which are still missing, and whether any existing\nartifact conflicts with the release material.\n\nBuildchain v3 models that work as a release transaction.\n\n## Why This Exists\n\nThe old ABV workflow made Git refs the visible release authority. That was\nenough when \"release\" meant \"create a version commit, move tags, and let\ndownstream jobs react.\" It is not enough when a single publish run must also\nupload packages and images.\n\nThe failure mode to avoid is:\n\n1. publish an external artifact;\n2. fail before moving the exact release tag or floating channel refs;\n3. rerun from a new job id with no memory of the artifact;\n4. either republish something different or move refs without proving the\n already-published artifact matches the release.\n\nThe transaction gives reruns a stable identity and a machine-readable state so\nBuildchain can resume safely. The identity is:\n\n```text\nrepository + version + source_sha + target_ref\n```\n\nIt is not the GitHub Actions run id.\n\n## Durable State\n\n`actions/promote-buildchain-ref` stores release transaction state in a\nmachine-managed Git branch:\n\n```text\nrefs/heads/buildchain/release-state/<version>\n```\n\nThe branch contains:\n\n```text\nstate.json\nevidence.json # present after publish evidence exists\nsealed-bundle/<candidate-root>/files/** # present for build-once publication\n```\n\nThe local `.buildchain/release-state/...` and\n`.buildchain/release-evidence/...` files are working copies. They are useful for\nlocal inspection and lifecycle commands, but they are not the durable truth for\nGitHub-hosted reruns. On action startup, Buildchain reads the durable state ref\nfirst, restores the local working copies, and only then decides whether to\npublish, repair, or finalize.\n\nEvery meaningful state transition is written to the durable ref before public\nrelease refs move. Release-state GitHub API reads and writes use retry/backoff\nfor transient service failures such as HTTP 5xx responses, connection resets,\ntimeouts, and \"other side closed\" socket failures. If the durable write still\ncannot be persisted after retries, the action fails closed.\n\nFor a sealed publication, `state.json` also carries the typed sealed-bundle\nmanifest, its candidate root, publication milestones, stable\n`publication_state`, and an exact resume command. The durable ref stores every\ndeclared bundle file as binary Git blobs before the publish lifecycle starts.\nA fresh runner restores those blobs into\n`.buildchain/recovered-publication/<version>/`, verifies every size and SHA-256\nagainst the manifest, and only then supplies the recovered paths to the publish\nlifecycle. A missing or changed tarball, PDF, source bundle, or manifest fails\nbefore registry publication.\n\nDurable release-state refs reserve their exact version even when the public exact\ntag was never created. If a later machine run sees a failed or repair-required\nstate for `vX.Y.Z-alpha.N` and cannot resume it with the same transaction\nidentity, alpha version selection must advance to the next prerelease instead\nof reusing or overwriting that failed transaction slot.\n\n## Lifecycle\n\nRepositories declare publish work in `.buildchain/buildchain.toml`:\n\n```toml\n[publish]\nmode = \"publish-final-version\"\nauth = \"trusted-publishing\"\ndist_tag = \"latest\"\npackage_set_order = \"platforms-first-main-last\"\nmain_package = \"@kungfu-tech/libnode\"\n\n[lifecycle.publish]\ncommands = [\n \"python scripts/publish_wheels.py\",\n \"node scripts/publish-images.mjs\",\n \"node scripts/write-publish-evidence.mjs\",\n]\n```\n\n`actions/promote-buildchain-ref` runs `lifecycle.publish` only when\n`publish-transaction: \"true\"` is set or when a `publish-command` input is\nprovided. The action sets:\n\n```text\nBUILDCHAIN_VERSION\nBUILDCHAIN_CHANNEL\nBUILDCHAIN_SOURCE_SHA\nBUILDCHAIN_TARGET_REF\nBUILDCHAIN_RELEASE_STATE\nBUILDCHAIN_EVIDENCE_DIR\nBUILDCHAIN_RELEASE_SHA\nBUILDCHAIN_RELEASE_MATERIAL_SHA\nBUILDCHAIN_PUBLISH_TOOLING_SHA\nBUILDCHAIN_PUBLISH_EVIDENCE\nBUILDCHAIN_SEALED_BUNDLE_ROOT\nBUILDCHAIN_SEALED_NPM_TARBALL\nBUILDCHAIN_SEALED_NPM_INTEGRITY\nBUILDCHAIN_SEALED_NPM_SHA256\nBUILDCHAIN_REQUIRED_ARTIFACTS\nBUILDCHAIN_PUBLISH_MODE\nBUILDCHAIN_PUBLISH_AUTH\nBUILDCHAIN_NPM_DIST_TAG\nBUILDCHAIN_PACKAGE_SET_ORDER\nBUILDCHAIN_PACKAGE_SET_MAIN_PACKAGE\n```\n\nBuildchain itself uses this contract for npm publishing:\n\n```toml\n[lifecycle.publish]\ncommand = \"node scripts/npm-publish-transaction.mjs\"\n```\n\nThat script validates that `package.json` matches `BUILDCHAIN_VERSION`, runs\n`npm publish --access public --tag <BUILDCHAIN_NPM_DIST_TAG>` through npm Trusted\nPublishing, and writes npm artifact evidence before the promotion action moves\npublic refs. When the sealed npm variables are present, the script verifies and\npublishes that exact `.tgz` file. It does not run `npm pack` again.\n\n`BUILDCHAIN_RELEASE_MATERIAL_SHA` is the source material whose artifacts must\nmatch. `BUILDCHAIN_PUBLISH_TOOLING_SHA` identifies the publishing code. A repair\nrun may change tooling, but material drift fails closed.\n\n## Post-Publish Requirements And Artifact Provenance\n\n`publish-required-artifacts-json` is a pre-publish family declaration, not a\nrequest to guess registry digests. A descriptor must include `kind + name`; it\nmay omit `ref` and `digest`. The action resolves a missing `ref` to the exact\n`BUILDCHAIN_VERSION`. Registries whose exact refs add a stable prefix or suffix\nmay instead declare a `ref_template` containing exactly one `{version}`, such\nas `v{version}`. The template is expanded only after exact version selection,\nso a resumed alpha transaction receives the newly selected prerelease rather\nthan the checked-out version. Declaring both `ref` and `ref_template`, using\nanother placeholder, or leaving unmatched braces fails before\n`lifecycle.publish`. The action exports the normalized exact refs as\n`BUILDCHAIN_REQUIRED_ARTIFACTS`, runs `lifecycle.publish`, and then requires the\nfinal evidence to contain every exact member with a non-empty digest. Existing\ncallers may continue supplying exact refs and digests.\n\nOCI publishers can opt into strict per-artifact provenance by adding\n`action: built` or `action: reused`. Those artifacts carry two separate\ncoordinates:\n\n- `content`: the version, ref, source SHA, and material SHA that produced the\n immutable content;\n- `release`: the exact current version/ref, target ref, source SHA, and release\n material SHA that bind that content into this release.\n\nThis distinction permits truthful cross-version reuse without claiming that an\nold OCI config was rebuilt from current material. For an OCI artifact with an\naction, final evidence also requires `platform`, positive `contract_major`, and\n`verification` containing a public manifest result, exact ref and digest,\nplatform, contract major, optional parent digest, evidence location, and a\npassed named smoke policy. Buildchain cross-checks those values against the\nartifact and current transaction. Missing family members and ref, digest,\ncontent, release, or verification conflicts enter `repair_required` before\npublic refs move.\n\nExample reused OCI evidence entry (the pre-publish requirement may omit\n`ref`, `digest`, `release`, and the observed verification values):\n\n```json\n{\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/base-linux\",\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"action\": \"reused\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"content\": {\n \"version\": \"1.1.9\",\n \"ref\": \"1.1.9\",\n \"source_sha\": \"<source-sha>\",\n \"material_sha\": \"<material-sha>\"\n },\n \"release\": {\n \"version\": \"1.2.0-alpha.3\",\n \"ref\": \"1.2.0-alpha.3\",\n \"target_ref\": \"alpha/v1/v1.2\",\n \"source_sha\": \"<current-source-sha>\",\n \"material_sha\": \"<current-material-sha>\"\n },\n \"verification\": {\n \"public_manifest\": true,\n \"ref\": \"1.2.0-alpha.3\",\n \"digest\": \"sha256:...\",\n \"platform\": \"linux/amd64\",\n \"contract_major\": 1,\n \"evidence\": \"registry-inspect.json\",\n \"smoke\": {\n \"policy\": \"manifest-contract\",\n \"passed\": true,\n \"evidence\": \"smoke.json\"\n }\n }\n}\n```\n\n## Release Modes And Auth\n\nBuildchain distinguishes two npm release modes:\n\n| Mode | Use | Auth | npm operation |\n| --- | --- | --- | --- |\n| `publish-final-version` | normal alpha or stable publication | `trusted-publishing` | `npm publish --tag <alpha|vX.Y-alpha|latest>` |\n| `promote-existing-version` | same-version alpha-to-latest recovery | `npm-token` | `npm dist-tag add <pkg>@<version> latest` |\n\nThe normal libnode path is `publish-final-version`: publish an alpha package set\nsuch as `22.22.3-kf.3-alpha.0` with the `alpha` dist-tag, then publish a\ndistinct final package set such as `22.22.3-kf.3` with the `latest` dist-tag.\nGitHub-hosted npm Trusted Publishing can authorize those `npm publish` calls\nwhen the workflow grants `id-token: write`.\n\n`promote-existing-version` is deliberately separate. npm Trusted Publishing does\nnot authorize arbitrary registry-management operations such as `npm dist-tag\nadd`; it authorizes publish-time package provenance. Therefore same-version\npromotion must declare `auth = \"npm-token\"`. Buildchain runs an npm token\npreflight with `npm whoami` before it writes any release transaction state or\nmoves a dist-tag. Missing token auth fails early with a contract error instead\nof a late `E401` after publish evidence has started to move.\n\nFor package sets, `package_set_order = \"platforms-first-main-last\"` makes the\nmain package the visibility gate. Platform package side effects are planned or\nretried first, and the main package or main dist-tag move happens last.\n\nWhen the transaction reaches `complete`, `actions/promote-buildchain-ref`\ngenerates `.buildchain/release-passport/buildchain.release.json` and persists\nthe `release-passport/*` files into the durable `buildchain/release-state/...`\nref. The passport is the stable release artifact for agents and people: it\nlinks the package set, npm publish evidence, dist-tag evidence, build summary,\nplatform artifact manifests, trusted publishing metadata, release-state ref,\ndurable release-state SHA, and transaction result in one schema. Consumer\nrepositories can set `release-passport-product-name` so the passport names their\nproduct instead of the Buildchain default.\n\n## Evidence\n\nThe publish lifecycle must write JSON evidence. Buildchain validates common\nfields and required artifact identities before final refs move.\n\n```json\n{\n \"schema\": 1,\n \"version\": \"2.0.11\",\n \"channel\": \"release\",\n \"source_sha\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n \"release_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"target_ref\": \"release/v3/v3.0\",\n \"release_material_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"publish_tooling_sha\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n \"artifacts\": [\n {\n \"group\": \"node\",\n \"kind\": \"npm\",\n \"name\": \"@kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n },\n {\n \"group\": \"image\",\n \"kind\": \"oci\",\n \"name\": \"ghcr.io/kungfu-systems/example\",\n \"ref\": \"2.0.11\",\n \"digest\": \"sha256:...\"\n }\n ]\n}\n```\n\nThe generic contract is intentionally small:\n\n- `version`, `channel`, `source_sha`, `release_sha`, and `target_ref` must match\n the promotion run;\n- dist-tag promotion evidence is written beside the publish evidence as\n `dist-tag-evidence.json` and is referenced from the generated passport;\n- required artifacts must appear in evidence;\n- evidence used by a GitHub-hosted rerun must either be stored in the durable\n state ref or be reconstructed by a machine-verifiable consumer command;\n- existing artifacts with the same identity and digest are accepted on rerun;\n- missing artifacts can be published by the next run;\n- an existing artifact with a different digest puts the transaction into\n `repair_required`.\n\nArtifact identity is `group + kind + name + ref`. A required artifact that omits\n`group` matches any group with the same `kind + name + ref`.\n\n## Registry Truth Contract\n\nBuildchain owns transaction orchestration, finalization ordering, durable state,\nand generic evidence validation. It does not embed registry clients for npm,\nPyPI, GHCR/OCI, GitHub Releases, S3, Conan, CMake packaging, or project-specific\ndownload pages.\n\nConsumer `lifecycle.publish` commands own registry truth. A valid consumer stage\nmust be idempotent and machine-verifiable:\n\n- inspect the target registry before publishing;\n- accept an existing exact artifact only when version, identity, digest, and\n release-source binding match;\n- publish missing required artifacts;\n- reject conflicting existing artifacts and write evidence that lets Buildchain\n move the transaction to `repair_required`;\n- write `BUILDCHAIN_PUBLISH_EVIDENCE` after every successful inspect/publish\n cycle;\n- leave floating aliases such as npm dist-tags, PyPI stable markers, OCI\n floating tags, GitHub Release \"published\" status, or download-page stable\n links to a finalization step after Buildchain evidence validation.\n\nThe first-class adapter surface is command-based. Projects may wrap npm, PyPI,\nGHCR/OCI, GitHub Release assets, archives, SBOMs, provenance, or checksums\nhowever they need, as long as they emit the common evidence contract.\n\n## States\n\nThe state machine is:\n\n```text\nprepared -> sealed -> publishing -> published -> finalizing -> complete\n | | |\n v v v\n publish_failed repair_required failed_permanently\n |\n v\n abandoned\n```\n\nSupported states:\n\n| State | Meaning |\n| -------------------- | -------------------------------------------------------------------------------------------------------- |\n| `prepared` | Transaction identity was created, but publish has not started. |\n| `sealed` | Exact candidate bytes and manifest are verified and durable; registry publication has not started. |\n| `publishing` | Publish lifecycle is running or may have been interrupted. |\n| `publish_failed` | Publish command failed before valid evidence was produced. |\n| `published` | Evidence is valid; refs have not necessarily finalized. |\n| `finalizing` | Buildchain is moving exact/floating refs or needs a later run to do it. |\n| `complete` | Required evidence is valid and refs have finalized. |\n| `repair_required` | Existing evidence or artifact state conflicts with expected release material. |\n| `abandoned` | A human or controlled process abandoned this transaction, usually because a newer version supersedes it. |\n| `failed_permanently` | Recovery should not continue without explicit override. |\n\n`repair_required`, `abandoned`, and `failed_permanently` fail closed unless the\noperator passes an explicit override. That override is for controlled repair\nruns, not normal retry behavior.\n\n`publication_state` is a stable operator-facing projection over the detailed\ntransaction state. Its successful progression is\n`prepared -> sealed -> package-published -> alpha-complete` for Alpha or\n`release-complete` for stable release. If npm succeeds but GitHub Release work\nis interrupted, the durable record remains `package-published`; the next run\nreuses the exact npm evidence and sealed release assets instead of rebuilding\nor republishing them.\n\n## Ref Ordering\n\nWhen publish transactions are enabled, promotion order is:\n\n1. verify target source and governance;\n2. create or reuse the version-state release commit;\n3. acquire or resume the release transaction;\n4. for build-once publication, verify and persist the complete sealed bundle;\n5. run `lifecycle.publish` from the exact sealed tarball or accept already-valid\n evidence;\n6. validate evidence and required artifacts;\n7. move exact release/prerelease tag;\n8. move floating tags and channel refs;\n9. mark the transaction `complete`;\n10. create or update the GitHub Release from restored sealed assets and record\n the `github_release` milestone.\n\nWhen a protected channel requires a generated version-state pull request, the\nfirst run can stop at `finalizing` after registry publication. If the reviewed\nmerge commit later contains that exact transaction release material but the\nexact tag is still absent, a retry performs finalization only: it reloads the\nsame durable source, release material, tooling, evidence, version, and target\nbindings; creates the exact tag at the transaction source SHA; moves floating\nrefs to the transaction release SHA; and completes the passport from the\ntransaction source tree. It does not rerun the provider mutation and does not\nauthorize the newer composite channel tree\nas published material. A different source tree still requires a new version and\na fresh release candidate.\n\nDeferred binary dispatch, controller-evidence bundling, and any consumer\npublication commit are skipped while `finalization-needed=true`. They run only\nafter the exact public tag and complete release passport exist.\n\nConsumer products that expose a signed well-known channel can opt into one\nadditional, deliberately final step with `publication-commit-command`. Before\nthat command runs, Buildchain has already completed the transaction, created\nthe public GitHub Release, and uploaded every release-passport file plus the\nexplicit PR-stage payload files selected by\n`github-release-payload-patterns`. The command is therefore a commit point for\ndiscovery authority, not another artifact publisher.\n\nThe command receives the exact version, source SHA, release SHA, release tag,\nrelease passport path, and downloaded payload directory through\n`BUILDCHAIN_PUBLICATION_COMMIT_*`. Optional consumer-owned dispatch/API\ncredentials and private signing material are exposed separately as\n`BUILDCHAIN_PUBLICATION_COMMIT_TOKEN` and\n`BUILDCHAIN_PUBLICATION_COMMIT_SIGNING_KEY`; Buildchain never logs, persists,\nor interprets either value. The command must write\n`.buildchain/publication-commit/evidence.json` (or another declared path below\n`.buildchain/`) with this contract:\n\n```json\n{\n \"schema\": \"kungfu-buildchain-publication-commit-evidence/v1\",\n \"status\": \"passed\",\n \"identity\": {\n \"version\": \"4.0.0-alpha.2\",\n \"sourceSha\": \"<source-sha>\",\n \"releaseSha\": \"<release-sha>\",\n \"releaseTag\": \"v4.0.0-alpha.2\"\n },\n \"publication\": {\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<64-lowercase-hex>\"\n },\n \"readback\": {\n \"status\": \"passed\",\n \"url\": \"https://example.test/.well-known/product/alpha.json\",\n \"payloadRoot\": \"sha256:<same-root>\"\n },\n \"recovery\": {\n \"previousAuthority\": \"preserved\",\n \"rollbackReference\": \"sha256:<previous-root>\"\n }\n}\n```\n\nBuildchain rejects stale evidence, identity drift, non-public or mutable URLs,\nread-back root drift, and missing recovery evidence. It also rejects\n`standalone-binary-distribution=true` with a final commit command because that\nwould queue product mutations after the authority moved. On any command or\nread-back failure, the consumer must leave the previous well-known document\nauthoritative; Buildchain does not retry the command behind a successful\nreceipt.\n\nIf protected branch finalization is interrupted after publish evidence is\nvalid, the transaction can stop in `finalizing` and output\n`finalization-needed=true`. A later run resumes from the same transaction state\nand completes ref movement without republishing matching artifacts. New\nBuildchain-managed promotions first try to finish generated version-state\nbookkeeping with the promotion token directly. Before patching a protected\ngenerated bookkeeping ref, Buildchain emits every configured required check on\nthe exact generated version-state commit so branch protection can\nvalidate the automation path without a second build, then uses the generated\nref update token for the protected ref PATCH. If release finalization\nbookkeeping is still rejected, Buildchain creates or reuses a same-repository\n`buildchain/version-state/*` PR and leaves the transaction resumable with\n`finalization-needed=true`. Strict alpha uses the same protected PR fallback\nfor both its target channel and subsequent dev reconciliation. A later\nidempotent run continues only after the provider shows that the PR reached the\nprotected branch. The reusable wrapper binds that token to the run-scoped\n`github.token` and rejects user, team, or alternate App bypass actors.\n\nIf finalization fails after an exact Git tag, a channel branch, or dev/alpha\nsync ref has already moved, the next run reads the durable `finalizing` state\nand continues from the recorded transaction. The current workflow SHA may be a\ngenerated version-state commit, or a historical version-state merge commit, that\ncontains or corresponds to the transaction's `release_material_sha`; it does not\nhave to equal the original `source_sha` or the transaction `release_sha`. Exact\ntags are accepted when they already point at the transaction release/material\nSHA or the finalized channel head. Floating\nchannel tags and dev/alpha refs are then retried idempotently, and the\ntransaction is marked `complete` only after those public refs are consistent.\nWriting `complete` clears any stale `failure` value from earlier attempts, so\nthe durable `state.json` represents the successful final state instead of the\nlast transient error seen before a rerun.\nAn exact tag at an unrelated SHA is still a material conflict and blocks\nrecovery.\n\nFor anchored package versions, the package version and internal line tag are\nseparate transaction coordinates. A retry can correct a stale internal tag on\nan unfinished `published` or `finalizing` transaction only when its validated\nevidence and complete artifact set match the same package version, source,\nrelease material, and target. Buildchain additionally requires that the stale\ntag does not already point at the transaction and that the newly selected tag\nis unclaimed or already points at accepted release material. No registry publish\ncommand is rerun during this exact-tag rebind.\n\nGoverned retries distinguish unrelated channel advancement from advancement\nmade by their own durable transaction. An unrelated descendant remains an\nauditable `superseded-promotion` no-op. When the target ref is exactly the\nrecorded `release_sha` for the requested source, target, and expected version,\nBuildchain resumes finalization, restores publish evidence, and emits the\nrelease-passport paths needed by downstream controller receipts.\n\nPublication authority planning applies the same occupied-version rule as the\nlater mutation step. If a current alpha transaction already contains published\nmaterial and regenerating version state would create new release material, the\nplanner advances to the next alpha before sealing authority. It never seals the\nold published version and then lets the publisher discover a different version\ninside the mutation boundary.\n\nIf finalization fails after an exact Git tag is created, the next run reads the\ndurable `finalizing` state, verifies the exact tag points at the recorded\nsource SHA (while accepting legacy release/material targets for recovery), and\nretries the remaining floating refs. An exact tag at an unrelated SHA is a\nmaterial conflict and blocks recovery.\n\n## CLI Recovery\n\nLocal recovery commands operate on the same state/evidence files:\n\n```bash\nnode scripts/release-transaction.mjs inspect --version v3.0.2\nnode scripts/release-transaction.mjs recover --version v3.0.2\nnode scripts/release-transaction.mjs finalize --version v3.0.2\nnode scripts/release-transaction.mjs abort --version v3.0.2 --superseded-by v3.0.3\n```\n\nThe CLI is a diagnostic and local repair surface. It reports the durable\n`state_ref`, but remote durable-ref writes and public Git ref finalization are\nowned by `actions/promote-buildchain-ref`, because that action runs inside the\nsame governed GitHub permissions and branch-protection checks as release\npromotion. In other words, CLI `finalize` can mark the local transaction state\ncomplete after valid evidence; the machine-operated public finalization path is\nto rerun the promotion action.\n\nWhen no state file exists, creation commands also require:\n\n```bash\n--repository kungfu-systems/buildchain \\\n--source-sha <sha> \\\n--release-sha <sha> \\\n--target-ref release/v3/v3.0 \\\n--channel release\n```\n\n## Build-Images Follow-Up\n\n`build-images` should consume this contract rather than inventing a separate\nworkflow rule. The expected integration shape is:\n\n- image build writes OCI digests into publish evidence;\n- required image families are passed through `publish-required-artifacts-json`\n before their final digests are known;\n- mixed built/reused evidence preserves content provenance separately from the\n current release binding;\n- reruns check GHCR or the target registry and accept existing images only when\n tag and digest match;\n- preview or alpha image tags remain non-stable until the transaction evidence\n validates;\n- production image aliases move only after all required image artifacts are\n present and the Buildchain exact release tag has finalized."
3001
3011
  },
3002
3012
  {
3003
3013
  "id": "manual:readme-badges",
@@ -3106,7 +3116,7 @@
3106
3116
  ],
3107
3117
  "maturity": "stable",
3108
3118
  "sourcePath": "docs/release-flow.md",
3109
- "digest": "sha256:bac959565c36cf39c3cd30dfce00dbbb59b73ed21b852a8f24a6cae281cb5f05",
3119
+ "digest": "sha256:dc54b8a264489341984ca6157b07d1956e934366f937b7412a21df7f025d2d8d",
3110
3120
  "headings": [
3111
3121
  {
3112
3122
  "level": 1,
@@ -3164,7 +3174,7 @@
3164
3174
  "anchor": "failure-boundaries"
3165
3175
  }
3166
3176
  ],
3167
- "markdown": "---\nstatus: active\nperiod: ongoing\ntheme: buildchain-release-flow\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-31\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-31\n invisible_context: not asserted\n---\n\n# Release Flow Diagrams\n\nThis document describes the Buildchain v3 branch, tag, and version-state flow.\nSee [Release governance](release-governance.md) for the design rationale.\n\n## Architecture\n\n```mermaid\nflowchart TD\n Maintainer[\"Maintainer opens channel PR\"]\n Verify[\"Release - Verify\"]\n Review[\"Protected branch review\"]\n Merge[\"Merge PR into alpha or release\"]\n Promotion[\"Buildchain Ref Promotion\"]\n StableDecision{\"Release channel?\"}\n StableGate[\"Stable only: exact-alpha canaries + soak + cooldown\"]\n Action[\"promote-buildchain-ref action\"]\n VersionState[\"Version-state commit\"]\n ExactTag[\"Exact tag\"]\n FloatingRefs[\"Floating tags and channel branches\"]\n Consumers[\"Consumers pin stable or exact refs\"]\n\n Maintainer --> Verify\n Verify --> Review\n Review --> Merge\n Merge --> Promotion\n Promotion --> StableDecision\n StableDecision -->|yes| StableGate\n StableDecision -->|no: alpha| Action\n StableGate --> Action\n Action --> VersionState\n Action --> ExactTag\n Action --> FloatingRefs\n ExactTag --> Consumers\n FloatingRefs --> Consumers\n```\n\nBuildchain treats the PR merge as release intent and the promotion action as the\nonly component allowed to turn that intent into release refs.\n\n`StableGate` applies only to Buildchain's release channel. Alpha and train\niteration bypass it. See [Stable Release Throttle And Canary Gate](release-governance.md#stable-release-throttle-and-canary-gate)\nfor the versioned policy and evidence contract.\n\n## Ref State\n\n| Ref kind | Example | Mutability | Purpose |\n| --- | --- | --- | --- |\n| Development branch | `dev/v3/v3.0` | moves | next source state for a minor line |\n| Alpha branch | `alpha/v3/v3.0` | moves | latest test state for a minor line |\n| Release branch | `release/v3/v3.0` | moves | latest production state for a minor line |\n| Major gate branch | `publish-gate/major` | moves | reviewed administrator gate for publishing the next major |\n| Exact alpha tag | `v3.0.3-alpha.0` | immutable | audit ref for one tested prerelease |\n| Exact release tag | `v3.0.2` | immutable | audit ref for one production release |\n| Floating alpha tag | `v3.0-alpha` | moves | latest test channel for a minor line |\n| Floating major alpha tag | `v3-alpha` | moves | latest test channel on the highest published alpha minor for a major line |\n| Floating minor tag | `v3.0` | moves | latest production patch on a minor line |\n| Floating major tag | `v3` | moves | selected stable major entrypoint |\n\n## Ref Protection Contract\n\nRepository rulesets must distinguish immutable evidence refs from mutable\nchannel refs.\n\nProtect exact release and alpha tags as immutable evidence:\n\n```text\nrefs/tags/v*.*.*\n```\n\nDo not apply immutable-tag rulesets to every `refs/tags/v*` ref. Buildchain\nmust be able to update floating channel tags such as `v3`, `v3.0`, `v3.0-alpha`,\nand `v3-alpha` after the exact tag and publish evidence are valid. A ruleset that\nmatches all `v*` tags also matches floating tags, so release finalization can\nfail with GitHub protected-ref errors even though the exact release tag and\npublished artifacts are already durable.\n\nThe intended governance split is:\n\n- exact tags such as `v3.0.2` and `v3.0.3-alpha.0` are immutable audit refs;\n- floating tags such as `v3`, `v3.0`, `v3.0-alpha`, and `v3-alpha` are mutable channel refs\n owned by the Buildchain promotion token;\n- protected branches still require reviewed channel PRs before Buildchain can\n move any exact or floating release refs.\n\n## Opening a Minor Line\n\nNew minor lines should be opened through Buildchain instead of hand-created\nbranches. The reusable entrypoint is the `Release Line Bootstrap` workflow. It\ndefaults to dry-run so maintainers can inspect the planned refs, protection\ncontract, initial version, and first alpha PR before any mutation.\n\nThe workflow is backed by the CLI command:\n\n```bash\nbuildchain release line open \\\n --major 3 \\\n --minor 1 \\\n --source-ref release/v3/v3.0 \\\n --json\n```\n\nWhen the workflow is run with `apply=true`, Buildchain:\n\n- writes the initial version-state commit, such as `3.1.0-alpha.0`;\n- creates `dev/v3/v3.1` from that commit;\n- creates `alpha/v3/v3.1` and `release/v3/v3.1` from the selected source ref;\n- applies branch protection with one approving review and the configured\n required status check; dev starts strict, while alpha and release also require\n the pair-specific `verify` aggregate without a source-up-to-date ancestry loop;\n- reconciles the new dev branch's explicitly declared merge queue, or inherits\n the exact queue parameters and bypass actors from the current default dev\n branch when the policy is `inherit` or absent;\n- switches the repository default branch to the new dev line when requested;\n- opens the first `dev/v3/v3.1 -> alpha/v3/v3.1` channel PR when requested.\n\nThis makes minor-line creation a single audited operation. The channel PR still\ngoes through the normal verify/review/promotion path before an alpha is\npublished. Queue reconciliation runs after branch protection and before the\ndefault-branch switch, so a failed governance apply leaves the old active line\nin place and the idempotently created new refs can be retried.\n\n## Alpha Promotion\n\n```mermaid\nsequenceDiagram\n participant Dev as dev/vX/vX.Y\n participant PR as PR dev -> alpha\n participant Verify as Release - Verify\n participant Alpha as alpha/vX/vX.Y\n participant Promote as Buildchain Ref Promotion\n participant Tags as Tags\n\n Dev->>PR: open channel PR\n PR->>Verify: run verification checks\n Verify-->>PR: check succeeds\n PR->>Alpha: reviewed merge\n Alpha->>Promote: Verify workflow_run completed\n Promote->>Promote: validate same-repo merged PR\n Promote->>Promote: compute next vX.Y.Z-alpha.N\n Promote->>Promote: write and verify version state\n Promote->>Tags: create or reuse vX.Y.Z-alpha.N\n Promote->>Tags: move vX.Y-alpha\n Promote->>Tags: move vX-alpha when X.Y is the highest published alpha minor\n Promote->>Alpha: move alpha/vX/vX.Y\n Promote->>Dev: move dev/vX/vX.Y\n```\n\nResult:\n\n```text\nvX.Y.Z-alpha.N\nvX.Y-alpha\nvX-alpha when X.Y is the highest published alpha minor\nalpha/vX/vX.Y\ndev/vX/vX.Y\n```\n\nall point at the generated alpha version-state commit.\n\n## Release Promotion\n\n```mermaid\nsequenceDiagram\n participant Alpha as alpha/vX/vX.Y\n participant PR as PR alpha -> release\n participant Verify as Release - Verify\n participant Release as release/vX/vX.Y\n participant Promote as Buildchain Ref Promotion\n participant Tags as Tags\n participant Dev as dev/vX/vX.Y\n\n Alpha->>PR: open channel PR\n PR->>Verify: run verification checks\n Verify-->>PR: check succeeds\n PR->>Release: reviewed merge\n Release->>Promote: Verify workflow_run completed\n Promote->>Promote: validate same-repo merged PR\n Promote->>Promote: find same-patch alpha tag\n Promote->>Promote: compare release tree with tested alpha tree\n Promote->>Promote: write final version state or verify anchored material\n Promote->>Tags: create or reuse vX.Y.Z\n Promote->>Tags: move vX.Y\n Promote->>Tags: move vX when eligible\n Promote->>Release: move release/vX/vX.Y\n Promote->>Promote: prepare vX.Y.(Z+1)-alpha.0\n Promote->>Tags: create or reuse vX.Y.(Z+1)-alpha.0\n Promote->>Tags: move vX.Y-alpha\n Promote->>Tags: move vX-alpha when X.Y is the highest published alpha minor\n Promote->>Alpha: move alpha/vX/vX.Y\n Promote->>Dev: move dev/vX/vX.Y\n```\n\nResult:\n\n```text\nvX.Y.Z\nvX.Y\nvX\nrelease/vX/vX.Y\n```\n\npoint at the production version-state commit, while:\n\n```text\nvX.Y.(Z+1)-alpha.0\nvX.Y-alpha\nvX-alpha when X.Y is the highest published alpha minor\nalpha/vX/vX.Y\ndev/vX/vX.Y\n```\n\npoint at the next alpha version-state commit.\n\n## State Machine\n\n```mermaid\nstateDiagram-v2\n [*] --> Development: work lands on dev/vX/vX.Y\n Development --> AlphaCandidate: PR dev -> alpha\n AlphaCandidate --> AlphaPublished: Verify + review + merge + promotion\n AlphaPublished --> ReleaseCandidate: PR alpha -> release\n ReleaseCandidate --> ProductionPublished: Verify + review + merge + promotion\n ProductionPublished --> NextAlphaPrepared: prepare vX.Y.(Z+1)-alpha.0\n NextAlphaPrepared --> Development: dev and alpha refs move to next alpha\n```\n\nThe same minor line can loop through this state machine many times.\n\n## Version Examples\n\nAssume `v3.0.2-alpha.1` has been tested and a maintainer merges\n`alpha/v3/v3.0 -> release/v3/v3.0`.\n\nBuildchain should produce:\n\n```text\nv3.0.2 exact production tag\nv3.0 floating minor tag\nv3 floating major tag when v3.0 is the selected major line\nrelease/v3/v3.0 production channel branch\n```\n\nIt should also prepare:\n\n```text\nv3.0.3-alpha.0 exact next alpha tag\nv3.0-alpha floating alpha tag\nv3-alpha floating major alpha tag when v3.0 is the highest published alpha minor\nalpha/v3/v3.0 alpha channel branch\ndev/v3/v3.0 development channel branch\n```\n\nThis is expected behavior. A production release closes one patch and opens the\nnext test patch on the same minor line.\n\n## Major Gate Promotion\n\n```mermaid\nsequenceDiagram\n participant Release as release/vX/vX.Y\n participant PR as PR release -> publish-gate/major\n participant Verify as Release - Verify\n participant Gate as publish-gate/major\n participant Promote as Buildchain Ref Promotion\n participant Tags as Tags\n participant Next as dev/alpha/release v(X+1).0\n\n Release->>PR: open administrator PR\n PR->>Verify: run verification checks\n Verify-->>PR: check succeeds\n PR->>Gate: reviewed merge\n Gate->>Promote: Verify workflow_run completed\n Promote->>Promote: validate same-repo release -> publish-gate/major PR\n Promote->>Promote: write v(X+1).0.0 version state\n Promote->>Tags: create v(X+1).0.0\n Promote->>Tags: move v(X+1).0 and v(X+1)\n Promote->>Gate: move publish-gate/major to v(X+1).0.0\n Promote->>Next: move release/v(X+1)/v(X+1).0 to v(X+1).0.0\n Promote->>Promote: prepare v(X+1).0.1-alpha.0\n Promote->>Next: move alpha/dev v(X+1).0 to next alpha\n```\n\n`publish-gate/major` is intentionally not an active source branch. It is the PR\ntarget for the administrator's \"publish the next major\" decision. The older\n`major-gate` name is a compatibility alias only.\n\n## Failure Boundaries\n\nPromotion should stop before moving refs when:\n\n- the run is a non-dry-run manual dispatch;\n- the expected same-repository PR cannot be found;\n- the PR was not merged;\n- the branch pair is not a valid channel path;\n- the required status check did not pass;\n- a release tree does not match the same-patch alpha tag tree, except for the\n declared anchored/manual version files and anchor manifest that\n `lifecycle.verify` or `verification-command` validates;\n- version-state verification fails;\n- a required exact tag already exists at a commit unrelated to the active\n transaction or finalized channel head.\n\nThese failures are intentional. They protect consumers from refs that look\nreleased but do not have a complete evidence chain.\n\nDuring transaction finalization recovery, the current channel head may be a\ngenerated version-state merge commit. Buildchain validates that the durable\ntransaction version, exact tag, evidence, and release material match, and that\nthe current target ref contains or corresponds to the recorded\n`release_material_sha`. It must then tolerate exact tags, dev refs, or alpha\nrefs that have already moved and continue filling any missing floating tags\nbefore writing the transaction state as `complete`."
3177
+ "markdown": "---\nstatus: active\nperiod: ongoing\ntheme: buildchain-release-flow\ndoc_type: technical-reference\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-07-31\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-31\n invisible_context: not asserted\n---\n\n# Release Flow Diagrams\n\nThis document describes the Buildchain v3 branch, tag, and version-state flow.\nSee [Release governance](release-governance.md) for the design rationale.\n\n## Architecture\n\n```mermaid\nflowchart TD\n Maintainer[\"Maintainer opens channel PR\"]\n Verify[\"Release - Verify\"]\n Review[\"Protected branch review\"]\n Merge[\"Merge PR into alpha or release\"]\n Promotion[\"Buildchain Ref Promotion\"]\n StableDecision{\"Release channel?\"}\n StableGate[\"Stable only: exact-alpha canaries + soak + cooldown\"]\n Action[\"promote-buildchain-ref action\"]\n VersionState[\"Version-state commit\"]\n ExactTag[\"Exact tag\"]\n FloatingRefs[\"Floating tags and channel branches\"]\n Consumers[\"Consumers pin stable or exact refs\"]\n\n Maintainer --> Verify\n Verify --> Review\n Review --> Merge\n Merge --> Promotion\n Promotion --> StableDecision\n StableDecision -->|yes| StableGate\n StableDecision -->|no: alpha| Action\n StableGate --> Action\n Action --> VersionState\n Action --> ExactTag\n Action --> FloatingRefs\n ExactTag --> Consumers\n FloatingRefs --> Consumers\n```\n\nBuildchain treats the PR merge as release intent and the promotion action as the\nonly component allowed to turn that intent into release refs.\n\n`StableGate` applies only to Buildchain's release channel. Alpha and train\niteration bypass it. See [Stable Release Throttle And Canary Gate](release-governance.md#stable-release-throttle-and-canary-gate)\nfor the versioned policy and evidence contract.\n\n## Ref State\n\n| Ref kind | Example | Mutability | Purpose |\n| --- | --- | --- | --- |\n| Development branch | `dev/v3/v3.0` | moves | next source state for a minor line |\n| Alpha branch | `alpha/v3/v3.0` | moves | latest test state for a minor line |\n| Release branch | `release/v3/v3.0` | moves | latest production state for a minor line |\n| Major gate branch | `publish-gate/major` | moves | reviewed administrator gate for publishing the next major |\n| Exact alpha tag | `v3.0.3-alpha.0` | immutable | audit ref for one tested prerelease |\n| Exact release tag | `v3.0.2` | immutable | audit ref for one production release |\n| Floating alpha tag | `v3.0-alpha` | moves | latest test channel for a minor line |\n| Floating major alpha tag | `v3-alpha` | moves | latest test channel on the highest published alpha minor for a major line |\n| Floating minor tag | `v3.0` | moves | latest production patch on a minor line |\n| Floating major tag | `v3` | moves | selected stable major entrypoint |\n\n## Ref Protection Contract\n\nRepository rulesets must distinguish immutable evidence refs from mutable\nchannel refs.\n\nProtect exact release and alpha tags as immutable evidence:\n\n```text\nrefs/tags/v*.*.*\n```\n\nDo not apply immutable-tag rulesets to every `refs/tags/v*` ref. Buildchain\nmust be able to update floating channel tags such as `v3`, `v3.0`, `v3.0-alpha`,\nand `v3-alpha` after the exact tag and publish evidence are valid. A ruleset that\nmatches all `v*` tags also matches floating tags, so release finalization can\nfail with GitHub protected-ref errors even though the exact release tag and\npublished artifacts are already durable.\n\nThe intended governance split is:\n\n- exact tags such as `v3.0.2` and `v3.0.3-alpha.0` are immutable audit refs;\n- for publish transactions, the exact tag points to the transaction\n `source_sha`, matching package-registry source metadata such as npm\n `gitHead`; generated version-state commits remain on protected branches and\n floating channel refs;\n- floating tags such as `v3`, `v3.0`, `v3.0-alpha`, and `v3-alpha` are mutable channel refs\n owned by the Buildchain promotion token;\n- protected branches still require reviewed channel PRs before Buildchain can\n move any exact or floating release refs.\n\n## Opening a Minor Line\n\nNew minor lines should be opened through Buildchain instead of hand-created\nbranches. The reusable entrypoint is the `Release Line Bootstrap` workflow. It\ndefaults to dry-run so maintainers can inspect the planned refs, protection\ncontract, initial version, and first alpha PR before any mutation.\n\nThe workflow is backed by the CLI command:\n\n```bash\nbuildchain release line open \\\n --major 3 \\\n --minor 1 \\\n --source-ref release/v3/v3.0 \\\n --json\n```\n\nWhen the workflow is run with `apply=true`, Buildchain:\n\n- writes the initial version-state commit, such as `3.1.0-alpha.0`;\n- creates `dev/v3/v3.1` from that commit;\n- creates `alpha/v3/v3.1` and `release/v3/v3.1` from the selected source ref;\n- applies branch protection with one approving review and the configured\n required status check; dev starts strict, while alpha and release also require\n the pair-specific `verify` aggregate without a source-up-to-date ancestry loop;\n- reconciles the new dev branch's explicitly declared merge queue, or inherits\n the exact queue parameters and bypass actors from the current default dev\n branch when the policy is `inherit` or absent;\n- switches the repository default branch to the new dev line when requested;\n- opens the first `dev/v3/v3.1 -> alpha/v3/v3.1` channel PR when requested.\n\nThis makes minor-line creation a single audited operation. The channel PR still\ngoes through the normal verify/review/promotion path before an alpha is\npublished. Queue reconciliation runs after branch protection and before the\ndefault-branch switch, so a failed governance apply leaves the old active line\nin place and the idempotently created new refs can be retried.\n\n## Alpha Promotion\n\n```mermaid\nsequenceDiagram\n participant Dev as dev/vX/vX.Y\n participant PR as PR dev -> alpha\n participant Verify as Release - Verify\n participant Alpha as alpha/vX/vX.Y\n participant Promote as Buildchain Ref Promotion\n participant Tags as Tags\n\n Dev->>PR: open channel PR\n PR->>Verify: run verification checks\n Verify-->>PR: check succeeds\n PR->>Alpha: reviewed merge\n Alpha->>Promote: Verify workflow_run completed\n Promote->>Promote: validate same-repo merged PR\n Promote->>Promote: compute next vX.Y.Z-alpha.N\n Promote->>Promote: write and verify version state\n Promote->>Tags: create or reuse vX.Y.Z-alpha.N\n Promote->>Tags: move vX.Y-alpha\n Promote->>Tags: move vX-alpha when X.Y is the highest published alpha minor\n Promote->>Alpha: move alpha/vX/vX.Y\n Promote->>Dev: move dev/vX/vX.Y\n```\n\nResult:\n\n```text\nvX.Y.Z-alpha.N\nvX.Y-alpha\nvX-alpha when X.Y is the highest published alpha minor\nalpha/vX/vX.Y\ndev/vX/vX.Y\n```\n\nall point at the generated alpha version-state commit.\n\n## Release Promotion\n\n```mermaid\nsequenceDiagram\n participant Alpha as alpha/vX/vX.Y\n participant PR as PR alpha -> release\n participant Verify as Release - Verify\n participant Release as release/vX/vX.Y\n participant Promote as Buildchain Ref Promotion\n participant Tags as Tags\n participant Dev as dev/vX/vX.Y\n\n Alpha->>PR: open channel PR\n PR->>Verify: run verification checks\n Verify-->>PR: check succeeds\n PR->>Release: reviewed merge\n Release->>Promote: Verify workflow_run completed\n Promote->>Promote: validate same-repo merged PR\n Promote->>Promote: find same-patch alpha tag\n Promote->>Promote: compare release tree with tested alpha tree\n Promote->>Promote: write final version state or verify anchored material\n Promote->>Tags: create or reuse vX.Y.Z\n Promote->>Tags: move vX.Y\n Promote->>Tags: move vX when eligible\n Promote->>Release: move release/vX/vX.Y\n Promote->>Promote: prepare vX.Y.(Z+1)-alpha.0\n Promote->>Tags: create or reuse vX.Y.(Z+1)-alpha.0\n Promote->>Tags: move vX.Y-alpha\n Promote->>Tags: move vX-alpha when X.Y is the highest published alpha minor\n Promote->>Alpha: move alpha/vX/vX.Y\n Promote->>Dev: move dev/vX/vX.Y\n```\n\nResult:\n\n```text\nvX.Y.Z\nvX.Y\nvX\nrelease/vX/vX.Y\n```\n\npoint at the production version-state commit, while:\n\n```text\nvX.Y.(Z+1)-alpha.0\nvX.Y-alpha\nvX-alpha when X.Y is the highest published alpha minor\nalpha/vX/vX.Y\ndev/vX/vX.Y\n```\n\npoint at the next alpha version-state commit.\n\n## State Machine\n\n```mermaid\nstateDiagram-v2\n [*] --> Development: work lands on dev/vX/vX.Y\n Development --> AlphaCandidate: PR dev -> alpha\n AlphaCandidate --> AlphaPublished: Verify + review + merge + promotion\n AlphaPublished --> ReleaseCandidate: PR alpha -> release\n ReleaseCandidate --> ProductionPublished: Verify + review + merge + promotion\n ProductionPublished --> NextAlphaPrepared: prepare vX.Y.(Z+1)-alpha.0\n NextAlphaPrepared --> Development: dev and alpha refs move to next alpha\n```\n\nThe same minor line can loop through this state machine many times.\n\n## Version Examples\n\nAssume `v3.0.2-alpha.1` has been tested and a maintainer merges\n`alpha/v3/v3.0 -> release/v3/v3.0`.\n\nBuildchain should produce:\n\n```text\nv3.0.2 exact production tag\nv3.0 floating minor tag\nv3 floating major tag when v3.0 is the selected major line\nrelease/v3/v3.0 production channel branch\n```\n\nIt should also prepare:\n\n```text\nv3.0.3-alpha.0 exact next alpha tag\nv3.0-alpha floating alpha tag\nv3-alpha floating major alpha tag when v3.0 is the highest published alpha minor\nalpha/v3/v3.0 alpha channel branch\ndev/v3/v3.0 development channel branch\n```\n\nThis is expected behavior. A production release closes one patch and opens the\nnext test patch on the same minor line.\n\n## Major Gate Promotion\n\n```mermaid\nsequenceDiagram\n participant Release as release/vX/vX.Y\n participant PR as PR release -> publish-gate/major\n participant Verify as Release - Verify\n participant Gate as publish-gate/major\n participant Promote as Buildchain Ref Promotion\n participant Tags as Tags\n participant Next as dev/alpha/release v(X+1).0\n\n Release->>PR: open administrator PR\n PR->>Verify: run verification checks\n Verify-->>PR: check succeeds\n PR->>Gate: reviewed merge\n Gate->>Promote: Verify workflow_run completed\n Promote->>Promote: validate same-repo release -> publish-gate/major PR\n Promote->>Promote: write v(X+1).0.0 version state\n Promote->>Tags: create v(X+1).0.0\n Promote->>Tags: move v(X+1).0 and v(X+1)\n Promote->>Gate: move publish-gate/major to v(X+1).0.0\n Promote->>Next: move release/v(X+1)/v(X+1).0 to v(X+1).0.0\n Promote->>Promote: prepare v(X+1).0.1-alpha.0\n Promote->>Next: move alpha/dev v(X+1).0 to next alpha\n```\n\n`publish-gate/major` is intentionally not an active source branch. It is the PR\ntarget for the administrator's \"publish the next major\" decision. The older\n`major-gate` name is a compatibility alias only.\n\n## Failure Boundaries\n\nPromotion should stop before moving refs when:\n\n- the run is a non-dry-run manual dispatch;\n- the expected same-repository PR cannot be found;\n- the PR was not merged;\n- the branch pair is not a valid channel path;\n- the required status check did not pass;\n- a release tree does not match the same-patch alpha tag tree, except for the\n declared anchored/manual version files and anchor manifest that\n `lifecycle.verify` or `verification-command` validates;\n- version-state verification fails;\n- a required exact tag already exists at a commit unrelated to the active\n transaction or finalized channel head.\n\nThese failures are intentional. They protect consumers from refs that look\nreleased but do not have a complete evidence chain.\n\nDuring transaction finalization recovery, the current channel head may be a\ngenerated version-state merge commit. Buildchain validates that the durable\ntransaction version, exact tag, evidence, and release material match, and that\nthe current target ref contains or corresponds to the recorded\n`release_material_sha`. It must then tolerate exact tags, dev refs, or alpha\nrefs that have already moved and continue filling any missing floating tags\nbefore writing the transaction state as `complete`."
3168
3178
  },
3169
3179
  {
3170
3180
  "id": "manual:release-governance",
@@ -3178,7 +3188,7 @@
3178
3188
  ],
3179
3189
  "maturity": "stable",
3180
3190
  "sourcePath": "docs/release-governance.md",
3181
- "digest": "sha256:60ec90d3294818d3c53e403312801a8ab47be7c0f34810002676fbfcdbceaa60",
3191
+ "digest": "sha256:96d78f800cd7d43d3a47a11863d4813711e360418ba33a4284688389c7e4b70c",
3182
3192
  "headings": [
3183
3193
  {
3184
3194
  "level": 1,
@@ -3271,7 +3281,7 @@
3271
3281
  "anchor": "operational-reading-order"
3272
3282
  }
3273
3283
  ],
3274
- "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-31\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 v3 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 `v3.0` and receive a tree whose package version still\n says the previous release;\n- a maintainer can move `v3` without producing an exact `v3.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 v3 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/v3/v3.0 \\\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 `v3.0` can produce many production\npatch releases:\n\n```text\nv3.0.0\nv3.0.1\nv3.0.2\n...\nv3.0.1234\n```\n\nThis is why Buildchain maintains both exact and floating refs:\n\n- `v3.0.2` is immutable release evidence;\n- `v3.0` is the latest production release on the `3.0` line;\n- `v3` is the selected stable major-line entrypoint;\n- `v3.0.3-alpha.0` is immutable alpha evidence;\n- `v3.0-alpha` is the latest test channel for the `3.0` line.\n- `v3-alpha` is the latest test channel on the highest published alpha minor in major `3`.\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`v3.0.2` and `v3.0.3-alpha.0` should be immutable. Floating channel tags such as\n`v3`, `v3.0`, `v3.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`v3.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 `v3`. 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 `v3` 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/v3/v3.0\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/buildchain.toml` is the v3 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 `v3.0.2`;\n- every production minor line has a floating tag such as `v3.0`;\n- every selected stable major has a floating tag such as `v3`;\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 `v3.0.3-alpha.0`;\n- every alpha minor line has a floating tag such as `v3.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\nv3.0.0\nv3.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."
3284
+ "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-31\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 v3 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 `v3.0` and receive a tree whose package version still\n says the previous release;\n- a maintainer can move `v3` without producing an exact `v3.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 v3 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/v3/v3.0 \\\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 `v3.0` can produce many production\npatch releases:\n\n```text\nv3.0.0\nv3.0.1\nv3.0.2\n...\nv3.0.1234\n```\n\nThis is why Buildchain maintains both exact and floating refs:\n\n- `v3.0.2` is immutable release evidence;\n- `v3.0` is the latest production release on the `3.0` line;\n- `v3` is the selected stable major-line entrypoint;\n- `v3.0.3-alpha.0` is immutable alpha evidence;\n- `v3.0-alpha` is the latest test channel for the `3.0` line.\n- `v3-alpha` is the latest test channel on the highest published alpha minor in major `3`.\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`v3.0.2` and `v3.0.3-alpha.0` should be immutable. Floating channel tags such as\n`v3`, `v3.0`, `v3.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`v3.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 `v3`. 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 `v3` 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/v3/v3.0\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/buildchain.toml` is the v3 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 `v3.0.2`;\n- every production minor line has a floating tag such as `v3.0`;\n- every selected stable major has a floating tag such as `v3`;\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 `v3.0.3-alpha.0`;\n- every alpha minor line has a floating tag such as `v3.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\nFor package publish transactions, the immutable public version tag points to\nthe transaction `source_sha`, so registry source metadata such as npm `gitHead`\nand the Git tag identify the same source commit. Protected branches and mutable\nchannel tags continue to point to the generated `release_sha`. Recovery accepts\nolder completed transactions whose exact tags already point to recorded release\nor release-material SHAs, but new tags are source-bound.\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\nv3.0.0\nv3.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."
3275
3285
  },
3276
3286
  {
3277
3287
  "id": "manual:release-passport",
@@ -3404,7 +3414,7 @@
3404
3414
  ],
3405
3415
  "maturity": "stable",
3406
3416
  "sourcePath": "docs/reusable-build-surface.md",
3407
- "digest": "sha256:8908e1f1589537ac8ef82ee1dacdd24072ca871e3e245825669e7cfbdebd2e3d",
3417
+ "digest": "sha256:baadd0c0ef61d877b409d2bdf0e696e218971eb541c5f51a1a8e88e75af4904d",
3408
3418
  "headings": [
3409
3419
  {
3410
3420
  "level": 1,
@@ -3512,7 +3522,7 @@
3512
3522
  "anchor": "fixture"
3513
3523
  }
3514
3524
  ],
3515
- "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-31\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 v3 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. Consumers keep\nthis configuration for both alpha development 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 v3 prerelease evaluation windows, canaries use `build.yml@v3-alpha`.\nThe same router then selects `v3-alpha` or stable `v3` as the runtime.\nProduction consumers use `build.yml@v3`; this keeps the routing shell itself on\na 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\nSet `self-hosted-offline-fallback: true` to inspect every exact-label\nself-hosted lane from the trusted Buildchain workflow shell before the matrix\nstarts. A lane with no matching online runner is replaced independently by its\nsupported GitHub-hosted runner: Kungfu Linux x64 uses `ubuntu-24.04`, macOS ARM64\nuses `macos-15`, and Windows x64 uses `windows-2022`. Online-but-busy runners\nremain online and keep their declared self-hosted route. Organization-owned\nrepositories inspect organization runner inventory so selected-repository runner\ngroups are not mistaken for an empty repository runner inventory. If the\ninventory token, permission, or API is unavailable, Buildchain preserves the\noriginal matrix instead of guessing that the fleet is offline. The public\nworkflow output `runner-routing-json` records only de-identified counts,\ninventory scope, and routing decisions.\n\n```yaml\nwith:\n runner-preset: kungfu-v4-native\n self-hosted-offline-fallback: true\nsecrets:\n BUILDCHAIN_PROMOTION_TOKEN: ${{ secrets.KUNGFU_GITHUB_TOKEN }}\n```\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. One\nnon-override exact-pin case is also admitted: when the reusable workflow itself\nis invoked from an exact Buildchain SHA and `buildchain-ref` names that identical\nSHA, the run records `pinned-self`. The input cannot select code other than the\nalready-running workflow shell, so protected push and pull-request publication\njobs can retain one immutable runtime root. Different SHA and train requests\nstill fail closed outside trusted manual dispatch.\n\nPull requests, including same-repository pull requests and fork-originated pull\nrequests, cannot select an independent `buildchain-ref` override. This keeps\nautomated PR builds on the stable or exact pinned-self 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`v3`, `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 `v3`, 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. `v3` 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\n## Auditable Compiler Cache\n\nConsumers can prepare `sccache` on selected platforms after the install\nlifecycle and before compilation:\n\n```yaml\nwith:\n compiler-cache-provider: sccache\n compiler-cache-platforms-json: '[\"windows-x64\"]'\n compiler-cache-required: true\n```\n\nThe consumer remains responsible for installing and pinning the tool before\nthe preparation step. Buildchain probes its version, runs `sccache\n--zero-stats`, and writes\n`compiler-cache-preparation.json`. The receipt binds the source commit/tree,\nBuildchain runtime, platform, cache profile, and any declared dependency,\ntoolchain, or policy roots. It resets counters only; it does not delete cached\ncompiler outputs.\n\nFinal diagnostics admit sccache hit/miss outcomes as current-run evidence only\nwhen that preparation receipt is present and valid. A bare `sccache\n--show-stats` result without the reset receipt remains cumulative and is\nreported as unavailable for the current run. The preparation receipt is copied\ninto the small diagnostics artifact and sealed by\n`diagnostics-manifest.json`.\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. The lifecycle runner automatically adds declarations\nselected for the current platform to the `build` manifest scan, including\nsubjects outside the caller's ordinary `artifact-paths`; this extends the\nevidence preimage without silently adding those subjects to the ordinary\nartifact upload.\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\nFor a declared `app-bundle`, the same protected authority extracts the sealed\napplication, derives and verifies its bundle identity, signs nested native code,\nsubmits both the application and disk image for notarization, staples and\nGatekeeper-assesses both deliverables, and returns a ZIP, DMG, evidence document,\nand source-bound manifest. The reusable workflow verifies those returned bytes\non GitHub-hosted infrastructure, adds them to the normal macOS platform payload,\nand publishes a separate `<artifact>-macos-credential-<source-sha>` projection\nfor release pipelines that consume the credential-island evidence contract.\nConsumers declare the `.app` under `[[signing.artifacts]]`; they do not configure\nan environment, certificate, notary credential, or authority workflow.\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`. The v3 stable and alpha lanes call\nthe hidden advanced workflow at the exact immutable SHA behind their selected\nv3 channel state and forward the complete internal promotion identity surface.\nThe logical shell identity remains `vN`, and the router retains it in the public\naudit outputs. The internal advanced-shell call receives the exact call ref\nselected by the routing configuration, so its called-workflow ref check and\ncheckout SHA both bind to the same immutable identity. Updating a routing pin\nafter a release does not require any consumer declaration change.\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 `v3` 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/v3/v3.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."
3525
+ "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-31\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 v3 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. Consumers keep\nthis configuration for both alpha development 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 v3 prerelease evaluation windows, canaries use `build.yml@v3-alpha`.\nThe same router then selects `v3-alpha` or stable `v3` as the runtime.\nProduction consumers use `build.yml@v3`; this keeps the routing shell itself on\na 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\nSet `self-hosted-offline-fallback: true` to inspect every exact-label\nself-hosted lane from the trusted Buildchain workflow shell before the matrix\nstarts. A lane with no matching online runner is replaced independently by its\nsupported GitHub-hosted runner: Kungfu Linux x64 uses `ubuntu-24.04`, macOS ARM64\nuses `macos-15`, and Windows x64 uses `windows-2022`. Online-but-busy runners\nremain online and keep their declared self-hosted route. Organization-owned\nrepositories inspect organization runner inventory so selected-repository runner\ngroups are not mistaken for an empty repository runner inventory. If the\ninventory token, permission, or API is unavailable, Buildchain preserves the\noriginal matrix instead of guessing that the fleet is offline. The public\nworkflow output `runner-routing-json` records only de-identified counts,\ninventory scope, and routing decisions.\n\n```yaml\nwith:\n runner-preset: kungfu-v4-native\n self-hosted-offline-fallback: true\nsecrets:\n BUILDCHAIN_PROMOTION_TOKEN: ${{ secrets.KUNGFU_GITHUB_TOKEN }}\n```\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. One\nnon-override exact-pin case is also admitted: when the reusable workflow itself\nis invoked from an exact Buildchain SHA and `buildchain-ref` names that identical\nSHA, the run records `pinned-self`. The input cannot select code other than the\nalready-running workflow shell, so protected push and pull-request publication\njobs can retain one immutable runtime root. Different SHA and train requests\nstill fail closed outside trusted manual dispatch.\n\nPull requests, including same-repository pull requests and fork-originated pull\nrequests, cannot select an independent `buildchain-ref` override. This keeps\nautomated PR builds on the stable or exact pinned-self 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`v3`, `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 `v3`, 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. `v3` 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 checkout-history-mode: shallow\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\n`checkout-history-mode` defaults to `shallow`, preserving the bounded single-\ncommit transport used by ordinary builds. Set it to `full` only when a\nconsumer gate must inspect source ancestry, for example when an Alpha pull\nrequest qualifies GitHub's synthetic merge ref while retained evidence is\nbound to an ancestor of the source-lock head. Full mode still verifies the\nresolved immutable `HEAD` and tree; it changes only whether the advertised\nsource ref is fetched with depth one or with its reachable history.\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\n## Auditable Compiler Cache\n\nConsumers can prepare `sccache` on selected platforms after the install\nlifecycle and before compilation:\n\n```yaml\nwith:\n compiler-cache-provider: sccache\n compiler-cache-platforms-json: '[\"windows-x64\"]'\n compiler-cache-required: true\n```\n\nThe consumer remains responsible for installing and pinning the tool before\nthe preparation step. Buildchain probes its version, runs `sccache\n--zero-stats`, and writes\n`compiler-cache-preparation.json`. The receipt binds the source commit/tree,\nBuildchain runtime, platform, cache profile, and any declared dependency,\ntoolchain, or policy roots. It resets counters only; it does not delete cached\ncompiler outputs.\n\nFinal diagnostics admit sccache hit/miss outcomes as current-run evidence only\nwhen that preparation receipt is present and valid. A bare `sccache\n--show-stats` result without the reset receipt remains cumulative and is\nreported as unavailable for the current run. The preparation receipt is copied\ninto the small diagnostics artifact and sealed by\n`diagnostics-manifest.json`.\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. The lifecycle runner automatically adds declarations\nselected for the current platform to the `build` manifest scan, including\nsubjects outside the caller's ordinary `artifact-paths`; this extends the\nevidence preimage without silently adding those subjects to the ordinary\nartifact upload.\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\nFor a declared `app-bundle`, the same protected authority extracts the sealed\napplication, derives and verifies its bundle identity, signs nested native code,\nsubmits both the application and disk image for notarization, staples and\nGatekeeper-assesses both deliverables, and returns a ZIP, DMG, evidence document,\nand source-bound manifest. The reusable workflow verifies those returned bytes\non GitHub-hosted infrastructure, adds them to the normal macOS platform payload,\nand publishes a separate `<artifact>-macos-credential-<source-sha>` projection\nfor release pipelines that consume the credential-island evidence contract.\nConsumers declare the `.app` under `[[signing.artifacts]]`; they do not configure\nan environment, certificate, notary credential, or authority workflow.\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`. The v3 stable and alpha lanes call\nthe hidden advanced workflow at the exact immutable SHA behind their selected\nv3 channel state and forward the complete internal promotion identity surface.\nThe logical shell identity remains `vN`, and the router retains it in the public\naudit outputs. The internal advanced-shell call receives the exact call ref\nselected by the routing configuration, so its called-workflow ref check and\ncheckout SHA both bind to the same immutable identity. Updating a routing pin\nafter a release does not require any consumer declaration change.\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 `v3` 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/v3/v3.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."
3516
3526
  },
3517
3527
  {
3518
3528
  "id": "manual:runtime-train-validation",
@@ -4551,7 +4561,7 @@
4551
4561
  "path": "docs/reusable-build-surface.md",
4552
4562
  "plane": "use",
4553
4563
  "exists": true,
4554
- "digest": "sha256:8908e1f1589537ac8ef82ee1dacdd24072ca871e3e245825669e7cfbdebd2e3d"
4564
+ "digest": "sha256:baadd0c0ef61d877b409d2bdf0e696e218971eb541c5f51a1a8e88e75af4904d"
4555
4565
  },
4556
4566
  {
4557
4567
  "id": "publish-transaction",
@@ -4559,7 +4569,7 @@
4559
4569
  "path": "docs/publish-transaction.md",
4560
4570
  "plane": "verify",
4561
4571
  "exists": true,
4562
- "digest": "sha256:9b830727e5b0d2192c937f19106c11ea5064dde77e30d290825f343df47f291b"
4572
+ "digest": "sha256:3a9ef099d5d93d9b558c7b6a0aa4403dfcce1479ce5d6fc36ecb3e34cdd5bb6d"
4563
4573
  },
4564
4574
  {
4565
4575
  "id": "release-governance",
@@ -4567,7 +4577,7 @@
4567
4577
  "path": "docs/release-governance.md",
4568
4578
  "plane": "why",
4569
4579
  "exists": true,
4570
- "digest": "sha256:60ec90d3294818d3c53e403312801a8ab47be7c0f34810002676fbfcdbceaa60"
4580
+ "digest": "sha256:96d78f800cd7d43d3a47a11863d4813711e360418ba33a4284688389c7e4b70c"
4571
4581
  },
4572
4582
  {
4573
4583
  "id": "release-flow",
@@ -4575,7 +4585,7 @@
4575
4585
  "path": "docs/release-flow.md",
4576
4586
  "plane": "verify",
4577
4587
  "exists": true,
4578
- "digest": "sha256:bac959565c36cf39c3cd30dfce00dbbb59b73ed21b852a8f24a6cae281cb5f05"
4588
+ "digest": "sha256:dc54b8a264489341984ca6157b07d1956e934366f937b7412a21df7f025d2d8d"
4579
4589
  },
4580
4590
  {
4581
4591
  "id": "runtime-train-validation",