@kungfu-tech/buildchain 2.12.7-alpha.6 → 2.12.7-alpha.7
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 +55 -7
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/node-api-registry.json +4 -4
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +62 -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 +62 -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/scripts/assemble-publication-artifact-admission.mjs +190 -0
- 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",
|
|
@@ -632,7 +636,7 @@
|
|
|
632
636
|
"target-ref",
|
|
633
637
|
"used-nonces-json"
|
|
634
638
|
],
|
|
635
|
-
"inputCount":
|
|
639
|
+
"inputCount": 27,
|
|
636
640
|
"secrets": [],
|
|
637
641
|
"secretCount": 0,
|
|
638
642
|
"outputs": [
|
|
@@ -1335,6 +1339,49 @@
|
|
|
1335
1339
|
"outputs": [],
|
|
1336
1340
|
"outputCount": 0
|
|
1337
1341
|
},
|
|
1342
|
+
{
|
|
1343
|
+
"id": "paper-release-sealed",
|
|
1344
|
+
"path": ".github/workflows/paper-release-sealed.yml",
|
|
1345
|
+
"reusable": true,
|
|
1346
|
+
"inputs": [
|
|
1347
|
+
"artifact-name",
|
|
1348
|
+
"artifact-paths",
|
|
1349
|
+
"artifact-retention-days",
|
|
1350
|
+
"branch-protection-bypass-apps",
|
|
1351
|
+
"branch-protection-bypass-teams",
|
|
1352
|
+
"branch-protection-bypass-users",
|
|
1353
|
+
"buildchain-contract-lock-path",
|
|
1354
|
+
"buildchain-ref",
|
|
1355
|
+
"buildchain-repository",
|
|
1356
|
+
"dry-run",
|
|
1357
|
+
"github-release",
|
|
1358
|
+
"github-release-notes",
|
|
1359
|
+
"github-release-title",
|
|
1360
|
+
"package-name",
|
|
1361
|
+
"publish-dist-tag",
|
|
1362
|
+
"publisher-workflow-path",
|
|
1363
|
+
"release-passport-impact-json",
|
|
1364
|
+
"release-passport-output-dir",
|
|
1365
|
+
"release-passport-product-name",
|
|
1366
|
+
"required-status-check",
|
|
1367
|
+
"target-ref",
|
|
1368
|
+
"target-sha",
|
|
1369
|
+
"toolchain-type",
|
|
1370
|
+
"verify-command",
|
|
1371
|
+
"working-directory"
|
|
1372
|
+
],
|
|
1373
|
+
"inputCount": 25,
|
|
1374
|
+
"secrets": [],
|
|
1375
|
+
"secretCount": 0,
|
|
1376
|
+
"outputs": [
|
|
1377
|
+
"github-release-url",
|
|
1378
|
+
"package-name",
|
|
1379
|
+
"package-version",
|
|
1380
|
+
"public-release-tag",
|
|
1381
|
+
"release-passport-path"
|
|
1382
|
+
],
|
|
1383
|
+
"outputCount": 5
|
|
1384
|
+
},
|
|
1338
1385
|
{
|
|
1339
1386
|
"id": "paper-release",
|
|
1340
1387
|
"path": ".github/workflows/paper-release.yml",
|
|
@@ -1492,7 +1539,11 @@
|
|
|
1492
1539
|
"buildchain-ref",
|
|
1493
1540
|
"buildchain-repository",
|
|
1494
1541
|
"node-version",
|
|
1542
|
+
"package-name",
|
|
1543
|
+
"prepare-paper-package",
|
|
1544
|
+
"publish-dist-tag",
|
|
1495
1545
|
"setup-node",
|
|
1546
|
+
"target-ref",
|
|
1496
1547
|
"toolchain-command",
|
|
1497
1548
|
"toolchain-digest",
|
|
1498
1549
|
"toolchain-image",
|
|
@@ -1500,7 +1551,7 @@
|
|
|
1500
1551
|
"verify-command",
|
|
1501
1552
|
"working-directory"
|
|
1502
1553
|
],
|
|
1503
|
-
"inputCount":
|
|
1554
|
+
"inputCount": 21,
|
|
1504
1555
|
"secrets": [],
|
|
1505
1556
|
"secretCount": 0,
|
|
1506
1557
|
"outputs": [
|
|
@@ -1511,13 +1562,17 @@
|
|
|
1511
1562
|
"controller-receipt-digest",
|
|
1512
1563
|
"controller-receipt-json",
|
|
1513
1564
|
"controller-receipt-status",
|
|
1565
|
+
"package-name",
|
|
1566
|
+
"package-version",
|
|
1514
1567
|
"publication-artifact-name",
|
|
1515
1568
|
"publication-manifest-json",
|
|
1516
1569
|
"publication-manifest-path",
|
|
1517
1570
|
"publication-passport-path",
|
|
1518
|
-
"publication-registry-path"
|
|
1571
|
+
"publication-registry-path",
|
|
1572
|
+
"publish-dist-tag",
|
|
1573
|
+
"runtime-sha"
|
|
1519
1574
|
],
|
|
1520
|
-
"outputCount":
|
|
1575
|
+
"outputCount": 16
|
|
1521
1576
|
},
|
|
1522
1577
|
{
|
|
1523
1578
|
"id": "release-candidate-promote",
|
|
@@ -3331,8 +3386,8 @@
|
|
|
3331
3386
|
"workflowRegistryPath": "dist/site/workflow-registry.json",
|
|
3332
3387
|
"pageRegistryPath": "dist/site/page-registry.json",
|
|
3333
3388
|
"cliRegistryDigest": "a6daf4822365bf576425777230246046ebca67b3f8dcfea61fc7a61773776185",
|
|
3334
|
-
"workflowRegistryDigest": "
|
|
3335
|
-
"pageRegistryDigest": "
|
|
3389
|
+
"workflowRegistryDigest": "89b7907718c8b4a7b22c3d36a971510a918f113b245bb7fe1d8982dbaf707c66",
|
|
3390
|
+
"pageRegistryDigest": "3a4779e3d85d0a175e33c7d2a8bee30ede3931cc721a7d8c422ab321f0dec723"
|
|
3336
3391
|
},
|
|
3337
3392
|
"comparison": {
|
|
3338
3393
|
"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:01:29.454Z",
|
|
5
|
+
"publishedAt": "2026-07-15T01:01:29.454Z",
|
|
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": "f0f922906c80f4096d2ef9871a4ad831be12f46e",
|
|
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.7",
|
|
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:01:29.454Z",
|
|
5
|
+
"publishedAt": "2026-07-15T01:01:29.454Z",
|
|
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": "f0f922906c80f4096d2ef9871a4ad831be12f46e",
|
|
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.7",
|
|
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",
|
|
@@ -238,7 +242,7 @@
|
|
|
238
242
|
"target-ref",
|
|
239
243
|
"used-nonces-json"
|
|
240
244
|
],
|
|
241
|
-
"inputCount":
|
|
245
|
+
"inputCount": 27,
|
|
242
246
|
"secrets": [],
|
|
243
247
|
"secretCount": 0,
|
|
244
248
|
"outputs": [
|
|
@@ -1025,6 +1029,52 @@
|
|
|
1025
1029
|
"capabilityGroup": "api-cli-reference",
|
|
1026
1030
|
"status": "active"
|
|
1027
1031
|
},
|
|
1032
|
+
{
|
|
1033
|
+
"id": "paper-release-sealed",
|
|
1034
|
+
"path": ".github/workflows/paper-release-sealed.yml",
|
|
1035
|
+
"reusable": true,
|
|
1036
|
+
"inputs": [
|
|
1037
|
+
"artifact-name",
|
|
1038
|
+
"artifact-paths",
|
|
1039
|
+
"artifact-retention-days",
|
|
1040
|
+
"branch-protection-bypass-apps",
|
|
1041
|
+
"branch-protection-bypass-teams",
|
|
1042
|
+
"branch-protection-bypass-users",
|
|
1043
|
+
"buildchain-contract-lock-path",
|
|
1044
|
+
"buildchain-ref",
|
|
1045
|
+
"buildchain-repository",
|
|
1046
|
+
"dry-run",
|
|
1047
|
+
"github-release",
|
|
1048
|
+
"github-release-notes",
|
|
1049
|
+
"github-release-title",
|
|
1050
|
+
"package-name",
|
|
1051
|
+
"publish-dist-tag",
|
|
1052
|
+
"publisher-workflow-path",
|
|
1053
|
+
"release-passport-impact-json",
|
|
1054
|
+
"release-passport-output-dir",
|
|
1055
|
+
"release-passport-product-name",
|
|
1056
|
+
"required-status-check",
|
|
1057
|
+
"target-ref",
|
|
1058
|
+
"target-sha",
|
|
1059
|
+
"toolchain-type",
|
|
1060
|
+
"verify-command",
|
|
1061
|
+
"working-directory"
|
|
1062
|
+
],
|
|
1063
|
+
"inputCount": 25,
|
|
1064
|
+
"secrets": [],
|
|
1065
|
+
"secretCount": 0,
|
|
1066
|
+
"outputs": [
|
|
1067
|
+
"github-release-url",
|
|
1068
|
+
"package-name",
|
|
1069
|
+
"package-version",
|
|
1070
|
+
"public-release-tag",
|
|
1071
|
+
"release-passport-path"
|
|
1072
|
+
],
|
|
1073
|
+
"outputCount": 5,
|
|
1074
|
+
"surface": "repository-workflow",
|
|
1075
|
+
"capabilityGroup": "api-cli-reference",
|
|
1076
|
+
"status": "active"
|
|
1077
|
+
},
|
|
1028
1078
|
{
|
|
1029
1079
|
"id": "paper-release",
|
|
1030
1080
|
"path": ".github/workflows/paper-release.yml",
|
|
@@ -1194,7 +1244,11 @@
|
|
|
1194
1244
|
"buildchain-ref",
|
|
1195
1245
|
"buildchain-repository",
|
|
1196
1246
|
"node-version",
|
|
1247
|
+
"package-name",
|
|
1248
|
+
"prepare-paper-package",
|
|
1249
|
+
"publish-dist-tag",
|
|
1197
1250
|
"setup-node",
|
|
1251
|
+
"target-ref",
|
|
1198
1252
|
"toolchain-command",
|
|
1199
1253
|
"toolchain-digest",
|
|
1200
1254
|
"toolchain-image",
|
|
@@ -1202,7 +1256,7 @@
|
|
|
1202
1256
|
"verify-command",
|
|
1203
1257
|
"working-directory"
|
|
1204
1258
|
],
|
|
1205
|
-
"inputCount":
|
|
1259
|
+
"inputCount": 21,
|
|
1206
1260
|
"secrets": [],
|
|
1207
1261
|
"secretCount": 0,
|
|
1208
1262
|
"outputs": [
|
|
@@ -1213,13 +1267,17 @@
|
|
|
1213
1267
|
"controller-receipt-digest",
|
|
1214
1268
|
"controller-receipt-json",
|
|
1215
1269
|
"controller-receipt-status",
|
|
1270
|
+
"package-name",
|
|
1271
|
+
"package-version",
|
|
1216
1272
|
"publication-artifact-name",
|
|
1217
1273
|
"publication-manifest-json",
|
|
1218
1274
|
"publication-manifest-path",
|
|
1219
1275
|
"publication-passport-path",
|
|
1220
|
-
"publication-registry-path"
|
|
1276
|
+
"publication-registry-path",
|
|
1277
|
+
"publish-dist-tag",
|
|
1278
|
+
"runtime-sha"
|
|
1221
1279
|
],
|
|
1222
|
-
"outputCount":
|
|
1280
|
+
"outputCount": 16,
|
|
1223
1281
|
"surface": "repository-workflow",
|
|
1224
1282
|
"capabilityGroup": "reusable-build",
|
|
1225
1283
|
"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.7",
|
|
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({
|