@kungfu-tech/buildchain 3.0.5-alpha.1 → 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-02T02:26:15.287Z",
5
- "publishedAt": "2026-08-02T02:26:15.287Z",
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": "8b96fb02e587124bad0f9055f9de6eb7c6b8081c",
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.1",
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:e3176cefb04f69df971f75aac8b99782366d3fcc363ea32a3f5b1a2bd94887ab",
449
+ "digest": "sha256:098bebc4d0d3d661fe8e0a32a14b153e8383ab8f8490d45913823f8b130f4c5d",
450
450
  "headings": [
451
451
  {
452
452
  "level": 1,
@@ -478,6 +478,11 @@
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",
@@ -499,7 +504,7 @@
499
504
  "anchor": "source-boundaries"
500
505
  }
501
506
  ],
502
- "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-01\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-<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### 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."
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."
503
508
  },
504
509
  {
505
510
  "id": "manual:binary-distribution",
@@ -3409,7 +3414,7 @@
3409
3414
  ],
3410
3415
  "maturity": "stable",
3411
3416
  "sourcePath": "docs/reusable-build-surface.md",
3412
- "digest": "sha256:8908e1f1589537ac8ef82ee1dacdd24072ca871e3e245825669e7cfbdebd2e3d",
3417
+ "digest": "sha256:baadd0c0ef61d877b409d2bdf0e696e218971eb541c5f51a1a8e88e75af4904d",
3413
3418
  "headings": [
3414
3419
  {
3415
3420
  "level": 1,
@@ -3517,7 +3522,7 @@
3517
3522
  "anchor": "fixture"
3518
3523
  }
3519
3524
  ],
3520
- "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."
3521
3526
  },
3522
3527
  {
3523
3528
  "id": "manual:runtime-train-validation",
@@ -4556,7 +4561,7 @@
4556
4561
  "path": "docs/reusable-build-surface.md",
4557
4562
  "plane": "use",
4558
4563
  "exists": true,
4559
- "digest": "sha256:8908e1f1589537ac8ef82ee1dacdd24072ca871e3e245825669e7cfbdebd2e3d"
4564
+ "digest": "sha256:baadd0c0ef61d877b409d2bdf0e696e218971eb541c5f51a1a8e88e75af4904d"
4560
4565
  },
4561
4566
  {
4562
4567
  "id": "publish-transaction",
@@ -258,6 +258,10 @@
258
258
  "classification": "digest-only",
259
259
  "source": "workflow-call-input"
260
260
  },
261
+ "checkout-history-mode": {
262
+ "classification": "included",
263
+ "source": "workflow-call-input"
264
+ },
261
265
  "compiler-cache-platforms-json": {
262
266
  "classification": "digest-only",
263
267
  "source": "workflow-call-input"
@@ -500,7 +504,7 @@
500
504
  "controller-receipt"
501
505
  ]
502
506
  },
503
- "digest": "sha256:2e4c9e9af2162de2333c9b57bffcff77a76e9ed384b9f001ab8ef63d527197f9"
507
+ "digest": "sha256:6088b850d18b276fccb71564a2afc7fbc1a3343036635b4fe2de960b99965498"
504
508
  },
505
509
  {
506
510
  "schemaVersion": 1,
@@ -696,6 +700,10 @@
696
700
  "classification": "digest-only",
697
701
  "source": "workflow-call-input"
698
702
  },
703
+ "checkout-history-mode": {
704
+ "classification": "included",
705
+ "source": "workflow-call-input"
706
+ },
699
707
  "compiler-cache-platforms-json": {
700
708
  "classification": "digest-only",
701
709
  "source": "workflow-call-input"
@@ -914,7 +922,7 @@
914
922
  "controller-receipt"
915
923
  ]
916
924
  },
917
- "digest": "sha256:105eed3eb8f772d4ed93d43e7da404d849b92eafab1b461c7a9b7cb94a8a6b3b"
925
+ "digest": "sha256:7668105652e29bf41bbdaf73288cc285c78a7beaa287bf8502b8abc107837372"
918
926
  },
919
927
  {
920
928
  "schemaVersion": 1,
@@ -2306,5 +2314,5 @@
2306
2314
  "digest": "sha256:b6f53563c0adc859bc586c05fce560a5094d7d9666438d63c5fd3e1fc4774555"
2307
2315
  }
2308
2316
  ],
2309
- "digest": "sha256:56e91795341365ea7684caed8f2d6fd81b1d5bd669b004545caefdbe63c0f98d"
2317
+ "digest": "sha256:1ca3dd555338d485c6aa052fa01a76fb0b20a4d0812a71965e97f7dfd748b111"
2310
2318
  }
@@ -21,7 +21,7 @@
21
21
  "contract": "kungfu-buildchain-public-surface-reverse-audit",
22
22
  "path": "dist/site/public-surface-audit.json",
23
23
  "status": "passed",
24
- "sha256": "b7081a8a21698767c8f1786df79344bfeb6fd5ef1e7ce4d8a298f97d4d1d177b",
24
+ "sha256": "c70607a9eb40f7a7636efceb766794a5bbaf65180467aa871d73d70ba8fdcd6f",
25
25
  "summary": {
26
26
  "cliCommandCount": 112,
27
27
  "workflowCount": 57,
@@ -246,7 +246,7 @@
246
246
  "contract": "kungfu-buildchain-public-surface-reverse-audit",
247
247
  "path": "dist/site/public-surface-audit.json",
248
248
  "status": "passed",
249
- "sha256": "b7081a8a21698767c8f1786df79344bfeb6fd5ef1e7ce4d8a298f97d4d1d177b",
249
+ "sha256": "c70607a9eb40f7a7636efceb766794a5bbaf65180467aa871d73d70ba8fdcd6f",
250
250
  "summary": {
251
251
  "cliCommandCount": 112,
252
252
  "workflowCount": 57,
@@ -3334,7 +3334,7 @@
3334
3334
  "visibility": "public",
3335
3335
  "participantFacing": true,
3336
3336
  "public": true,
3337
- "inputCount": 82,
3337
+ "inputCount": 83,
3338
3338
  "inputs": [
3339
3339
  "artifact-compression-level",
3340
3340
  "artifact-name",
@@ -3370,6 +3370,7 @@
3370
3370
  "checkout-cache-mode",
3371
3371
  "checkout-cache-reference-repository-template",
3372
3372
  "checkout-cache-timeout-seconds",
3373
+ "checkout-history-mode",
3373
3374
  "compiler-cache-platforms-json",
3374
3375
  "compiler-cache-provider",
3375
3376
  "compiler-cache-required",
@@ -4029,7 +4030,7 @@
4029
4030
  "visibility": "public",
4030
4031
  "participantFacing": true,
4031
4032
  "public": true,
4032
- "inputCount": 85,
4033
+ "inputCount": 86,
4033
4034
  "inputs": [
4034
4035
  "artifact-compression-level",
4035
4036
  "artifact-name",
@@ -4068,6 +4069,7 @@
4068
4069
  "checkout-cache-mode",
4069
4070
  "checkout-cache-reference-repository-template",
4070
4071
  "checkout-cache-timeout-seconds",
4072
+ "checkout-history-mode",
4071
4073
  "compiler-cache-platforms-json",
4072
4074
  "compiler-cache-provider",
4073
4075
  "compiler-cache-required",