@kungfu-tech/buildchain 2.13.0-alpha.2 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@
4
4
  "product": {
5
5
  "name": "Buildchain",
6
6
  "package": "@kungfu-tech/buildchain",
7
- "version": "2.13.0-alpha.2",
7
+ "version": "2.13.0",
8
8
  "repository": "https://github.com/kungfu-systems/buildchain"
9
9
  },
10
10
  "majorLine": "v2",
@@ -2704,5 +2704,5 @@
2704
2704
  }
2705
2705
  ],
2706
2706
  "compatibilityDigest": "sha256:af162b86ab4506e9b5f1d3c59b41f3fd57fbad79ff4d749a7f12ff16460517f8",
2707
- "contractDigest": "sha256:63f9ef6dd9ce1f312c8e975479134e3be2dfb51ead80a73f0ed1c3d32781f516"
2707
+ "contractDigest": "sha256:96593e211e995724701c04826ecf885e3a1fa0c49ab38948b5e481d44b3eba6b"
2708
2708
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-bundle",
4
- "generatedAt": "2026-07-16T02:29:49.950Z",
5
- "publishedAt": "2026-07-16T02:29:49.950Z",
4
+ "generatedAt": "2026-07-16T07:45:10.422Z",
5
+ "publishedAt": "2026-07-16T07:45:10.422Z",
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": "87491685ffc7b6522176f8e7519f6c595e9bf5a7",
22
+ "sourceRevision": "eeac02686f845b4f496b2cbaa78713e6d4b2532b",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "package": {
39
39
  "name": "@kungfu-tech/buildchain",
40
- "version": "2.13.0-alpha.2",
40
+ "version": "2.13.0",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "source": {
@@ -1855,7 +1855,7 @@
1855
1855
  ],
1856
1856
  "maturity": "stable",
1857
1857
  "sourcePath": "docs/stable-candidate-patrol.md",
1858
- "digest": "sha256:3af06c896769f5b71e1e0658035e8b4393b7f4ba263f77921cb9f9b8483518c7",
1858
+ "digest": "sha256:87111303392ee9af59506e0d2b82fc1c4432d3953ca1efcd65b6c181a93d5385",
1859
1859
  "headings": [
1860
1860
  {
1861
1861
  "level": 1,
@@ -1888,7 +1888,7 @@
1888
1888
  "anchor": "durable-recovery"
1889
1889
  }
1890
1890
  ],
1891
- "markdown": "---\nstatus: preview\nperiod: ongoing\ntheme: stable-candidate-patrol\ndoc_type: architecture-and-usage\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-11\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-11\n visible_context: Buildchain candidate passport, stable gate, Patrol, publish transaction, exact source-lock PR contracts, tests, and user consensus.\n invisible_context_boundary: No credentials, private logs, or unpublished consumer content were used.\n---\n\n# Stable Candidate Patrol\n\nBuildchain can treat every exact alpha as an independent stable candidate. A\nnew alpha creates a new candidate; it does not silently revoke an older alpha\nthat already completed its checks and soak interval.\n\n## Candidate lifecycle\n\nThe durable ledger contract is `kungfu-buildchain-stable-candidate-ledger`:\n\n```text\nregistered -> soaking -> qualified -> promoted\n \\-> revoked\n```\n\nEach entry binds an exact alpha version to one immutable commit SHA. Registering\nthe same version at another SHA fails closed. Qualification records the required\nchecks, their completion times, the derived soak start, and elapsed time.\n\nScheduled selection chooses the newest `qualified` candidate that is neither\n`revoked` nor already `promoted`. A newer alpha that is still `soaking` does not\nhide an older qualified candidate. Once a stable version is promoted, remaining\nalphas for that exact stable version are closed because that immutable stable\nversion has been consumed; their later product changes continue through the\nnext patch alpha prepared by the normal release transaction.\n\nFor Buildchain's own release line, successful Alpha Self-Dogfood starts an\nidempotent qualification producer for the exact alpha SHA. It dispatches the\nexisting `Build Surface Fixture` at the immutable exact tag, runs the existing\n`site-libkungfu-dev` no-apply canary with the exact SHA, and writes the declared\ncommit-status attestation only after that authoritative canary succeeds. The\nproducer creates evidence only: Patrol still owns qualification and selection,\nand the normal source-lock PR, stable gate, transaction, and branch protections\nremain mandatory.\n\nCross-repository dispatch and repository-local attestation use separate tokens.\nThe promotion token can start the no-apply consumer workflow, while the\nrepository-scoped Actions token writes the status as `github-actions[bot]` only\nafter the producer has observed the successful authoritative workflow run. The\nstable gate still verifies the workflow identity, exact runtime SHA, target URL,\nand allowed attestor before accepting that status.\n\nWhen a candidate changes the outer reusable-workflow YAML itself, the stable\nshell cannot exercise that change through a runtime override. A maintainer may\nmanually qualify it with `canary-ref` set to an exact 40-character commit in the\nconsumer repository whose canary workflow pins the candidate's exact Buildchain\nSHA. The qualification producer still dispatches and observes the authoritative\nconsumer workflow before writing the normal candidate-bound status. Floating\nconsumer branches are rejected, and the automatic path continues to use the\nconsumer default branch.\n\n## Repository policy\n\nDeclare the default once in `.buildchain/buildchain.toml`:\n\n```toml\n[release.stable]\nstrategy = \"latest-qualified-alpha\"\ntimezone = \"Asia/Shanghai\"\npublish_at = \"03:00\"\nminimum_soak_seconds = 3600\nrequired_checks = [\n \"alpha-release\",\n \"workflow:Build\",\n \"status:buildchain-canary/consumer\",\n]\nauto_promote = true\nauto_merge = true\n```\n\nCheck identifiers use these forms:\n\n- `alpha-release`: the exact GitHub prerelease publication fact;\n- `workflow:<name>`: a successful Actions workflow run on the exact candidate SHA;\n- `status:<context>`: a successful commit status on the exact candidate SHA;\n- an unprefixed value: an exact status context or check-run name.\n\nCandidate discovery uses immutable exact alpha Git tags. When a repository also\npublishes GitHub prereleases, `alpha-release` binds qualification to that public\nrelease fact. Repositories that intentionally disable GitHub Releases omit\n`alpha-release` and declare their own exact-SHA workflow/status evidence; tag\ncommit time remains the earliest possible soak start.\n\n`publish_at` and `timezone` are the auditable policy declaration. GitHub only\nstarts scheduled workflows from caller-owned cron, so the thin caller keeps the\nmatching UTC trigger:\n\n```yaml\nname: Stable Candidate Patrol\n\non:\n schedule:\n - cron: \"0 19 * * *\" # 03:00 Asia/Shanghai\n workflow_dispatch:\n inputs:\n release-now:\n description: Exact alpha selected by explicit human authority\n required: false\n default: \"\"\n\npermissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n\njobs:\n stable:\n uses: kungfu-systems/buildchain/.github/workflows/stable-candidate-patrol.yml@v2\n with:\n release-now: ${{ inputs.release-now }}\n dry-run: false\n secrets:\n promotion-token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n approval-token: ${{ secrets.BUILDCHAIN_APPROVAL_TOKEN }}\n```\n\nThe promotion token must be repository-owned and capable of creating the\nmachine ledger branch, exact source-lock branch, and pull request. Repositories\nthat require an approving review can pass an independent repository-owned App,\nbot, or human service-account token as `approval-token`. The approval identity\nmust differ from the promotion token identity that opens the PR. When no\n`approval-token` is passed, `auto-approve: true` falls back to the caller\n`github.token` and therefore requires GitHub Actions approval permission. The\ngenerated PR may use auto-merge, but it never bypasses the target branch checks.\n\nBefore enabling `auto-approve` and `auto-merge`, the caller repository must\nprovide one approval path and enable auto-merge:\n\n- pass an independent `approval-token`; or enable **Actions > General >\n Workflow permissions > Allow GitHub Actions to create and approve pull\n requests**, so the caller `github.token` can approve independently from the\n promotion token that opened the PR;\n- **General > Pull Requests > Allow auto-merge**, so Patrol can arm the\n protected merge while required reviews and checks are still pending.\n\nGitHub rejects approval from the same identity that opened the pull request;\nPatrol propagates that review failure instead of reporting success.\n\nPatrol treats a GraphQL refusal to enable auto-merge as a hard failure. A run\nmust not report publication authority when GitHub accepted the HTTP request but\nreturned a GraphQL error in the response body.\n\n## Exact-source stable promotion\n\nFor a selected `2.12.0-alpha.4`, Patrol creates the immutable source branch:\n\n```text\npublish-gate/release/v2/v2.12/2.12.0-alpha.4\n```\n\nand opens it against `release/v2/v2.12`. This is an existing strict Buildchain\ngovernance path. The PR freezes the qualified candidate even if `v2.12-alpha`\nor `alpha/v2/v2.12` has already moved to alpha.5. Normal Verify,\nrelease-candidate resolution, source-tree equivalence, publish transaction,\npassport, registry, tag, and floating-ref checks still run.\n\n## Hold, revoke, and immediate release\n\nPersistent repository controls can be supplied as reusable-workflow inputs or\nrepository variables:\n\n```text\nBUILDCHAIN_STABLE_HOLD=true\nBUILDCHAIN_STABLE_HOLD_REASON=release freeze\nBUILDCHAIN_STABLE_REVOKED_ALPHA_VERSIONS=2.12.0-alpha.5,2.12.0-alpha.7\nBUILDCHAIN_STABLE_REVOKE_REASON=consumer regression\n```\n\nRevocation is explicit evidence; publishing a newer alpha alone is not\nrevocation. A manual `workflow_dispatch` `release-now` chooses one exact,\nnon-revoked candidate immediately. It may bypass the scheduled soak decision,\nbut cannot change candidate SHA, reuse a consumed stable version, or bypass the\nsource-lock PR and publish transaction.\n\nFor Buildchain's own stable gate, Patrol automatically projects that explicit\nhuman decision into the exact-candidate `BUILDCHAIN_STABLE_RELEASE_NOW` and\nreason variables. A later Patrol run removes them after it observes the public\nstable release. This is an internal compatibility projection, not a manual user\nstep; the durable authority record remains the candidate ledger entry and PR.\n\n## Durable recovery\n\nThe default ledger ref is derived from the release line, for example:\n\n```text\nbuildchain/candidate-ledger/v2/v2.12\n```\n\nIt stores `.buildchain/stable-candidate-ledger.json`. Patrol runs are serialized\nper repository and release line. Repeated runs reuse the same exact source-lock\nbranch and PR, while later runs observe the public stable release and mark the\ncandidate `promoted`.\n\nWhen Patrol is enabled after a stable version already exists, it reconstructs\nthat consumed patch from GitHub Release truth and closes historical alpha\ncandidates for the same stable version. It never attempts to republish an\nalready claimed exact stable version."
1891
+ "markdown": "---\nstatus: preview\nperiod: ongoing\ntheme: stable-candidate-patrol\ndoc_type: architecture-and-usage\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-11\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-11\n visible_context: Buildchain candidate passport, stable gate, Patrol, publish transaction, exact source-lock PR contracts, tests, and user consensus.\n invisible_context_boundary: No credentials, private logs, or unpublished consumer content were used.\n---\n\n# Stable Candidate Patrol\n\nBuildchain can treat every exact alpha as an independent stable candidate. A\nnew alpha creates a new candidate; it does not silently revoke an older alpha\nthat already completed its checks and soak interval.\n\n## Candidate lifecycle\n\nThe durable ledger contract is `kungfu-buildchain-stable-candidate-ledger`:\n\n```text\nregistered -> soaking -> qualified -> promoted\n \\-> revoked\n```\n\nEach entry binds an exact alpha version to one immutable commit SHA. Registering\nthe same version at another SHA fails closed. Qualification records the required\nchecks, their completion times, the derived soak start, and elapsed time.\n\nScheduled selection chooses the newest `qualified` candidate that is neither\n`revoked` nor already `promoted`. A newer alpha that is still `soaking` does not\nhide an older qualified candidate. Once a stable version is promoted, remaining\nalphas for that exact stable version are closed because that immutable stable\nversion has been consumed; their later product changes continue through the\nnext patch alpha prepared by the normal release transaction.\n\nFor Buildchain's own release line, successful Alpha Self-Dogfood starts an\nidempotent qualification producer for the exact alpha SHA. It dispatches the\nexisting `Build Surface Fixture` at the immutable exact tag, runs the existing\n`site-libkungfu-dev` no-apply canary with the exact SHA, and writes the declared\ncommit-status attestation only after that authoritative canary succeeds. The\nproducer creates evidence only: Patrol still owns qualification and selection,\nand the normal source-lock PR, stable gate, transaction, and branch protections\nremain mandatory.\n\nCross-repository dispatch and repository-local attestation use separate tokens.\nThe promotion token can start the no-apply consumer workflow, while the\nrepository-scoped Actions token writes the status as `github-actions[bot]` only\nafter the producer has observed the successful authoritative workflow run. The\nstable gate still verifies the workflow identity, exact runtime SHA, target URL,\nand allowed attestor before accepting that status.\n\nWhen a candidate changes the outer reusable-workflow YAML itself, the stable\nshell cannot exercise that change through a runtime override. A maintainer may\nmanually qualify it with `canary-ref` set to a dispatchable consumer branch or\ntag and `canary-sha` set to the exact 40-character commit that ref must resolve\nto. That consumer workflow pins the candidate's exact Buildchain SHA. The\nqualification producer verifies the ref binding before dispatch, accepts only a\nrun whose source SHA matches, and writes the normal candidate-bound status only\nafter success. The automatic path continues to use the consumer default branch.\n\n## Repository policy\n\nDeclare the default once in `.buildchain/buildchain.toml`:\n\n```toml\n[release.stable]\nstrategy = \"latest-qualified-alpha\"\ntimezone = \"Asia/Shanghai\"\npublish_at = \"03:00\"\nminimum_soak_seconds = 3600\nrequired_checks = [\n \"alpha-release\",\n \"workflow:Build\",\n \"status:buildchain-canary/consumer\",\n]\nauto_promote = true\nauto_merge = true\n```\n\nCheck identifiers use these forms:\n\n- `alpha-release`: the exact GitHub prerelease publication fact;\n- `workflow:<name>`: a successful Actions workflow run on the exact candidate SHA;\n- `status:<context>`: a successful commit status on the exact candidate SHA;\n- an unprefixed value: an exact status context or check-run name.\n\nCandidate discovery uses immutable exact alpha Git tags. When a repository also\npublishes GitHub prereleases, `alpha-release` binds qualification to that public\nrelease fact. Repositories that intentionally disable GitHub Releases omit\n`alpha-release` and declare their own exact-SHA workflow/status evidence; tag\ncommit time remains the earliest possible soak start.\n\n`publish_at` and `timezone` are the auditable policy declaration. GitHub only\nstarts scheduled workflows from caller-owned cron, so the thin caller keeps the\nmatching UTC trigger:\n\n```yaml\nname: Stable Candidate Patrol\n\non:\n schedule:\n - cron: \"0 19 * * *\" # 03:00 Asia/Shanghai\n workflow_dispatch:\n inputs:\n release-now:\n description: Exact alpha selected by explicit human authority\n required: false\n default: \"\"\n\npermissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n\njobs:\n stable:\n uses: kungfu-systems/buildchain/.github/workflows/stable-candidate-patrol.yml@v2\n with:\n release-now: ${{ inputs.release-now }}\n dry-run: false\n secrets:\n promotion-token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n approval-token: ${{ secrets.BUILDCHAIN_APPROVAL_TOKEN }}\n```\n\nThe promotion token must be repository-owned and capable of creating the\nmachine ledger branch, exact source-lock branch, and pull request. Repositories\nthat require an approving review can pass an independent repository-owned App,\nbot, or human service-account token as `approval-token`. The approval identity\nmust differ from the promotion token identity that opens the PR. When no\n`approval-token` is passed, `auto-approve: true` falls back to the caller\n`github.token` and therefore requires GitHub Actions approval permission. The\ngenerated PR may use auto-merge, but it never bypasses the target branch checks.\n\nBefore enabling `auto-approve` and `auto-merge`, the caller repository must\nprovide one approval path and enable auto-merge:\n\n- pass an independent `approval-token`; or enable **Actions > General >\n Workflow permissions > Allow GitHub Actions to create and approve pull\n requests**, so the caller `github.token` can approve independently from the\n promotion token that opened the PR;\n- **General > Pull Requests > Allow auto-merge**, so Patrol can arm the\n protected merge while required reviews and checks are still pending.\n\nGitHub rejects approval from the same identity that opened the pull request;\nPatrol propagates that review failure instead of reporting success.\n\nPatrol treats a GraphQL refusal to enable auto-merge as a hard failure. A run\nmust not report publication authority when GitHub accepted the HTTP request but\nreturned a GraphQL error in the response body.\n\n## Exact-source stable promotion\n\nFor a selected `2.12.0-alpha.4`, Patrol creates the immutable source branch:\n\n```text\npublish-gate/release/v2/v2.12/2.12.0-alpha.4\n```\n\nand opens it against `release/v2/v2.12`. This is an existing strict Buildchain\ngovernance path. The PR freezes the qualified candidate even if `v2.12-alpha`\nor `alpha/v2/v2.12` has already moved to alpha.5. Normal Verify,\nrelease-candidate resolution, source-tree equivalence, publish transaction,\npassport, registry, tag, and floating-ref checks still run.\n\n## Hold, revoke, and immediate release\n\nPersistent repository controls can be supplied as reusable-workflow inputs or\nrepository variables:\n\n```text\nBUILDCHAIN_STABLE_HOLD=true\nBUILDCHAIN_STABLE_HOLD_REASON=release freeze\nBUILDCHAIN_STABLE_REVOKED_ALPHA_VERSIONS=2.12.0-alpha.5,2.12.0-alpha.7\nBUILDCHAIN_STABLE_REVOKE_REASON=consumer regression\n```\n\nRevocation is explicit evidence; publishing a newer alpha alone is not\nrevocation. A manual `workflow_dispatch` `release-now` chooses one exact,\nnon-revoked candidate immediately. It may bypass the scheduled soak decision,\nbut cannot change candidate SHA, reuse a consumed stable version, or bypass the\nsource-lock PR and publish transaction.\n\nFor Buildchain's own stable gate, Patrol automatically projects that explicit\nhuman decision into the exact-candidate `BUILDCHAIN_STABLE_RELEASE_NOW` and\nreason variables. A later Patrol run removes them after it observes the public\nstable release. This is an internal compatibility projection, not a manual user\nstep; the durable authority record remains the candidate ledger entry and PR.\n\n## Durable recovery\n\nThe default ledger ref is derived from the release line, for example:\n\n```text\nbuildchain/candidate-ledger/v2/v2.12\n```\n\nIt stores `.buildchain/stable-candidate-ledger.json`. Patrol runs are serialized\nper repository and release line. Repeated runs reuse the same exact source-lock\nbranch and PR, while later runs observe the public stable release and mark the\ncandidate `promoted`.\n\nWhen Patrol is enabled after a stable version already exists, it reconstructs\nthat consumed patch from GitHub Release truth and closes historical alpha\ncandidates for the same stable version. It never attempts to republish an\nalready claimed exact stable version."
1892
1892
  },
1893
1893
  {
1894
1894
  "id": "manual:toolkit-observability",
@@ -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": "c96852b69afe7c6f8a42824d03d877d988a4bf5908aafc4f07ec5e79193daf3f",
24
+ "sha256": "e6103b9ce8a24475e86b4347c0828052d5b112355ce9ff77c4d5dc2141ff64a4",
25
25
  "summary": {
26
26
  "cliCommandCount": 81,
27
27
  "workflowCount": 47,
@@ -225,7 +225,7 @@
225
225
  "contract": "kungfu-buildchain-public-surface-reverse-audit",
226
226
  "path": "dist/site/public-surface-audit.json",
227
227
  "status": "passed",
228
- "sha256": "c96852b69afe7c6f8a42824d03d877d988a4bf5908aafc4f07ec5e79193daf3f",
228
+ "sha256": "e6103b9ce8a24475e86b4347c0828052d5b112355ce9ff77c4d5dc2141ff64a4",
229
229
  "summary": {
230
230
  "cliCommandCount": 81,
231
231
  "workflowCount": 47,
@@ -4,7 +4,7 @@
4
4
  "product": {
5
5
  "id": "kungfu-tech-buildchain",
6
6
  "name": "@kungfu-tech/buildchain",
7
- "version": "2.13.0-alpha.2",
7
+ "version": "2.13.0",
8
8
  "repository": "https://github.com/kungfu-systems/buildchain"
9
9
  },
10
10
  "source": {
@@ -1789,7 +1789,7 @@
1789
1789
  ],
1790
1790
  "maturity": "stable",
1791
1791
  "sourcePath": "docs/stable-candidate-patrol.md",
1792
- "digest": "sha256:3af06c896769f5b71e1e0658035e8b4393b7f4ba263f77921cb9f9b8483518c7",
1792
+ "digest": "sha256:87111303392ee9af59506e0d2b82fc1c4432d3953ca1efcd65b6c181a93d5385",
1793
1793
  "headings": [
1794
1794
  {
1795
1795
  "level": 1,
@@ -1822,7 +1822,7 @@
1822
1822
  "anchor": "durable-recovery"
1823
1823
  }
1824
1824
  ],
1825
- "markdown": "---\nstatus: preview\nperiod: ongoing\ntheme: stable-candidate-patrol\ndoc_type: architecture-and-usage\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-11\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-11\n visible_context: Buildchain candidate passport, stable gate, Patrol, publish transaction, exact source-lock PR contracts, tests, and user consensus.\n invisible_context_boundary: No credentials, private logs, or unpublished consumer content were used.\n---\n\n# Stable Candidate Patrol\n\nBuildchain can treat every exact alpha as an independent stable candidate. A\nnew alpha creates a new candidate; it does not silently revoke an older alpha\nthat already completed its checks and soak interval.\n\n## Candidate lifecycle\n\nThe durable ledger contract is `kungfu-buildchain-stable-candidate-ledger`:\n\n```text\nregistered -> soaking -> qualified -> promoted\n \\-> revoked\n```\n\nEach entry binds an exact alpha version to one immutable commit SHA. Registering\nthe same version at another SHA fails closed. Qualification records the required\nchecks, their completion times, the derived soak start, and elapsed time.\n\nScheduled selection chooses the newest `qualified` candidate that is neither\n`revoked` nor already `promoted`. A newer alpha that is still `soaking` does not\nhide an older qualified candidate. Once a stable version is promoted, remaining\nalphas for that exact stable version are closed because that immutable stable\nversion has been consumed; their later product changes continue through the\nnext patch alpha prepared by the normal release transaction.\n\nFor Buildchain's own release line, successful Alpha Self-Dogfood starts an\nidempotent qualification producer for the exact alpha SHA. It dispatches the\nexisting `Build Surface Fixture` at the immutable exact tag, runs the existing\n`site-libkungfu-dev` no-apply canary with the exact SHA, and writes the declared\ncommit-status attestation only after that authoritative canary succeeds. The\nproducer creates evidence only: Patrol still owns qualification and selection,\nand the normal source-lock PR, stable gate, transaction, and branch protections\nremain mandatory.\n\nCross-repository dispatch and repository-local attestation use separate tokens.\nThe promotion token can start the no-apply consumer workflow, while the\nrepository-scoped Actions token writes the status as `github-actions[bot]` only\nafter the producer has observed the successful authoritative workflow run. The\nstable gate still verifies the workflow identity, exact runtime SHA, target URL,\nand allowed attestor before accepting that status.\n\nWhen a candidate changes the outer reusable-workflow YAML itself, the stable\nshell cannot exercise that change through a runtime override. A maintainer may\nmanually qualify it with `canary-ref` set to an exact 40-character commit in the\nconsumer repository whose canary workflow pins the candidate's exact Buildchain\nSHA. The qualification producer still dispatches and observes the authoritative\nconsumer workflow before writing the normal candidate-bound status. Floating\nconsumer branches are rejected, and the automatic path continues to use the\nconsumer default branch.\n\n## Repository policy\n\nDeclare the default once in `.buildchain/buildchain.toml`:\n\n```toml\n[release.stable]\nstrategy = \"latest-qualified-alpha\"\ntimezone = \"Asia/Shanghai\"\npublish_at = \"03:00\"\nminimum_soak_seconds = 3600\nrequired_checks = [\n \"alpha-release\",\n \"workflow:Build\",\n \"status:buildchain-canary/consumer\",\n]\nauto_promote = true\nauto_merge = true\n```\n\nCheck identifiers use these forms:\n\n- `alpha-release`: the exact GitHub prerelease publication fact;\n- `workflow:<name>`: a successful Actions workflow run on the exact candidate SHA;\n- `status:<context>`: a successful commit status on the exact candidate SHA;\n- an unprefixed value: an exact status context or check-run name.\n\nCandidate discovery uses immutable exact alpha Git tags. When a repository also\npublishes GitHub prereleases, `alpha-release` binds qualification to that public\nrelease fact. Repositories that intentionally disable GitHub Releases omit\n`alpha-release` and declare their own exact-SHA workflow/status evidence; tag\ncommit time remains the earliest possible soak start.\n\n`publish_at` and `timezone` are the auditable policy declaration. GitHub only\nstarts scheduled workflows from caller-owned cron, so the thin caller keeps the\nmatching UTC trigger:\n\n```yaml\nname: Stable Candidate Patrol\n\non:\n schedule:\n - cron: \"0 19 * * *\" # 03:00 Asia/Shanghai\n workflow_dispatch:\n inputs:\n release-now:\n description: Exact alpha selected by explicit human authority\n required: false\n default: \"\"\n\npermissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n\njobs:\n stable:\n uses: kungfu-systems/buildchain/.github/workflows/stable-candidate-patrol.yml@v2\n with:\n release-now: ${{ inputs.release-now }}\n dry-run: false\n secrets:\n promotion-token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n approval-token: ${{ secrets.BUILDCHAIN_APPROVAL_TOKEN }}\n```\n\nThe promotion token must be repository-owned and capable of creating the\nmachine ledger branch, exact source-lock branch, and pull request. Repositories\nthat require an approving review can pass an independent repository-owned App,\nbot, or human service-account token as `approval-token`. The approval identity\nmust differ from the promotion token identity that opens the PR. When no\n`approval-token` is passed, `auto-approve: true` falls back to the caller\n`github.token` and therefore requires GitHub Actions approval permission. The\ngenerated PR may use auto-merge, but it never bypasses the target branch checks.\n\nBefore enabling `auto-approve` and `auto-merge`, the caller repository must\nprovide one approval path and enable auto-merge:\n\n- pass an independent `approval-token`; or enable **Actions > General >\n Workflow permissions > Allow GitHub Actions to create and approve pull\n requests**, so the caller `github.token` can approve independently from the\n promotion token that opened the PR;\n- **General > Pull Requests > Allow auto-merge**, so Patrol can arm the\n protected merge while required reviews and checks are still pending.\n\nGitHub rejects approval from the same identity that opened the pull request;\nPatrol propagates that review failure instead of reporting success.\n\nPatrol treats a GraphQL refusal to enable auto-merge as a hard failure. A run\nmust not report publication authority when GitHub accepted the HTTP request but\nreturned a GraphQL error in the response body.\n\n## Exact-source stable promotion\n\nFor a selected `2.12.0-alpha.4`, Patrol creates the immutable source branch:\n\n```text\npublish-gate/release/v2/v2.12/2.12.0-alpha.4\n```\n\nand opens it against `release/v2/v2.12`. This is an existing strict Buildchain\ngovernance path. The PR freezes the qualified candidate even if `v2.12-alpha`\nor `alpha/v2/v2.12` has already moved to alpha.5. Normal Verify,\nrelease-candidate resolution, source-tree equivalence, publish transaction,\npassport, registry, tag, and floating-ref checks still run.\n\n## Hold, revoke, and immediate release\n\nPersistent repository controls can be supplied as reusable-workflow inputs or\nrepository variables:\n\n```text\nBUILDCHAIN_STABLE_HOLD=true\nBUILDCHAIN_STABLE_HOLD_REASON=release freeze\nBUILDCHAIN_STABLE_REVOKED_ALPHA_VERSIONS=2.12.0-alpha.5,2.12.0-alpha.7\nBUILDCHAIN_STABLE_REVOKE_REASON=consumer regression\n```\n\nRevocation is explicit evidence; publishing a newer alpha alone is not\nrevocation. A manual `workflow_dispatch` `release-now` chooses one exact,\nnon-revoked candidate immediately. It may bypass the scheduled soak decision,\nbut cannot change candidate SHA, reuse a consumed stable version, or bypass the\nsource-lock PR and publish transaction.\n\nFor Buildchain's own stable gate, Patrol automatically projects that explicit\nhuman decision into the exact-candidate `BUILDCHAIN_STABLE_RELEASE_NOW` and\nreason variables. A later Patrol run removes them after it observes the public\nstable release. This is an internal compatibility projection, not a manual user\nstep; the durable authority record remains the candidate ledger entry and PR.\n\n## Durable recovery\n\nThe default ledger ref is derived from the release line, for example:\n\n```text\nbuildchain/candidate-ledger/v2/v2.12\n```\n\nIt stores `.buildchain/stable-candidate-ledger.json`. Patrol runs are serialized\nper repository and release line. Repeated runs reuse the same exact source-lock\nbranch and PR, while later runs observe the public stable release and mark the\ncandidate `promoted`.\n\nWhen Patrol is enabled after a stable version already exists, it reconstructs\nthat consumed patch from GitHub Release truth and closes historical alpha\ncandidates for the same stable version. It never attempts to republish an\nalready claimed exact stable version."
1825
+ "markdown": "---\nstatus: preview\nperiod: ongoing\ntheme: stable-candidate-patrol\ndoc_type: architecture-and-usage\nsource_level: local-files\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: self-reviewed\nlast_reviewed: 2026-07-11\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-07-11\n visible_context: Buildchain candidate passport, stable gate, Patrol, publish transaction, exact source-lock PR contracts, tests, and user consensus.\n invisible_context_boundary: No credentials, private logs, or unpublished consumer content were used.\n---\n\n# Stable Candidate Patrol\n\nBuildchain can treat every exact alpha as an independent stable candidate. A\nnew alpha creates a new candidate; it does not silently revoke an older alpha\nthat already completed its checks and soak interval.\n\n## Candidate lifecycle\n\nThe durable ledger contract is `kungfu-buildchain-stable-candidate-ledger`:\n\n```text\nregistered -> soaking -> qualified -> promoted\n \\-> revoked\n```\n\nEach entry binds an exact alpha version to one immutable commit SHA. Registering\nthe same version at another SHA fails closed. Qualification records the required\nchecks, their completion times, the derived soak start, and elapsed time.\n\nScheduled selection chooses the newest `qualified` candidate that is neither\n`revoked` nor already `promoted`. A newer alpha that is still `soaking` does not\nhide an older qualified candidate. Once a stable version is promoted, remaining\nalphas for that exact stable version are closed because that immutable stable\nversion has been consumed; their later product changes continue through the\nnext patch alpha prepared by the normal release transaction.\n\nFor Buildchain's own release line, successful Alpha Self-Dogfood starts an\nidempotent qualification producer for the exact alpha SHA. It dispatches the\nexisting `Build Surface Fixture` at the immutable exact tag, runs the existing\n`site-libkungfu-dev` no-apply canary with the exact SHA, and writes the declared\ncommit-status attestation only after that authoritative canary succeeds. The\nproducer creates evidence only: Patrol still owns qualification and selection,\nand the normal source-lock PR, stable gate, transaction, and branch protections\nremain mandatory.\n\nCross-repository dispatch and repository-local attestation use separate tokens.\nThe promotion token can start the no-apply consumer workflow, while the\nrepository-scoped Actions token writes the status as `github-actions[bot]` only\nafter the producer has observed the successful authoritative workflow run. The\nstable gate still verifies the workflow identity, exact runtime SHA, target URL,\nand allowed attestor before accepting that status.\n\nWhen a candidate changes the outer reusable-workflow YAML itself, the stable\nshell cannot exercise that change through a runtime override. A maintainer may\nmanually qualify it with `canary-ref` set to a dispatchable consumer branch or\ntag and `canary-sha` set to the exact 40-character commit that ref must resolve\nto. That consumer workflow pins the candidate's exact Buildchain SHA. The\nqualification producer verifies the ref binding before dispatch, accepts only a\nrun whose source SHA matches, and writes the normal candidate-bound status only\nafter success. The automatic path continues to use the consumer default branch.\n\n## Repository policy\n\nDeclare the default once in `.buildchain/buildchain.toml`:\n\n```toml\n[release.stable]\nstrategy = \"latest-qualified-alpha\"\ntimezone = \"Asia/Shanghai\"\npublish_at = \"03:00\"\nminimum_soak_seconds = 3600\nrequired_checks = [\n \"alpha-release\",\n \"workflow:Build\",\n \"status:buildchain-canary/consumer\",\n]\nauto_promote = true\nauto_merge = true\n```\n\nCheck identifiers use these forms:\n\n- `alpha-release`: the exact GitHub prerelease publication fact;\n- `workflow:<name>`: a successful Actions workflow run on the exact candidate SHA;\n- `status:<context>`: a successful commit status on the exact candidate SHA;\n- an unprefixed value: an exact status context or check-run name.\n\nCandidate discovery uses immutable exact alpha Git tags. When a repository also\npublishes GitHub prereleases, `alpha-release` binds qualification to that public\nrelease fact. Repositories that intentionally disable GitHub Releases omit\n`alpha-release` and declare their own exact-SHA workflow/status evidence; tag\ncommit time remains the earliest possible soak start.\n\n`publish_at` and `timezone` are the auditable policy declaration. GitHub only\nstarts scheduled workflows from caller-owned cron, so the thin caller keeps the\nmatching UTC trigger:\n\n```yaml\nname: Stable Candidate Patrol\n\non:\n schedule:\n - cron: \"0 19 * * *\" # 03:00 Asia/Shanghai\n workflow_dispatch:\n inputs:\n release-now:\n description: Exact alpha selected by explicit human authority\n required: false\n default: \"\"\n\npermissions:\n contents: write\n pull-requests: write\n checks: read\n statuses: read\n\njobs:\n stable:\n uses: kungfu-systems/buildchain/.github/workflows/stable-candidate-patrol.yml@v2\n with:\n release-now: ${{ inputs.release-now }}\n dry-run: false\n secrets:\n promotion-token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}\n approval-token: ${{ secrets.BUILDCHAIN_APPROVAL_TOKEN }}\n```\n\nThe promotion token must be repository-owned and capable of creating the\nmachine ledger branch, exact source-lock branch, and pull request. Repositories\nthat require an approving review can pass an independent repository-owned App,\nbot, or human service-account token as `approval-token`. The approval identity\nmust differ from the promotion token identity that opens the PR. When no\n`approval-token` is passed, `auto-approve: true` falls back to the caller\n`github.token` and therefore requires GitHub Actions approval permission. The\ngenerated PR may use auto-merge, but it never bypasses the target branch checks.\n\nBefore enabling `auto-approve` and `auto-merge`, the caller repository must\nprovide one approval path and enable auto-merge:\n\n- pass an independent `approval-token`; or enable **Actions > General >\n Workflow permissions > Allow GitHub Actions to create and approve pull\n requests**, so the caller `github.token` can approve independently from the\n promotion token that opened the PR;\n- **General > Pull Requests > Allow auto-merge**, so Patrol can arm the\n protected merge while required reviews and checks are still pending.\n\nGitHub rejects approval from the same identity that opened the pull request;\nPatrol propagates that review failure instead of reporting success.\n\nPatrol treats a GraphQL refusal to enable auto-merge as a hard failure. A run\nmust not report publication authority when GitHub accepted the HTTP request but\nreturned a GraphQL error in the response body.\n\n## Exact-source stable promotion\n\nFor a selected `2.12.0-alpha.4`, Patrol creates the immutable source branch:\n\n```text\npublish-gate/release/v2/v2.12/2.12.0-alpha.4\n```\n\nand opens it against `release/v2/v2.12`. This is an existing strict Buildchain\ngovernance path. The PR freezes the qualified candidate even if `v2.12-alpha`\nor `alpha/v2/v2.12` has already moved to alpha.5. Normal Verify,\nrelease-candidate resolution, source-tree equivalence, publish transaction,\npassport, registry, tag, and floating-ref checks still run.\n\n## Hold, revoke, and immediate release\n\nPersistent repository controls can be supplied as reusable-workflow inputs or\nrepository variables:\n\n```text\nBUILDCHAIN_STABLE_HOLD=true\nBUILDCHAIN_STABLE_HOLD_REASON=release freeze\nBUILDCHAIN_STABLE_REVOKED_ALPHA_VERSIONS=2.12.0-alpha.5,2.12.0-alpha.7\nBUILDCHAIN_STABLE_REVOKE_REASON=consumer regression\n```\n\nRevocation is explicit evidence; publishing a newer alpha alone is not\nrevocation. A manual `workflow_dispatch` `release-now` chooses one exact,\nnon-revoked candidate immediately. It may bypass the scheduled soak decision,\nbut cannot change candidate SHA, reuse a consumed stable version, or bypass the\nsource-lock PR and publish transaction.\n\nFor Buildchain's own stable gate, Patrol automatically projects that explicit\nhuman decision into the exact-candidate `BUILDCHAIN_STABLE_RELEASE_NOW` and\nreason variables. A later Patrol run removes them after it observes the public\nstable release. This is an internal compatibility projection, not a manual user\nstep; the durable authority record remains the candidate ledger entry and PR.\n\n## Durable recovery\n\nThe default ledger ref is derived from the release line, for example:\n\n```text\nbuildchain/candidate-ledger/v2/v2.12\n```\n\nIt stores `.buildchain/stable-candidate-ledger.json`. Patrol runs are serialized\nper repository and release line. Repeated runs reuse the same exact source-lock\nbranch and PR, while later runs observe the public stable release and mark the\ncandidate `promoted`.\n\nWhen Patrol is enabled after a stable version already exists, it reconstructs\nthat consumed patch from GitHub Release truth and closes historical alpha\ncandidates for the same stable version. It never attempts to republish an\nalready claimed exact stable version."
1826
1826
  },
1827
1827
  {
1828
1828
  "id": "manual:toolkit-observability",
@@ -3442,7 +3442,7 @@
3442
3442
  "pageRegistryPath": "dist/site/page-registry.json",
3443
3443
  "cliRegistryDigest": "ff1772371ec74271c21b6723dad0745a080347be8feef36a37e3948ebd19993d",
3444
3444
  "workflowRegistryDigest": "f4dc70cba95a30b79eadbffc3a4ba5f8d382aae220a7bc13965365c2bbd5e674",
3445
- "pageRegistryDigest": "5df8fd620c13f894a00ecc7159b7a53f518692d3d2ab3ad0b78687582efa406a"
3445
+ "pageRegistryDigest": "32a749f66f77ed976da58b16564ae2b27dfd16125ad3b47d49e261c09ec82f58"
3446
3446
  },
3447
3447
  "comparison": {
3448
3448
  "missingCliRegistry": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-16T02:29:49.950Z",
5
- "publishedAt": "2026-07-16T02:29:49.950Z",
4
+ "generatedAt": "2026-07-16T07:45:10.422Z",
5
+ "publishedAt": "2026-07-16T07:45:10.422Z",
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": "87491685ffc7b6522176f8e7519f6c595e9bf5a7",
22
+ "sourceRevision": "eeac02686f845b4f496b2cbaa78713e6d4b2532b",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "package": {
34
34
  "name": "@kungfu-tech/buildchain",
35
- "version": "2.13.0-alpha.2",
35
+ "version": "2.13.0",
36
36
  "versionSource": "package.json#version"
37
37
  },
38
38
  "sourceKind": "package-site-bundle",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-manifest",
4
- "generatedAt": "2026-07-16T02:29:49.950Z",
5
- "publishedAt": "2026-07-16T02:29:49.950Z",
4
+ "generatedAt": "2026-07-16T07:45:10.422Z",
5
+ "publishedAt": "2026-07-16T07:45:10.422Z",
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": "87491685ffc7b6522176f8e7519f6c595e9bf5a7",
22
+ "sourceRevision": "eeac02686f845b4f496b2cbaa78713e6d4b2532b",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "package": {
39
39
  "name": "@kungfu-tech/buildchain",
40
- "version": "2.13.0-alpha.2",
40
+ "version": "2.13.0",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -61,12 +61,12 @@ and allowed attestor before accepting that status.
61
61
 
62
62
  When a candidate changes the outer reusable-workflow YAML itself, the stable
63
63
  shell cannot exercise that change through a runtime override. A maintainer may
64
- manually qualify it with `canary-ref` set to an exact 40-character commit in the
65
- consumer repository whose canary workflow pins the candidate's exact Buildchain
66
- SHA. The qualification producer still dispatches and observes the authoritative
67
- consumer workflow before writing the normal candidate-bound status. Floating
68
- consumer branches are rejected, and the automatic path continues to use the
69
- consumer default branch.
64
+ manually qualify it with `canary-ref` set to a dispatchable consumer branch or
65
+ tag and `canary-sha` set to the exact 40-character commit that ref must resolve
66
+ to. That consumer workflow pins the candidate's exact Buildchain SHA. The
67
+ qualification producer verifies the ref binding before dispatch, accepts only a
68
+ run whose source SHA matches, and writes the normal candidate-bound status only
69
+ after success. The automatic path continues to use the consumer default branch.
70
70
 
71
71
  ## Repository policy
72
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.13.0-alpha.2",
3
+ "version": "2.13.0",
4
4
  "private": false,
5
5
  "description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
6
6
  "repository": "https://github.com/kungfu-systems/buildchain",
@@ -41,11 +41,27 @@ function optionalSha(value, label) {
41
41
  return normalized;
42
42
  }
43
43
 
44
+ function optionalRef(value, label) {
45
+ const normalized = text(value);
46
+ if (
47
+ normalized &&
48
+ (!/^[A-Za-z0-9._/-]+$/.test(normalized) || normalized.includes("..") || normalized.startsWith("/") || normalized.endsWith("/"))
49
+ ) {
50
+ throw new Error(`${label} must be a safe branch or tag ref, got ${normalized}`);
51
+ }
52
+ return normalized;
53
+ }
54
+
44
55
  export function normalizeStableCandidateQualificationOptions(options = {}) {
45
56
  const candidateSha = text(options.candidateSha ?? process.env.BUILDCHAIN_QUALIFICATION_CANDIDATE_SHA);
46
57
  if (!/^[0-9a-f]{40}$/i.test(candidateSha)) {
47
58
  throw new Error(`candidate SHA must be 40 hexadecimal characters, got ${candidateSha || "<empty>"}`);
48
59
  }
60
+ const canaryRef = optionalRef(options.canaryRef ?? process.env.BUILDCHAIN_QUALIFICATION_CANARY_REF, "canary ref");
61
+ const canarySha = optionalSha(options.canarySha ?? process.env.BUILDCHAIN_QUALIFICATION_CANARY_SHA, "canary SHA");
62
+ if (Boolean(canaryRef) !== Boolean(canarySha)) {
63
+ throw new Error("canary ref and canary SHA must be provided together");
64
+ }
49
65
  return {
50
66
  repository: repository(options.repository ?? process.env.BUILDCHAIN_QUALIFICATION_REPOSITORY ?? process.env.GITHUB_REPOSITORY),
51
67
  candidateSha,
@@ -55,7 +71,8 @@ export function normalizeStableCandidateQualificationOptions(options = {}) {
55
71
  canaryWorkflowFile: text(options.canaryWorkflowFile ?? process.env.BUILDCHAIN_QUALIFICATION_CANARY_WORKFLOW_FILE) || DEFAULTS.canaryWorkflowFile,
56
72
  canaryWorkflowName: text(options.canaryWorkflowName ?? process.env.BUILDCHAIN_QUALIFICATION_CANARY_WORKFLOW_NAME) || DEFAULTS.canaryWorkflowName,
57
73
  canaryStatusContext: text(options.canaryStatusContext ?? process.env.BUILDCHAIN_QUALIFICATION_CANARY_STATUS_CONTEXT) || DEFAULTS.canaryStatusContext,
58
- canaryRef: optionalSha(options.canaryRef ?? process.env.BUILDCHAIN_QUALIFICATION_CANARY_REF, "canary ref"),
74
+ canaryRef,
75
+ canarySha,
59
76
  pollAttempts: integer(options.pollAttempts ?? process.env.BUILDCHAIN_QUALIFICATION_POLL_ATTEMPTS, DEFAULTS.pollAttempts),
60
77
  pollIntervalMs: integer(options.pollIntervalMs ?? process.env.BUILDCHAIN_QUALIFICATION_POLL_INTERVAL_MS, DEFAULTS.pollIntervalMs),
61
78
  dryRun: bool(options.dryRun ?? process.env.BUILDCHAIN_QUALIFICATION_DRY_RUN, false),
@@ -70,8 +87,8 @@ function active(run) {
70
87
  return run && run.status !== "completed";
71
88
  }
72
89
 
73
- async function ensureWorkflowEvidence({ client, repository, workflowFile, workflowName, ref, headSha, runName, options }) {
74
- let run = await client.findWorkflowRun({ repository, workflowFile, workflowName, headSha, runName });
90
+ async function ensureWorkflowEvidence({ client, repository, workflowFile, workflowName, ref, headSha, runName, sourceSha = "", options }) {
91
+ let run = await client.findWorkflowRun({ repository, workflowFile, workflowName, headSha, runName, sourceSha });
75
92
  if (successful(run)) return { state: "existing", run };
76
93
 
77
94
  if (!active(run)) {
@@ -86,6 +103,7 @@ async function ensureWorkflowEvidence({ client, repository, workflowFile, workfl
86
103
  workflowName,
87
104
  headSha,
88
105
  runName,
106
+ sourceSha,
89
107
  attempts: options.pollAttempts,
90
108
  intervalMs: options.pollIntervalMs,
91
109
  });
@@ -118,6 +136,12 @@ export async function runStableCandidateQualification(optionsInput = {}, clientI
118
136
  let canary = { state: status?.state === "success" ? "existing" : "pending", run: undefined };
119
137
  if (status?.state !== "success") {
120
138
  const canaryRef = options.canaryRef || await client.defaultBranch(options.canaryRepository);
139
+ if (options.canaryRef) {
140
+ const resolvedCanarySha = await client.resolveCommitSha(options.canaryRepository, canaryRef);
141
+ if (resolvedCanarySha !== options.canarySha) {
142
+ throw new Error(`canary ref ${canaryRef} resolved to ${resolvedCanarySha || "<missing>"}, expected ${options.canarySha}`);
143
+ }
144
+ }
121
145
  canary = await ensureWorkflowEvidence({
122
146
  client,
123
147
  repository: options.canaryRepository,
@@ -126,6 +150,7 @@ export async function runStableCandidateQualification(optionsInput = {}, clientI
126
150
  ref: canaryRef,
127
151
  headSha: options.candidateSha,
128
152
  runName: `${options.canaryWorkflowName} / ${options.candidateSha}`,
153
+ sourceSha: options.canarySha,
129
154
  options,
130
155
  });
131
156
  if (!options.dryRun) {
@@ -146,7 +171,12 @@ export async function runStableCandidateQualification(optionsInput = {}, clientI
146
171
  dryRun: options.dryRun,
147
172
  candidate,
148
173
  build: { state: build.state, url: build.run?.html_url || "" },
149
- canary: { state: canary.state, ref: options.canaryRef || "default-branch", url: canary.run?.html_url || status?.target_url || "" },
174
+ canary: {
175
+ state: canary.state,
176
+ ref: options.canaryRef || "default-branch",
177
+ sha: options.canarySha,
178
+ url: canary.run?.html_url || status?.target_url || "",
179
+ },
150
180
  attestation: { context: options.canaryStatusContext, state: options.dryRun ? "planned" : status?.state || "" },
151
181
  };
152
182
  }
@@ -185,8 +215,11 @@ export function createGitHubQualificationClient({
185
215
  const payload = await api(`/repos/${query.repository}/actions/workflows/${encodeURIComponent(query.workflowFile)}/runs?per_page=100`);
186
216
  return (payload.workflow_runs || [])
187
217
  .filter((run) => (
188
- (!query.runName && (!query.headSha || run.head_sha === query.headSha))
189
- || (query.runName && (run.display_title === query.runName || run.name === query.runName))
218
+ (
219
+ (!query.runName && (!query.headSha || run.head_sha === query.headSha))
220
+ || (query.runName && (run.display_title === query.runName || run.name === query.runName))
221
+ )
222
+ && (!query.sourceSha || run.head_sha === query.sourceSha)
190
223
  ))
191
224
  .sort((left, right) => String(right.created_at).localeCompare(String(left.created_at)))[0];
192
225
  }
@@ -203,6 +236,9 @@ export function createGitHubQualificationClient({
203
236
  async defaultBranch(repositoryName) {
204
237
  return (await api(`/repos/${repositoryName}`)).default_branch;
205
238
  },
239
+ async resolveCommitSha(repositoryName, ref) {
240
+ return (await api(`/repos/${repositoryName}/commits/${encodeURIComponent(ref)}`)).sha || "";
241
+ },
206
242
  findWorkflowRun: matchingRun,
207
243
  async dispatchWorkflow({ repository: repositoryName, workflowFile, ref, inputs }) {
208
244
  await api(`/repos/${repositoryName}/actions/workflows/${encodeURIComponent(workflowFile)}/dispatches`, { method: "POST", body: { ref, inputs } });