@kungfu-tech/buildchain 2.12.2 → 2.12.3-alpha.2
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/actions/promote-buildchain-ref/README.md +20 -1
- package/dist/site/buildchain-contract.json +54 -8
- package/dist/site/buildchain-site.json +23 -18
- package/dist/site/kfd-claims.json +4 -3
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +4 -4
- package/dist/site/node-api-registry.json +2 -2
- package/dist/site/page-registry.json +15 -10
- package/dist/site/public-surface-audit.json +4 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +8 -8
- package/dist/site/workflow-registry.json +2 -1
- package/docs/publish-transaction.md +82 -1
- package/docs/release-governance.md +8 -0
- package/docs/release-passport.md +3 -1
- package/docs/reusable-build-surface.md +4 -1
- package/package.json +1 -1
- package/packages/core/buildchain-contract.js +34 -0
- package/packages/core/publish-transaction.js +287 -15
- package/packages/core/release-passport.js +60 -6
|
@@ -50,6 +50,97 @@ function optionalString(value) {
|
|
|
50
50
|
return value === undefined || value === null ? "" : String(value);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function hasOwn(value, key) {
|
|
54
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function optionalPositiveInteger(value, label) {
|
|
58
|
+
if (value === undefined || value === null || value === "") {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const normalized = Number(value);
|
|
62
|
+
if (!Number.isInteger(normalized) || normalized < 1) {
|
|
63
|
+
throw new Error(`${label} must be a positive integer`);
|
|
64
|
+
}
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeArtifactCoordinate(value, label, { partial = false } = {}) {
|
|
69
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
70
|
+
throw new Error(`${label} must be an object`);
|
|
71
|
+
}
|
|
72
|
+
const field = (key) => {
|
|
73
|
+
if (partial && !hasOwn(value, key)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return assertNonEmptyString(value[key], `${label}.${key}`);
|
|
77
|
+
};
|
|
78
|
+
const normalized = {};
|
|
79
|
+
for (const key of ["version", "ref", "source_sha", "material_sha"]) {
|
|
80
|
+
const item = field(key);
|
|
81
|
+
if (item !== undefined) {
|
|
82
|
+
normalized[key] = item;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (hasOwn(value, "target_ref")) {
|
|
86
|
+
normalized.target_ref = assertNonEmptyString(value.target_ref, `${label}.target_ref`);
|
|
87
|
+
}
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeArtifactVerification(value, label, { partial = false } = {}) {
|
|
92
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
93
|
+
throw new Error(`${label} must be an object`);
|
|
94
|
+
}
|
|
95
|
+
const normalized = {};
|
|
96
|
+
const stringField = (key) => {
|
|
97
|
+
if (hasOwn(value, key)) {
|
|
98
|
+
normalized[key] = assertNonEmptyString(value[key], `${label}.${key}`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
for (const key of ["ref", "digest", "platform", "parent_digest", "evidence"]) {
|
|
102
|
+
stringField(key);
|
|
103
|
+
}
|
|
104
|
+
if (hasOwn(value, "public_manifest")) {
|
|
105
|
+
normalized.public_manifest = value.public_manifest === true;
|
|
106
|
+
}
|
|
107
|
+
const contractMajor = optionalPositiveInteger(value.contract_major, `${label}.contract_major`);
|
|
108
|
+
if (contractMajor !== undefined) {
|
|
109
|
+
normalized.contract_major = contractMajor;
|
|
110
|
+
}
|
|
111
|
+
if (hasOwn(value, "smoke")) {
|
|
112
|
+
if (!value.smoke || typeof value.smoke !== "object" || Array.isArray(value.smoke)) {
|
|
113
|
+
throw new Error(`${label}.smoke must be an object`);
|
|
114
|
+
}
|
|
115
|
+
normalized.smoke = {};
|
|
116
|
+
for (const key of ["policy", "evidence"]) {
|
|
117
|
+
if (hasOwn(value.smoke, key)) {
|
|
118
|
+
normalized.smoke[key] = assertNonEmptyString(value.smoke[key], `${label}.smoke.${key}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (hasOwn(value.smoke, "passed")) {
|
|
122
|
+
normalized.smoke.passed = value.smoke.passed === true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!partial) {
|
|
126
|
+
for (const key of ["ref", "digest", "platform", "evidence"]) {
|
|
127
|
+
assertNonEmptyString(normalized[key], `${label}.${key}`);
|
|
128
|
+
}
|
|
129
|
+
if (normalized.public_manifest !== true) {
|
|
130
|
+
throw new Error(`${label}.public_manifest must be true`);
|
|
131
|
+
}
|
|
132
|
+
if (normalized.contract_major === undefined) {
|
|
133
|
+
throw new Error(`${label}.contract_major must be a positive integer`);
|
|
134
|
+
}
|
|
135
|
+
if (!normalized.smoke || normalized.smoke.passed !== true) {
|
|
136
|
+
throw new Error(`${label}.smoke.passed must be true`);
|
|
137
|
+
}
|
|
138
|
+
assertNonEmptyString(normalized.smoke.policy, `${label}.smoke.policy`);
|
|
139
|
+
assertNonEmptyString(normalized.smoke.evidence, `${label}.smoke.evidence`);
|
|
140
|
+
}
|
|
141
|
+
return normalized;
|
|
142
|
+
}
|
|
143
|
+
|
|
53
144
|
function posixPath(value) {
|
|
54
145
|
return String(value || "").split(path.sep).join("/");
|
|
55
146
|
}
|
|
@@ -215,7 +306,11 @@ function artifactMatchesRequirement(actual, required) {
|
|
|
215
306
|
);
|
|
216
307
|
}
|
|
217
308
|
|
|
218
|
-
export function normalizePublishArtifact(
|
|
309
|
+
export function normalizePublishArtifact(
|
|
310
|
+
artifact,
|
|
311
|
+
label = "artifact",
|
|
312
|
+
{ requireDigest = true, partialProvenance = !requireDigest } = {},
|
|
313
|
+
) {
|
|
219
314
|
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) {
|
|
220
315
|
throw new Error(`${label} must be an object`);
|
|
221
316
|
}
|
|
@@ -223,15 +318,100 @@ export function normalizePublishArtifact(artifact, label = "artifact") {
|
|
|
223
318
|
if (role && !["main", "platform"].includes(role)) {
|
|
224
319
|
throw new Error(`${label}.role must be one of main or platform`);
|
|
225
320
|
}
|
|
226
|
-
|
|
321
|
+
const action = optionalString(artifact.action);
|
|
322
|
+
if (action && !["built", "reused"].includes(action)) {
|
|
323
|
+
throw new Error(`${label}.action must be one of built or reused`);
|
|
324
|
+
}
|
|
325
|
+
const digest = requireDigest
|
|
326
|
+
? assertNonEmptyString(artifact.digest, `${label}.digest`)
|
|
327
|
+
: optionalString(artifact.digest);
|
|
328
|
+
const contractMajor = optionalPositiveInteger(artifact.contract_major, `${label}.contract_major`);
|
|
329
|
+
const ref = optionalString(artifact.ref);
|
|
330
|
+
const refTemplate = optionalString(artifact.ref_template);
|
|
331
|
+
if (/[{}]/.test(ref)) {
|
|
332
|
+
throw new Error(`${label}.ref must be exact and cannot contain template braces; use ref_template`);
|
|
333
|
+
}
|
|
334
|
+
if (ref && refTemplate) {
|
|
335
|
+
throw new Error(`${label} cannot declare both ref and ref_template`);
|
|
336
|
+
}
|
|
337
|
+
if (refTemplate && !/^[^{}]*\{version\}[^{}]*$/.test(refTemplate)) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
`${label}.ref_template must contain exactly one {version} placeholder and no other braces`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const normalized = {
|
|
227
343
|
group: optionalString(artifact.group),
|
|
228
344
|
kind: assertNonEmptyString(artifact.kind, `${label}.kind`),
|
|
229
345
|
name: assertNonEmptyString(artifact.name, `${label}.name`),
|
|
230
|
-
ref
|
|
231
|
-
digest
|
|
346
|
+
ref,
|
|
347
|
+
digest,
|
|
232
348
|
role,
|
|
233
349
|
required: artifact.required === undefined ? true : Boolean(artifact.required),
|
|
234
350
|
};
|
|
351
|
+
if (refTemplate) {
|
|
352
|
+
normalized.ref_template = refTemplate;
|
|
353
|
+
}
|
|
354
|
+
if (action) {
|
|
355
|
+
normalized.action = action;
|
|
356
|
+
}
|
|
357
|
+
for (const key of ["platform", "parent_digest"]) {
|
|
358
|
+
if (hasOwn(artifact, key)) {
|
|
359
|
+
normalized[key] = assertNonEmptyString(artifact[key], `${label}.${key}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (contractMajor !== undefined) {
|
|
363
|
+
normalized.contract_major = contractMajor;
|
|
364
|
+
}
|
|
365
|
+
for (const key of ["content", "release"]) {
|
|
366
|
+
if (hasOwn(artifact, key)) {
|
|
367
|
+
normalized[key] = normalizeArtifactCoordinate(
|
|
368
|
+
artifact[key],
|
|
369
|
+
`${label}.${key}`,
|
|
370
|
+
{ partial: partialProvenance },
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (hasOwn(artifact, "verification")) {
|
|
375
|
+
normalized.verification = normalizeArtifactVerification(
|
|
376
|
+
artifact.verification,
|
|
377
|
+
`${label}.verification`,
|
|
378
|
+
{ partial: partialProvenance },
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
if (requireDigest && action) {
|
|
382
|
+
for (const key of ["content", "release"]) {
|
|
383
|
+
if (!normalized[key]) {
|
|
384
|
+
throw new Error(`${label}.${key} is required when action is ${action}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (normalized.kind === "oci") {
|
|
388
|
+
if (!normalized.platform) {
|
|
389
|
+
throw new Error(`${label}.platform is required for OCI provenance`);
|
|
390
|
+
}
|
|
391
|
+
if (!normalized.contract_major) {
|
|
392
|
+
throw new Error(`${label}.contract_major is required for OCI provenance`);
|
|
393
|
+
}
|
|
394
|
+
if (!normalized.verification) {
|
|
395
|
+
throw new Error(`${label}.verification is required for OCI provenance`);
|
|
396
|
+
}
|
|
397
|
+
const verification = normalized.verification;
|
|
398
|
+
const comparisons = [
|
|
399
|
+
[verification.ref, normalized.ref, "ref"],
|
|
400
|
+
[verification.digest, normalized.digest, "digest"],
|
|
401
|
+
[verification.platform, normalized.platform, "platform"],
|
|
402
|
+
[verification.contract_major, normalized.contract_major, "contract_major"],
|
|
403
|
+
[verification.parent_digest || "", normalized.parent_digest || "", "parent_digest"],
|
|
404
|
+
];
|
|
405
|
+
for (const [actual, expected, field] of comparisons) {
|
|
406
|
+
if (actual !== expected) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`${label}.verification.${field} mismatch: expected ${expected || "<empty>"}, got ${actual || "<empty>"}`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return normalized;
|
|
235
415
|
}
|
|
236
416
|
|
|
237
417
|
export function parsePublishArtifactsJson(value, label = "publish artifacts") {
|
|
@@ -244,7 +424,7 @@ export function parsePublishArtifactsJson(value, label = "publish artifacts") {
|
|
|
244
424
|
throw new Error(`${label} must be a JSON array`);
|
|
245
425
|
}
|
|
246
426
|
return parsed.map((artifact, index) =>
|
|
247
|
-
normalizePublishArtifact(artifact, `${label}[${index}]
|
|
427
|
+
normalizePublishArtifact(artifact, `${label}[${index}]`, { requireDigest: false }),
|
|
248
428
|
);
|
|
249
429
|
}
|
|
250
430
|
|
|
@@ -260,6 +440,14 @@ export function normalizePublishEvidence(evidence) {
|
|
|
260
440
|
normalizePublishArtifact(artifact, `artifacts[${index}]`),
|
|
261
441
|
)
|
|
262
442
|
: [];
|
|
443
|
+
const identities = new Set();
|
|
444
|
+
for (const artifact of artifacts) {
|
|
445
|
+
const identity = artifactIdentity(artifact);
|
|
446
|
+
if (identities.has(identity)) {
|
|
447
|
+
throw new Error(`duplicate publish artifact: ${artifact.kind}:${artifact.name}:${artifact.ref}`);
|
|
448
|
+
}
|
|
449
|
+
identities.add(identity);
|
|
450
|
+
}
|
|
263
451
|
return {
|
|
264
452
|
schema: 1,
|
|
265
453
|
version: assertNonEmptyString(evidence.version, "evidence.version"),
|
|
@@ -282,25 +470,49 @@ export function readPublishEvidence(filePath) {
|
|
|
282
470
|
|
|
283
471
|
export function planArtifactPublish({ requiredArtifacts = [], existingArtifacts = [] } = {}) {
|
|
284
472
|
const required = requiredArtifacts.map((artifact, index) =>
|
|
285
|
-
normalizePublishArtifact(artifact, `requiredArtifacts[${index}]
|
|
473
|
+
normalizePublishArtifact(artifact, `requiredArtifacts[${index}]`, { requireDigest: false }),
|
|
286
474
|
);
|
|
287
475
|
const existing = existingArtifacts.map((artifact, index) =>
|
|
288
476
|
normalizePublishArtifact(artifact, `existingArtifacts[${index}]`),
|
|
289
477
|
);
|
|
290
|
-
const existingByIdentity = new Map(existing.map((artifact) => [artifactIdentity(artifact), artifact]));
|
|
291
478
|
const publish = [];
|
|
292
479
|
const accepted = [];
|
|
293
480
|
const conflicts = [];
|
|
294
481
|
for (const artifact of required) {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
482
|
+
const candidates = existing.filter((candidate) => (
|
|
483
|
+
candidate.kind === artifact.kind &&
|
|
484
|
+
candidate.name === artifact.name &&
|
|
485
|
+
(!artifact.group || candidate.group === artifact.group)
|
|
486
|
+
));
|
|
487
|
+
const current = candidates.find((candidate) => artifactMatchesRequirement(candidate, artifact));
|
|
298
488
|
if (!current) {
|
|
489
|
+
if (candidates.length > 0) {
|
|
490
|
+
conflicts.push({ expected: artifact, actual: candidates[0], fields: ["ref"] });
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
299
493
|
publish.push(artifact);
|
|
300
494
|
continue;
|
|
301
495
|
}
|
|
302
|
-
|
|
303
|
-
|
|
496
|
+
const fields = [];
|
|
497
|
+
if (artifact.digest && current.digest !== artifact.digest) {
|
|
498
|
+
fields.push("digest");
|
|
499
|
+
}
|
|
500
|
+
const compareSubset = (actual, expected, prefix = "") => {
|
|
501
|
+
for (const [key, value] of Object.entries(expected || {})) {
|
|
502
|
+
if (["group", "kind", "name", "ref", "digest", "role", "required"].includes(key) && !prefix) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const field = prefix ? `${prefix}.${key}` : key;
|
|
506
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
507
|
+
compareSubset(actual?.[key], value, field);
|
|
508
|
+
} else if (actual?.[key] !== value) {
|
|
509
|
+
fields.push(field);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
compareSubset(current, artifact);
|
|
514
|
+
if (fields.length > 0) {
|
|
515
|
+
conflicts.push({ expected: artifact, actual: current, fields });
|
|
304
516
|
continue;
|
|
305
517
|
}
|
|
306
518
|
accepted.push(current);
|
|
@@ -314,6 +526,45 @@ export function planArtifactPublish({ requiredArtifacts = [], existingArtifacts
|
|
|
314
526
|
};
|
|
315
527
|
}
|
|
316
528
|
|
|
529
|
+
export function resolvePublishArtifactRequirements(
|
|
530
|
+
requiredArtifacts,
|
|
531
|
+
{ version, targetRef, sourceSha, releaseMaterialSha } = {},
|
|
532
|
+
) {
|
|
533
|
+
return requiredArtifacts.map((artifact, index) => {
|
|
534
|
+
const normalized = normalizePublishArtifact(
|
|
535
|
+
artifact,
|
|
536
|
+
`requiredArtifacts[${index}]`,
|
|
537
|
+
{ requireDigest: false },
|
|
538
|
+
);
|
|
539
|
+
const exactVersion = assertNonEmptyString(version, "version");
|
|
540
|
+
if (normalized.ref_template) {
|
|
541
|
+
normalized.ref = normalized.ref_template.replace("{version}", exactVersion);
|
|
542
|
+
delete normalized.ref_template;
|
|
543
|
+
}
|
|
544
|
+
normalized.ref ||= exactVersion;
|
|
545
|
+
if (!normalized.action) {
|
|
546
|
+
return normalized;
|
|
547
|
+
}
|
|
548
|
+
const current = {
|
|
549
|
+
version: exactVersion,
|
|
550
|
+
ref: normalized.ref,
|
|
551
|
+
source_sha: assertNonEmptyString(sourceSha, "sourceSha"),
|
|
552
|
+
material_sha: assertNonEmptyString(releaseMaterialSha, "releaseMaterialSha"),
|
|
553
|
+
};
|
|
554
|
+
if (normalized.action === "built") {
|
|
555
|
+
normalized.content = { ...current, ...(normalized.content || {}) };
|
|
556
|
+
} else if (!normalized.content) {
|
|
557
|
+
throw new Error(`requiredArtifacts[${index}].content is required when action is reused`);
|
|
558
|
+
}
|
|
559
|
+
normalized.release = {
|
|
560
|
+
...current,
|
|
561
|
+
target_ref: assertNonEmptyString(targetRef, "targetRef"),
|
|
562
|
+
...(normalized.release || {}),
|
|
563
|
+
};
|
|
564
|
+
return normalized;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
317
568
|
export function validatePublishEvidence({
|
|
318
569
|
evidence,
|
|
319
570
|
version,
|
|
@@ -354,9 +605,30 @@ export function validatePublishEvidence({
|
|
|
354
605
|
);
|
|
355
606
|
}
|
|
356
607
|
for (const conflict of artifactPlan.conflicts) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
608
|
+
const fields = conflict.fields || ["digest"];
|
|
609
|
+
const category = fields.length === 1 && fields[0] === "digest"
|
|
610
|
+
? "artifact digest mismatch"
|
|
611
|
+
: "artifact coordinate or provenance mismatch";
|
|
612
|
+
errors.push(`${category}: ${conflict.expected.kind}:${conflict.expected.name}:${fields.join(",")}`);
|
|
613
|
+
}
|
|
614
|
+
for (const [index, artifact] of normalized.artifacts.entries()) {
|
|
615
|
+
if (!artifact.action) {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const checks = [
|
|
619
|
+
[artifact.release?.version, version, "release.version"],
|
|
620
|
+
[artifact.release?.ref, artifact.ref, "release.ref"],
|
|
621
|
+
[artifact.release?.target_ref, targetRef, "release.target_ref"],
|
|
622
|
+
[artifact.release?.source_sha, sourceSha, "release.source_sha"],
|
|
623
|
+
[artifact.release?.material_sha, releaseMaterialSha, "release.material_sha"],
|
|
624
|
+
];
|
|
625
|
+
for (const [actual, expected, field] of checks) {
|
|
626
|
+
if (actual !== expected) {
|
|
627
|
+
errors.push(
|
|
628
|
+
`artifact provenance mismatch: artifacts[${index}].${field}: expected ${expected || "<empty>"}, got ${actual || "<empty>"}`,
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
360
632
|
}
|
|
361
633
|
|
|
362
634
|
return {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
validateKfd3CollaborationInterfaceReleaseGateEvidence,
|
|
16
16
|
} from "./kfd-gate.js";
|
|
17
17
|
import { createSurfaceTimestampPolicy } from "./surface-manifest.js";
|
|
18
|
+
import { validatePublishEvidence as validateTransactionPublishEvidence } from "./publish-transaction.js";
|
|
18
19
|
|
|
19
20
|
export const RELEASE_PASSPORT_CONTRACT = "kungfu-buildchain-release-passport";
|
|
20
21
|
export const ARTIFACT_EVIDENCE_CONTRACT = "kungfu-buildchain-artifact-evidence";
|
|
@@ -521,7 +522,7 @@ function normalizePlatformArtifactManifest(meta, index = 0) {
|
|
|
521
522
|
|
|
522
523
|
function normalizePublishArtifact(artifact = {}, index = 0) {
|
|
523
524
|
const name = nonEmptyString(artifact.name, `publishEvidence.artifacts[${index}].name`);
|
|
524
|
-
|
|
525
|
+
const normalized = {
|
|
525
526
|
group: optionalString(artifact.group),
|
|
526
527
|
kind: optionalString(artifact.kind),
|
|
527
528
|
name,
|
|
@@ -529,6 +530,20 @@ function normalizePublishArtifact(artifact = {}, index = 0) {
|
|
|
529
530
|
digest: optionalString(artifact.digest || artifact.sha256 || artifact.integrity || artifact.shasum),
|
|
530
531
|
evidence: optionalString(artifact.evidence),
|
|
531
532
|
};
|
|
533
|
+
for (const key of [
|
|
534
|
+
"action",
|
|
535
|
+
"platform",
|
|
536
|
+
"contract_major",
|
|
537
|
+
"parent_digest",
|
|
538
|
+
"content",
|
|
539
|
+
"release",
|
|
540
|
+
"verification",
|
|
541
|
+
]) {
|
|
542
|
+
if (Object.prototype.hasOwnProperty.call(artifact, key)) {
|
|
543
|
+
normalized[key] = structuredClone(artifact[key]);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return normalized;
|
|
532
547
|
}
|
|
533
548
|
|
|
534
549
|
function normalizePublishEvidence(value = undefined) {
|
|
@@ -1138,11 +1153,7 @@ export function createReleasePassport({
|
|
|
1138
1153
|
url: asset.url,
|
|
1139
1154
|
})),
|
|
1140
1155
|
...publishArtifacts.map((artifact) => ({
|
|
1141
|
-
|
|
1142
|
-
kind: artifact.kind,
|
|
1143
|
-
name: artifact.name,
|
|
1144
|
-
ref: artifact.ref,
|
|
1145
|
-
digest: artifact.digest,
|
|
1156
|
+
...artifact,
|
|
1146
1157
|
evidence: publishEvidencePath || artifact.evidence || artifactEvidencePath,
|
|
1147
1158
|
})),
|
|
1148
1159
|
],
|
|
@@ -1652,6 +1663,29 @@ export function createReleaseCheckReport({
|
|
|
1652
1663
|
if (!Array.isArray(normalizedPublishEvidence?.artifacts) || normalizedPublishEvidence.artifacts.length === 0) {
|
|
1653
1664
|
issues.push(issue("error", "publishEvidence.artifacts", "publishEvidence.artifacts must include published artifacts"));
|
|
1654
1665
|
}
|
|
1666
|
+
if ((normalizedPublishEvidence?.artifacts || []).some((artifact) => artifact.action)) {
|
|
1667
|
+
try {
|
|
1668
|
+
const validation = validateTransactionPublishEvidence({
|
|
1669
|
+
evidence: publishEvidence,
|
|
1670
|
+
version: normalizedPublishEvidence.version,
|
|
1671
|
+
channel: normalizedPublishEvidence.channel,
|
|
1672
|
+
sourceSha: normalizedPublishEvidence.sourceSha,
|
|
1673
|
+
releaseSha: normalizedPublishEvidence.releaseSha,
|
|
1674
|
+
targetRef: normalizedPublishEvidence.targetRef,
|
|
1675
|
+
releaseMaterialSha: normalizedPublishEvidence.releaseMaterialSha,
|
|
1676
|
+
publishToolingSha: normalizedPublishEvidence.publishToolingSha,
|
|
1677
|
+
});
|
|
1678
|
+
for (const error of validation.errors) {
|
|
1679
|
+
issues.push(issue("error", "publishEvidence.artifactProvenance", error));
|
|
1680
|
+
}
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
issues.push(issue(
|
|
1683
|
+
"error",
|
|
1684
|
+
"publishEvidence.artifactProvenance",
|
|
1685
|
+
`publish artifact provenance is invalid: ${error.message}`,
|
|
1686
|
+
));
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1655
1689
|
if (passport?.release?.sourceSha && normalizedPublishEvidence?.sourceSha && passport.release.sourceSha !== normalizedPublishEvidence.sourceSha) {
|
|
1656
1690
|
issues.push(issue("error", "publishEvidence.sourceSha.mismatch", "publishEvidence.sourceSha must match passport.release.sourceSha"));
|
|
1657
1691
|
}
|
|
@@ -1686,6 +1720,26 @@ export function createReleaseCheckReport({
|
|
|
1686
1720
|
if (artifact.digest && evidenceDigest && artifact.digest !== evidenceDigest && artifact.digest !== `sha256:${evidenceDigest}`) {
|
|
1687
1721
|
issues.push(issue("error", "artifact.digest.mismatch", `artifact ${artifact.name} digest differs between passport and evidence`));
|
|
1688
1722
|
}
|
|
1723
|
+
for (const field of [
|
|
1724
|
+
"action",
|
|
1725
|
+
"platform",
|
|
1726
|
+
"contract_major",
|
|
1727
|
+
"parent_digest",
|
|
1728
|
+
"content",
|
|
1729
|
+
"release",
|
|
1730
|
+
"verification",
|
|
1731
|
+
]) {
|
|
1732
|
+
if (
|
|
1733
|
+
Object.prototype.hasOwnProperty.call(evidence, field) &&
|
|
1734
|
+
stableJson(artifact[field]) !== stableJson(evidence[field])
|
|
1735
|
+
) {
|
|
1736
|
+
issues.push(issue(
|
|
1737
|
+
"error",
|
|
1738
|
+
`artifact.${field}.mismatch`,
|
|
1739
|
+
`artifact ${artifact.name} ${field} differs between passport and publish evidence`,
|
|
1740
|
+
));
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1689
1743
|
}
|
|
1690
1744
|
if (passport?.packageSet) {
|
|
1691
1745
|
const main = passport.packageSet.main || {};
|