@kungfu-tech/buildchain 3.0.2-alpha.7 → 3.0.2-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.
Files changed (54) hide show
  1. package/README.md +3 -2
  2. package/contracts/auditable-demo-media-profiles-v1.json +168 -0
  3. package/contracts/evidence/auditable-demo-web-delivery-v1.json +103 -0
  4. package/contracts/fixtures/auditable-demo-web-delivery-v1/complete-transcript.txt +2 -0
  5. package/contracts/fixtures/auditable-demo-web-delivery-v1/public-projection.json +16 -0
  6. package/contracts/fixtures/auditable-demo-web-delivery-v1/scene.json +12 -0
  7. package/dist/site/buildchain-contract.json +44 -26
  8. package/dist/site/buildchain-site.json +42 -32
  9. package/dist/site/capability-registry.json +3 -3
  10. package/dist/site/controller-registry.json +11 -3
  11. package/dist/site/kfd-claims.json +74 -8
  12. package/dist/site/kfd-upstream-aggregate.json +1 -1
  13. package/dist/site/manual-registry.json +7 -7
  14. package/dist/site/node-api-registry.json +44 -5
  15. package/dist/site/page-registry.json +30 -20
  16. package/dist/site/public-surface-audit.json +38 -10
  17. package/dist/site/publication-authority-registry.json +26 -1
  18. package/dist/site/publication-registry.json +4 -4
  19. package/dist/site/release-provenance.json +3 -0
  20. package/dist/site/site-manifest.json +11 -11
  21. package/dist/site/workflow-registry.json +41 -7
  22. package/docs/MAP.md +2 -0
  23. package/docs/auditable-demo.md +55 -3
  24. package/docs/dev-alpha-candidate-patrol.md +9 -0
  25. package/docs/github-artifact-attestation.md +1 -1
  26. package/docs/release-governance.md +32 -0
  27. package/docs/reusable-build-surface.md +73 -58
  28. package/docs/runtime-train-validation.md +21 -0
  29. package/docs/versioning.md +1 -0
  30. package/package.json +5 -1
  31. package/packages/core/artifact-signing-result.js +228 -0
  32. package/packages/core/artifact-signing.js +412 -0
  33. package/packages/core/buildchain-config.js +58 -0
  34. package/packages/core/buildchain-contract.js +8 -0
  35. package/packages/core/buildchain-publication-authority.js +1 -0
  36. package/packages/core/detached-artifact-signature.js +121 -0
  37. package/packages/core/github-governance-authority.js +6 -0
  38. package/packages/core/index.js +27 -0
  39. package/scripts/auditable-demo.mjs +491 -22
  40. package/scripts/buildchain-channel-router.mjs +8 -2
  41. package/scripts/check-inventory.mjs +5 -0
  42. package/scripts/dev-alpha-candidate-patrol.mjs +18 -0
  43. package/scripts/dispatch-artifact-signing-authority.mjs +152 -0
  44. package/scripts/finalize-native-artifact-signing-result.mjs +96 -0
  45. package/scripts/generate-site-bundle.mjs +3 -0
  46. package/scripts/import-artifact-signing-results.mjs +76 -0
  47. package/scripts/inspect-artifact-signing-requests.mjs +101 -0
  48. package/scripts/materialize-artifact-signing-request.mjs +66 -0
  49. package/scripts/merge-artifact-signing-results.mjs +76 -0
  50. package/scripts/release-line-policy.mjs +27 -0
  51. package/scripts/runtime-ref-core.mjs +10 -6
  52. package/scripts/seal-artifact-signing-requests.mjs +368 -0
  53. package/scripts/sign-detached-artifact-requests.mjs +237 -0
  54. package/scripts/verify-artifact-signing-results.mjs +99 -0
@@ -1,3 +1,21 @@
1
+ ---
2
+ status: draft
3
+ period: ongoing
4
+ theme: buildchain-reusable-build
5
+ doc_type: technical-reference
6
+ source_level: local-files
7
+ confidence: high
8
+ sensitivity: public
9
+ evidence_grade: A
10
+ review_state: unreviewed
11
+ last_reviewed: 2026-07-27
12
+ ai_provenance:
13
+ model_family: GPT-5
14
+ product: Codex
15
+ generated_at: 2026-07-27
16
+ invisible_context: not asserted
17
+ ---
18
+
1
19
  # Reusable Build Surface
2
20
 
3
21
  Buildchain v2 provides a reusable build workflow for repositories that need
@@ -35,7 +53,7 @@ jobs:
35
53
 
36
54
  `buildchain-channel` defaults to `auto`. Selection uses this precedence:
37
55
 
38
- 1. an explicit `buildchain-ref` train, SHA, or official channel;
56
+ 1. an explicit `buildchain-ref` protected authority, train, SHA, or official channel;
39
57
  2. an explicit `buildchain-channel: alpha|stable`;
40
58
  3. `publish-channel: alpha|release|major`;
41
59
  4. GitHub release prerelease metadata;
@@ -482,8 +500,8 @@ The reusable workflow exposes the resolved contract:
482
500
  | `trusted-event` | `true` when the event is trusted enough to reach build runners |
483
501
  | `buildchain-runtime-ref` | Runtime ref selected after applying the empty-default or override policy |
484
502
  | `buildchain-runtime-sha` | Immutable Buildchain runtime commit used by all runtime checkouts |
485
- | `buildchain-runtime-class` | `stable`, `alpha`, `train`, `exact-sha`, or `development` |
486
- | `buildchain-runtime-override` | `true` when a train or exact-SHA `buildchain-ref` override was accepted |
503
+ | `buildchain-runtime-class` | `stable`, `alpha`, `authority`, `train`, `exact-sha`, or `development` |
504
+ | `buildchain-runtime-override` | `true` when an authority, train, or exact-SHA `buildchain-ref` override was accepted |
487
505
  | `buildchain-runtime-trust-decision` | Runtime override trust decision |
488
506
  | `buildchain-contract-lock-status` | `unchanged`, `compatible-drift`, `breaking-drift`, `missing-lock`, `non-floating-runtime`, or first-release `runtime-contract-unavailable` |
489
507
  | `buildchain-contract-lock-drift` | `true` when the floating runtime SHA or contract digest changed |
@@ -523,65 +541,62 @@ release jobs can detect drifting diagnostics JSON contracts and missing or
523
541
  drifting diagnostics sidecar manifests without downloading the per-platform
524
542
  diagnostics artifacts first.
525
543
 
526
- ## macOS Credential Island
527
-
528
- Alpha consumers can ask the build controller to seal one exact macOS app and
529
- hand it to a separate protected signing job. The built-in mode is suitable
530
- when the protected environment belongs to the repository that defines the
531
- reusable workflow:
544
+ ## Artifact Signing Authority
532
545
 
533
- ```yaml
534
- with:
535
- credential-island-macos-app-path: product/dist/desktop/mac-arm64/Kungfu Episodes.app
536
- credential-island-environment: alpha-macos-signing
537
- credential-island-macos-platform-id: macos-arm64
538
- ```
546
+ Artifact signing is a Buildchain capability, not a macOS application workflow.
547
+ Consumers declare desired signature state next to their artifact facts; they do
548
+ not configure certificates, Team IDs, notary credentials, protected
549
+ environments, authority roles, or signing jobs:
539
550
 
540
- For a cross-repository reusable workflow, keep the credentials in the consumer
541
- repository and select caller-owned mode:
542
-
543
- ```yaml
544
- with:
545
- credential-island-macos-app-path: product/dist/desktop/mac-arm64/Kungfu Episodes.app
546
- credential-island-caller-owned: true
547
- credential-island-macos-platform-id: macos-arm64
551
+ ```toml
552
+ [[signing.artifacts]]
553
+ id = "native-engine"
554
+ path = "dist/kungfu-engine"
555
+ profile = "auto"
556
+ kind = "mach-o"
557
+ platforms = ["macos-arm64", "macos-x64"]
548
558
  ```
549
559
 
550
- The caller then runs its own no-checkout macOS job after the reusable build,
551
- binds the consumer-owned protected environment on that job, and downloads the
552
- source-bound input plus immutable action runtime from the same workflow run.
553
- This is required for cross-repository callers because environment credentials
554
- must never be widened into repository secrets merely to cross a reusable
555
- workflow boundary.
556
-
557
- The ordinary matrix uploads
558
- `credential-island-input-<platform>-<source-sha>`, containing a `ditto`
559
- archive and a manifest bound to the caller repository, source commit, source
560
- tree, bundle identity, version, size, and digest. The pinned reusable workflow
561
- either starts its built-in GitHub-hosted macOS job or leaves that job to the
562
- caller. In both modes the credential-bearing job has no source checkout or
563
- package-manager step: it downloads the immutable Buildchain action runtime plus
564
- the sealed app and never invokes consumer code.
565
-
566
- The protected environment supplies these non-secret variables:
567
-
568
- - `BUILDCHAIN_MACOS_EXPECTED_BUNDLE_ID`
569
- - `BUILDCHAIN_MACOS_EXPECTED_TEAM_ID`
570
- - `BUILDCHAIN_MACOS_CERTIFICATE_SHA1`
571
-
572
- and these secrets:
573
-
574
- - `BUILDCHAIN_MACOS_CERTIFICATE_P12_BASE64`
575
- - `BUILDCHAIN_MACOS_CERTIFICATE_PASSWORD`
576
- - `BUILDCHAIN_MACOS_NOTARY_API_KEY_P8_BASE64`
577
- - `BUILDCHAIN_MACOS_NOTARY_API_KEY_ID`
578
- - `BUILDCHAIN_MACOS_NOTARY_API_ISSUER`
579
-
580
- The signed DMG, signed app ZIP, credential evidence, and their source-bound
581
- platform manifest form one additional release-candidate platform. Reviewers can
582
- therefore verify the exact caller source and immutable Buildchain runtime that
583
- entered the credential island before admitting or publishing the signed
584
- artifacts.
560
+ Every native and container build lane reads this declaration after the build
561
+ lifecycle and before verification. Buildchain binds the exact artifact bytes or directory tree to
562
+ the caller repository, source commit, source tree, immutable runtime, platform,
563
+ and requested signature semantics, then publishes a deterministic
564
+ `<artifact>-signing-request-<platform>-<source-sha>` request. No consumer
565
+ workflow step is required.
566
+
567
+ `profile = "auto"` resolves signable Apple artifacts such as Mach-O files,
568
+ `.dylib`, `.framework`, `.app`, `.xpc`, `.plugin`, `.pkg`, and `.dmg` to the
569
+ native `apple-developer-id` provider. Windows `pe` and `binary` artifacts
570
+ resolve to timestamped native `windows-authenticode`; Windows PE never falls
571
+ back to a detached signature. Linux and other non-native binary files,
572
+ archives, blobs, and directories resolve to `detached-signature-v1`. Buildchain records that as a
573
+ detached cryptographic signature and never misrepresents it as an operating
574
+ system code signature. Explicit incompatible provider/kind/platform
575
+ combinations fail closed.
576
+
577
+ The request schema rejects credential and authority-infrastructure fields. The
578
+ Buildchain-owned signing authority is responsible for credential selection,
579
+ native signing, notarization where applicable, immutable result delivery, and a
580
+ receipt bound to the request digest, runtime SHA, output digest, and signature
581
+ evidence. Consumer repositories neither receive nor duplicate credential-island
582
+ material. The reusable workflow dispatches the sealed request to the
583
+ Buildchain repository, waits for its protected authority workflow, verifies the
584
+ immutable result, replaces only the declared artifact with the returned final
585
+ bytes, and then runs the consumer's normal verification. Platform manifests,
586
+ KFD evidence, checksums, and Release Passport inputs therefore observe the
587
+ final signed artifact rather than the pre-signing build output.
588
+
589
+ The durable v3 authority runtime is
590
+ `authority/v3/v3.0/artifact-signing`. It is channel-neutral: alpha and stable
591
+ release work use the same protected `buildchain-artifact-signing` environment
592
+ and provider identities. The authority ref is protected independently from
593
+ release channels and can advance only through reviewed, checked changes; the
594
+ temporary `train/v3/v3.0/artifact-signing-authority` ref is retained only as a
595
+ bounded migration rollback.
596
+
597
+ The older `credential-island-macos-*` reusable-workflow inputs remain a
598
+ compatibility surface while existing callers migrate. They are not the target
599
+ consumer contract and must not be used to design new integrations.
585
600
 
586
601
  ## Artifact Transfer Relay
587
602
 
@@ -64,6 +64,27 @@ item that still needs to be merged; it is only a temporary fast-use,
64
64
  diagnostic, and rollback channel for initiating repositories. Retained trains
65
65
  are cleaned up by a separate periodic Buildchain cleanup task.
66
66
 
67
+ ## Formal artifact-signing authority ref
68
+
69
+ Artifact signing uses a durable, channel-neutral authority ref after its
70
+ runtime has passed downstream validation:
71
+
72
+ ```text
73
+ authority/v3/v3.0/artifact-signing
74
+ ```
75
+
76
+ Unlike a train, this ref is a protected execution boundary. Alpha and stable
77
+ release intent use the same authority ref and the same
78
+ `buildchain-artifact-signing` environment; channel promotion never selects a
79
+ different certificate environment. Updates to the authority ref require a
80
+ reviewed pull request, the normal `check` and `verify` status contexts, and a
81
+ fast-forward-safe protected branch policy. Deletion and non-fast-forward
82
+ updates are forbidden.
83
+
84
+ The temporary `train/v3/v3.0/artifact-signing-authority` ref remains a bounded
85
+ rollback and diagnostic pointer during migration. It is not the production
86
+ identity and must not regain credential ownership.
87
+
67
88
  ## Consumer workflow requirement
68
89
 
69
90
  Consumers keep their reusable workflow pinned to the stable shell:
@@ -61,6 +61,7 @@ repository changes are patch".
61
61
 
62
62
  | Date | Action | Line | Faces | Class | Rationale | PR |
63
63
  | --- | --- | --- | --- | --- | --- | --- |
64
+ | 2026-07-28 | extend-minor | `v3.0` | auditable-demo-workflow, auditable-demo-media-profile, auditable-demo-media-receipt | additive | Add opt-in archive, web-delivery, and site-hero profiles; independently bind codec, container, audio, layout, byte-budget, role, and fast-start facts into a v2 media receipt while preserving the existing archive default. | |
64
65
  | 2026-07-26 | extend-minor | `v3.0` | auditable-demo-workflow, auditable-demo-evidence | additive | Forward-port the consumer-neutral reusable Gate that binds exact same-run GitHub Artifacts to checked-in adapters and immutable renderer evidence, with optional media rendering only from the exact passing Gate bundle. | #1862 |
65
66
  | 2026-07-23 | extend-minor | `v2.14` | credential-island-macos-input, protected-signer-job, macos-signing-evidence, action-subpaths | additive | The reusable build surface can seal an exact source-bound macOS app and hand it to a protected caller environment, where an immutable Buildchain action signs, notarizes, staples, Gatekeeper-assesses, and returns an auditable additional release-candidate platform without exposing credentials to consumer lifecycle jobs. | |
66
67
  | 2026-07-20 | extend-minor | `v2.14` | anchored-derived-version-material, build-controller-evidence, release-passport, package-subpaths, release-propagation-controller | additive | Anchored/manual consumers can declare derived version witnesses that Buildchain regenerates and verifies before heavy builds, binds to exact alpha/release trees and passports, and admits during protected promotion; propagation receipts now model their existing optional consumer stages. | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "3.0.2-alpha.7",
3
+ "version": "3.0.2-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",
@@ -18,6 +18,9 @@
18
18
  "./artifact-passport": "./packages/core/artifact-passport.js",
19
19
  "./artifact-verification-envelope": "./packages/core/artifact-verification-envelope.js",
20
20
  "./github-artifact-attestation": "./packages/core/github-artifact-attestation.js",
21
+ "./artifact-signing": "./packages/core/artifact-signing.js",
22
+ "./artifact-signing-result": "./packages/core/artifact-signing-result.js",
23
+ "./detached-artifact-signature": "./packages/core/detached-artifact-signature.js",
21
24
  "./anchored-version-material": "./packages/core/anchored-version-material.js",
22
25
  "./buildchain-contract": "./packages/core/buildchain-contract.js",
23
26
  "./candidate-timeline": "./packages/core/candidate-timeline.js",
@@ -82,6 +85,7 @@
82
85
  "AGENTS.md",
83
86
  "bin/",
84
87
  "CONTRIBUTING.md",
88
+ "contracts/",
85
89
  "LICENSE-POLICY.md",
86
90
  "scripts/*.mjs",
87
91
  "packages/core/",
@@ -0,0 +1,228 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import {
6
+ ARTIFACT_SIGNING_AUTHORITY_CONTRACT,
7
+ artifactSigningDigest,
8
+ validateArtifactSigningReceipt,
9
+ validateArtifactSigningRequest,
10
+ } from "./artifact-signing.js";
11
+
12
+ export const ARTIFACT_SIGNING_RESULT_CONTRACT =
13
+ "kungfu-buildchain-artifact-signing-result/v1";
14
+
15
+ const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/u;
16
+
17
+ function required(value, label) {
18
+ const normalized = String(value || "").trim();
19
+ if (!normalized) throw new Error(`${label} must be a non-empty string`);
20
+ return normalized;
21
+ }
22
+
23
+ function digest(value, label) {
24
+ const normalized = required(value, label).toLowerCase();
25
+ if (!SHA256_PATTERN.test(normalized)) {
26
+ throw new Error(`${label} must be a sha256 digest`);
27
+ }
28
+ return normalized;
29
+ }
30
+
31
+ function stableJson(value) {
32
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
33
+ if (value && typeof value === "object") {
34
+ return `{${Object.keys(value)
35
+ .sort()
36
+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
37
+ .join(",")}}`;
38
+ }
39
+ return JSON.stringify(value);
40
+ }
41
+
42
+ function documentDigest(value) {
43
+ const { digest: _digest, ...basis } = value;
44
+ return artifactSigningDigest(basis);
45
+ }
46
+
47
+ function normalizeEvidence(evidence = []) {
48
+ if (!Array.isArray(evidence) || evidence.length === 0) {
49
+ throw new Error("artifact signing result requires verification evidence");
50
+ }
51
+ return evidence.map((entry, index) => ({
52
+ kind: required(entry?.kind, `evidence[${index}].kind`),
53
+ path: required(entry?.path, `evidence[${index}].path`),
54
+ digest: digest(entry?.digest, `evidence[${index}].digest`),
55
+ }));
56
+ }
57
+
58
+ export function artifactSigningEvidenceDigest(evidence = []) {
59
+ const normalized = normalizeEvidence(evidence);
60
+ return artifactSigningDigest(
61
+ normalized.map(({ kind, digest: entryDigest }) => ({
62
+ kind,
63
+ digest: entryDigest,
64
+ })),
65
+ );
66
+ }
67
+
68
+ export function createArtifactSigningResult({
69
+ request,
70
+ receipt,
71
+ receiptPath = "receipt.json",
72
+ payload = {},
73
+ evidence = [],
74
+ verification = {},
75
+ } = {}) {
76
+ const requestCheck = validateArtifactSigningRequest(request);
77
+ if (!requestCheck.ok) {
78
+ throw new Error(
79
+ `invalid artifact signing request: ${requestCheck.issues.join(", ")}`,
80
+ );
81
+ }
82
+ const receiptCheck = validateArtifactSigningReceipt(receipt, { request });
83
+ if (!receiptCheck.ok || receipt?.status !== "passed") {
84
+ throw new Error(
85
+ `invalid passed artifact signing receipt: ${receiptCheck.issues.join(", ")}`,
86
+ );
87
+ }
88
+ const normalizedEvidence = normalizeEvidence(evidence);
89
+ const evidenceDigest = artifactSigningEvidenceDigest(normalizedEvidence);
90
+ const payloadDigest = digest(payload.digest, "payload digest");
91
+ const payloadBytes = Number(payload.bytes);
92
+ if (!Number.isSafeInteger(payloadBytes) || payloadBytes < 0) {
93
+ throw new Error("payload bytes must be a non-negative safe integer");
94
+ }
95
+ if (receipt.result?.artifactDigest !== payloadDigest) {
96
+ throw new Error("receipt artifact digest does not bind the result payload");
97
+ }
98
+ if (receipt.result?.evidenceDigest !== evidenceDigest) {
99
+ throw new Error("receipt evidence digest does not bind the result evidence");
100
+ }
101
+ const provider = required(verification.provider, "verification provider");
102
+ if (provider !== request.signature.provider) {
103
+ throw new Error("verification provider does not match the requested provider");
104
+ }
105
+ if (verification.status !== "passed") {
106
+ throw new Error("artifact signing result verification must pass");
107
+ }
108
+ if (!Array.isArray(verification.checks) || verification.checks.length === 0) {
109
+ throw new Error("artifact signing result requires provider verification checks");
110
+ }
111
+ const result = {
112
+ schemaVersion: 1,
113
+ contract: ARTIFACT_SIGNING_RESULT_CONTRACT,
114
+ requestDigest: request.digest,
115
+ authority: {
116
+ contract: ARTIFACT_SIGNING_AUTHORITY_CONTRACT,
117
+ id: receipt.authority.id,
118
+ runtimeSha: receipt.authority.runtimeSha,
119
+ },
120
+ source: { ...request.source },
121
+ signature: { ...request.signature },
122
+ artifact: {
123
+ id: request.artifact.id,
124
+ inputDigest: request.artifact.digest,
125
+ path: required(payload.path, "payload path"),
126
+ bytes: payloadBytes,
127
+ digest: payloadDigest,
128
+ },
129
+ evidence: normalizedEvidence,
130
+ evidenceDigest,
131
+ verification: {
132
+ status: "passed",
133
+ provider,
134
+ checks: verification.checks.map((check, index) =>
135
+ required(check, `verification.checks[${index}]`),
136
+ ),
137
+ },
138
+ receipt: {
139
+ path: required(receiptPath, "receipt path"),
140
+ digest: digest(receipt.digest, "receipt digest"),
141
+ },
142
+ };
143
+ result.digest = documentDigest(result);
144
+ return result;
145
+ }
146
+
147
+ export function validateArtifactSigningResult(result, { request, receipt } = {}) {
148
+ const issues = [];
149
+ try {
150
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
151
+ throw new Error("artifact signing result must be an object");
152
+ }
153
+ if (result.contract !== ARTIFACT_SIGNING_RESULT_CONTRACT) {
154
+ issues.push("artifact signing result contract mismatch");
155
+ }
156
+ const rebuilt = createArtifactSigningResult({
157
+ request,
158
+ receipt,
159
+ receiptPath: result.receipt?.path,
160
+ payload: result.artifact,
161
+ evidence: result.evidence,
162
+ verification: result.verification,
163
+ });
164
+ if (result.digest !== rebuilt.digest) {
165
+ issues.push("artifact signing result digest mismatch");
166
+ }
167
+ if (stableJson(result) !== stableJson(rebuilt)) {
168
+ issues.push("artifact signing result contains unsupported or stale fields");
169
+ }
170
+ } catch (error) {
171
+ issues.push(String(error?.message || error));
172
+ }
173
+ return { ok: issues.length === 0, issues };
174
+ }
175
+
176
+ function resolveFile(root, relative, label) {
177
+ const resolvedRoot = path.resolve(root);
178
+ const resolved = path.resolve(resolvedRoot, required(relative, label));
179
+ const rel = path.relative(resolvedRoot, resolved);
180
+ if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
181
+ throw new Error(`${label} must resolve below the result root`);
182
+ }
183
+ return resolved;
184
+ }
185
+
186
+ function sha256File(filePath) {
187
+ return `sha256:${crypto
188
+ .createHash("sha256")
189
+ .update(fs.readFileSync(filePath))
190
+ .digest("hex")}`;
191
+ }
192
+
193
+ export function verifyArtifactSigningResultFiles({
194
+ root,
195
+ request,
196
+ receipt,
197
+ result,
198
+ } = {}) {
199
+ const issues = [];
200
+ const resultCheck = validateArtifactSigningResult(result, { request, receipt });
201
+ issues.push(...resultCheck.issues);
202
+ try {
203
+ const payloadPath = resolveFile(root, result.artifact.path, "payload path");
204
+ const stat = fs.statSync(payloadPath);
205
+ if (!stat.isFile()) issues.push("result payload is not a regular file");
206
+ if (stat.size !== result.artifact.bytes) issues.push("result payload byte count mismatch");
207
+ if (sha256File(payloadPath) !== result.artifact.digest) {
208
+ issues.push("result payload digest mismatch");
209
+ }
210
+ const receiptPath = resolveFile(root, result.receipt.path, "receipt path");
211
+ const receiptFromFile = JSON.parse(fs.readFileSync(receiptPath, "utf8"));
212
+ if (
213
+ receiptFromFile.digest !== result.receipt.digest ||
214
+ stableJson(receiptFromFile) !== stableJson(receipt)
215
+ ) {
216
+ issues.push("result receipt file does not match the bound receipt");
217
+ }
218
+ for (const entry of result.evidence || []) {
219
+ const evidencePath = resolveFile(root, entry.path, "evidence path");
220
+ if (sha256File(evidencePath) !== entry.digest) {
221
+ issues.push(`result evidence digest mismatch: ${entry.kind}`);
222
+ }
223
+ }
224
+ } catch (error) {
225
+ issues.push(String(error?.message || error));
226
+ }
227
+ return { ok: issues.length === 0, issues };
228
+ }