@kungfu-tech/buildchain 2.12.7-alpha.6 → 2.12.7-alpha.8
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/dist/site/buildchain-contract.json +34 -14
- package/dist/site/buildchain-site.json +6 -6
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/controller-registry.json +18 -2
- package/dist/site/kfd-claims.json +56 -7
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/node-api-registry.json +5 -5
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +63 -7
- package/dist/site/publication-authority-registry.json +25 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +4 -4
- package/dist/site/workflow-registry.json +63 -4
- package/docs/publication-artifacts.md +17 -19
- package/package.json +1 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/index.js +6 -0
- package/packages/core/publication-artifact-candidate.js +116 -0
- package/packages/core/publication-authority.js +41 -2
- package/packages/core/publication-control-plane-audit.js +3 -1
- package/scripts/assemble-publication-artifact-admission.mjs +190 -0
- package/scripts/audit-publication-control-plane.mjs +5 -2
- package/scripts/publication-artifact-candidate.mjs +125 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"status": "passed",
|
|
5
5
|
"summary": {
|
|
6
6
|
"cliCommandCount": 79,
|
|
7
|
-
"workflowCount":
|
|
7
|
+
"workflowCount": 47,
|
|
8
8
|
"actionCount": 4,
|
|
9
9
|
"sitePageCount": 49,
|
|
10
10
|
"docCommandRefCount": 246,
|
|
@@ -609,14 +609,18 @@
|
|
|
609
609
|
"reusable": true,
|
|
610
610
|
"inputs": [
|
|
611
611
|
"admission-json",
|
|
612
|
+
"authority-workflow-path",
|
|
612
613
|
"auto-admission",
|
|
614
|
+
"auto-admission-kind",
|
|
613
615
|
"buildchain-ref",
|
|
614
616
|
"buildchain-repository",
|
|
615
617
|
"control-plane-audit-json",
|
|
616
618
|
"dry-run",
|
|
619
|
+
"evidence-controller-artifact",
|
|
617
620
|
"evidence-manifest-pattern",
|
|
618
621
|
"evidence-passport-artifact",
|
|
619
622
|
"evidence-payload-pattern",
|
|
623
|
+
"evidence-publication-artifact",
|
|
620
624
|
"evidence-repository",
|
|
621
625
|
"evidence-run-id",
|
|
622
626
|
"evidence-summary-artifact",
|
|
@@ -627,12 +631,13 @@
|
|
|
627
631
|
"publication-target",
|
|
628
632
|
"publication-version",
|
|
629
633
|
"publisher-workflow-path",
|
|
634
|
+
"required-status-check",
|
|
630
635
|
"runner-provenance-json",
|
|
631
636
|
"source-sha",
|
|
632
637
|
"target-ref",
|
|
633
638
|
"used-nonces-json"
|
|
634
639
|
],
|
|
635
|
-
"inputCount":
|
|
640
|
+
"inputCount": 28,
|
|
636
641
|
"secrets": [],
|
|
637
642
|
"secretCount": 0,
|
|
638
643
|
"outputs": [
|
|
@@ -1335,6 +1340,49 @@
|
|
|
1335
1340
|
"outputs": [],
|
|
1336
1341
|
"outputCount": 0
|
|
1337
1342
|
},
|
|
1343
|
+
{
|
|
1344
|
+
"id": "paper-release-sealed",
|
|
1345
|
+
"path": ".github/workflows/paper-release-sealed.yml",
|
|
1346
|
+
"reusable": true,
|
|
1347
|
+
"inputs": [
|
|
1348
|
+
"artifact-name",
|
|
1349
|
+
"artifact-paths",
|
|
1350
|
+
"artifact-retention-days",
|
|
1351
|
+
"branch-protection-bypass-apps",
|
|
1352
|
+
"branch-protection-bypass-teams",
|
|
1353
|
+
"branch-protection-bypass-users",
|
|
1354
|
+
"buildchain-contract-lock-path",
|
|
1355
|
+
"buildchain-ref",
|
|
1356
|
+
"buildchain-repository",
|
|
1357
|
+
"dry-run",
|
|
1358
|
+
"github-release",
|
|
1359
|
+
"github-release-notes",
|
|
1360
|
+
"github-release-title",
|
|
1361
|
+
"package-name",
|
|
1362
|
+
"publish-dist-tag",
|
|
1363
|
+
"publisher-workflow-path",
|
|
1364
|
+
"release-passport-impact-json",
|
|
1365
|
+
"release-passport-output-dir",
|
|
1366
|
+
"release-passport-product-name",
|
|
1367
|
+
"required-status-check",
|
|
1368
|
+
"target-ref",
|
|
1369
|
+
"target-sha",
|
|
1370
|
+
"toolchain-type",
|
|
1371
|
+
"verify-command",
|
|
1372
|
+
"working-directory"
|
|
1373
|
+
],
|
|
1374
|
+
"inputCount": 25,
|
|
1375
|
+
"secrets": [],
|
|
1376
|
+
"secretCount": 0,
|
|
1377
|
+
"outputs": [
|
|
1378
|
+
"github-release-url",
|
|
1379
|
+
"package-name",
|
|
1380
|
+
"package-version",
|
|
1381
|
+
"public-release-tag",
|
|
1382
|
+
"release-passport-path"
|
|
1383
|
+
],
|
|
1384
|
+
"outputCount": 5
|
|
1385
|
+
},
|
|
1338
1386
|
{
|
|
1339
1387
|
"id": "paper-release",
|
|
1340
1388
|
"path": ".github/workflows/paper-release.yml",
|
|
@@ -1492,7 +1540,11 @@
|
|
|
1492
1540
|
"buildchain-ref",
|
|
1493
1541
|
"buildchain-repository",
|
|
1494
1542
|
"node-version",
|
|
1543
|
+
"package-name",
|
|
1544
|
+
"prepare-paper-package",
|
|
1545
|
+
"publish-dist-tag",
|
|
1495
1546
|
"setup-node",
|
|
1547
|
+
"target-ref",
|
|
1496
1548
|
"toolchain-command",
|
|
1497
1549
|
"toolchain-digest",
|
|
1498
1550
|
"toolchain-image",
|
|
@@ -1500,7 +1552,7 @@
|
|
|
1500
1552
|
"verify-command",
|
|
1501
1553
|
"working-directory"
|
|
1502
1554
|
],
|
|
1503
|
-
"inputCount":
|
|
1555
|
+
"inputCount": 21,
|
|
1504
1556
|
"secrets": [],
|
|
1505
1557
|
"secretCount": 0,
|
|
1506
1558
|
"outputs": [
|
|
@@ -1511,13 +1563,17 @@
|
|
|
1511
1563
|
"controller-receipt-digest",
|
|
1512
1564
|
"controller-receipt-json",
|
|
1513
1565
|
"controller-receipt-status",
|
|
1566
|
+
"package-name",
|
|
1567
|
+
"package-version",
|
|
1514
1568
|
"publication-artifact-name",
|
|
1515
1569
|
"publication-manifest-json",
|
|
1516
1570
|
"publication-manifest-path",
|
|
1517
1571
|
"publication-passport-path",
|
|
1518
|
-
"publication-registry-path"
|
|
1572
|
+
"publication-registry-path",
|
|
1573
|
+
"publish-dist-tag",
|
|
1574
|
+
"runtime-sha"
|
|
1519
1575
|
],
|
|
1520
|
-
"outputCount":
|
|
1576
|
+
"outputCount": 16
|
|
1521
1577
|
},
|
|
1522
1578
|
{
|
|
1523
1579
|
"id": "release-candidate-promote",
|
|
@@ -3331,8 +3387,8 @@
|
|
|
3331
3387
|
"workflowRegistryPath": "dist/site/workflow-registry.json",
|
|
3332
3388
|
"pageRegistryPath": "dist/site/page-registry.json",
|
|
3333
3389
|
"cliRegistryDigest": "a6daf4822365bf576425777230246046ebca67b3f8dcfea61fc7a61773776185",
|
|
3334
|
-
"workflowRegistryDigest": "
|
|
3335
|
-
"pageRegistryDigest": "
|
|
3390
|
+
"workflowRegistryDigest": "a61b85b07185e02e340c27d50f77a6710968db87dd42d1b1f4dd81260779a0ed",
|
|
3391
|
+
"pageRegistryDigest": "3a4779e3d85d0a175e33c7d2a8bee30ede3931cc721a7d8c422ab321f0dec723"
|
|
3336
3392
|
},
|
|
3337
3393
|
"comparison": {
|
|
3338
3394
|
"missingCliRegistry": [],
|
|
@@ -306,6 +306,22 @@
|
|
|
306
306
|
"runnerPolicy": "unqualified",
|
|
307
307
|
"notes": ""
|
|
308
308
|
},
|
|
309
|
+
{
|
|
310
|
+
"workflowPath": ".github/workflows/paper-release-sealed.yml",
|
|
311
|
+
"authorityClass": "product-publication",
|
|
312
|
+
"publicationCapable": true,
|
|
313
|
+
"capabilityIds": [
|
|
314
|
+
"github-release",
|
|
315
|
+
"npm-publish"
|
|
316
|
+
],
|
|
317
|
+
"credentialMode": "trusted-publishing",
|
|
318
|
+
"publisherWorkflowMode": "caller-bound",
|
|
319
|
+
"publisherWorkflowPath": ".github/workflows/paper-release-sealed.yml",
|
|
320
|
+
"environment": "none",
|
|
321
|
+
"environmentMode": "fixed",
|
|
322
|
+
"runnerPolicy": "qualified-measured",
|
|
323
|
+
"notes": ""
|
|
324
|
+
},
|
|
309
325
|
{
|
|
310
326
|
"workflowPath": ".github/workflows/paper-release.yml",
|
|
311
327
|
"authorityClass": "product-publication",
|
|
@@ -655,6 +671,14 @@
|
|
|
655
671
|
],
|
|
656
672
|
"classified": true
|
|
657
673
|
},
|
|
674
|
+
{
|
|
675
|
+
"path": ".github/workflows/paper-release-sealed.yml",
|
|
676
|
+
"signals": [
|
|
677
|
+
"write-permission",
|
|
678
|
+
"oidc"
|
|
679
|
+
],
|
|
680
|
+
"classified": true
|
|
681
|
+
},
|
|
658
682
|
{
|
|
659
683
|
"path": ".github/workflows/paper-release.yml",
|
|
660
684
|
"signals": [
|
|
@@ -750,5 +774,5 @@
|
|
|
750
774
|
"classified": false
|
|
751
775
|
}
|
|
752
776
|
],
|
|
753
|
-
"registryDigest": "
|
|
777
|
+
"registryDigest": "760215fb39e6c85890c925e924c538a766d78abd6319794c8799fb47c23dec3d"
|
|
754
778
|
}
|
|
@@ -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-15T01:31:56.311Z",
|
|
5
|
+
"publishedAt": "2026-07-15T01:31:56.311Z",
|
|
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": "a2f11bfd2bae6539f0196ef8fd9571130a809918",
|
|
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.12.7-alpha.
|
|
35
|
+
"version": "2.12.7-alpha.8",
|
|
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-
|
|
5
|
-
"publishedAt": "2026-07-
|
|
4
|
+
"generatedAt": "2026-07-15T01:31:56.311Z",
|
|
5
|
+
"publishedAt": "2026-07-15T01:31:56.311Z",
|
|
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": "a2f11bfd2bae6539f0196ef8fd9571130a809918",
|
|
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.12.7-alpha.
|
|
40
|
+
"version": "2.12.7-alpha.8",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -215,14 +215,18 @@
|
|
|
215
215
|
"reusable": true,
|
|
216
216
|
"inputs": [
|
|
217
217
|
"admission-json",
|
|
218
|
+
"authority-workflow-path",
|
|
218
219
|
"auto-admission",
|
|
220
|
+
"auto-admission-kind",
|
|
219
221
|
"buildchain-ref",
|
|
220
222
|
"buildchain-repository",
|
|
221
223
|
"control-plane-audit-json",
|
|
222
224
|
"dry-run",
|
|
225
|
+
"evidence-controller-artifact",
|
|
223
226
|
"evidence-manifest-pattern",
|
|
224
227
|
"evidence-passport-artifact",
|
|
225
228
|
"evidence-payload-pattern",
|
|
229
|
+
"evidence-publication-artifact",
|
|
226
230
|
"evidence-repository",
|
|
227
231
|
"evidence-run-id",
|
|
228
232
|
"evidence-summary-artifact",
|
|
@@ -233,12 +237,13 @@
|
|
|
233
237
|
"publication-target",
|
|
234
238
|
"publication-version",
|
|
235
239
|
"publisher-workflow-path",
|
|
240
|
+
"required-status-check",
|
|
236
241
|
"runner-provenance-json",
|
|
237
242
|
"source-sha",
|
|
238
243
|
"target-ref",
|
|
239
244
|
"used-nonces-json"
|
|
240
245
|
],
|
|
241
|
-
"inputCount":
|
|
246
|
+
"inputCount": 28,
|
|
242
247
|
"secrets": [],
|
|
243
248
|
"secretCount": 0,
|
|
244
249
|
"outputs": [
|
|
@@ -1025,6 +1030,52 @@
|
|
|
1025
1030
|
"capabilityGroup": "api-cli-reference",
|
|
1026
1031
|
"status": "active"
|
|
1027
1032
|
},
|
|
1033
|
+
{
|
|
1034
|
+
"id": "paper-release-sealed",
|
|
1035
|
+
"path": ".github/workflows/paper-release-sealed.yml",
|
|
1036
|
+
"reusable": true,
|
|
1037
|
+
"inputs": [
|
|
1038
|
+
"artifact-name",
|
|
1039
|
+
"artifact-paths",
|
|
1040
|
+
"artifact-retention-days",
|
|
1041
|
+
"branch-protection-bypass-apps",
|
|
1042
|
+
"branch-protection-bypass-teams",
|
|
1043
|
+
"branch-protection-bypass-users",
|
|
1044
|
+
"buildchain-contract-lock-path",
|
|
1045
|
+
"buildchain-ref",
|
|
1046
|
+
"buildchain-repository",
|
|
1047
|
+
"dry-run",
|
|
1048
|
+
"github-release",
|
|
1049
|
+
"github-release-notes",
|
|
1050
|
+
"github-release-title",
|
|
1051
|
+
"package-name",
|
|
1052
|
+
"publish-dist-tag",
|
|
1053
|
+
"publisher-workflow-path",
|
|
1054
|
+
"release-passport-impact-json",
|
|
1055
|
+
"release-passport-output-dir",
|
|
1056
|
+
"release-passport-product-name",
|
|
1057
|
+
"required-status-check",
|
|
1058
|
+
"target-ref",
|
|
1059
|
+
"target-sha",
|
|
1060
|
+
"toolchain-type",
|
|
1061
|
+
"verify-command",
|
|
1062
|
+
"working-directory"
|
|
1063
|
+
],
|
|
1064
|
+
"inputCount": 25,
|
|
1065
|
+
"secrets": [],
|
|
1066
|
+
"secretCount": 0,
|
|
1067
|
+
"outputs": [
|
|
1068
|
+
"github-release-url",
|
|
1069
|
+
"package-name",
|
|
1070
|
+
"package-version",
|
|
1071
|
+
"public-release-tag",
|
|
1072
|
+
"release-passport-path"
|
|
1073
|
+
],
|
|
1074
|
+
"outputCount": 5,
|
|
1075
|
+
"surface": "repository-workflow",
|
|
1076
|
+
"capabilityGroup": "api-cli-reference",
|
|
1077
|
+
"status": "active"
|
|
1078
|
+
},
|
|
1028
1079
|
{
|
|
1029
1080
|
"id": "paper-release",
|
|
1030
1081
|
"path": ".github/workflows/paper-release.yml",
|
|
@@ -1194,7 +1245,11 @@
|
|
|
1194
1245
|
"buildchain-ref",
|
|
1195
1246
|
"buildchain-repository",
|
|
1196
1247
|
"node-version",
|
|
1248
|
+
"package-name",
|
|
1249
|
+
"prepare-paper-package",
|
|
1250
|
+
"publish-dist-tag",
|
|
1197
1251
|
"setup-node",
|
|
1252
|
+
"target-ref",
|
|
1198
1253
|
"toolchain-command",
|
|
1199
1254
|
"toolchain-digest",
|
|
1200
1255
|
"toolchain-image",
|
|
@@ -1202,7 +1257,7 @@
|
|
|
1202
1257
|
"verify-command",
|
|
1203
1258
|
"working-directory"
|
|
1204
1259
|
],
|
|
1205
|
-
"inputCount":
|
|
1260
|
+
"inputCount": 21,
|
|
1206
1261
|
"secrets": [],
|
|
1207
1262
|
"secretCount": 0,
|
|
1208
1263
|
"outputs": [
|
|
@@ -1213,13 +1268,17 @@
|
|
|
1213
1268
|
"controller-receipt-digest",
|
|
1214
1269
|
"controller-receipt-json",
|
|
1215
1270
|
"controller-receipt-status",
|
|
1271
|
+
"package-name",
|
|
1272
|
+
"package-version",
|
|
1216
1273
|
"publication-artifact-name",
|
|
1217
1274
|
"publication-manifest-json",
|
|
1218
1275
|
"publication-manifest-path",
|
|
1219
1276
|
"publication-passport-path",
|
|
1220
|
-
"publication-registry-path"
|
|
1277
|
+
"publication-registry-path",
|
|
1278
|
+
"publish-dist-tag",
|
|
1279
|
+
"runtime-sha"
|
|
1221
1280
|
],
|
|
1222
|
-
"outputCount":
|
|
1281
|
+
"outputCount": 16,
|
|
1223
1282
|
"surface": "repository-workflow",
|
|
1224
1283
|
"capabilityGroup": "reusable-build",
|
|
1225
1284
|
"status": "active"
|
|
@@ -182,40 +182,37 @@ on:
|
|
|
182
182
|
|
|
183
183
|
jobs:
|
|
184
184
|
paper-release:
|
|
185
|
-
uses: kungfu-systems/buildchain/.github/workflows/paper-release.yml@v2
|
|
185
|
+
uses: kungfu-systems/buildchain/.github/workflows/paper-release-sealed.yml@v2
|
|
186
186
|
permissions:
|
|
187
|
+
actions: read
|
|
187
188
|
checks: write
|
|
188
189
|
contents: write
|
|
189
190
|
id-token: write
|
|
190
191
|
issues: write
|
|
191
192
|
with:
|
|
192
193
|
buildchain-ref: ${{ inputs.buildchain-ref || '' }}
|
|
193
|
-
|
|
194
|
-
publication-runner-provenance-json: ${{ needs.authority.outputs.runner-provenance-json }}
|
|
195
|
-
publication-control-plane-audit-json: ${{ needs.authority.outputs.control-plane-audit-json }}
|
|
196
|
-
publication-expected-json: ${{ needs.authority.outputs.expected-json }}
|
|
194
|
+
publisher-workflow-path: .github/workflows/paper-release.yml
|
|
197
195
|
toolchain-type: config
|
|
198
196
|
verify-command: make check
|
|
197
|
+
artifact-paths: _build/paper-name.pdf
|
|
199
198
|
buildchain-contract-lock-path: .buildchain/contract-lock.json
|
|
200
199
|
```
|
|
201
200
|
|
|
202
|
-
The
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
201
|
+
The sealed preset does not accept a long-lived promotion token. It builds and
|
|
202
|
+
packages the paper in a read-only job, then a credential-free authority job
|
|
203
|
+
downloads that exact candidate, audits the external control plane, and seals a
|
|
204
|
+
capability over the source tree, Buildchain runtime, controller receipt, PDF,
|
|
205
|
+
and npm package bytes. Only the final job receives write and OIDC permissions;
|
|
206
|
+
it downloads the admitted candidate, recomputes the capability binding, and
|
|
207
|
+
publishes without executing consumer build commands. npm binds the OIDC identity
|
|
208
|
+
to the consumer workflow named by `publisher-workflow-path`.
|
|
210
209
|
|
|
211
210
|
The preset:
|
|
212
211
|
|
|
213
|
-
- resolves the
|
|
214
|
-
|
|
215
|
-
- verifies that the declared promotion authority can read the protected target
|
|
216
|
-
channel before starting the publication build;
|
|
212
|
+
- resolves the floating Buildchain runtime once and binds the exact SHA into the
|
|
213
|
+
publication candidate and authority capability;
|
|
217
214
|
- builds the PDF through the declared pinned LaTeX Docker toolchain or custom
|
|
218
|
-
command;
|
|
215
|
+
command in a read-only job;
|
|
219
216
|
- verifies the paper repository;
|
|
220
217
|
- writes the publication manifest, publication passport, optional archive
|
|
221
218
|
registry, and source bundle;
|
|
@@ -226,7 +223,8 @@ The preset:
|
|
|
226
223
|
- creates a `publish-gate/<alpha|release>/.../<version>` source lock for the
|
|
227
224
|
channel commit and requires `promote-buildchain-ref` to verify that lock
|
|
228
225
|
before any publish side effect;
|
|
229
|
-
-
|
|
226
|
+
- verifies the complete candidate again after authority and publishes the
|
|
227
|
+
package through npm Trusted Publishing without rebuilding it;
|
|
230
228
|
- writes Buildchain release/passport evidence; and
|
|
231
229
|
- creates or updates the exact-version GitHub Release by default, uploading
|
|
232
230
|
every file declared by `publication.primary_artifact` and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.12.7-alpha.
|
|
3
|
+
"version": "2.12.7-alpha.8",
|
|
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",
|
|
@@ -27,6 +27,7 @@ const DESCRIPTORS = Object.freeze([
|
|
|
27
27
|
[".github/workflows/buildchain-stable-candidate-patrol.yml", "governance-write"],
|
|
28
28
|
[".github/workflows/dev-pr-auto-merge.yml", "governance-write"],
|
|
29
29
|
[".github/workflows/npm-publish.yml", "dry-run-only"],
|
|
30
|
+
[".github/workflows/paper-release-sealed.yml", "product-publication", true, ["npm-publish", "github-release"], "trusted-publishing", "none", "fixed", "caller-bound"],
|
|
30
31
|
[".github/workflows/paper-release.yml", "product-publication", true, ["npm-publish", "github-release"], "trusted-publishing", "none", "fixed", "caller-bound"],
|
|
31
32
|
[".github/workflows/patrol-daily.yml", "governance-write"],
|
|
32
33
|
[".github/workflows/patrol-monthly.yml", "governance-write"],
|
package/packages/core/index.js
CHANGED
|
@@ -153,6 +153,12 @@ export {
|
|
|
153
153
|
verifyPublicationAdmission,
|
|
154
154
|
} from "./publication-authority.js";
|
|
155
155
|
|
|
156
|
+
export {
|
|
157
|
+
PUBLICATION_ARTIFACT_CANDIDATE_CONTRACT,
|
|
158
|
+
createPublicationArtifactCandidate,
|
|
159
|
+
publicationArtifactCandidateDigest,
|
|
160
|
+
} from "./publication-artifact-candidate.js";
|
|
161
|
+
|
|
156
162
|
export {
|
|
157
163
|
buildchainPublicationAuthorityDescriptors,
|
|
158
164
|
createBuildchainPublicationAuthorityRegistry,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { validateControllerReceipt } from "./controller-evidence.js";
|
|
4
|
+
|
|
5
|
+
export const PUBLICATION_ARTIFACT_CANDIDATE_CONTRACT =
|
|
6
|
+
"kungfu-buildchain-publication-artifact-candidate";
|
|
7
|
+
|
|
8
|
+
const MANIFEST_CONTRACT = "kungfu-buildchain-publication-artifact-manifest";
|
|
9
|
+
const PASSPORT_CONTRACT = "kungfu-buildchain-publication-artifact-passport";
|
|
10
|
+
|
|
11
|
+
function stableJson(value) {
|
|
12
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
13
|
+
if (value && typeof value === "object") {
|
|
14
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
15
|
+
}
|
|
16
|
+
return JSON.stringify(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function publicationArtifactCandidateDigest(value) {
|
|
20
|
+
return crypto.createHash("sha256").update(stableJson(value)).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requiredString(value, label) {
|
|
24
|
+
const normalized = String(value || "").trim();
|
|
25
|
+
if (!normalized) throw new Error(`${label} must be a non-empty string`);
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeDigest(value, label) {
|
|
30
|
+
const normalized = requiredString(value, label).replace(/^sha256:/, "").toLowerCase();
|
|
31
|
+
if (!/^[0-9a-f]{64}$/.test(normalized)) throw new Error(`${label} must be a sha256 digest`);
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeGitSha(value, label) {
|
|
36
|
+
const normalized = requiredString(value, label).toLowerCase();
|
|
37
|
+
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(normalized)) {
|
|
38
|
+
throw new Error(`${label} must be a 40- or 64-character Git SHA`);
|
|
39
|
+
}
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createPublicationArtifactCandidate({
|
|
44
|
+
repository,
|
|
45
|
+
sourceSha,
|
|
46
|
+
sourceTreeSha,
|
|
47
|
+
runtimeSha,
|
|
48
|
+
manifest,
|
|
49
|
+
passport,
|
|
50
|
+
controllerReceipt,
|
|
51
|
+
files = [],
|
|
52
|
+
} = {}) {
|
|
53
|
+
const normalizedRepository = requiredString(repository, "repository");
|
|
54
|
+
const normalizedSourceSha = normalizeGitSha(sourceSha, "sourceSha");
|
|
55
|
+
const normalizedSourceTreeSha = normalizeGitSha(sourceTreeSha, "sourceTreeSha");
|
|
56
|
+
const normalizedRuntimeSha = normalizeGitSha(runtimeSha, "runtimeSha");
|
|
57
|
+
if (manifest?.contract !== MANIFEST_CONTRACT) throw new Error("publication artifact manifest contract mismatch");
|
|
58
|
+
if (passport?.contract !== PASSPORT_CONTRACT || passport.status !== "passed") {
|
|
59
|
+
throw new Error("publication artifact passport is not qualifying");
|
|
60
|
+
}
|
|
61
|
+
if (manifest.source?.sha !== normalizedSourceSha || passport.source?.sha !== normalizedSourceSha) {
|
|
62
|
+
throw new Error("publication artifact source SHA mismatch");
|
|
63
|
+
}
|
|
64
|
+
if (manifest.source?.treeSha !== normalizedSourceTreeSha || passport.source?.treeSha !== normalizedSourceTreeSha) {
|
|
65
|
+
throw new Error("publication artifact source tree mismatch");
|
|
66
|
+
}
|
|
67
|
+
const manifestDigest = crypto.createHash("sha256").update(JSON.stringify(manifest, null, 2)).digest("hex");
|
|
68
|
+
if (normalizeDigest(passport.manifestDigest, "passport.manifestDigest") !== manifestDigest) {
|
|
69
|
+
throw new Error("publication artifact passport manifest digest mismatch");
|
|
70
|
+
}
|
|
71
|
+
const controllerValidation = validateControllerReceipt(controllerReceipt, {
|
|
72
|
+
expectedSourceSha: normalizedSourceSha,
|
|
73
|
+
expectedRuntimeSha: normalizedRuntimeSha,
|
|
74
|
+
});
|
|
75
|
+
if (!controllerValidation.ok || !controllerValidation.qualifying) {
|
|
76
|
+
throw new Error(`publication artifact controller receipt did not qualify: ${controllerValidation.issues.join("; ")}`);
|
|
77
|
+
}
|
|
78
|
+
const evidenceKinds = new Set((controllerReceipt.evidence || []).map((entry) => entry.kind));
|
|
79
|
+
for (const kind of ["publication-manifest", "publication-passport"]) {
|
|
80
|
+
if (!evidenceKinds.has(kind)) throw new Error(`publication artifact controller receipt is missing ${kind} evidence`);
|
|
81
|
+
}
|
|
82
|
+
const normalizedFiles = files.map((entry, index) => {
|
|
83
|
+
const filePath = requiredString(entry.path, `files[${index}].path`).replaceAll("\\", "/");
|
|
84
|
+
if (filePath.startsWith("/") || filePath.split("/").includes("..")) {
|
|
85
|
+
throw new Error(`publication artifact candidate contains an unsafe path: ${filePath}`);
|
|
86
|
+
}
|
|
87
|
+
const size = Number(entry.size ?? entry.bytes);
|
|
88
|
+
if (!Number.isSafeInteger(size) || size < 0) throw new Error(`files[${index}].size must be a non-negative safe integer`);
|
|
89
|
+
return { path: filePath, size, sha256: normalizeDigest(entry.sha256, `files[${index}].sha256`) };
|
|
90
|
+
}).sort((left, right) => left.path.localeCompare(right.path));
|
|
91
|
+
if (new Set(normalizedFiles.map((entry) => entry.path)).size !== normalizedFiles.length) {
|
|
92
|
+
throw new Error("publication artifact candidate file paths must be unique");
|
|
93
|
+
}
|
|
94
|
+
const byPath = new Map(normalizedFiles.map((entry) => [entry.path, entry]));
|
|
95
|
+
for (const [index, artifact] of (manifest.artifacts || []).entries()) {
|
|
96
|
+
const artifactPath = requiredString(artifact.path, `manifest.artifacts[${index}].path`);
|
|
97
|
+
const actual = byPath.get(artifactPath);
|
|
98
|
+
if (!actual) throw new Error(`publication artifact candidate is missing declared artifact: ${artifactPath}`);
|
|
99
|
+
if (actual.size !== Number(artifact.bytes) || actual.sha256 !== normalizeDigest(artifact.sha256, `manifest.artifacts[${index}].sha256`)) {
|
|
100
|
+
throw new Error(`publication artifact candidate bytes do not match manifest: ${artifactPath}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const payload = {
|
|
104
|
+
schemaVersion: 1,
|
|
105
|
+
contract: PUBLICATION_ARTIFACT_CANDIDATE_CONTRACT,
|
|
106
|
+
repository: normalizedRepository,
|
|
107
|
+
sourceSha: normalizedSourceSha,
|
|
108
|
+
sourceTreeSha: normalizedSourceTreeSha,
|
|
109
|
+
runtimeSha: normalizedRuntimeSha,
|
|
110
|
+
manifestDigest,
|
|
111
|
+
passportDigest: publicationArtifactCandidateDigest(passport),
|
|
112
|
+
controllerReceiptDigest: normalizeDigest(controllerReceipt.digest, "controllerReceipt.digest"),
|
|
113
|
+
files: normalizedFiles,
|
|
114
|
+
};
|
|
115
|
+
return { ...payload, candidateDigest: publicationArtifactCandidateDigest(payload) };
|
|
116
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { validateControllerReceipt } from "./controller-evidence.js";
|
|
4
|
+
import { createPublicationArtifactCandidate } from "./publication-artifact-candidate.js";
|
|
4
5
|
import { sha256Json, validateReleaseCandidatePassport } from "./release-candidate.js";
|
|
5
6
|
|
|
6
7
|
export const PUBLICATION_AUTHORITY_REGISTRY_CONTRACT =
|
|
@@ -381,6 +382,7 @@ export function createPublicationArtifactManifestSet({
|
|
|
381
382
|
}
|
|
382
383
|
|
|
383
384
|
function validateGateAggregate(gateAggregate, { admission, passport }) {
|
|
385
|
+
const passportSourceSha = passport.source?.headSha || passport.source?.sha;
|
|
384
386
|
if (gateAggregate?.contract === "buildchain.shifu-gate-aggregate/v1") {
|
|
385
387
|
const { digest, ...payload } = gateAggregate;
|
|
386
388
|
const actualDigest = sha256Json(payload);
|
|
@@ -390,7 +392,7 @@ function validateGateAggregate(gateAggregate, { admission, passport }) {
|
|
|
390
392
|
if (gateAggregate.status !== "pass" || gateAggregate.qualifying !== true) {
|
|
391
393
|
throw new Error("gate aggregate is not qualifying");
|
|
392
394
|
}
|
|
393
|
-
if (![admission.sourceSha,
|
|
395
|
+
if (![admission.sourceSha, passportSourceSha].includes(gateAggregate.sourceSha)) {
|
|
394
396
|
throw new Error("gate aggregate source SHA mismatch");
|
|
395
397
|
}
|
|
396
398
|
normalizeDigest(gateAggregate.registry?.digest, "gateAggregate.registry.digest");
|
|
@@ -408,7 +410,7 @@ function validateGateAggregate(gateAggregate, { admission, passport }) {
|
|
|
408
410
|
if (gateAggregate.required !== false) {
|
|
409
411
|
throw new Error("a required Gate policy must supply a qualifying Shifu Gate aggregate");
|
|
410
412
|
}
|
|
411
|
-
if (![admission.sourceSha,
|
|
413
|
+
if (![admission.sourceSha, passportSourceSha].includes(gateAggregate.sourceSha)) {
|
|
412
414
|
throw new Error("publication Gate decision source SHA mismatch");
|
|
413
415
|
}
|
|
414
416
|
const normalizedPolicy = {
|
|
@@ -428,6 +430,43 @@ function validatePublicationEvidence(publicationEvidence, admission) {
|
|
|
428
430
|
if (!publicationEvidence || typeof publicationEvidence !== "object" || Array.isArray(publicationEvidence)) {
|
|
429
431
|
throw new Error("independent publication evidence is required");
|
|
430
432
|
}
|
|
433
|
+
if (publicationEvidence.publicationArtifactCandidate) {
|
|
434
|
+
const candidate = createPublicationArtifactCandidate(publicationEvidence.publicationArtifactCandidate);
|
|
435
|
+
const passport = publicationEvidence.publicationArtifactCandidate.passport;
|
|
436
|
+
const controllerReceipt = publicationEvidence.publicationArtifactCandidate.controllerReceipt;
|
|
437
|
+
const controllerReceiptDigest = normalizeDigest(controllerReceipt.digest, "controllerReceipt.digest");
|
|
438
|
+
if (controllerReceiptDigest !== normalizeDigest(admission.controllerReceiptDigest, "admission.controllerReceiptDigest")) {
|
|
439
|
+
throw new Error("controller receipt evidence binding mismatch");
|
|
440
|
+
}
|
|
441
|
+
const contractDigest = normalizeDigest(controllerReceipt.runtime?.contractDigest, "controllerReceipt.runtime.contractDigest");
|
|
442
|
+
if (contractDigest !== normalizeDigest(admission.contractDigest, "admission.contractDigest")) {
|
|
443
|
+
throw new Error("runtime contract evidence binding mismatch");
|
|
444
|
+
}
|
|
445
|
+
const gate = validateGateAggregate(publicationEvidence.gateAggregate, { admission, passport });
|
|
446
|
+
if (gate.gateAggregateDigest !== normalizeDigest(admission.gateAggregateDigest, "admission.gateAggregateDigest")) {
|
|
447
|
+
throw new Error("Gate aggregate evidence binding mismatch");
|
|
448
|
+
}
|
|
449
|
+
if (gate.policyDigest !== normalizeDigest(admission.policyDigest, "admission.policyDigest")) {
|
|
450
|
+
throw new Error("Gate policy evidence binding mismatch");
|
|
451
|
+
}
|
|
452
|
+
if (candidate.candidateDigest !== normalizeDigest(admission.artifactDigest, "admission.artifactDigest")) {
|
|
453
|
+
throw new Error("publication artifact candidate evidence binding mismatch");
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
sourceTreeSha: candidate.sourceTreeSha,
|
|
457
|
+
controllerReceiptDigest,
|
|
458
|
+
contractDigest,
|
|
459
|
+
gateAggregateDigest: gate.gateAggregateDigest,
|
|
460
|
+
policyDigest: gate.policyDigest,
|
|
461
|
+
artifactDigest: candidate.candidateDigest,
|
|
462
|
+
evidenceDigest: publicationAuthorityDigest({
|
|
463
|
+
sourceTreeSha: candidate.sourceTreeSha,
|
|
464
|
+
controllerReceiptDigest,
|
|
465
|
+
gateAggregateDigest: gate.gateAggregateDigest,
|
|
466
|
+
artifactDigest: candidate.candidateDigest,
|
|
467
|
+
}),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
431
470
|
const passport = publicationEvidence.releaseCandidatePassport;
|
|
432
471
|
const buildSummary = publicationEvidence.buildSummary;
|
|
433
472
|
const passportValidation = validateReleaseCandidatePassport({
|