@kungfu-tech/buildchain 3.0.1 → 3.0.2-alpha.1
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/README.md +5 -3
- package/actions/promote-buildchain-ref/README.md +7 -0
- package/bin/buildchain.mjs +173 -513
- package/bin/internal/cli-options.mjs +79 -0
- package/bin/internal/trust-release-cli.mjs +469 -0
- package/dist/site/agent-index.json +2 -0
- package/dist/site/artifact-schemas.json +5 -0
- package/dist/site/badge-endpoint-registry.json +18 -18
- package/dist/site/badges/v1/kfd-12/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-12/declared.json +1 -1
- package/dist/site/badges/v1/kfd-12/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-12/draft.json +1 -1
- package/dist/site/badges/v1/kfd-12/failed.json +1 -1
- package/dist/site/badges/v1/kfd-12/missing.json +1 -1
- package/dist/site/badges/v1/kfd-12/passed.json +1 -1
- package/dist/site/badges/v1/kfd-12/planned.json +1 -1
- package/dist/site/badges/v1/kfd-13/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-13/declared.json +1 -1
- package/dist/site/badges/v1/kfd-13/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-13/draft.json +1 -1
- package/dist/site/badges/v1/kfd-13/failed.json +1 -1
- package/dist/site/badges/v1/kfd-13/missing.json +1 -1
- package/dist/site/badges/v1/kfd-13/passed.json +1 -1
- package/dist/site/badges/v1/kfd-13/planned.json +1 -1
- package/dist/site/buildchain-contract.json +68 -28
- package/dist/site/buildchain-site.json +28 -18
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/cli-registry.json +108 -0
- package/dist/site/controller-registry.json +36 -4
- package/dist/site/kfd-claims.json +156 -12
- package/dist/site/kfd-upstream-aggregate.json +20 -11
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/node-api-registry.json +36 -10
- package/dist/site/page-registry.json +20 -10
- package/dist/site/public-surface-audit.json +122 -10
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +11 -0
- package/dist/site/release-provenance.json +4 -0
- package/dist/site/schemas/kfd-product-gate-input-v1.schema.json +164 -0
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +106 -0
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +7 -7
- package/dist/site/workflow-registry.json +17 -5
- package/docs/cli.md +9 -2
- package/docs/kfd-support.md +43 -7
- package/docs/release-passport.md +15 -0
- package/package.json +7 -3
- package/packages/core/cache-evidence.js +288 -0
- package/packages/core/diagnostics.js +276 -10
- package/packages/core/github-governance-authority.js +71 -16
- package/packages/core/index.js +27 -0
- package/packages/core/kfd-product-gates.js +865 -0
- package/packages/core/kfd.js +16 -7
- package/packages/core/kfd3-surface-register.js +105 -1
- package/packages/core/public-surface-audit.js +6 -1
- package/packages/core/release-passport-contract.js +13 -0
- package/packages/core/release-passport.js +87 -3
- package/scripts/check-internal-architecture.mjs +171 -0
- package/scripts/check-inventory.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +24 -0
- package/scripts/locked-source-checkout.mjs +48 -0
- package/scripts/promotion-routing-evidence.mjs +73 -0
- package/scripts/reconcile-github-governance.mjs +158 -25
- package/scripts/shifu-gate-profile.mjs +26 -20
|
@@ -92,6 +92,9 @@
|
|
|
92
92
|
"buildchain-contract-lock-path",
|
|
93
93
|
"buildchain-ref",
|
|
94
94
|
"buildchain-repository",
|
|
95
|
+
"cache-dependency-lock-root",
|
|
96
|
+
"cache-policy-root",
|
|
97
|
+
"cache-toolchain-root",
|
|
95
98
|
"cargo-registry-index",
|
|
96
99
|
"checkout-cache-fallback",
|
|
97
100
|
"checkout-cache-fetch-attempts",
|
|
@@ -141,7 +144,7 @@
|
|
|
141
144
|
"verify-command",
|
|
142
145
|
"working-directory"
|
|
143
146
|
],
|
|
144
|
-
"inputCount":
|
|
147
|
+
"inputCount": 69,
|
|
145
148
|
"secrets": [
|
|
146
149
|
"BUILDCHAIN_ARTIFACT_RELAY_S3_DOWNLOAD_ROLE_ARN",
|
|
147
150
|
"BUILDCHAIN_ARTIFACT_RELAY_S3_ROLE_ARN",
|
|
@@ -412,6 +415,8 @@
|
|
|
412
415
|
"release-passport-kfd-3-artifact-verify-command",
|
|
413
416
|
"release-passport-kfd-3-artifact-witness-jsons",
|
|
414
417
|
"release-passport-kfd-3-prebuild-witness-jsons",
|
|
418
|
+
"release-passport-kfd-product-gate-jsons",
|
|
419
|
+
"release-passport-kfd-support-matrix-json",
|
|
415
420
|
"release-passport-output-dir",
|
|
416
421
|
"release-passport-platform-manifest-paths",
|
|
417
422
|
"release-passport-product-name",
|
|
@@ -423,7 +428,7 @@
|
|
|
423
428
|
"target-sha",
|
|
424
429
|
"trusted-publishing"
|
|
425
430
|
],
|
|
426
|
-
"inputCount":
|
|
431
|
+
"inputCount": 85,
|
|
427
432
|
"secrets": [
|
|
428
433
|
"BUILDCHAIN_ISSUE_APP_ID",
|
|
429
434
|
"BUILDCHAIN_ISSUE_APP_PRIVATE_KEY",
|
|
@@ -911,6 +916,9 @@
|
|
|
911
916
|
"buildchain-ref",
|
|
912
917
|
"buildchain-repository",
|
|
913
918
|
"buildchain-stable-contract-lock-path",
|
|
919
|
+
"cache-dependency-lock-root",
|
|
920
|
+
"cache-policy-root",
|
|
921
|
+
"cache-toolchain-root",
|
|
914
922
|
"cargo-registry-index",
|
|
915
923
|
"checkout-cache-fallback",
|
|
916
924
|
"checkout-cache-fetch-attempts",
|
|
@@ -960,7 +968,7 @@
|
|
|
960
968
|
"verify-command",
|
|
961
969
|
"working-directory"
|
|
962
970
|
],
|
|
963
|
-
"inputCount":
|
|
971
|
+
"inputCount": 72,
|
|
964
972
|
"secrets": [
|
|
965
973
|
"BUILDCHAIN_ARTIFACT_RELAY_S3_DOWNLOAD_ROLE_ARN",
|
|
966
974
|
"BUILDCHAIN_ARTIFACT_RELAY_S3_ROLE_ARN",
|
|
@@ -1641,6 +1649,8 @@
|
|
|
1641
1649
|
"release-passport-kfd-3-artifact-verify-command",
|
|
1642
1650
|
"release-passport-kfd-3-artifact-witness-jsons",
|
|
1643
1651
|
"release-passport-kfd-3-prebuild-witness-jsons",
|
|
1652
|
+
"release-passport-kfd-product-gate-jsons",
|
|
1653
|
+
"release-passport-kfd-support-matrix-json",
|
|
1644
1654
|
"release-passport-output-dir",
|
|
1645
1655
|
"release-passport-platform-manifest-paths",
|
|
1646
1656
|
"release-passport-product-name",
|
|
@@ -1652,7 +1662,7 @@
|
|
|
1652
1662
|
"target-sha",
|
|
1653
1663
|
"trusted-publishing"
|
|
1654
1664
|
],
|
|
1655
|
-
"inputCount":
|
|
1665
|
+
"inputCount": 77,
|
|
1656
1666
|
"secrets": [
|
|
1657
1667
|
"BUILDCHAIN_ISSUE_APP_ID",
|
|
1658
1668
|
"BUILDCHAIN_ISSUE_APP_PRIVATE_KEY",
|
|
@@ -1972,6 +1982,8 @@
|
|
|
1972
1982
|
"release-passport-kfd-3-artifact-verify-command",
|
|
1973
1983
|
"release-passport-kfd-3-artifact-witness-jsons",
|
|
1974
1984
|
"release-passport-kfd-3-prebuild-witness-jsons",
|
|
1985
|
+
"release-passport-kfd-product-gate-jsons",
|
|
1986
|
+
"release-passport-kfd-support-matrix-json",
|
|
1975
1987
|
"release-passport-output-dir",
|
|
1976
1988
|
"release-passport-platform-manifest-paths",
|
|
1977
1989
|
"release-passport-product-name",
|
|
@@ -1988,7 +2000,7 @@
|
|
|
1988
2000
|
"transaction-state-path",
|
|
1989
2001
|
"verification-command"
|
|
1990
2002
|
],
|
|
1991
|
-
"inputCount":
|
|
2003
|
+
"inputCount": 66,
|
|
1992
2004
|
"capabilityGroup": "release-passport-trust",
|
|
1993
2005
|
"status": "active"
|
|
1994
2006
|
},
|
package/docs/cli.md
CHANGED
|
@@ -281,8 +281,9 @@ assertPublicSurfaceReverseAudit(collectPublicSurfaceReverseAudit({ root: process
|
|
|
281
281
|
`buildchain kfd` is the product-facing KFD namespace. Schema commands expose the
|
|
282
282
|
machine-readable KFD standards shipped by `@kungfu-tech/kfd`, while versioned
|
|
283
283
|
subcommands host concrete product workflows. KFD-1, KFD-2, and KFD-3 are
|
|
284
|
-
first-class Buildchain surfaces. KFD-4
|
|
285
|
-
|
|
284
|
+
first-class Buildchain surfaces. KFD-4, KFD-5, and KFD-7 expose fail-closed
|
|
285
|
+
product-evidence gate and verification protocols; passing those protocols does
|
|
286
|
+
not itself qualify, certify, activate, or ship support.
|
|
286
287
|
|
|
287
288
|
`status` reports implemented support and the active repo-owned file layout.
|
|
288
289
|
`migrate-layout` moves legacy root files into `.buildchain/`:
|
|
@@ -290,6 +291,12 @@ real verification protocol for it.
|
|
|
290
291
|
```bash
|
|
291
292
|
buildchain kfd status --json
|
|
292
293
|
buildchain kfd migrate-layout --write
|
|
294
|
+
buildchain kfd 4 gate --input-json kfd-4-gate-input.json --output kfd-4-gate.json
|
|
295
|
+
buildchain kfd 5 gate --input-json kfd-5-gate-input.json --output kfd-5-gate.json
|
|
296
|
+
buildchain kfd 7 gate --input-json kfd-7-gate-input.json --output kfd-7-gate.json
|
|
297
|
+
buildchain kfd support project --matrix-json support-matrix.json \
|
|
298
|
+
--gate-json kfd-4-gate.json --gate-json kfd-5-gate.json \
|
|
299
|
+
--gate-json kfd-7-gate.json --output kfd-support.json
|
|
293
300
|
```
|
|
294
301
|
|
|
295
302
|
KFD-1 commands generate and validate contract-world release evidence:
|
package/docs/kfd-support.md
CHANGED
|
@@ -7,8 +7,9 @@ facts, not as README prose. The machine-readable sources are:
|
|
|
7
7
|
collaboration surfaces;
|
|
8
8
|
- `dist/site/public-surface-audit.json` for reverse enumeration of exposed CLI,
|
|
9
9
|
workflow, action, site, and documented command surfaces;
|
|
10
|
-
- `buildchain.release.json`
|
|
11
|
-
|
|
10
|
+
- `buildchain.release.json` plus its `kfd-support.json` sibling for
|
|
11
|
+
release-specific KFD-1/2/3 evidence and the exact KFD-1..13 support
|
|
12
|
+
projection;
|
|
12
13
|
- `.buildchain/buildchain.toml` for repository-owned Buildchain configuration;
|
|
13
14
|
- `.buildchain/kfd/kfd-2/registry.json` for product-owned KFD-2 public claim
|
|
14
15
|
declarations;
|
|
@@ -41,11 +42,43 @@ buildchain kfd upstream check --json
|
|
|
41
42
|
buildchain kfd aggregate --json
|
|
42
43
|
buildchain kfd 3 query buildchain --json
|
|
43
44
|
buildchain kfd 4 schema --json
|
|
45
|
+
buildchain kfd 4 gate --input-json kfd-4-gate-input.json --json
|
|
46
|
+
buildchain kfd 5 gate --input-json kfd-5-gate-input.json --json
|
|
47
|
+
buildchain kfd 7 gate --input-json kfd-7-gate-input.json --json
|
|
48
|
+
buildchain kfd support project --matrix-json support-matrix.json \
|
|
49
|
+
--gate-json kfd-4-gate.json --gate-json kfd-5-gate.json \
|
|
50
|
+
--gate-json kfd-7-gate.json --json
|
|
44
51
|
```
|
|
45
52
|
|
|
46
|
-
KFD-1, KFD-2, and KFD-3 have concrete Buildchain workflows. KFD-4
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
KFD-1, KFD-2, and KFD-3 have concrete Buildchain workflows. KFD-4, KFD-5, and
|
|
54
|
+
KFD-7 additionally have product-evidence gates backed by the installed KFD
|
|
55
|
+
package and its offline WASM verifier. These gates verify exact source,
|
|
56
|
+
freshness, record bytes, negative evidence, and product-owned responsibility;
|
|
57
|
+
they do not self-qualify, self-certify, activate, or silently widen support.
|
|
58
|
+
KFD-6 remains an explicit unsupported barrier. KFD-8 through KFD-13 remain
|
|
59
|
+
draft adopter-evidence barriers until their standards and product gates mature.
|
|
60
|
+
|
|
61
|
+
## KFD-4 / KFD-5 / KFD-7 Product Gates
|
|
62
|
+
|
|
63
|
+
Each gate input uses
|
|
64
|
+
`kungfu-buildchain-kfd-product-gate-input` version 1 and binds repository-relative
|
|
65
|
+
KFD records and evidence by SHA-256. The result uses
|
|
66
|
+
`kungfu-buildchain-kfd-product-gate` version 1 and always records
|
|
67
|
+
`qualifying: false` and `selfCertified: false`.
|
|
68
|
+
|
|
69
|
+
- KFD-4 requires a verified observer perspective, contrastive perspective
|
|
70
|
+
replay, declared loss, preserved evidence boundaries, projection fsck, and a
|
|
71
|
+
negative fixture.
|
|
72
|
+
- KFD-5 requires an accepted Primitive discovery record whose minimum-closure,
|
|
73
|
+
deletion, fuse, and dogfood tests pass with retained evidence, plus explicit
|
|
74
|
+
falsifiers and a negative fixture.
|
|
75
|
+
- KFD-7 requires a qualified and activated Domain Profile, independent review,
|
|
76
|
+
product witnesses, all 13 evidence obligations, and negative evidence.
|
|
77
|
+
|
|
78
|
+
`buildchain kfd support project` joins the three gate results with the
|
|
79
|
+
product-owned KFD-1..13 matrix. It rejects normative metadata drift, source or
|
|
80
|
+
freshness mismatch, gate/matrix disagreement, KFD-4/5 candidate widening,
|
|
81
|
+
KFD-6 support, KFD-8..13 promotion, or any unsupported shipped-support claim.
|
|
49
82
|
|
|
50
83
|
## KFD-1
|
|
51
84
|
|
|
@@ -349,12 +382,15 @@ Inspect KFD-owned schema facts:
|
|
|
349
382
|
buildchain kfd schema list --json
|
|
350
383
|
buildchain kfd schema show kfd-1 --json
|
|
351
384
|
buildchain kfd 4 schema --json
|
|
385
|
+
buildchain kfd 5 schema --json
|
|
386
|
+
buildchain kfd 7 schema --json
|
|
352
387
|
```
|
|
353
388
|
|
|
354
389
|
The KFD schema namespace is discovered from `@kungfu-tech/kfd/standards.json`.
|
|
355
390
|
For KFD-2 this includes `trustClaims`, `trustAssessment`, `trustTaxonomy`,
|
|
356
|
-
`releaseClaims`, and `releaseTrustPassport`.
|
|
357
|
-
|
|
391
|
+
`releaseClaims`, and `releaseTrustPassport`. KFD-4 exposes both
|
|
392
|
+
`observerPerspective` and `perspectiveReplay`; KFD-5 exposes
|
|
393
|
+
`primitiveDiscovery`; KFD-7 exposes `domainProfileDeclaration`.
|
|
358
394
|
|
|
359
395
|
Detect public surface candidates:
|
|
360
396
|
|
package/docs/release-passport.md
CHANGED
|
@@ -380,6 +380,21 @@ inspect `buildchain.release.json` and know whether the released package
|
|
|
380
380
|
actually exposes no more and no less than the declared collaboration interface,
|
|
381
381
|
instead of trusting docs or release notes.
|
|
382
382
|
|
|
383
|
+
### KFD support projection
|
|
384
|
+
|
|
385
|
+
Pass `--kfd-support-matrix-json` together with three
|
|
386
|
+
`--kfd-product-gate-json` arguments for KFD-4, KFD-5, and KFD-7. The collector
|
|
387
|
+
verifies the product-owned KFD-1..13 matrix against the installed
|
|
388
|
+
`@kungfu-tech/kfd` standards, binds each gate to the exact release source and
|
|
389
|
+
fresh evidence cut, and emits `kfd-support.json`. The identical projection is
|
|
390
|
+
embedded as `kfdSupport` in `buildchain.release.json`.
|
|
391
|
+
|
|
392
|
+
Release verification fails closed when the sibling is missing or differs,
|
|
393
|
+
when a gate expires, when matrix and gate status disagree, or when a matrix
|
|
394
|
+
widens KFD-4/5 candidate, KFD-6 unsupported, KFD-8..13 draft, or any non-shipped
|
|
395
|
+
state. A passed projection is release evidence only; product qualification,
|
|
396
|
+
activation, certification, and support ownership remain outside Buildchain.
|
|
397
|
+
|
|
383
398
|
### Floating Buildchain contract lock
|
|
384
399
|
|
|
385
400
|
KFD-1 protects release payload surfaces. Floating ref contract locks protect the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "3.0.1",
|
|
3
|
+
"version": "3.0.2-alpha.1",
|
|
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",
|
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
"./anchored-version-material": "./packages/core/anchored-version-material.js",
|
|
21
21
|
"./buildchain-contract": "./packages/core/buildchain-contract.js",
|
|
22
22
|
"./candidate-timeline": "./packages/core/candidate-timeline.js",
|
|
23
|
+
"./cache-evidence": "./packages/core/cache-evidence.js",
|
|
23
24
|
"./controller-evidence": "./packages/core/controller-evidence.js",
|
|
24
25
|
"./diagnostics": "./packages/core/diagnostics.js",
|
|
25
26
|
"./homebrew": "./packages/core/homebrew.js",
|
|
26
27
|
"./issue-reporting": "./packages/core/issue-reporting.js",
|
|
27
28
|
"./kfd": "./packages/core/kfd.js",
|
|
29
|
+
"./kfd-product-gates": "./packages/core/kfd-product-gates.js",
|
|
28
30
|
"./kfd-agent-hub": "./packages/core/kfd-agent-hub.js",
|
|
29
31
|
"./buildchain-layout": "./packages/core/buildchain-layout.js",
|
|
30
32
|
"./release-line-bootstrap": "./packages/core/release-line-bootstrap.js",
|
|
@@ -66,6 +68,8 @@
|
|
|
66
68
|
"./site/release-passport-check-manifest.json": "./dist/site/release-passport-check-manifest.json",
|
|
67
69
|
"./site/schemas/release-passport-v1.schema.json": "./dist/site/schemas/release-passport-v1.schema.json",
|
|
68
70
|
"./site/schemas/kfd-agent-hub-adoption.schema.json": "./dist/site/schemas/kfd-agent-hub-adoption.schema.json",
|
|
71
|
+
"./site/schemas/kfd-product-gate-input-v1.schema.json": "./dist/site/schemas/kfd-product-gate-input-v1.schema.json",
|
|
72
|
+
"./site/schemas/kfd-support-projection-v1.schema.json": "./dist/site/schemas/kfd-support-projection-v1.schema.json",
|
|
69
73
|
"./site/buildchain-contract.json": "./dist/site/buildchain-contract.json",
|
|
70
74
|
"./site/product-mechanism.json": "./dist/site/product-mechanism.json",
|
|
71
75
|
"./site/release-provenance.json": "./dist/site/release-provenance.json",
|
|
@@ -95,7 +99,7 @@
|
|
|
95
99
|
},
|
|
96
100
|
"packageManager": "pnpm@11.7.0",
|
|
97
101
|
"scripts": {
|
|
98
|
-
"check": "node scripts/check-inventory.mjs && pnpm run check:site && pnpm run check:workflows && pnpm run test:unit && node scripts/check-action-bundles.mjs",
|
|
102
|
+
"check": "node scripts/check-internal-architecture.mjs && node scripts/check-inventory.mjs && pnpm run check:site && pnpm run check:workflows && pnpm run test:unit && node scripts/check-action-bundles.mjs",
|
|
99
103
|
"check:workflows": "bash scripts/check-workflows.sh",
|
|
100
104
|
"generate:site": "node scripts/generate-site-bundle.mjs",
|
|
101
105
|
"check:site": "node scripts/generate-site-bundle.mjs --check",
|
|
@@ -113,7 +117,7 @@
|
|
|
113
117
|
"vitest": "^4.1.9"
|
|
114
118
|
},
|
|
115
119
|
"dependencies": {
|
|
116
|
-
"@kungfu-tech/kfd": "1.0.0-alpha.
|
|
120
|
+
"@kungfu-tech/kfd": "1.0.0-alpha.47",
|
|
117
121
|
"smol-toml": "1.7.0"
|
|
118
122
|
}
|
|
119
123
|
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const BUILDCHAIN_CACHE_OPERATION_RECEIPT_CONTRACT =
|
|
4
|
+
"buildchain.cache-operation-receipt/v1";
|
|
5
|
+
export const BUILDCHAIN_CACHE_EVIDENCE_SET_CONTRACT =
|
|
6
|
+
"buildchain.cache-evidence-set/v1";
|
|
7
|
+
|
|
8
|
+
const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
|
|
9
|
+
const OUTCOMES = new Set([
|
|
10
|
+
"hit",
|
|
11
|
+
"miss",
|
|
12
|
+
"partial",
|
|
13
|
+
"bypassed",
|
|
14
|
+
"poisoned",
|
|
15
|
+
"unavailable",
|
|
16
|
+
]);
|
|
17
|
+
const METRIC_UNITS = Object.freeze({
|
|
18
|
+
lookupDuration: "ms",
|
|
19
|
+
restoreDuration: "ms",
|
|
20
|
+
saveDuration: "ms",
|
|
21
|
+
restoredBytes: "bytes",
|
|
22
|
+
writtenBytes: "bytes",
|
|
23
|
+
savedTime: "ms",
|
|
24
|
+
});
|
|
25
|
+
const METRIC_STATUSES = new Set(["observed", "unavailable", "not-applicable"]);
|
|
26
|
+
const SAVED_TIME_METHODS = new Set(["producer-measured", "provider-reported"]);
|
|
27
|
+
|
|
28
|
+
function assert(condition, message) {
|
|
29
|
+
if (!condition) throw new Error(message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ordered(value) {
|
|
33
|
+
if (Array.isArray(value)) return value.map(ordered);
|
|
34
|
+
if (value && typeof value === "object") {
|
|
35
|
+
return Object.fromEntries(
|
|
36
|
+
Object.keys(value)
|
|
37
|
+
.sort()
|
|
38
|
+
.map((key) => [key, ordered(value[key])]),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stableJson(value) {
|
|
45
|
+
return JSON.stringify(ordered(value));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function cacheEvidenceDigest(value) {
|
|
49
|
+
return `sha256:${crypto
|
|
50
|
+
.createHash("sha256")
|
|
51
|
+
.update(typeof value === "string" ? value : stableJson(value))
|
|
52
|
+
.digest("hex")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function exactKeys(value, allowed, label) {
|
|
56
|
+
assert(
|
|
57
|
+
value && typeof value === "object" && !Array.isArray(value),
|
|
58
|
+
`${label} must be an object`,
|
|
59
|
+
);
|
|
60
|
+
for (const key of Object.keys(value)) {
|
|
61
|
+
assert(allowed.has(key), `${label}.${key} is not allowed`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function text(value, label) {
|
|
66
|
+
assert(
|
|
67
|
+
typeof value === "string" && value.trim() === value && value.length > 0,
|
|
68
|
+
`${label} is required`,
|
|
69
|
+
);
|
|
70
|
+
assert(!/[\r\n\0]/.test(value), `${label} contains control characters`);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function digest(value, label) {
|
|
75
|
+
assert(DIGEST_RE.test(value), `${label} must be a sha256 digest`);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeBindings(bindings = {}) {
|
|
80
|
+
exactKeys(
|
|
81
|
+
bindings,
|
|
82
|
+
new Set([
|
|
83
|
+
"sourceCommit",
|
|
84
|
+
"sourceTree",
|
|
85
|
+
"runtimeCommit",
|
|
86
|
+
"dependencyLockRoot",
|
|
87
|
+
"toolchainRoot",
|
|
88
|
+
"policyRoot",
|
|
89
|
+
"platformRoot",
|
|
90
|
+
"cacheProfileRoot",
|
|
91
|
+
]),
|
|
92
|
+
"bindings",
|
|
93
|
+
);
|
|
94
|
+
return Object.fromEntries(
|
|
95
|
+
Object.entries(bindings)
|
|
96
|
+
.filter(([, value]) => value !== null && value !== undefined && value !== "")
|
|
97
|
+
.map(([key, value]) => [key, text(value, `bindings.${key}`)]),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeMetric(metric, name) {
|
|
102
|
+
exactKeys(
|
|
103
|
+
metric,
|
|
104
|
+
new Set([
|
|
105
|
+
"status",
|
|
106
|
+
"unit",
|
|
107
|
+
"value",
|
|
108
|
+
"source",
|
|
109
|
+
"reason",
|
|
110
|
+
"evidenceRoot",
|
|
111
|
+
"method",
|
|
112
|
+
]),
|
|
113
|
+
`metrics.${name}`,
|
|
114
|
+
);
|
|
115
|
+
const status = text(metric.status, `metrics.${name}.status`);
|
|
116
|
+
assert(
|
|
117
|
+
METRIC_STATUSES.has(status),
|
|
118
|
+
`metrics.${name}.status must be observed, unavailable, or not-applicable`,
|
|
119
|
+
);
|
|
120
|
+
assert(
|
|
121
|
+
metric.unit === METRIC_UNITS[name],
|
|
122
|
+
`metrics.${name}.unit must be ${METRIC_UNITS[name]}`,
|
|
123
|
+
);
|
|
124
|
+
if (status === "observed") {
|
|
125
|
+
assert(
|
|
126
|
+
Number.isFinite(metric.value) && metric.value >= 0,
|
|
127
|
+
`metrics.${name}.value must be a non-negative finite number`,
|
|
128
|
+
);
|
|
129
|
+
const normalized = {
|
|
130
|
+
status,
|
|
131
|
+
unit: metric.unit,
|
|
132
|
+
value: metric.value,
|
|
133
|
+
source: text(metric.source, `metrics.${name}.source`),
|
|
134
|
+
reason: null,
|
|
135
|
+
evidenceRoot: digest(
|
|
136
|
+
metric.evidenceRoot,
|
|
137
|
+
`metrics.${name}.evidenceRoot`,
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
if (name === "savedTime") {
|
|
141
|
+
assert(
|
|
142
|
+
typeof metric.method === "string" &&
|
|
143
|
+
SAVED_TIME_METHODS.has(metric.method),
|
|
144
|
+
"observed saved time requires producer-measured or provider-reported evidence",
|
|
145
|
+
);
|
|
146
|
+
normalized.method = metric.method;
|
|
147
|
+
} else if (metric.method) {
|
|
148
|
+
normalized.method = text(metric.method, `metrics.${name}.method`);
|
|
149
|
+
}
|
|
150
|
+
return normalized;
|
|
151
|
+
}
|
|
152
|
+
assert(
|
|
153
|
+
metric.value === null || metric.value === undefined,
|
|
154
|
+
`metrics.${name}.value must be null when ${status}`,
|
|
155
|
+
);
|
|
156
|
+
return {
|
|
157
|
+
status,
|
|
158
|
+
unit: metric.unit,
|
|
159
|
+
value: null,
|
|
160
|
+
source: metric.source ? text(metric.source, `metrics.${name}.source`) : null,
|
|
161
|
+
reason: text(metric.reason, `metrics.${name}.reason`),
|
|
162
|
+
evidenceRoot: metric.evidenceRoot
|
|
163
|
+
? digest(metric.evidenceRoot, `metrics.${name}.evidenceRoot`)
|
|
164
|
+
: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function createCacheOperationReceipt({
|
|
169
|
+
operationId,
|
|
170
|
+
operation,
|
|
171
|
+
provider,
|
|
172
|
+
producer,
|
|
173
|
+
platform,
|
|
174
|
+
cacheKey,
|
|
175
|
+
cacheRoot,
|
|
176
|
+
outcome,
|
|
177
|
+
bindings = {},
|
|
178
|
+
metrics,
|
|
179
|
+
evidence,
|
|
180
|
+
} = {}) {
|
|
181
|
+
assert(
|
|
182
|
+
["restore", "save", "lookup"].includes(operation),
|
|
183
|
+
"operation must be restore, save, or lookup",
|
|
184
|
+
);
|
|
185
|
+
assert(OUTCOMES.has(outcome), "unsupported cache outcome");
|
|
186
|
+
exactKeys(
|
|
187
|
+
metrics,
|
|
188
|
+
new Set(Object.keys(METRIC_UNITS)),
|
|
189
|
+
"metrics",
|
|
190
|
+
);
|
|
191
|
+
for (const name of Object.keys(METRIC_UNITS)) {
|
|
192
|
+
assert(metrics[name], `metrics.${name} is required`);
|
|
193
|
+
}
|
|
194
|
+
exactKeys(
|
|
195
|
+
evidence,
|
|
196
|
+
new Set(["kind", "root", "locator"]),
|
|
197
|
+
"evidence",
|
|
198
|
+
);
|
|
199
|
+
const receipt = {
|
|
200
|
+
schema: BUILDCHAIN_CACHE_OPERATION_RECEIPT_CONTRACT,
|
|
201
|
+
operationId: text(operationId, "operationId"),
|
|
202
|
+
operation,
|
|
203
|
+
provider: text(provider, "provider"),
|
|
204
|
+
producer: text(producer, "producer"),
|
|
205
|
+
platform: text(platform, "platform"),
|
|
206
|
+
cacheKey: cacheKey ? text(cacheKey, "cacheKey") : null,
|
|
207
|
+
cacheRoot: cacheRoot ? text(cacheRoot, "cacheRoot") : null,
|
|
208
|
+
outcome,
|
|
209
|
+
bindings: normalizeBindings(bindings),
|
|
210
|
+
metrics: Object.fromEntries(
|
|
211
|
+
Object.keys(METRIC_UNITS).map((name) => [
|
|
212
|
+
name,
|
|
213
|
+
normalizeMetric(metrics[name], name),
|
|
214
|
+
]),
|
|
215
|
+
),
|
|
216
|
+
evidence: {
|
|
217
|
+
kind: text(evidence.kind, "evidence.kind"),
|
|
218
|
+
root: digest(evidence.root, "evidence.root"),
|
|
219
|
+
locator: text(evidence.locator, "evidence.locator"),
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
return { ...receipt, receiptRoot: cacheEvidenceDigest(receipt) };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function verifyCacheOperationReceipt(receipt) {
|
|
226
|
+
assert(
|
|
227
|
+
receipt?.schema === BUILDCHAIN_CACHE_OPERATION_RECEIPT_CONTRACT,
|
|
228
|
+
"cache operation receipt schema mismatch",
|
|
229
|
+
);
|
|
230
|
+
const { receiptRoot, ...body } = receipt;
|
|
231
|
+
assert(
|
|
232
|
+
receiptRoot === cacheEvidenceDigest(body),
|
|
233
|
+
"cache operation receipt root mismatch",
|
|
234
|
+
);
|
|
235
|
+
const rebuilt = createCacheOperationReceipt(body);
|
|
236
|
+
assert(
|
|
237
|
+
stableJson(rebuilt) === stableJson(receipt),
|
|
238
|
+
"cache operation receipt normalization drift",
|
|
239
|
+
);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function createCacheEvidenceSet({
|
|
244
|
+
repository,
|
|
245
|
+
sourceCommit,
|
|
246
|
+
sourceTree = "",
|
|
247
|
+
runtimeCommit = "",
|
|
248
|
+
platform,
|
|
249
|
+
operations = [],
|
|
250
|
+
} = {}) {
|
|
251
|
+
assert(Array.isArray(operations), "cache evidence operations must be an array");
|
|
252
|
+
operations.forEach(verifyCacheOperationReceipt);
|
|
253
|
+
const operationIds = operations.map(({ operationId }) => operationId);
|
|
254
|
+
assert(
|
|
255
|
+
operationIds.length === new Set(operationIds).size,
|
|
256
|
+
"cache evidence operation ids must be unique",
|
|
257
|
+
);
|
|
258
|
+
const value = {
|
|
259
|
+
schema: BUILDCHAIN_CACHE_EVIDENCE_SET_CONTRACT,
|
|
260
|
+
repository: text(repository, "repository"),
|
|
261
|
+
sourceCommit: text(sourceCommit, "sourceCommit"),
|
|
262
|
+
sourceTree: sourceTree ? text(sourceTree, "sourceTree") : null,
|
|
263
|
+
runtimeCommit: runtimeCommit ? text(runtimeCommit, "runtimeCommit") : null,
|
|
264
|
+
platform: text(platform, "platform"),
|
|
265
|
+
operations: [...operations].sort((left, right) =>
|
|
266
|
+
left.operationId.localeCompare(right.operationId),
|
|
267
|
+
),
|
|
268
|
+
};
|
|
269
|
+
return { ...value, evidenceRoot: cacheEvidenceDigest(value) };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function verifyCacheEvidenceSet(receipt) {
|
|
273
|
+
assert(
|
|
274
|
+
receipt?.schema === BUILDCHAIN_CACHE_EVIDENCE_SET_CONTRACT,
|
|
275
|
+
"cache evidence set schema mismatch",
|
|
276
|
+
);
|
|
277
|
+
const { evidenceRoot, ...body } = receipt;
|
|
278
|
+
assert(
|
|
279
|
+
evidenceRoot === cacheEvidenceDigest(body),
|
|
280
|
+
"cache evidence set root mismatch",
|
|
281
|
+
);
|
|
282
|
+
const rebuilt = createCacheEvidenceSet(body);
|
|
283
|
+
assert(
|
|
284
|
+
stableJson(rebuilt) === stableJson(receipt),
|
|
285
|
+
"cache evidence set normalization drift",
|
|
286
|
+
);
|
|
287
|
+
return true;
|
|
288
|
+
}
|