@kungfu-tech/buildchain 2.12.2 → 2.12.3-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/actions/promote-buildchain-ref/README.md +17 -1
- package/dist/site/buildchain-contract.json +52 -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 +76 -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 +32 -0
- package/packages/core/publish-transaction.js +265 -14
- 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,84 @@ 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 normalized = {
|
|
227
330
|
group: optionalString(artifact.group),
|
|
228
331
|
kind: assertNonEmptyString(artifact.kind, `${label}.kind`),
|
|
229
332
|
name: assertNonEmptyString(artifact.name, `${label}.name`),
|
|
230
333
|
ref: optionalString(artifact.ref),
|
|
231
|
-
digest
|
|
334
|
+
digest,
|
|
232
335
|
role,
|
|
233
336
|
required: artifact.required === undefined ? true : Boolean(artifact.required),
|
|
234
337
|
};
|
|
338
|
+
if (action) {
|
|
339
|
+
normalized.action = action;
|
|
340
|
+
}
|
|
341
|
+
for (const key of ["platform", "parent_digest"]) {
|
|
342
|
+
if (hasOwn(artifact, key)) {
|
|
343
|
+
normalized[key] = assertNonEmptyString(artifact[key], `${label}.${key}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (contractMajor !== undefined) {
|
|
347
|
+
normalized.contract_major = contractMajor;
|
|
348
|
+
}
|
|
349
|
+
for (const key of ["content", "release"]) {
|
|
350
|
+
if (hasOwn(artifact, key)) {
|
|
351
|
+
normalized[key] = normalizeArtifactCoordinate(
|
|
352
|
+
artifact[key],
|
|
353
|
+
`${label}.${key}`,
|
|
354
|
+
{ partial: partialProvenance },
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (hasOwn(artifact, "verification")) {
|
|
359
|
+
normalized.verification = normalizeArtifactVerification(
|
|
360
|
+
artifact.verification,
|
|
361
|
+
`${label}.verification`,
|
|
362
|
+
{ partial: partialProvenance },
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
if (requireDigest && action) {
|
|
366
|
+
for (const key of ["content", "release"]) {
|
|
367
|
+
if (!normalized[key]) {
|
|
368
|
+
throw new Error(`${label}.${key} is required when action is ${action}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (normalized.kind === "oci") {
|
|
372
|
+
if (!normalized.platform) {
|
|
373
|
+
throw new Error(`${label}.platform is required for OCI provenance`);
|
|
374
|
+
}
|
|
375
|
+
if (!normalized.contract_major) {
|
|
376
|
+
throw new Error(`${label}.contract_major is required for OCI provenance`);
|
|
377
|
+
}
|
|
378
|
+
if (!normalized.verification) {
|
|
379
|
+
throw new Error(`${label}.verification is required for OCI provenance`);
|
|
380
|
+
}
|
|
381
|
+
const verification = normalized.verification;
|
|
382
|
+
const comparisons = [
|
|
383
|
+
[verification.ref, normalized.ref, "ref"],
|
|
384
|
+
[verification.digest, normalized.digest, "digest"],
|
|
385
|
+
[verification.platform, normalized.platform, "platform"],
|
|
386
|
+
[verification.contract_major, normalized.contract_major, "contract_major"],
|
|
387
|
+
[verification.parent_digest || "", normalized.parent_digest || "", "parent_digest"],
|
|
388
|
+
];
|
|
389
|
+
for (const [actual, expected, field] of comparisons) {
|
|
390
|
+
if (actual !== expected) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`${label}.verification.${field} mismatch: expected ${expected || "<empty>"}, got ${actual || "<empty>"}`,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return normalized;
|
|
235
399
|
}
|
|
236
400
|
|
|
237
401
|
export function parsePublishArtifactsJson(value, label = "publish artifacts") {
|
|
@@ -244,7 +408,7 @@ export function parsePublishArtifactsJson(value, label = "publish artifacts") {
|
|
|
244
408
|
throw new Error(`${label} must be a JSON array`);
|
|
245
409
|
}
|
|
246
410
|
return parsed.map((artifact, index) =>
|
|
247
|
-
normalizePublishArtifact(artifact, `${label}[${index}]
|
|
411
|
+
normalizePublishArtifact(artifact, `${label}[${index}]`, { requireDigest: false }),
|
|
248
412
|
);
|
|
249
413
|
}
|
|
250
414
|
|
|
@@ -260,6 +424,14 @@ export function normalizePublishEvidence(evidence) {
|
|
|
260
424
|
normalizePublishArtifact(artifact, `artifacts[${index}]`),
|
|
261
425
|
)
|
|
262
426
|
: [];
|
|
427
|
+
const identities = new Set();
|
|
428
|
+
for (const artifact of artifacts) {
|
|
429
|
+
const identity = artifactIdentity(artifact);
|
|
430
|
+
if (identities.has(identity)) {
|
|
431
|
+
throw new Error(`duplicate publish artifact: ${artifact.kind}:${artifact.name}:${artifact.ref}`);
|
|
432
|
+
}
|
|
433
|
+
identities.add(identity);
|
|
434
|
+
}
|
|
263
435
|
return {
|
|
264
436
|
schema: 1,
|
|
265
437
|
version: assertNonEmptyString(evidence.version, "evidence.version"),
|
|
@@ -282,25 +454,49 @@ export function readPublishEvidence(filePath) {
|
|
|
282
454
|
|
|
283
455
|
export function planArtifactPublish({ requiredArtifacts = [], existingArtifacts = [] } = {}) {
|
|
284
456
|
const required = requiredArtifacts.map((artifact, index) =>
|
|
285
|
-
normalizePublishArtifact(artifact, `requiredArtifacts[${index}]
|
|
457
|
+
normalizePublishArtifact(artifact, `requiredArtifacts[${index}]`, { requireDigest: false }),
|
|
286
458
|
);
|
|
287
459
|
const existing = existingArtifacts.map((artifact, index) =>
|
|
288
460
|
normalizePublishArtifact(artifact, `existingArtifacts[${index}]`),
|
|
289
461
|
);
|
|
290
|
-
const existingByIdentity = new Map(existing.map((artifact) => [artifactIdentity(artifact), artifact]));
|
|
291
462
|
const publish = [];
|
|
292
463
|
const accepted = [];
|
|
293
464
|
const conflicts = [];
|
|
294
465
|
for (const artifact of required) {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
466
|
+
const candidates = existing.filter((candidate) => (
|
|
467
|
+
candidate.kind === artifact.kind &&
|
|
468
|
+
candidate.name === artifact.name &&
|
|
469
|
+
(!artifact.group || candidate.group === artifact.group)
|
|
470
|
+
));
|
|
471
|
+
const current = candidates.find((candidate) => artifactMatchesRequirement(candidate, artifact));
|
|
298
472
|
if (!current) {
|
|
473
|
+
if (candidates.length > 0) {
|
|
474
|
+
conflicts.push({ expected: artifact, actual: candidates[0], fields: ["ref"] });
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
299
477
|
publish.push(artifact);
|
|
300
478
|
continue;
|
|
301
479
|
}
|
|
302
|
-
|
|
303
|
-
|
|
480
|
+
const fields = [];
|
|
481
|
+
if (artifact.digest && current.digest !== artifact.digest) {
|
|
482
|
+
fields.push("digest");
|
|
483
|
+
}
|
|
484
|
+
const compareSubset = (actual, expected, prefix = "") => {
|
|
485
|
+
for (const [key, value] of Object.entries(expected || {})) {
|
|
486
|
+
if (["group", "kind", "name", "ref", "digest", "role", "required"].includes(key) && !prefix) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const field = prefix ? `${prefix}.${key}` : key;
|
|
490
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
491
|
+
compareSubset(actual?.[key], value, field);
|
|
492
|
+
} else if (actual?.[key] !== value) {
|
|
493
|
+
fields.push(field);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
compareSubset(current, artifact);
|
|
498
|
+
if (fields.length > 0) {
|
|
499
|
+
conflicts.push({ expected: artifact, actual: current, fields });
|
|
304
500
|
continue;
|
|
305
501
|
}
|
|
306
502
|
accepted.push(current);
|
|
@@ -314,6 +510,40 @@ export function planArtifactPublish({ requiredArtifacts = [], existingArtifacts
|
|
|
314
510
|
};
|
|
315
511
|
}
|
|
316
512
|
|
|
513
|
+
export function resolvePublishArtifactRequirements(
|
|
514
|
+
requiredArtifacts,
|
|
515
|
+
{ version, targetRef, sourceSha, releaseMaterialSha } = {},
|
|
516
|
+
) {
|
|
517
|
+
return requiredArtifacts.map((artifact, index) => {
|
|
518
|
+
const normalized = normalizePublishArtifact(
|
|
519
|
+
artifact,
|
|
520
|
+
`requiredArtifacts[${index}]`,
|
|
521
|
+
{ requireDigest: false },
|
|
522
|
+
);
|
|
523
|
+
normalized.ref ||= assertNonEmptyString(version, "version");
|
|
524
|
+
if (!normalized.action) {
|
|
525
|
+
return normalized;
|
|
526
|
+
}
|
|
527
|
+
const current = {
|
|
528
|
+
version: assertNonEmptyString(version, "version"),
|
|
529
|
+
ref: normalized.ref,
|
|
530
|
+
source_sha: assertNonEmptyString(sourceSha, "sourceSha"),
|
|
531
|
+
material_sha: assertNonEmptyString(releaseMaterialSha, "releaseMaterialSha"),
|
|
532
|
+
};
|
|
533
|
+
if (normalized.action === "built") {
|
|
534
|
+
normalized.content = { ...current, ...(normalized.content || {}) };
|
|
535
|
+
} else if (!normalized.content) {
|
|
536
|
+
throw new Error(`requiredArtifacts[${index}].content is required when action is reused`);
|
|
537
|
+
}
|
|
538
|
+
normalized.release = {
|
|
539
|
+
...current,
|
|
540
|
+
target_ref: assertNonEmptyString(targetRef, "targetRef"),
|
|
541
|
+
...(normalized.release || {}),
|
|
542
|
+
};
|
|
543
|
+
return normalized;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
317
547
|
export function validatePublishEvidence({
|
|
318
548
|
evidence,
|
|
319
549
|
version,
|
|
@@ -354,9 +584,30 @@ export function validatePublishEvidence({
|
|
|
354
584
|
);
|
|
355
585
|
}
|
|
356
586
|
for (const conflict of artifactPlan.conflicts) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
587
|
+
const fields = conflict.fields || ["digest"];
|
|
588
|
+
const category = fields.length === 1 && fields[0] === "digest"
|
|
589
|
+
? "artifact digest mismatch"
|
|
590
|
+
: "artifact coordinate or provenance mismatch";
|
|
591
|
+
errors.push(`${category}: ${conflict.expected.kind}:${conflict.expected.name}:${fields.join(",")}`);
|
|
592
|
+
}
|
|
593
|
+
for (const [index, artifact] of normalized.artifacts.entries()) {
|
|
594
|
+
if (!artifact.action) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
const checks = [
|
|
598
|
+
[artifact.release?.version, version, "release.version"],
|
|
599
|
+
[artifact.release?.ref, artifact.ref, "release.ref"],
|
|
600
|
+
[artifact.release?.target_ref, targetRef, "release.target_ref"],
|
|
601
|
+
[artifact.release?.source_sha, sourceSha, "release.source_sha"],
|
|
602
|
+
[artifact.release?.material_sha, releaseMaterialSha, "release.material_sha"],
|
|
603
|
+
];
|
|
604
|
+
for (const [actual, expected, field] of checks) {
|
|
605
|
+
if (actual !== expected) {
|
|
606
|
+
errors.push(
|
|
607
|
+
`artifact provenance mismatch: artifacts[${index}].${field}: expected ${expected || "<empty>"}, got ${actual || "<empty>"}`,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
360
611
|
}
|
|
361
612
|
|
|
362
613
|
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 || {};
|