@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.
- package/actions/promote-buildchain-ref/README.md +6 -4
- package/dist/site/buildchain-contract.json +75 -21
- package/dist/site/buildchain-site.json +26 -25
- package/dist/site/controller-registry.json +47 -1
- package/dist/site/kfd-claims.json +10 -5
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +5 -5
- package/dist/site/node-api-registry.json +5 -5
- package/dist/site/page-registry.json +12 -12
- package/dist/site/public-surface-audit.json +9 -4
- package/dist/site/publication-authority-registry.json +3 -2
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-model.json +4 -2
- package/dist/site/site-manifest.json +9 -9
- package/dist/site/workflow-registry.json +7 -2
- package/docs/binary-distribution.md +22 -8
- package/docs/release-candidate.md +4 -2
- package/docs/release-governance.md +5 -3
- package/docs/site-bundle-contract.md +4 -0
- package/docs/web-surface-deployments.md +6 -1
- package/package.json +1 -1
- package/packages/core/buildchain-contract.js +2 -1
- package/packages/core/controller-evidence.js +8 -0
- package/packages/core/issue-reporting.js +9 -4
- package/packages/core/publication-authority.js +75 -0
- package/scripts/assemble-binary-release-admission.mjs +201 -0
- package/scripts/check-inventory.mjs +1 -1
- package/scripts/ensure-github-release.mjs +20 -6
- package/scripts/generate-channel-promotion-workflow.mjs +3 -3
- package/scripts/generate-site-bundle.mjs +8 -4
- package/scripts/runtime-ref-core.mjs +12 -0
- package/scripts/site-bundle-homepage.mjs +39 -0
- 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
|
-
"
|
|
452
|
+
"publication-version",
|
|
453
|
+
"release-tag",
|
|
454
|
+
"source-sha",
|
|
455
|
+
"target-ref"
|
|
451
456
|
],
|
|
452
|
-
"inputCount":
|
|
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": "
|
|
3693
|
-
"pageRegistryDigest": "
|
|
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": "
|
|
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-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
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": "
|
|
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.
|
|
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
|
-
"
|
|
42
|
-
"
|
|
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-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
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": "
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
"
|
|
21
|
+
"publication-version",
|
|
22
|
+
"release-tag",
|
|
23
|
+
"source-sha",
|
|
24
|
+
"target-ref"
|
|
20
25
|
],
|
|
21
|
-
"inputCount":
|
|
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
|
-
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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
|
|
114
|
-
|
|
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.
|
|
817
|
-
|
|
818
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
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
|
+
}
|