@kungfu-tech/buildchain 2.14.14-alpha.0 → 2.14.14-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.
Files changed (33) hide show
  1. package/actions/promote-buildchain-ref/README.md +6 -4
  2. package/dist/site/buildchain-contract.json +75 -21
  3. package/dist/site/buildchain-site.json +26 -25
  4. package/dist/site/controller-registry.json +47 -1
  5. package/dist/site/kfd-claims.json +10 -5
  6. package/dist/site/kfd-upstream-aggregate.json +1 -1
  7. package/dist/site/manual-registry.json +5 -5
  8. package/dist/site/node-api-registry.json +5 -5
  9. package/dist/site/page-registry.json +12 -12
  10. package/dist/site/public-surface-audit.json +9 -4
  11. package/dist/site/publication-authority-registry.json +3 -2
  12. package/dist/site/publication-registry.json +4 -4
  13. package/dist/site/release-model.json +4 -2
  14. package/dist/site/site-manifest.json +9 -9
  15. package/dist/site/workflow-registry.json +7 -2
  16. package/docs/binary-distribution.md +22 -8
  17. package/docs/release-candidate.md +4 -2
  18. package/docs/release-governance.md +5 -3
  19. package/docs/site-bundle-contract.md +4 -0
  20. package/docs/web-surface-deployments.md +6 -1
  21. package/package.json +1 -1
  22. package/packages/core/buildchain-contract.js +2 -1
  23. package/packages/core/controller-evidence.js +8 -0
  24. package/packages/core/issue-reporting.js +9 -4
  25. package/packages/core/publication-authority.js +75 -0
  26. package/scripts/assemble-binary-release-admission.mjs +201 -0
  27. package/scripts/check-inventory.mjs +1 -1
  28. package/scripts/ensure-github-release.mjs +20 -6
  29. package/scripts/generate-channel-promotion-workflow.mjs +3 -3
  30. package/scripts/generate-site-bundle.mjs +8 -4
  31. package/scripts/runtime-ref-core.mjs +12 -0
  32. package/scripts/site-bundle-homepage.mjs +39 -0
  33. package/scripts/web-surface-cloudfront-rewrite.mjs +100 -53
@@ -439,17 +439,22 @@
439
439
  "path": ".github/workflows/.binary-release-assets.yml",
440
440
  "reusable": true,
441
441
  "inputs": [
442
+ "auto-admission",
442
443
  "buildchain-ref",
443
444
  "buildchain-repository",
444
445
  "evidence-run-id",
445
446
  "publication-admission-json",
446
447
  "publication-control-plane-audit-json",
447
448
  "publication-expected-json",
449
+ "publication-gate-aggregate-json",
448
450
  "publication-runner-provenance-json",
449
451
  "publication-used-nonces-json",
450
- "release-tag"
452
+ "publication-version",
453
+ "release-tag",
454
+ "source-sha",
455
+ "target-ref"
451
456
  ],
452
- "inputCount": 9,
457
+ "inputCount": 14,
453
458
  "secrets": [],
454
459
  "secretCount": 0,
455
460
  "outputs": [],
@@ -3689,8 +3694,8 @@
3689
3694
  "workflowRegistryPath": "dist/site/workflow-registry.json",
3690
3695
  "pageRegistryPath": "dist/site/page-registry.json",
3691
3696
  "cliRegistryDigest": "90c8650f870c350ca187c612c37f2459dcbfe265aa9b1d5b375ee5c4fb31fab6",
3692
- "workflowRegistryDigest": "02950eef2769b1bd2d899099ef23c7b9ae716b18d602e25aef169d788bc6ece0",
3693
- "pageRegistryDigest": "bb87721a858798cacd2bc79e9e16c78ae32aa8a563e2adcfebc6918b293ef36a"
3697
+ "workflowRegistryDigest": "cdc3924bccc6ddf1242603f0a588e69cbb45882e602f4fba60fa23866813eb50",
3698
+ "pageRegistryDigest": "b544ae35ecc853f227e6b9b233ef5b58b3f8b4867c28377a07cb5cb325f56454"
3694
3699
  },
3695
3700
  "comparison": {
3696
3701
  "missingCliRegistry": [],
@@ -624,7 +624,8 @@
624
624
  {
625
625
  "path": ".github/workflows/binary-release-assets.yml",
626
626
  "signals": [
627
- "write-permission"
627
+ "write-permission",
628
+ "github-release"
628
629
  ],
629
630
  "classified": true
630
631
  },
@@ -846,5 +847,5 @@
846
847
  "classified": false
847
848
  }
848
849
  ],
849
- "registryDigest": "588256fc42c36c1e03286f1de1f7abe1e393ce2e452ae1bf548668f017316590"
850
+ "registryDigest": "9981084c8a18fe72639de7441112a14e32f16b8aa49c0ba0ab9b92dee9bb4df3"
850
851
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-21T20:21:16.004Z",
5
- "publishedAt": "2026-07-21T20:21:16.004Z",
4
+ "generatedAt": "2026-07-22T01:54:29.864Z",
5
+ "publishedAt": "2026-07-22T01:54:29.864Z",
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": "6d2cbfecd9aa3cd90bbd083d1f6403824e24aa3a",
22
+ "sourceRevision": "907987a0d059e1f92a2664509514487722dcf04f",
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.14.14-alpha.0",
35
+ "version": "2.14.14-alpha.2",
36
36
  "versionSource": "package.json#version"
37
37
  },
38
38
  "sourceKind": "package-site-bundle",
@@ -38,8 +38,10 @@
38
38
  },
39
39
  "githubRelease": {
40
40
  "exactTagRelease": true,
41
- "prereleaseTags": "semver prerelease tags set prerelease=true and make_latest=false",
42
- "stableTags": "stable semver tags set make_latest=true",
41
+ "authoritativeIntent": "Buildchain publication channel controls prerelease/latest metadata when available",
42
+ "alphaChannel": "alpha publication sets prerelease=true and make_latest=false",
43
+ "stableChannels": "release, stable, and major publication set prerelease=false and make_latest=true",
44
+ "tagFallback": "ordinary callers without publication intent use semver prerelease syntax",
43
45
  "evidenceAssets": [
44
46
  "publish evidence JSON",
45
47
  "buildchain.release.json",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-manifest",
4
- "generatedAt": "2026-07-21T20:21:16.004Z",
5
- "publishedAt": "2026-07-21T20:21:16.004Z",
4
+ "generatedAt": "2026-07-22T01:54:29.864Z",
5
+ "publishedAt": "2026-07-22T01:54:29.864Z",
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": "6d2cbfecd9aa3cd90bbd083d1f6403824e24aa3a",
22
+ "sourceRevision": "907987a0d059e1f92a2664509514487722dcf04f",
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.14.14-alpha.0",
40
+ "version": "2.14.14-alpha.2",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -93,7 +93,7 @@
93
93
  "path": "docs/release-candidate.md",
94
94
  "plane": "verify",
95
95
  "exists": true,
96
- "digest": "sha256:5528710ec63dbdf86a34e69a7ea86dbb4194d72522951fc67108f5cfc58fdd99"
96
+ "digest": "sha256:2127c22b7b567befd05cba7d50ed0ca229b366072506e06fa09a0962ccfc1db7"
97
97
  },
98
98
  {
99
99
  "id": "release-propagation",
@@ -125,7 +125,7 @@
125
125
  "path": "docs/binary-distribution.md",
126
126
  "plane": "verify",
127
127
  "exists": true,
128
- "digest": "sha256:08be4e97e1940a45a2c0aa2506012255888c23c1cbf8f5362b27c2b21089eb58"
128
+ "digest": "sha256:ae0f8e6cafbb4c39732e6a7252d46b523b0a69e8e05d3ce7ee0b51a916b1b027"
129
129
  },
130
130
  {
131
131
  "id": "consumer-issue-reporting",
@@ -157,7 +157,7 @@
157
157
  "path": "docs/site-bundle-contract.md",
158
158
  "plane": "use",
159
159
  "exists": true,
160
- "digest": "sha256:d67a874b356fbfbee9a5d5ece7021fb52b2848347dc6a14f5562c98570096916"
160
+ "digest": "sha256:583661a7886bdca7e8352a5325a105a9df5d134b3896dbda13ad9b637697481f"
161
161
  },
162
162
  {
163
163
  "id": "migration-inventory",
@@ -237,7 +237,7 @@
237
237
  "path": "docs/release-governance.md",
238
238
  "plane": "why",
239
239
  "exists": true,
240
- "digest": "sha256:adb3c5d2eb414ffde6e75032c8f795de0c4ad929baa2d1e5d0f8cd7a9af39f1f"
240
+ "digest": "sha256:4476d9464d29b4ff3951a362607a11c78b7a3b723247af93ded1b15ff8f0b83e"
241
241
  },
242
242
  {
243
243
  "id": "release-flow",
@@ -269,7 +269,7 @@
269
269
  "path": "docs/web-surface-deployments.md",
270
270
  "plane": "use",
271
271
  "exists": true,
272
- "digest": "sha256:856a7602cf1c2d3a0a540cdf0d64566914f79dde24417c92089f0466bee25df1"
272
+ "digest": "sha256:efbfc97a947978e61c79dac9d67f87050b1bef2297617303340af8aa499703c9"
273
273
  }
274
274
  ],
275
275
  "facts": [
@@ -8,17 +8,22 @@
8
8
  "path": ".github/workflows/.binary-release-assets.yml",
9
9
  "reusable": true,
10
10
  "inputs": [
11
+ "auto-admission",
11
12
  "buildchain-ref",
12
13
  "buildchain-repository",
13
14
  "evidence-run-id",
14
15
  "publication-admission-json",
15
16
  "publication-control-plane-audit-json",
16
17
  "publication-expected-json",
18
+ "publication-gate-aggregate-json",
17
19
  "publication-runner-provenance-json",
18
20
  "publication-used-nonces-json",
19
- "release-tag"
21
+ "publication-version",
22
+ "release-tag",
23
+ "source-sha",
24
+ "target-ref"
20
25
  ],
21
- "inputCount": 9,
26
+ "inputCount": 14,
22
27
  "secrets": [],
23
28
  "secretCount": 0,
24
29
  "outputs": [],
@@ -16,16 +16,25 @@ The release lane does not upload loose top-level `buildchain` or
16
16
  inside their archives, so top-level loose assets would collide when matrix
17
17
  artifacts are merged.
18
18
 
19
- Release asset upload is gated before the matrix starts. Manual
20
- `workflow_dispatch` runs are binary dry-runs only and must keep
21
- `upload-release=false`; real GitHub Release uploads must come from a true
22
- `v*` tag-triggered run so an invalid manual upload request cannot spend the
23
- three-platform build matrix and then fail at `gh release upload`.
24
-
25
- GitHub Release metadata is deterministic and tag-derived. Exact alpha tags such
19
+ `Binary Distribution` is evidence-only. It never receives `contents: write`
20
+ and never calls `gh release upload`; `upload-release=true` is rejected before
21
+ the matrix starts. A completed promotion explicitly dispatches the workflow at
22
+ the exact public tag because tags created by `GITHUB_TOKEN` do not recursively
23
+ start ordinary `push` workflows.
24
+
25
+ After all three runners qualify, the workflow seals a
26
+ `binary-distribution` controller receipt over the exact source/runtime SHA,
27
+ release bundle, and Release Passport. `Binary Release Assets` observes the
28
+ successful evidence run, rechecks that its source SHA still equals the exact
29
+ tag, derives the governed alpha/release branch, and asks the credential-free
30
+ publication authority to assemble a short-lived capability. Only the nested
31
+ publisher owns `contents: write`, and it runs behind the protected
32
+ `buildchain-release-assets` Environment.
33
+
34
+ GitHub Release metadata remains deterministic and tag-derived. Exact alpha tags such
26
35
  as `v2.6.2-alpha.0` are created or updated with `prerelease=true` and
27
36
  `make_latest=false`; exact stable tags such as `v2.6.1` are created or updated
28
- with `prerelease=false` and `make_latest=true`. The workflow uses
37
+ with `prerelease=false` and `make_latest=true`. The sealed publisher uses
29
38
  `scripts/ensure-github-release.mjs` before asset upload instead of relying on
30
39
  GitHub's default latest-release heuristic.
31
40
 
@@ -85,6 +94,11 @@ buildchain-release-bundle/
85
94
  file digest. Consumers can download the bundle when they want one artifact for
86
95
  offline review, mirroring, or site ingestion.
87
96
 
97
+ Publication fails closed unless the bundle contains all three archives,
98
+ `checksums.txt`, and `buildchain.release.json`; its archive digest, controller
99
+ receipt, live control-plane audit, runner provenance, source/runtime SHA, and
100
+ exact target tag must all match the sealed admission.
101
+
88
102
  ## Local Smoke
89
103
 
90
104
  ```bash
@@ -110,8 +110,10 @@ router failure.
110
110
  By default, the wrapper forwards GitHub Release publication to the underlying
111
111
  `promote-buildchain-ref` semver model. Once the release transaction is complete,
112
112
  the action creates or updates the public GitHub Release, applies
113
- prerelease/latest metadata from the semver tag, and uploads the publish evidence
114
- file together with the generated release passport assets. This keeps
113
+ prerelease/latest metadata from the authoritative publication channel (falling
114
+ back to semver tag syntax only for ordinary callers without that intent), and
115
+ uploads the publish evidence file together with the generated release passport
116
+ assets. This keeps
115
117
  npm/registry publication, Buildchain release passport persistence, and
116
118
  `release.published` propagation in one declarative reusable workflow. Consumers
117
119
  that do not publish GitHub Releases can opt out with `github-release: false`.
@@ -813,9 +813,11 @@ generated channel router, where GitHub Release publication is enabled by default
813
813
  disabled with `github-release: false`; the wrapper passes that declaration to
814
814
  the action. After the publish transaction reaches `complete`, Buildchain creates
815
815
  or updates the public GitHub Release and uploads the generated
816
- `buildchain.release.json`, release-passport assets, and publish evidence. Semver
817
- prerelease tags are marked `prerelease=true` and `make_latest=false`; stable
818
- semver tags are marked latest. For anchored/manual package releases, the public
816
+ `buildchain.release.json`, release-passport assets, and publish evidence. The
817
+ authoritative publication channel controls GitHub metadata: alpha is marked
818
+ `prerelease=true` and `make_latest=false`; release/stable/major is marked latest.
819
+ Semver tag syntax remains the fallback for ordinary callers without explicit
820
+ publication intent. For anchored/manual package releases, the public
819
821
  release tag defaults to `v<publishedVersion>` while the internal exact
820
822
  transaction tag remains in the release passport and release-state ref. This is
821
823
  the supported path for downstream
@@ -35,6 +35,10 @@ It includes a `homepage` object generated from `README.md`, including
35
35
  `homepage.rendererContract` that is implementation metadata rather than
36
36
  ordinary homepage copy. Site repositories should consume those fields instead
37
37
  of parsing `README.md` themselves.
38
+ When the README intro starts with the managed Buildchain badge block,
39
+ `homepage.lead` contains that complete marker-delimited block and
40
+ `homepage.mechanismSummary` starts with prose after the block. A renderer must
41
+ not repair or recombine split badge fragments.
38
42
  It also includes a `pages` collection that mirrors `page-registry.json`, so a
39
43
  site repository can build the full Buildchain public documentation surface from
40
44
  the npm package without scanning the source checkout.
@@ -456,7 +456,12 @@ compatibility evidence, but root correctness comes from the viewer-request
456
456
  rewrite contract, not from extensionless S3 keys. If Buildchain-managed mode
457
457
  finds a distribution with a different viewer-request function, apply fails
458
458
  closed and records that conflict in the apply result instead of silently serving
459
- 403s. The reusable workflow uploads `buildchain-web-surface-*-diagnostics`
459
+ 403s. If CloudFront rejects `UpdateDistribution` because its optimistic-lock
460
+ ETag became stale, Buildchain re-reads the distribution and retries with the
461
+ new ETag at most twice. A concurrent update that already attached the intended
462
+ function is accepted as converged; a different viewer-request function or any
463
+ non-ETag AWS error still fails immediately. The reusable workflow uploads
464
+ `buildchain-web-surface-*-diagnostics`
460
465
  artifacts containing the apply and health JSON so the failing AWS operation or
461
466
  HTTP check is visible from the consumer run.
462
467
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.14.14-alpha.0",
3
+ "version": "2.14.14-alpha.2",
4
4
  "private": false,
5
5
  "description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
6
6
  "repository": "https://github.com/kungfu-systems/buildchain",
@@ -91,6 +91,7 @@ export function createBuildchainContractWorld({
91
91
  "release-candidate-promote",
92
92
  ".release-candidate-promote",
93
93
  "release-propagation",
94
+ "binary-distribution",
94
95
  ]);
95
96
  const hasCompleteControllerSource = [...controllerWorkflowIds]
96
97
  .every((id) => workflowDescriptors.some((workflow) => workflow.id === id));
@@ -348,7 +349,7 @@ export function createBuildchainContractWorld({
348
349
  "protected release refs and durable release-state are finalized by Buildchain",
349
350
  "release passport finalization is idempotent after publish side effects",
350
351
  "publish transactions can require a resolved publish-gate source lock to prevent floating-ref drift",
351
- "semver GitHub Releases are created or updated only after transaction completion, with prerelease/latest metadata derived from the public release tag",
352
+ "semver GitHub Releases are created or updated only after transaction completion, with prerelease/latest metadata bound to the authoritative publication channel and tag syntax retained as the fallback for ordinary callers",
352
353
  ],
353
354
  }),
354
355
  surface(root, {
@@ -113,6 +113,14 @@ const CONTROLLER_SPECS = [
113
113
  ],
114
114
  evidence: ["propagation-plan", "propagation-lock", "controller-receipt"],
115
115
  },
116
+ {
117
+ id: "binary-distribution",
118
+ workflowId: "binary-distribution",
119
+ version: 1,
120
+ capabilities: ["standalone-binary-build", "release-evidence-bundle", "artifact-admission"],
121
+ stages: ["resolve-runtime", "build", "verify", "bundle", "aggregate"],
122
+ evidence: ["release-evidence-bundle", "release-passport", "controller-receipt"],
123
+ },
116
124
  ];
117
125
 
118
126
  function stableJson(value) {
@@ -112,17 +112,22 @@ export function buildConsumerIssueReport(options = {}) {
112
112
  const consumerRef = options.consumerRef || env.GITHUB_REF || "";
113
113
  const consumerSha = options.consumerSha || env.GITHUB_SHA || "";
114
114
  const failureCode = options.failureCode || "consumer-report";
115
+ const contractDrift = /^buildchain-contract-(?:compatible|breaking)-drift$/.test(failureCode);
115
116
  const fingerprint =
116
117
  options.fingerprint ||
117
118
  computeConsumerIssueFingerprint({
118
119
  targetRepository: target.fullName,
119
120
  consumerRepository,
120
- workflow,
121
- job,
122
121
  failureCode,
123
122
  buildchainRef: options.buildchainRef,
124
- buildchainVersion: options.buildchainVersion,
125
- title: options.title,
123
+ ...(contractDrift
124
+ ? {}
125
+ : {
126
+ workflow,
127
+ job,
128
+ buildchainVersion: options.buildchainVersion,
129
+ title: options.title,
130
+ }),
126
131
  });
127
132
  const title = redactIssueText(
128
133
  options.title ||
@@ -465,6 +465,81 @@ function validatePublicationEvidence(publicationEvidence, admission) {
465
465
  if (!publicationEvidence || typeof publicationEvidence !== "object" || Array.isArray(publicationEvidence)) {
466
466
  throw new Error("independent publication evidence is required");
467
467
  }
468
+ if (publicationEvidence.binaryReleaseEvidence) {
469
+ const evidence = publicationEvidence.binaryReleaseEvidence;
470
+ const manifest = evidence.bundleManifest;
471
+ if (manifest?.contract !== "kungfu-buildchain-release-evidence-bundle") {
472
+ throw new Error("binary release evidence bundle contract mismatch");
473
+ }
474
+ if (manifest.release?.tag !== `v${admission.version}`) {
475
+ throw new Error("binary release evidence tag mismatch");
476
+ }
477
+ if (normalizeGitSha(manifest.release?.sourceSha, "bundleManifest.release.sourceSha") !== admission.sourceSha) {
478
+ throw new Error("binary release evidence source mismatch");
479
+ }
480
+ const archiveDigest = normalizeDigest(evidence.bundleArchiveDigest, "binaryReleaseEvidence.bundleArchiveDigest");
481
+ const manifestDigest = normalizeDigest(manifest.bundle?.sha256, "bundleManifest.bundle.sha256");
482
+ if (archiveDigest !== manifestDigest) {
483
+ throw new Error("binary release evidence archive digest mismatch");
484
+ }
485
+ if (manifestDigest !== normalizeDigest(admission.artifactDigest, "admission.artifactDigest")) {
486
+ throw new Error("binary release evidence admission binding mismatch");
487
+ }
488
+ const fileNames = new Set((manifest.files || []).map((entry) => entry.bundlePath));
489
+ for (const required of [
490
+ "release-assets/buildchain-aarch64-apple-darwin.tar.gz",
491
+ "release-assets/buildchain-x86_64-unknown-linux-gnu.tar.gz",
492
+ "release-assets/buildchain-x86_64-pc-windows-msvc.zip",
493
+ "release-assets/checksums.txt",
494
+ "release-passport/buildchain.release.json",
495
+ ]) {
496
+ if (!fileNames.has(required)) throw new Error(`binary release evidence is missing ${required}`);
497
+ }
498
+ const controllerReceipt = evidence.controllerReceipt;
499
+ const controllerValidation = validateControllerReceipt(controllerReceipt, {
500
+ expectedSourceSha: admission.sourceSha,
501
+ expectedRuntimeSha: admission.runtimeSha,
502
+ });
503
+ if (!controllerValidation.ok || !controllerValidation.qualifying) {
504
+ throw new Error(`binary distribution controller receipt did not qualify: ${controllerValidation.issues.join("; ")}`);
505
+ }
506
+ if (controllerReceipt.controller?.id !== "binary-distribution") {
507
+ throw new Error("binary release evidence controller mismatch");
508
+ }
509
+ const controllerReceiptDigest = normalizeDigest(controllerReceipt.digest, "controllerReceipt.digest");
510
+ if (controllerReceiptDigest !== normalizeDigest(admission.controllerReceiptDigest, "admission.controllerReceiptDigest")) {
511
+ throw new Error("binary release controller receipt binding mismatch");
512
+ }
513
+ const contractDigest = normalizeDigest(controllerReceipt.runtime?.contractDigest, "controllerReceipt.runtime.contractDigest");
514
+ if (contractDigest !== normalizeDigest(admission.contractDigest, "admission.contractDigest")) {
515
+ throw new Error("binary release runtime contract binding mismatch");
516
+ }
517
+ const gate = validateGateAggregate(publicationEvidence.gateAggregate, {
518
+ admission,
519
+ sourceSha: admission.sourceSha,
520
+ });
521
+ if (gate.gateAggregateDigest !== normalizeDigest(admission.gateAggregateDigest, "admission.gateAggregateDigest")) {
522
+ throw new Error("binary release Gate aggregate binding mismatch");
523
+ }
524
+ if (gate.policyDigest !== normalizeDigest(admission.policyDigest, "admission.policyDigest")) {
525
+ throw new Error("binary release Gate policy binding mismatch");
526
+ }
527
+ return {
528
+ sourceTreeSha: normalizeGitSha(evidence.sourceTreeSha, "binaryReleaseEvidence.sourceTreeSha"),
529
+ controllerReceiptDigest,
530
+ contractDigest,
531
+ gateAggregateDigest: gate.gateAggregateDigest,
532
+ policyDigest: gate.policyDigest,
533
+ gateRegistryDigest: gate.registryDigest,
534
+ artifactDigest: manifestDigest,
535
+ evidenceDigest: publicationAuthorityDigest({
536
+ sourceTreeSha: evidence.sourceTreeSha,
537
+ controllerReceiptDigest,
538
+ gateAggregateDigest: gate.gateAggregateDigest,
539
+ artifactDigest: manifestDigest,
540
+ }),
541
+ };
542
+ }
468
543
  if (publicationEvidence.webSurfaceCandidate) {
469
544
  const evidence = publicationEvidence.webSurfaceCandidate;
470
545
  const candidate = createWebSurfacePublicationCandidate(evidence);
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { execFileSync } from "node:child_process";
6
+
7
+ import { validateControllerReceipt } from "../packages/core/controller-evidence.js";
8
+ import {
9
+ createPublicationAdmission,
10
+ createPublicationGateDecision,
11
+ createRunnerProvenance,
12
+ publicationGateAggregateBindings,
13
+ } from "../packages/core/publication-authority.js";
14
+
15
+ function required(name) {
16
+ const value = String(process.env[name] || "").trim();
17
+ if (!value) throw new Error(`${name} is required`);
18
+ return value;
19
+ }
20
+
21
+ function sha256(value) {
22
+ return crypto.createHash("sha256").update(String(value)).digest("hex");
23
+ }
24
+
25
+ function sha256File(filePath) {
26
+ return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
27
+ }
28
+
29
+ function filesNamed(root, name) {
30
+ const matches = [];
31
+ const pending = [root];
32
+ while (pending.length) {
33
+ const current = pending.pop();
34
+ if (!fs.existsSync(current)) continue;
35
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
36
+ const full = path.join(current, entry.name);
37
+ if (entry.isDirectory()) pending.push(full);
38
+ else if (entry.isFile() && entry.name === name) matches.push(full);
39
+ }
40
+ }
41
+ return matches.sort();
42
+ }
43
+
44
+ function oneFile(root, name) {
45
+ const matches = filesNamed(root, name);
46
+ if (matches.length !== 1) {
47
+ throw new Error(`expected exactly one ${name} under ${root}, found ${matches.length}`);
48
+ }
49
+ return matches[0];
50
+ }
51
+
52
+ function readJson(filePath) {
53
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
54
+ }
55
+
56
+ function writeBundle(outputDir, values) {
57
+ fs.mkdirSync(outputDir, { recursive: true });
58
+ for (const [name, value] of Object.entries(values)) {
59
+ fs.writeFileSync(path.join(outputDir, `${name}.json`), `${JSON.stringify(value, null, 2)}\n`);
60
+ }
61
+ if (!process.env.GITHUB_OUTPUT) return;
62
+ const output = fs.createWriteStream(process.env.GITHUB_OUTPUT, { flags: "a" });
63
+ for (const [name, value] of Object.entries(values)) {
64
+ output.write(`${name.replaceAll("_", "-")}-json=${JSON.stringify(value)}\n`);
65
+ }
66
+ output.end();
67
+ }
68
+
69
+ function validateBundle(manifest, archivePath, { sourceSha, releaseTag }) {
70
+ if (manifest?.contract !== "kungfu-buildchain-release-evidence-bundle") {
71
+ throw new Error("binary release evidence bundle contract mismatch");
72
+ }
73
+ if (manifest.release?.tag !== releaseTag) throw new Error("binary release evidence tag mismatch");
74
+ if (String(manifest.release?.sourceSha || "").toLowerCase() !== sourceSha) {
75
+ throw new Error("binary release evidence source mismatch");
76
+ }
77
+ if (sha256File(archivePath) !== String(manifest.bundle?.sha256 || "").replace(/^sha256:/, "")) {
78
+ throw new Error("binary release evidence archive digest mismatch");
79
+ }
80
+ const names = new Set((manifest.files || []).map((entry) => entry.bundlePath));
81
+ for (const requiredPath of [
82
+ "release-assets/buildchain-aarch64-apple-darwin.tar.gz",
83
+ "release-assets/buildchain-x86_64-unknown-linux-gnu.tar.gz",
84
+ "release-assets/buildchain-x86_64-pc-windows-msvc.zip",
85
+ "release-assets/checksums.txt",
86
+ "release-passport/buildchain.release.json",
87
+ ]) {
88
+ if (!names.has(requiredPath)) throw new Error(`binary release evidence is missing ${requiredPath}`);
89
+ }
90
+ }
91
+
92
+ function main() {
93
+ const evidenceRoot = process.env.BUILDCHAIN_EVIDENCE_ROOT || ".buildchain/publication-evidence";
94
+ const runtimeRoot = process.env.BUILDCHAIN_RUNTIME_ROOT || ".buildchain/authority-runtime";
95
+ const repository = required("BUILDCHAIN_REPOSITORY");
96
+ const sourceSha = required("BUILDCHAIN_SOURCE_SHA").toLowerCase();
97
+ const releaseTag = required("BUILDCHAIN_RELEASE_TAG");
98
+ const version = required("BUILDCHAIN_PUBLICATION_VERSION");
99
+ if (releaseTag !== `v${version}`) throw new Error("binary publication version and release tag mismatch");
100
+
101
+ const passportRoot = path.join(evidenceRoot, "binary-passport");
102
+ const controllerRoot = path.join(evidenceRoot, "binary-controller");
103
+ const manifest = readJson(oneFile(passportRoot, "buildchain-release-bundle.json"));
104
+ const archivePath = oneFile(passportRoot, "buildchain-release-bundle.tar.gz");
105
+ const controllerReceipt = readJson(oneFile(controllerRoot, "receipt.json"));
106
+ validateBundle(manifest, archivePath, { sourceSha, releaseTag });
107
+
108
+ const runtimeSha = execFileSync("git", ["-C", runtimeRoot, "rev-parse", "HEAD"], { encoding: "utf8" })
109
+ .trim()
110
+ .toLowerCase();
111
+ const controllerValidation = validateControllerReceipt(controllerReceipt, {
112
+ expectedSourceSha: sourceSha,
113
+ expectedRuntimeSha: runtimeSha,
114
+ });
115
+ if (!controllerValidation.ok || !controllerValidation.qualifying) {
116
+ throw new Error(`binary distribution controller receipt did not qualify: ${controllerValidation.issues.join("; ")}`);
117
+ }
118
+ if (controllerReceipt.controller?.id !== "binary-distribution") {
119
+ throw new Error("binary distribution controller receipt has the wrong controller id");
120
+ }
121
+
122
+ const controlPlaneAudit = readJson(required("BUILDCHAIN_CONTROL_PLANE_AUDIT_PATH"));
123
+ const registry = readJson(path.join(runtimeRoot, "dist/site/publication-authority-registry.json"));
124
+ const suppliedGate = String(process.env.BUILDCHAIN_GATE_AGGREGATE_JSON || "").trim();
125
+ let gateAggregate;
126
+ if (suppliedGate) {
127
+ gateAggregate = JSON.parse(suppliedGate);
128
+ } else {
129
+ if (process.env.BUILDCHAIN_ALLOW_NO_GATE !== "true") {
130
+ throw new Error("binary release admission requires a Gate aggregate or explicit no-Gate decision");
131
+ }
132
+ gateAggregate = createPublicationGateDecision({
133
+ sourceSha,
134
+ profile: "buildchain-binary-release-no-gate",
135
+ required: false,
136
+ rationale: "Buildchain standalone release assets carry provider-owned evidence and declare no consumer Shifu Gate registry.",
137
+ policy: { scope: "binary-release-assets", repository },
138
+ });
139
+ }
140
+ const gateBindings = publicationGateAggregateBindings(gateAggregate);
141
+ const runnerProvenance = createRunnerProvenance({
142
+ runnerClass: "ephemeral",
143
+ os: required("RUNNER_OS"),
144
+ architecture: required("RUNNER_ARCH"),
145
+ imageDigest: sha256(`${process.env.ImageOS || "unknown"}|${process.env.ImageVersion || "unknown"}`),
146
+ measurementDigest: sha256([
147
+ process.env.GITHUB_WORKFLOW,
148
+ process.env.GITHUB_JOB,
149
+ process.env.GITHUB_RUN_ID,
150
+ process.env.GITHUB_RUN_ATTEMPT,
151
+ process.env.RUNNER_ENVIRONMENT,
152
+ ].join("|")),
153
+ isolation: "github-hosted-single-job",
154
+ });
155
+ const issuedAt = new Date();
156
+ const admission = createPublicationAdmission({
157
+ registryDigest: registry.registryDigest,
158
+ workflowPath: required("BUILDCHAIN_AUTHORITY_WORKFLOW_PATH"),
159
+ publisherWorkflowPath: required("BUILDCHAIN_PUBLISHER_WORKFLOW_PATH"),
160
+ repository,
161
+ sourceSha,
162
+ runtimeSha,
163
+ contractDigest: controllerReceipt.runtime?.contractDigest,
164
+ policyDigest: gateBindings.policyDigest,
165
+ gateRegistryDigest: gateBindings.registryDigest,
166
+ controllerReceiptDigest: controllerReceipt.digest,
167
+ runnerProvenanceDigest: runnerProvenance.receiptDigest,
168
+ controlPlaneAuditDigest: controlPlaneAudit.receiptDigest,
169
+ gateAggregateDigest: gateBindings.gateAggregateDigest,
170
+ environment: "buildchain-release-assets",
171
+ product: "Buildchain standalone binary",
172
+ target: `github-release:${repository}@${releaseTag}`,
173
+ version,
174
+ channel: "release-assets",
175
+ artifactDigest: manifest.bundle.sha256,
176
+ nonce: `${required("GITHUB_RUN_ID")}:${required("GITHUB_RUN_ATTEMPT")}:${sourceSha}:binary-release-assets`,
177
+ issuedAt: issuedAt.toISOString(),
178
+ expiresAt: new Date(issuedAt.getTime() + 10 * 60 * 1000).toISOString(),
179
+ qualification: { required: false, predicateId: "", predicateDigest: "" },
180
+ });
181
+ const expectedKeys = [
182
+ "repository", "publisherWorkflowPath", "sourceSha", "runtimeSha", "contractDigest", "policyDigest",
183
+ "gateRegistryDigest", "controllerReceiptDigest", "gateAggregateDigest", "environment", "product", "target",
184
+ "version", "channel", "artifactDigest",
185
+ ];
186
+ const expected = Object.fromEntries(expectedKeys.map((name) => [name, admission[name]]));
187
+ writeBundle(process.env.BUILDCHAIN_OUTPUT_DIR || ".buildchain/publication-authority/auto", {
188
+ admission,
189
+ runner_provenance: runnerProvenance,
190
+ control_plane_audit: controlPlaneAudit,
191
+ gate_aggregate: gateAggregate,
192
+ expected,
193
+ });
194
+ }
195
+
196
+ try {
197
+ main();
198
+ } catch (error) {
199
+ console.error(`assemble binary release admission: ${error.message}`);
200
+ process.exitCode = 1;
201
+ }