@kungfu-tech/buildchain 3.0.5 → 3.0.6-alpha.0

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 (83) hide show
  1. package/README.md +31 -0
  2. package/actions/macos-credential-island/README.md +8 -0
  3. package/bin/buildchain.mjs +1 -1
  4. package/contracts/auditable-demo-media-profiles-v1.json +61 -0
  5. package/contracts/auditable-demo-scenario-v1.schema.json +164 -0
  6. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +3 -3
  7. package/contracts/evidence/auditable-demo-web-delivery-v1.json +3 -3
  8. package/dist/site/buildchain-contract.json +107 -26
  9. package/dist/site/buildchain-site.json +121 -49
  10. package/dist/site/capability-registry.json +3 -3
  11. package/dist/site/cli-registry.json +9 -3
  12. package/dist/site/controller-registry.json +27 -3
  13. package/dist/site/kfd-claims.json +101 -15
  14. package/dist/site/kfd-upstream-aggregate.json +1 -1
  15. package/dist/site/manual-registry.json +10 -9
  16. package/dist/site/node-api-registry.json +540 -31
  17. package/dist/site/page-registry.json +105 -29
  18. package/dist/site/public-surface-audit.json +184 -11
  19. package/dist/site/publication-authority-registry.json +62 -1
  20. package/dist/site/publication-registry.json +4 -4
  21. package/dist/site/site-manifest.json +13 -13
  22. package/dist/site/workflow-registry.json +118 -4
  23. package/docs/MAP.md +4 -1
  24. package/docs/auditable-demo.md +90 -7
  25. package/docs/aws-us-elastic-runner-burst-plane.md +151 -16
  26. package/docs/cli-reference.md +35 -2
  27. package/docs/dev-alpha-candidate-patrol.md +14 -5
  28. package/docs/dev-qualification-patrol.md +108 -0
  29. package/docs/node-api-reference.md +123 -72
  30. package/docs/publish-transaction.md +7 -5
  31. package/docs/release-flow.md +4 -0
  32. package/docs/release-governance.md +39 -0
  33. package/docs/release-propagation.md +93 -0
  34. package/docs/reusable-build-surface.md +53 -12
  35. package/docs/stable-candidate-patrol.md +5 -2
  36. package/package.json +1 -1
  37. package/packages/core/buildchain-contract.js +36 -1
  38. package/packages/core/buildchain-publication-authority.js +3 -0
  39. package/packages/core/public-surface-audit.js +4 -35
  40. package/packages/core/release-propagation-agent-entry.js +185 -0
  41. package/packages/core/release-propagation-pickup.js +387 -0
  42. package/packages/core/release-propagation-push.js +293 -0
  43. package/packages/core/release-propagation-release.js +61 -18
  44. package/packages/core/release-propagation-stage-evidence.js +36 -0
  45. package/packages/core/release-propagation-work.js +8 -4
  46. package/packages/core/release-propagation.js +27 -2
  47. package/packages/core/workflow-call-contract.js +308 -0
  48. package/packages/core/workflow-yaml-contract.js +272 -0
  49. package/scripts/artifact-signing-controller-core.mjs +525 -0
  50. package/scripts/artifact-signing-controller.mjs +302 -0
  51. package/scripts/artifact-signing-delegation.mjs +51 -3
  52. package/scripts/auditable-demo-platform.mjs +564 -0
  53. package/scripts/auditable-demo-renditions.mjs +7 -2
  54. package/scripts/auditable-demo-transport-smoke.mjs +176 -0
  55. package/scripts/auditable-demo.mjs +27 -21
  56. package/scripts/aws-macos-jit-controller-core.mjs +389 -0
  57. package/scripts/aws-macos-jit-controller-runtime.mjs +82 -0
  58. package/scripts/aws-macos-jit-controller.mjs +558 -0
  59. package/scripts/aws-macos-jit-job-controller.mjs +401 -0
  60. package/scripts/aws-windows-jit-campaign-core.mjs +296 -0
  61. package/scripts/aws-windows-jit-campaign.mjs +202 -0
  62. package/scripts/aws-windows-jit-controller-core.mjs +20 -2
  63. package/scripts/aws-windows-jit-controller.mjs +184 -109
  64. package/scripts/aws-windows-jit-core.mjs +21 -3
  65. package/scripts/aws-windows-jit.mjs +2 -0
  66. package/scripts/build-standalone-binary.mjs +6 -0
  67. package/scripts/buildchain-cli-help.mjs +2 -1
  68. package/scripts/capture-package-release-propagation.mjs +24 -1
  69. package/scripts/check-inventory.mjs +6 -0
  70. package/scripts/compiler-cache-evidence.mjs +90 -7
  71. package/scripts/dev-alpha-candidate-patrol.mjs +80 -5
  72. package/scripts/dev-pr-auto-merge.mjs +4 -4
  73. package/scripts/dev-qualification-patrol.mjs +594 -0
  74. package/scripts/dispatch-artifact-signing-authority.mjs +312 -61
  75. package/scripts/finalize-native-artifact-signing-result.mjs +37 -15
  76. package/scripts/generate-site-bundle.mjs +5 -0
  77. package/scripts/inspect-artifact-signing-requests.mjs +12 -0
  78. package/scripts/locked-source-checkout.mjs +17 -3
  79. package/scripts/release-propagation.mjs +150 -2
  80. package/scripts/run-lifecycle-core.mjs +25 -0
  81. package/scripts/site-capability-metadata.mjs +1 -1
  82. package/scripts/stable-candidate-patrol.mjs +30 -4
  83. package/scripts/workflow-call-contract.mjs +133 -0
@@ -0,0 +1,525 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const REQUEST_CONTRACT =
7
+ "kungfu-buildchain-artifact-signing-control-request/v1";
8
+ export const RECEIPT_CONTRACT =
9
+ "kungfu-buildchain-artifact-signing-controller-receipt/v1";
10
+
11
+ export function required(value, label) {
12
+ const normalized = String(value || "").trim();
13
+ if (!normalized) throw new Error(`${label} is required`);
14
+ if (/[\r\n\0]/u.test(normalized)) {
15
+ throw new Error(`${label} contains control characters`);
16
+ }
17
+ return normalized;
18
+ }
19
+
20
+ function optional(value, label) {
21
+ const normalized = String(value || "").trim();
22
+ if (/[\r\n\0]/u.test(normalized)) {
23
+ throw new Error(`${label} contains control characters`);
24
+ }
25
+ return normalized;
26
+ }
27
+
28
+ function positiveInteger(value, label, { allowZero = false } = {}) {
29
+ const normalized = Number(value);
30
+ if (!Number.isSafeInteger(normalized) || normalized < (allowZero ? 0 : 1)) {
31
+ throw new Error(
32
+ `${label} must be ${allowZero ? "a non-negative" : "a positive"} integer`,
33
+ );
34
+ }
35
+ return normalized;
36
+ }
37
+
38
+ function exactSha(value, label) {
39
+ const normalized = required(value, label);
40
+ if (!/^[0-9a-f]{40}$/u.test(normalized)) {
41
+ throw new Error(`${label} must be an exact SHA`);
42
+ }
43
+ return normalized;
44
+ }
45
+
46
+ function sha256Root(value, label) {
47
+ const normalized = required(value, label);
48
+ if (!/^sha256:[0-9a-f]{64}$/u.test(normalized)) {
49
+ throw new Error(`${label} must be a canonical sha256 root`);
50
+ }
51
+ return normalized;
52
+ }
53
+
54
+ function repository(value, label) {
55
+ const normalized = required(value, label);
56
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized)) {
57
+ throw new Error(`${label} must be owner/repository`);
58
+ }
59
+ return normalized;
60
+ }
61
+
62
+ function safeRelativePath(value, label) {
63
+ const normalized = required(value, label).replaceAll("\\", "/");
64
+ const resolved = path.posix.normalize(normalized);
65
+ if (
66
+ resolved === ".." ||
67
+ resolved.startsWith("../") ||
68
+ path.posix.isAbsolute(resolved)
69
+ ) {
70
+ throw new Error(`${label} must be a safe relative path`);
71
+ }
72
+ if (!/^[A-Za-z0-9._ /-]+$/u.test(resolved)) {
73
+ throw new Error(`${label} contains unsafe shell characters`);
74
+ }
75
+ return resolved;
76
+ }
77
+
78
+ function timestamp(value, label) {
79
+ const normalized = required(value, label);
80
+ const parsed = new Date(normalized);
81
+ if (
82
+ !Number.isFinite(parsed.getTime()) ||
83
+ parsed.toISOString() !== normalized
84
+ ) {
85
+ throw new Error(`${label} must be a canonical ISO timestamp`);
86
+ }
87
+ return normalized;
88
+ }
89
+
90
+ function canonical(value) {
91
+ if (Array.isArray(value)) return value.map(canonical);
92
+ if (value && typeof value === "object") {
93
+ return Object.fromEntries(
94
+ Object.keys(value)
95
+ .sort()
96
+ .map((key) => [key, canonical(value[key])]),
97
+ );
98
+ }
99
+ return value;
100
+ }
101
+
102
+ export function digestDocument(value) {
103
+ const { digest: _digest, ...body } = value;
104
+ return `sha256:${crypto
105
+ .createHash("sha256")
106
+ .update(JSON.stringify(canonical(body)))
107
+ .digest("hex")}`;
108
+ }
109
+
110
+ export function artifactSigningRequestRoot(index) {
111
+ if (!index || typeof index !== "object" || Array.isArray(index)) {
112
+ throw new Error("signing request index must be an object");
113
+ }
114
+ if (
115
+ index.schemaVersion !== 1 ||
116
+ index.contract !== "kungfu-buildchain-artifact-signing-request-index/v1" ||
117
+ !Array.isArray(index.requests)
118
+ ) {
119
+ throw new Error("signing request index contract mismatch");
120
+ }
121
+ for (const [position, entry] of index.requests.entries()) {
122
+ required(entry?.id, `request index entry ${position} id`);
123
+ sha256Root(entry?.digest, `request index entry ${position} digest`);
124
+ safeRelativePath(entry?.path, `request index entry ${position} path`);
125
+ }
126
+ return `sha256:${crypto
127
+ .createHash("sha256")
128
+ .update(JSON.stringify(canonical(index)))
129
+ .digest("hex")}`;
130
+ }
131
+
132
+ export function artifactSigningCorrelation({
133
+ sourceRunId,
134
+ sourceRunAttempt,
135
+ runtimeSha,
136
+ platformId,
137
+ requestRoot,
138
+ }) {
139
+ return [
140
+ required(sourceRunId, "source run ID"),
141
+ positiveInteger(sourceRunAttempt, "source run attempt"),
142
+ exactSha(runtimeSha, "runtime SHA").slice(0, 12),
143
+ required(platformId, "platform ID").replace(/[^A-Za-z0-9._-]+/gu, "-"),
144
+ sha256Root(requestRoot, "request root").slice("sha256:".length, 19),
145
+ ].join("-");
146
+ }
147
+
148
+ export function validateArtifactSigningControlRequest(value) {
149
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
150
+ throw new Error("artifact signing control request must be an object");
151
+ }
152
+ if (value.schemaVersion !== 1 || value.contract !== REQUEST_CONTRACT) {
153
+ throw new Error("artifact signing control request contract mismatch");
154
+ }
155
+ const requestCount = positiveInteger(value.request?.count, "request.count", {
156
+ allowZero: true,
157
+ });
158
+ const request = {
159
+ schemaVersion: 1,
160
+ contract: REQUEST_CONTRACT,
161
+ source: {
162
+ repository: repository(value.source?.repository, "source.repository"),
163
+ runId: required(value.source?.runId, "source.runId"),
164
+ runAttempt: positiveInteger(
165
+ value.source?.runAttempt,
166
+ "source.runAttempt",
167
+ ),
168
+ sha: exactSha(value.source?.sha, "source.sha"),
169
+ treeSha: exactSha(value.source?.treeSha, "source.treeSha"),
170
+ },
171
+ runtime: {
172
+ repository: repository(value.runtime?.repository, "runtime.repository"),
173
+ ref: required(value.runtime?.ref, "runtime.ref"),
174
+ sha: exactSha(value.runtime?.sha, "runtime.sha"),
175
+ },
176
+ platform: {
177
+ id: required(value.platform?.id, "platform.id"),
178
+ name: required(value.platform?.name, "platform.name"),
179
+ },
180
+ request: {
181
+ count: requestCount,
182
+ artifact:
183
+ requestCount > 0
184
+ ? required(value.request?.artifact, "request.artifact")
185
+ : optional(value.request?.artifact, "request.artifact"),
186
+ root: sha256Root(value.request?.root, "request.root"),
187
+ },
188
+ authority: {
189
+ repository: repository(
190
+ value.authority?.repository,
191
+ "authority.repository",
192
+ ),
193
+ resultArtifact:
194
+ requestCount > 0
195
+ ? required(
196
+ value.authority?.resultArtifact,
197
+ "authority.resultArtifact",
198
+ )
199
+ : optional(
200
+ value.authority?.resultArtifact,
201
+ "authority.resultArtifact",
202
+ ),
203
+ correlationId: required(
204
+ value.authority?.correlationId,
205
+ "authority.correlationId",
206
+ ),
207
+ },
208
+ artifact: {
209
+ name: required(value.artifact?.name, "artifact.name"),
210
+ manifestArtifact: required(
211
+ value.artifact?.manifestArtifact,
212
+ "artifact.manifestArtifact",
213
+ ),
214
+ diagnosticsArtifact: required(
215
+ value.artifact?.diagnosticsArtifact,
216
+ "artifact.diagnosticsArtifact",
217
+ ),
218
+ },
219
+ workingDirectory: safeRelativePath(
220
+ value.workingDirectory || ".",
221
+ "workingDirectory",
222
+ ),
223
+ sealedAt: timestamp(value.sealedAt, "sealedAt"),
224
+ digest: sha256Root(value.digest, "digest"),
225
+ };
226
+ const expectedCorrelation = artifactSigningCorrelation({
227
+ sourceRunId: request.source.runId,
228
+ sourceRunAttempt: request.source.runAttempt,
229
+ runtimeSha: request.runtime.sha,
230
+ platformId: request.platform.id,
231
+ requestRoot: request.request.root,
232
+ });
233
+ if (request.authority.correlationId !== expectedCorrelation) {
234
+ throw new Error("artifact signing control request correlation mismatch");
235
+ }
236
+ if (requestCount === 0 && request.authority.resultArtifact) {
237
+ throw new Error(
238
+ "no-signing control request must not name a result artifact",
239
+ );
240
+ }
241
+ if (request.digest !== digestDocument(request)) {
242
+ throw new Error("artifact signing control request digest mismatch");
243
+ }
244
+ return request;
245
+ }
246
+
247
+ export function createArtifactSigningControlRequest({
248
+ sourceRepository = process.env.GITHUB_REPOSITORY,
249
+ sourceRunId = process.env.GITHUB_RUN_ID,
250
+ sourceRunAttempt = process.env.GITHUB_RUN_ATTEMPT || "1",
251
+ sourceSha = process.env.BUILDCHAIN_SOURCE_SHA,
252
+ sourceTreeSha = process.env.BUILDCHAIN_SOURCE_TREE_SHA,
253
+ runtimeRepository = process.env.BUILDCHAIN_RUNTIME_REPOSITORY,
254
+ runtimeRef = process.env.BUILDCHAIN_RUNTIME_REF,
255
+ runtimeSha = process.env.BUILDCHAIN_RUNTIME_SHA,
256
+ platformId = process.env.BUILDCHAIN_PLATFORM_ID,
257
+ platformName = process.env.BUILDCHAIN_PLATFORM_NAME,
258
+ requestCount = process.env.BUILDCHAIN_SIGNING_REQUEST_COUNT || "0",
259
+ requestArtifact = process.env.BUILDCHAIN_SIGNING_REQUEST_ARTIFACT || "",
260
+ requestIndexPath = process.env.BUILDCHAIN_SIGNING_REQUEST_INDEX,
261
+ authorityRepository = process.env.BUILDCHAIN_AUTHORITY_REPOSITORY,
262
+ resultArtifact = process.env.BUILDCHAIN_SIGNING_RESULT_ARTIFACT || "",
263
+ artifactName = process.env.BUILDCHAIN_ARTIFACT_NAME,
264
+ manifestArtifact = process.env.BUILDCHAIN_MANIFEST_ARTIFACT_NAME,
265
+ diagnosticsArtifact = process.env.BUILDCHAIN_DIAGNOSTICS_ARTIFACT_NAME,
266
+ workingDirectory = process.env.BUILDCHAIN_SIGNING_CWD || ".",
267
+ sealedAt = new Date().toISOString(),
268
+ } = {}) {
269
+ const indexPath = path.resolve(
270
+ required(requestIndexPath, "signing request index path"),
271
+ );
272
+ const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
273
+ const root = artifactSigningRequestRoot(index);
274
+ const count = positiveInteger(requestCount, "request count", {
275
+ allowZero: true,
276
+ });
277
+ if (index.requests.length !== count) {
278
+ throw new Error("signing request count does not match the sealed index");
279
+ }
280
+ const correlationId = artifactSigningCorrelation({
281
+ sourceRunId,
282
+ sourceRunAttempt,
283
+ runtimeSha,
284
+ platformId,
285
+ requestRoot: root,
286
+ });
287
+ const request = {
288
+ schemaVersion: 1,
289
+ contract: REQUEST_CONTRACT,
290
+ source: {
291
+ repository: sourceRepository,
292
+ runId: sourceRunId,
293
+ runAttempt: Number(sourceRunAttempt),
294
+ sha: sourceSha,
295
+ treeSha: sourceTreeSha,
296
+ },
297
+ runtime: {
298
+ repository: runtimeRepository,
299
+ ref: runtimeRef,
300
+ sha: runtimeSha,
301
+ },
302
+ platform: { id: platformId, name: platformName },
303
+ request: {
304
+ count,
305
+ artifact: count > 0 ? requestArtifact : "",
306
+ root,
307
+ },
308
+ authority: {
309
+ repository: authorityRepository,
310
+ resultArtifact: count > 0 ? resultArtifact : "",
311
+ correlationId,
312
+ },
313
+ artifact: { name: artifactName, manifestArtifact, diagnosticsArtifact },
314
+ workingDirectory,
315
+ sealedAt,
316
+ };
317
+ request.digest = digestDocument(request);
318
+ return validateArtifactSigningControlRequest(request);
319
+ }
320
+
321
+ export function sealArtifactSigningControlRequest({
322
+ outputPath = process.env.BUILDCHAIN_SIGNING_CONTROL_REQUEST_PATH,
323
+ ...values
324
+ } = {}) {
325
+ const request = createArtifactSigningControlRequest(values);
326
+ const target = path.resolve(required(outputPath, "control request path"));
327
+ fs.mkdirSync(path.dirname(target), { recursive: true });
328
+ fs.writeFileSync(target, `${JSON.stringify(request, null, 2)}\n`);
329
+ return request;
330
+ }
331
+
332
+ export function readArtifactSigningControlRequest(
333
+ inputPath = process.env.BUILDCHAIN_SIGNING_CONTROL_REQUEST_PATH,
334
+ ) {
335
+ const target = path.resolve(required(inputPath, "control request path"));
336
+ return validateArtifactSigningControlRequest(
337
+ JSON.parse(fs.readFileSync(target, "utf8")),
338
+ );
339
+ }
340
+
341
+ export function assertArtifactSigningControlRequestContext(
342
+ request,
343
+ {
344
+ sourceRepository = process.env.BUILDCHAIN_EXPECTED_SOURCE_REPOSITORY || "",
345
+ sourceRunId = process.env.BUILDCHAIN_EXPECTED_SOURCE_RUN_ID || "",
346
+ sourceRunAttempt = process.env.BUILDCHAIN_EXPECTED_SOURCE_RUN_ATTEMPT || "",
347
+ sourceSha = process.env.BUILDCHAIN_EXPECTED_SOURCE_SHA || "",
348
+ sourceTreeSha = process.env.BUILDCHAIN_EXPECTED_SOURCE_TREE_SHA || "",
349
+ runtimeRepository = process.env.BUILDCHAIN_EXPECTED_RUNTIME_REPOSITORY ||
350
+ "",
351
+ runtimeSha = process.env.BUILDCHAIN_EXPECTED_RUNTIME_SHA || "",
352
+ platformId = process.env.BUILDCHAIN_EXPECTED_PLATFORM_ID || "",
353
+ } = {},
354
+ ) {
355
+ const value = validateArtifactSigningControlRequest(request);
356
+ const expectations = [
357
+ [sourceRepository, value.source.repository, "source repository"],
358
+ [sourceRunId, value.source.runId, "source run ID"],
359
+ [sourceRunAttempt, String(value.source.runAttempt), "source run attempt"],
360
+ [sourceSha, value.source.sha, "source SHA"],
361
+ [sourceTreeSha, value.source.treeSha, "source tree SHA"],
362
+ [runtimeRepository, value.runtime.repository, "runtime repository"],
363
+ [runtimeSha, value.runtime.sha, "runtime SHA"],
364
+ [platformId, value.platform.id, "platform ID"],
365
+ ];
366
+ for (const [expected, actual, label] of expectations) {
367
+ if (expected && String(expected) !== actual) {
368
+ throw new Error(`artifact signing control request ${label} mismatch`);
369
+ }
370
+ }
371
+ return value;
372
+ }
373
+
374
+ export function artifactSigningControlRequestOutputs(request) {
375
+ const value = validateArtifactSigningControlRequest(request);
376
+ return {
377
+ "request-count": String(value.request.count),
378
+ "request-artifact": value.request.artifact,
379
+ "request-root": value.request.root,
380
+ "result-artifact": value.authority.resultArtifact,
381
+ "correlation-id": value.authority.correlationId,
382
+ "authority-repository": value.authority.repository,
383
+ "authority-ref": value.runtime.ref,
384
+ "runtime-sha": value.runtime.sha,
385
+ };
386
+ }
387
+
388
+ export function normalizeControllerStatus(value) {
389
+ const normalized = required(value, "authority status");
390
+ if (
391
+ !["succeeded", "failed", "timed-out", "cancelled", "skipped"].includes(
392
+ normalized,
393
+ )
394
+ ) {
395
+ throw new Error(`unsupported authority status: ${normalized}`);
396
+ }
397
+ return normalized;
398
+ }
399
+
400
+ export function validateArtifactSigningControllerReceipt(value) {
401
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
402
+ throw new Error("artifact signing controller receipt must be an object");
403
+ }
404
+ if (value.schemaVersion !== 1 || value.contract !== RECEIPT_CONTRACT) {
405
+ throw new Error("artifact signing controller receipt contract mismatch");
406
+ }
407
+ const receipt = {
408
+ schemaVersion: 1,
409
+ contract: RECEIPT_CONTRACT,
410
+ requestDigest: sha256Root(value.requestDigest, "requestDigest"),
411
+ source: {
412
+ repository: repository(value.source?.repository, "source.repository"),
413
+ runId: required(value.source?.runId, "source.runId"),
414
+ runAttempt: positiveInteger(
415
+ value.source?.runAttempt,
416
+ "source.runAttempt",
417
+ ),
418
+ sha: exactSha(value.source?.sha, "source.sha"),
419
+ treeSha: exactSha(value.source?.treeSha, "source.treeSha"),
420
+ },
421
+ runtime: {
422
+ repository: repository(value.runtime?.repository, "runtime.repository"),
423
+ sha: exactSha(value.runtime?.sha, "runtime.sha"),
424
+ },
425
+ platform: {
426
+ id: required(value.platform?.id, "platform.id"),
427
+ name: required(value.platform?.name, "platform.name"),
428
+ },
429
+ request: {
430
+ count: positiveInteger(value.request?.count, "request.count", {
431
+ allowZero: true,
432
+ }),
433
+ artifact: optional(value.request?.artifact, "request.artifact"),
434
+ root: sha256Root(value.request?.root, "request.root"),
435
+ },
436
+ authority: {
437
+ repository: repository(
438
+ value.authority?.repository,
439
+ "authority.repository",
440
+ ),
441
+ runtimeSha: value.authority?.runtimeSha
442
+ ? exactSha(value.authority.runtimeSha, "authority.runtimeSha")
443
+ : "",
444
+ runId: optional(value.authority?.runId, "authority.runId"),
445
+ runUrl: optional(value.authority?.runUrl, "authority.runUrl"),
446
+ resultArtifact: optional(
447
+ value.authority?.resultArtifact,
448
+ "authority.resultArtifact",
449
+ ),
450
+ correlationId: required(
451
+ value.authority?.correlationId,
452
+ "authority.correlationId",
453
+ ),
454
+ conclusion: required(value.authority?.conclusion, "authority.conclusion"),
455
+ },
456
+ controller: {
457
+ repository: repository(
458
+ value.controller?.repository,
459
+ "controller.repository",
460
+ ),
461
+ runId: required(value.controller?.runId, "controller.runId"),
462
+ runAttempt: positiveInteger(
463
+ value.controller?.runAttempt,
464
+ "controller.runAttempt",
465
+ ),
466
+ job: required(value.controller?.job, "controller.job"),
467
+ runnerOs: required(value.controller?.runnerOs, "controller.runnerOs"),
468
+ startedAt: timestamp(value.controller?.startedAt, "controller.startedAt"),
469
+ completedAt: timestamp(
470
+ value.controller?.completedAt,
471
+ "controller.completedAt",
472
+ ),
473
+ status: normalizeControllerStatus(value.controller?.status),
474
+ },
475
+ qualifying: value.qualifying === true,
476
+ digest: sha256Root(value.digest, "digest"),
477
+ };
478
+ if (
479
+ new Date(receipt.controller.completedAt) <
480
+ new Date(receipt.controller.startedAt)
481
+ ) {
482
+ throw new Error("controller completion predates controller start");
483
+ }
484
+ if (receipt.controller.runnerOs.toLowerCase() === "macos") {
485
+ throw new Error("artifact signing controller must not run on macOS");
486
+ }
487
+ const expectedQualifying =
488
+ receipt.controller.status === "succeeded" ||
489
+ receipt.controller.status === "skipped";
490
+ if (receipt.qualifying !== expectedQualifying) {
491
+ throw new Error("controller receipt qualifying status mismatch");
492
+ }
493
+ if (
494
+ (receipt.request.count === 0) !==
495
+ (receipt.controller.status === "skipped")
496
+ ) {
497
+ throw new Error("controller receipt signing mode and status mismatch");
498
+ }
499
+ if (
500
+ receipt.controller.status === "succeeded" &&
501
+ receipt.authority.conclusion !== "success"
502
+ ) {
503
+ throw new Error("successful controller receipt lacks authority success");
504
+ }
505
+ if (receipt.request.count > 0 && receipt.qualifying) {
506
+ required(receipt.request.artifact, "request.artifact");
507
+ required(receipt.authority.runId, "authority.runId");
508
+ required(receipt.authority.runUrl, "authority.runUrl");
509
+ required(receipt.authority.resultArtifact, "authority.resultArtifact");
510
+ exactSha(receipt.authority.runtimeSha, "authority.runtimeSha");
511
+ }
512
+ if (
513
+ receipt.request.count === 0 &&
514
+ (receipt.authority.runId ||
515
+ receipt.authority.runUrl ||
516
+ receipt.authority.runtimeSha ||
517
+ receipt.authority.resultArtifact)
518
+ ) {
519
+ throw new Error("no-signing controller receipt has authority coordinates");
520
+ }
521
+ if (receipt.digest !== digestDocument(receipt)) {
522
+ throw new Error("artifact signing controller receipt digest mismatch");
523
+ }
524
+ return receipt;
525
+ }