@kungfu-tech/buildchain 2.3.1-alpha.0 → 2.3.1-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/AGENTS.md +11 -0
- package/CONTRIBUTING.md +12 -2
- package/README.md +21 -0
- package/actions/promote-buildchain-ref/README.md +39 -1
- package/actions/run-lifecycle/README.md +20 -0
- package/bin/buildchain.mjs +253 -8
- package/dist/site/cli-registry.json +5 -0
- package/dist/site/release-provenance.json +1 -0
- package/docs/MAP.md +4 -0
- package/docs/cli.md +125 -2
- package/docs/install.md +25 -1
- package/docs/publish-transaction.md +10 -0
- package/docs/release-passport.md +35 -0
- package/docs/reusable-build-surface.md +152 -3
- package/docs/toolkit-observability.md +235 -0
- package/docs/web-surface-deployments.md +35 -2
- package/fixtures/libnode-shaped/README.md +2 -1
- package/package.json +2 -1
- package/packages/core/README.md +22 -0
- package/packages/core/buildchain-config.js +42 -0
- package/packages/core/diagnostics.js +1392 -0
- package/packages/core/index.js +32 -0
- package/packages/core/logging.js +51 -0
- package/packages/core/release-passport.js +581 -13
- package/scripts/aggregate-build-summary.mjs +30 -0
- package/scripts/aggregate-diagnostics-summary.mjs +54 -0
- package/scripts/check-inventory.mjs +19 -0
- package/scripts/generate-site-bundle.mjs +1 -0
- package/scripts/init-repo.mjs +14 -0
- package/scripts/npm-publish-dry-run.mjs +6 -0
- package/scripts/run-lifecycle-core.mjs +391 -17
- package/scripts/run-lifecycle.mjs +16 -0
- package/scripts/runtime-ref-core.mjs +109 -0
- package/scripts/web-surface-core.mjs +8 -0
- package/scripts/web-surface.mjs +4 -0
|
@@ -183,6 +183,26 @@ function parseJsonInput(value, fallback = undefined) {
|
|
|
183
183
|
return JSON.parse(input);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
function parseJsonInputWithMeta(value, fallback = undefined) {
|
|
187
|
+
const input = String(value || "").trim();
|
|
188
|
+
if (!input) {
|
|
189
|
+
return { value: fallback, path: "", sha256: "" };
|
|
190
|
+
}
|
|
191
|
+
if (fs.existsSync(input)) {
|
|
192
|
+
return {
|
|
193
|
+
value: readJsonFile(input),
|
|
194
|
+
path: input,
|
|
195
|
+
sha256: sha256File(input),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const parsed = JSON.parse(input);
|
|
199
|
+
return {
|
|
200
|
+
value: parsed,
|
|
201
|
+
path: "",
|
|
202
|
+
sha256: sha256Text(stableJson(parsed)),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
186
206
|
function discoverAssetsFromDir(dir) {
|
|
187
207
|
if (!dir || !fs.existsSync(dir)) {
|
|
188
208
|
return [];
|
|
@@ -214,6 +234,214 @@ function readPackageVersion(cwd) {
|
|
|
214
234
|
}
|
|
215
235
|
}
|
|
216
236
|
|
|
237
|
+
function normalizePackageEntry(entry = {}) {
|
|
238
|
+
return {
|
|
239
|
+
name: optionalString(entry.name),
|
|
240
|
+
version: optionalString(entry.version || entry.ref),
|
|
241
|
+
distTag: optionalString(entry.distTag || entry.dist_tag),
|
|
242
|
+
digest: optionalString(entry.digest || entry.integrity || entry.sha256 || entry.shasum),
|
|
243
|
+
registry: optionalString(entry.registry),
|
|
244
|
+
platform: optionalString(entry.platform),
|
|
245
|
+
action: optionalString(entry.action),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function normalizePackageSet(value = undefined, { packageName = "", packageVersion = "", publish = {} } = {}) {
|
|
250
|
+
if (!value) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
const rawMain = value.main || value.mainPackage || {
|
|
254
|
+
name: value.main_package || packageName,
|
|
255
|
+
version: value.version || packageVersion,
|
|
256
|
+
distTag: value.distTag || value.dist_tag || publish.distTag,
|
|
257
|
+
};
|
|
258
|
+
const platforms = Array.isArray(value.platforms)
|
|
259
|
+
? value.platforms.map((entry) => normalizePackageEntry(entry)).filter((entry) => entry.name || entry.version)
|
|
260
|
+
: [];
|
|
261
|
+
return {
|
|
262
|
+
order: optionalString(value.order || value.packageSetOrder || value.package_set_order || publish.packageSetOrder),
|
|
263
|
+
registry: optionalString(value.registry || publish.registry),
|
|
264
|
+
main: normalizePackageEntry(rawMain),
|
|
265
|
+
platforms,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function normalizeAnchorManifest(meta) {
|
|
270
|
+
const value = meta?.value;
|
|
271
|
+
if (!value) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
path: optionalString(value.path || meta.path),
|
|
276
|
+
sha256: optionalString(value.sha256 || meta.sha256),
|
|
277
|
+
fields: value.fields && typeof value.fields === "object" && !Array.isArray(value.fields)
|
|
278
|
+
? value.fields
|
|
279
|
+
: value,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function normalizeEvidenceDocument(meta, label) {
|
|
284
|
+
const value = meta?.value;
|
|
285
|
+
if (!value) {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
289
|
+
throw new Error(`${label} must be a JSON object`);
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
path: optionalString(meta.path),
|
|
293
|
+
sha256: optionalString(meta.sha256),
|
|
294
|
+
fields: value,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function normalizePlatformArtifactManifest(meta, index = 0) {
|
|
299
|
+
const normalized = normalizeEvidenceDocument(meta, `platformArtifactManifests[${index}]`);
|
|
300
|
+
if (!normalized) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
const fields = normalized.fields || {};
|
|
304
|
+
return {
|
|
305
|
+
...normalized,
|
|
306
|
+
platform: optionalString(fields.platform?.id || fields.platformId || fields.platform || ""),
|
|
307
|
+
artifactName: optionalString(fields.artifactName || fields.links?.artifactName || ""),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function normalizePublishArtifact(artifact = {}, index = 0) {
|
|
312
|
+
const name = nonEmptyString(artifact.name, `publishEvidence.artifacts[${index}].name`);
|
|
313
|
+
return {
|
|
314
|
+
group: optionalString(artifact.group),
|
|
315
|
+
kind: optionalString(artifact.kind),
|
|
316
|
+
name,
|
|
317
|
+
ref: optionalString(artifact.ref || artifact.version),
|
|
318
|
+
digest: optionalString(artifact.digest || artifact.sha256 || artifact.integrity || artifact.shasum),
|
|
319
|
+
evidence: optionalString(artifact.evidence),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizePublishEvidence(value = undefined) {
|
|
324
|
+
if (!value) {
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
schema: Number(value.schema || value.schemaVersion || 1),
|
|
329
|
+
version: optionalString(value.version),
|
|
330
|
+
channel: optionalString(value.channel),
|
|
331
|
+
sourceSha: optionalString(value.source_sha || value.sourceSha),
|
|
332
|
+
releaseSha: optionalString(value.release_sha || value.releaseSha),
|
|
333
|
+
targetRef: optionalString(value.target_ref || value.targetRef),
|
|
334
|
+
releaseMaterialSha: optionalString(value.release_material_sha || value.releaseMaterialSha),
|
|
335
|
+
publishToolingSha: optionalString(value.publish_tooling_sha || value.publishToolingSha),
|
|
336
|
+
artifacts: Array.isArray(value.artifacts)
|
|
337
|
+
? value.artifacts.map((artifact, index) => normalizePublishArtifact(artifact, index))
|
|
338
|
+
: [],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function packageSetEntries(packageSet = undefined) {
|
|
343
|
+
if (!packageSet) {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
return [
|
|
347
|
+
...(packageSet.main?.name ? [{ role: "main", ...packageSet.main }] : []),
|
|
348
|
+
...((packageSet.platforms || []).map((entry) => ({ role: "platform", ...entry }))),
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function normalizePublishSummary({ packageSet = undefined, publishEvidence = undefined, publish = {} } = {}) {
|
|
353
|
+
const packages = packageSetEntries(packageSet);
|
|
354
|
+
if (packages.length === 0) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
const artifacts = publishEvidence?.artifacts || [];
|
|
358
|
+
const artifactByPackage = new Map(artifacts.map((artifact) => [
|
|
359
|
+
[artifact.name, artifact.ref || artifact.version || ""].join("\0"),
|
|
360
|
+
artifact,
|
|
361
|
+
]));
|
|
362
|
+
const normalizedPackages = packages.map((entry) => {
|
|
363
|
+
const artifact = artifactByPackage.get([entry.name, entry.version].join("\0")) || {};
|
|
364
|
+
return {
|
|
365
|
+
role: entry.role,
|
|
366
|
+
name: entry.name,
|
|
367
|
+
publishedVersion: entry.version,
|
|
368
|
+
distTag: entry.distTag,
|
|
369
|
+
digest: entry.digest || artifact.digest || "",
|
|
370
|
+
registry: entry.registry || packageSet.registry || publish.registry || "",
|
|
371
|
+
platform: entry.platform || "",
|
|
372
|
+
action: entry.action || artifact.action || "",
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
const distTags = [...new Set(normalizedPackages.map((entry) => entry.distTag).filter(Boolean))];
|
|
376
|
+
return {
|
|
377
|
+
registry: optionalString(packageSet.registry || publish.registry),
|
|
378
|
+
channel: optionalString(publishEvidence?.channel || publish.channel),
|
|
379
|
+
distTag: distTags.length === 1 ? distTags[0] : "",
|
|
380
|
+
source: "packageSet+publishEvidence",
|
|
381
|
+
packages: normalizedPackages,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function normalizeTrustedPublishing(value = undefined, { workflow = {}, publish = {} } = {}) {
|
|
386
|
+
if (!value && publish.auth !== "trusted-publishing") {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
const raw = value || {};
|
|
390
|
+
return {
|
|
391
|
+
provider: optionalString(raw.provider || "npm"),
|
|
392
|
+
enabled: raw.enabled === undefined ? publish.auth === "trusted-publishing" : Boolean(raw.enabled),
|
|
393
|
+
auth: optionalString(raw.auth || publish.auth),
|
|
394
|
+
workflowRunId: optionalString(raw.workflowRunId || raw.workflow_run_id || workflow.runId),
|
|
395
|
+
workflowRunAttempt: optionalString(raw.workflowRunAttempt || raw.workflow_run_attempt || workflow.runAttempt),
|
|
396
|
+
evidence: optionalString(raw.evidence),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function normalizeTransactionResult(value = {}) {
|
|
401
|
+
const result = {};
|
|
402
|
+
if (value.command) {
|
|
403
|
+
result.command = optionalString(value.command);
|
|
404
|
+
}
|
|
405
|
+
if (value.validation && typeof value.validation === "object" && !Array.isArray(value.validation)) {
|
|
406
|
+
result.validation = {
|
|
407
|
+
valid: Boolean(value.validation.valid),
|
|
408
|
+
errors: Array.isArray(value.validation.errors) ? value.validation.errors : [],
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (value.recovery && typeof value.recovery === "object" && !Array.isArray(value.recovery)) {
|
|
412
|
+
result.recovery = value.recovery;
|
|
413
|
+
}
|
|
414
|
+
if (value.publishAction || value.distTag) {
|
|
415
|
+
result.publish = {
|
|
416
|
+
action: optionalString(value.publishAction),
|
|
417
|
+
distTag: optionalString(value.distTag),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function normalizeTransaction(value = undefined) {
|
|
424
|
+
if (!value) {
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
const raw = value.transaction && typeof value.transaction === "object" ? value.transaction : value;
|
|
428
|
+
return {
|
|
429
|
+
id: optionalString(raw.id || value.id),
|
|
430
|
+
version: optionalString(raw.version || value.version),
|
|
431
|
+
state: optionalString(raw.state || value.state),
|
|
432
|
+
previousState: optionalString(raw.previous_state || raw.previousState),
|
|
433
|
+
exactTag: optionalString(raw.exact_tag || raw.exactTag || value.exactTag),
|
|
434
|
+
releaseSha: optionalString(raw.release_sha || raw.releaseSha || value.releaseSha),
|
|
435
|
+
releaseMaterialSha: optionalString(raw.release_material_sha || raw.releaseMaterialSha || value.releaseMaterialSha),
|
|
436
|
+
stateRef: optionalString(raw.state_ref || raw.stateRef || value.stateRef),
|
|
437
|
+
statePath: optionalString(raw.state_path || raw.statePath || value.statePath),
|
|
438
|
+
stateSha: optionalString(value.stateSha || value.state_sha || value.durable?.sha),
|
|
439
|
+
evidencePath: optionalString(raw.evidence_path || raw.evidencePath || value.evidencePath),
|
|
440
|
+
updatedAt: optionalString(raw.updated_at || raw.updatedAt),
|
|
441
|
+
result: normalizeTransactionResult(value),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
217
445
|
export function createArtifactEvidence({ assets = [], repository = "", tag = "", sourceSha = "", workflow = {} } = {}) {
|
|
218
446
|
const normalizedAssets = assets.map((asset, index) => normalizeAsset(asset, index));
|
|
219
447
|
return {
|
|
@@ -254,6 +482,7 @@ export function createReleasePassport({
|
|
|
254
482
|
tag = "",
|
|
255
483
|
sourceSha = "",
|
|
256
484
|
line = "",
|
|
485
|
+
productName = "Buildchain",
|
|
257
486
|
packageName = "@kungfu-tech/buildchain",
|
|
258
487
|
packageVersion = "",
|
|
259
488
|
productMechanismPath = "product-mechanism.json",
|
|
@@ -261,17 +490,59 @@ export function createReleasePassport({
|
|
|
261
490
|
impactPath = "impact.json",
|
|
262
491
|
agentIndexPath = "agent-index.json",
|
|
263
492
|
checkReportPath = "check-report.json",
|
|
493
|
+
publishEvidencePath = "",
|
|
494
|
+
transactionStatePath = "",
|
|
264
495
|
assets = [],
|
|
496
|
+
packageSet = undefined,
|
|
497
|
+
anchorManifest = undefined,
|
|
498
|
+
publishEvidence = undefined,
|
|
499
|
+
trustedPublishing = undefined,
|
|
500
|
+
transaction = undefined,
|
|
501
|
+
buildSummary = undefined,
|
|
502
|
+
platformArtifactManifests = [],
|
|
503
|
+
distTagPromotionEvidence = undefined,
|
|
504
|
+
release = {},
|
|
505
|
+
publish = {},
|
|
265
506
|
workflow = {},
|
|
266
507
|
} = {}) {
|
|
267
508
|
const normalizedTag = nonEmptyString(tag, "tag");
|
|
268
509
|
const artifactEvidence = createArtifactEvidence({ assets, repository, tag: normalizedTag, sourceSha, workflow });
|
|
510
|
+
const normalizedPublishEvidence = normalizePublishEvidence(publishEvidence);
|
|
511
|
+
const normalizedPackageSet = normalizePackageSet(packageSet, { packageName, packageVersion, publish });
|
|
512
|
+
const normalizedTrustedPublishing = normalizeTrustedPublishing(trustedPublishing, { workflow, publish });
|
|
513
|
+
const normalizedTransaction = normalizeTransaction(transaction);
|
|
514
|
+
const normalizedBuildSummary = buildSummary ? normalizeEvidenceDocument(buildSummary, "buildSummary") : undefined;
|
|
515
|
+
const normalizedPlatformArtifactManifests = (platformArtifactManifests || [])
|
|
516
|
+
.map((manifest, index) => normalizePlatformArtifactManifest(manifest, index))
|
|
517
|
+
.filter(Boolean);
|
|
518
|
+
const normalizedDistTagPromotionEvidence = distTagPromotionEvidence
|
|
519
|
+
? normalizeEvidenceDocument(distTagPromotionEvidence, "distTagPromotionEvidence")
|
|
520
|
+
: undefined;
|
|
521
|
+
const publishArtifacts = normalizedPublishEvidence?.artifacts || [];
|
|
522
|
+
const normalizedPublishSummary = normalizePublishSummary({
|
|
523
|
+
packageSet: normalizedPackageSet,
|
|
524
|
+
publishEvidence: normalizedPublishEvidence,
|
|
525
|
+
publish,
|
|
526
|
+
});
|
|
527
|
+
const releaseMaterialSha = optionalString(
|
|
528
|
+
release.releaseMaterialSha ||
|
|
529
|
+
release.release_material_sha ||
|
|
530
|
+
normalizedPublishEvidence?.releaseMaterialSha ||
|
|
531
|
+
normalizedTransaction?.releaseMaterialSha,
|
|
532
|
+
);
|
|
533
|
+
const releaseSha = optionalString(
|
|
534
|
+
release.releaseSha ||
|
|
535
|
+
release.release_sha ||
|
|
536
|
+
normalizedPublishEvidence?.releaseSha ||
|
|
537
|
+
normalizedTransaction?.releaseSha,
|
|
538
|
+
);
|
|
539
|
+
const targetRef = optionalString(release.targetRef || release.target_ref || normalizedPublishEvidence?.targetRef);
|
|
269
540
|
return {
|
|
270
541
|
schemaVersion: 1,
|
|
271
542
|
contract: RELEASE_PASSPORT_CONTRACT,
|
|
272
543
|
generatedAt: nowIso(),
|
|
273
544
|
product: {
|
|
274
|
-
name: "Buildchain",
|
|
545
|
+
name: optionalString(productName || "Buildchain"),
|
|
275
546
|
repository: optionalString(repository),
|
|
276
547
|
mechanism: productMechanismPath,
|
|
277
548
|
},
|
|
@@ -279,6 +550,22 @@ export function createReleasePassport({
|
|
|
279
550
|
tag: normalizedTag,
|
|
280
551
|
line: optionalString(line),
|
|
281
552
|
sourceSha: optionalString(sourceSha),
|
|
553
|
+
channel: optionalString(release.channel || normalizedPublishEvidence?.channel || publish.channel),
|
|
554
|
+
targetRef,
|
|
555
|
+
releaseSha,
|
|
556
|
+
releaseMaterialSha,
|
|
557
|
+
publishToolingSha: optionalString(
|
|
558
|
+
release.publishToolingSha ||
|
|
559
|
+
release.publish_tooling_sha ||
|
|
560
|
+
normalizedPublishEvidence?.publishToolingSha,
|
|
561
|
+
),
|
|
562
|
+
releaseStateRef: optionalString(
|
|
563
|
+
release.releaseStateRef ||
|
|
564
|
+
release.release_state_ref ||
|
|
565
|
+
normalizedTransaction?.stateRef ||
|
|
566
|
+
(normalizedTag ? `refs/heads/buildchain/release-state/${normalizedTag.replace(/^v/, "").replace(/[^0-9A-Za-z]+/g, "-")}` : ""),
|
|
567
|
+
),
|
|
568
|
+
releaseStateSha: optionalString(release.releaseStateSha || release.release_state_sha || normalizedTransaction?.stateSha),
|
|
282
569
|
package: {
|
|
283
570
|
name: packageName,
|
|
284
571
|
version: packageVersion || readPackageVersion(cwd),
|
|
@@ -296,15 +583,47 @@ export function createReleasePassport({
|
|
|
296
583
|
compatibilityFixture: "self-hosted",
|
|
297
584
|
note: "Runner facts are recorded in artifact evidence; the protocol does not require self-hosted runners.",
|
|
298
585
|
},
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
})
|
|
586
|
+
...(normalizedPackageSet ? { packageSet: normalizedPackageSet } : {}),
|
|
587
|
+
...(normalizedPublishSummary ? { publish: normalizedPublishSummary } : {}),
|
|
588
|
+
...(anchorManifest ? { anchorManifest } : {}),
|
|
589
|
+
...(normalizedTrustedPublishing ? { trustedPublishing: normalizedTrustedPublishing } : {}),
|
|
590
|
+
...(normalizedTransaction ? { transaction: normalizedTransaction } : {}),
|
|
591
|
+
...(normalizedBuildSummary ? { buildSummary: normalizedBuildSummary } : {}),
|
|
592
|
+
...(normalizedPlatformArtifactManifests.length > 0 ? { platformArtifactManifests: normalizedPlatformArtifactManifests } : {}),
|
|
593
|
+
...(normalizedDistTagPromotionEvidence ? { distTagPromotion: normalizedDistTagPromotionEvidence } : {}),
|
|
594
|
+
artifacts: [
|
|
595
|
+
...artifactEvidence.artifacts.map((asset) => ({
|
|
596
|
+
group: "release",
|
|
597
|
+
kind: asset.kind || "release-asset",
|
|
598
|
+
name: asset.name,
|
|
599
|
+
platform: asset.platform,
|
|
600
|
+
ref: normalizedTag,
|
|
601
|
+
sha256: asset.sha256,
|
|
602
|
+
digest: asset.sha256 ? `sha256:${asset.sha256}` : "",
|
|
603
|
+
evidence: artifactEvidencePath,
|
|
604
|
+
url: asset.url,
|
|
605
|
+
})),
|
|
606
|
+
...publishArtifacts.map((artifact) => ({
|
|
607
|
+
group: artifact.group,
|
|
608
|
+
kind: artifact.kind,
|
|
609
|
+
name: artifact.name,
|
|
610
|
+
ref: artifact.ref,
|
|
611
|
+
digest: artifact.digest,
|
|
612
|
+
evidence: publishEvidencePath || artifact.evidence || artifactEvidencePath,
|
|
613
|
+
})),
|
|
614
|
+
],
|
|
306
615
|
evidence: {
|
|
307
616
|
artifactEvidence: artifactEvidencePath,
|
|
617
|
+
publishEvidence: publishEvidencePath,
|
|
618
|
+
transactionState: transactionStatePath,
|
|
619
|
+
buildSummary: normalizedBuildSummary?.path || "",
|
|
620
|
+
platformArtifactManifests: normalizedPlatformArtifactManifests.map((manifest) => ({
|
|
621
|
+
path: manifest.path,
|
|
622
|
+
sha256: manifest.sha256,
|
|
623
|
+
platform: manifest.platform,
|
|
624
|
+
artifactName: manifest.artifactName,
|
|
625
|
+
})),
|
|
626
|
+
distTagPromotionEvidence: normalizedDistTagPromotionEvidence?.path || "",
|
|
308
627
|
impact: impactPath,
|
|
309
628
|
checkReport: checkReportPath,
|
|
310
629
|
agentIndex: agentIndexPath,
|
|
@@ -329,10 +648,32 @@ export function collectGitHubReleasePassport({
|
|
|
329
648
|
productName = "Buildchain",
|
|
330
649
|
packageName = "@kungfu-tech/buildchain",
|
|
331
650
|
packageVersion = "",
|
|
651
|
+
packageSetJson = "",
|
|
652
|
+
publishEvidenceJson = "",
|
|
653
|
+
trustedPublishingJson = "",
|
|
654
|
+
transactionJson = "",
|
|
655
|
+
anchorManifestJson = "",
|
|
656
|
+
buildSummaryJson = "",
|
|
657
|
+
platformManifestJsons = [],
|
|
658
|
+
distTagEvidenceJson = "",
|
|
659
|
+
releaseJsonExtra = "",
|
|
660
|
+
publishJson = "",
|
|
332
661
|
workflow = {},
|
|
333
662
|
} = {}) {
|
|
334
663
|
const release = parseJsonInput(releaseJson, {});
|
|
664
|
+
const releaseExtra = parseJsonInput(releaseJsonExtra, {});
|
|
335
665
|
const assetsFromJson = parseJsonInput(assetsJson, []);
|
|
666
|
+
const packageSet = parseJsonInput(packageSetJson, undefined);
|
|
667
|
+
const publishEvidenceMeta = parseJsonInputWithMeta(publishEvidenceJson, undefined);
|
|
668
|
+
const trustedPublishing = parseJsonInput(trustedPublishingJson, undefined);
|
|
669
|
+
const transactionMeta = parseJsonInputWithMeta(transactionJson, undefined);
|
|
670
|
+
const anchorManifest = normalizeAnchorManifest(parseJsonInputWithMeta(anchorManifestJson, undefined));
|
|
671
|
+
const buildSummaryMeta = parseJsonInputWithMeta(buildSummaryJson, undefined);
|
|
672
|
+
const platformManifestMetas = (platformManifestJsons || [])
|
|
673
|
+
.filter(Boolean)
|
|
674
|
+
.map((manifestJson) => parseJsonInputWithMeta(manifestJson, undefined));
|
|
675
|
+
const distTagEvidenceMeta = parseJsonInputWithMeta(distTagEvidenceJson, undefined);
|
|
676
|
+
const publish = parseJsonInput(publishJson, {});
|
|
336
677
|
const assets = [
|
|
337
678
|
...(Array.isArray(release.assets) ? release.assets : []),
|
|
338
679
|
...(Array.isArray(assetsFromJson) ? assetsFromJson : []),
|
|
@@ -350,14 +691,43 @@ export function collectGitHubReleasePassport({
|
|
|
350
691
|
tag: resolvedTag,
|
|
351
692
|
sourceSha,
|
|
352
693
|
line,
|
|
694
|
+
productName,
|
|
353
695
|
packageName,
|
|
354
696
|
packageVersion,
|
|
355
697
|
assets,
|
|
698
|
+
packageSet,
|
|
699
|
+
anchorManifest,
|
|
700
|
+
publishEvidence: publishEvidenceMeta.value,
|
|
701
|
+
trustedPublishing,
|
|
702
|
+
transaction: transactionMeta.value,
|
|
703
|
+
buildSummary: buildSummaryMeta.value
|
|
704
|
+
? {
|
|
705
|
+
...buildSummaryMeta,
|
|
706
|
+
path: buildSummaryMeta.path ? path.relative(resolvedOutputDir, buildSummaryMeta.path).split(path.sep).join("/") : "",
|
|
707
|
+
}
|
|
708
|
+
: undefined,
|
|
709
|
+
platformArtifactManifests: platformManifestMetas
|
|
710
|
+
.filter((meta) => meta.value)
|
|
711
|
+
.map((meta) => ({
|
|
712
|
+
...meta,
|
|
713
|
+
path: meta.path ? path.relative(resolvedOutputDir, meta.path).split(path.sep).join("/") : "",
|
|
714
|
+
})),
|
|
715
|
+
distTagPromotionEvidence: distTagEvidenceMeta.value
|
|
716
|
+
? {
|
|
717
|
+
...distTagEvidenceMeta,
|
|
718
|
+
path: distTagEvidenceMeta.path ? path.relative(resolvedOutputDir, distTagEvidenceMeta.path).split(path.sep).join("/") : "",
|
|
719
|
+
}
|
|
720
|
+
: undefined,
|
|
721
|
+
release: releaseExtra,
|
|
722
|
+
publish,
|
|
723
|
+
publishEvidencePath: publishEvidenceMeta.path ? path.relative(resolvedOutputDir, publishEvidenceMeta.path).split(path.sep).join("/") : "",
|
|
724
|
+
transactionStatePath: transactionMeta.path ? path.relative(resolvedOutputDir, transactionMeta.path).split(path.sep).join("/") : "",
|
|
356
725
|
workflow,
|
|
357
726
|
});
|
|
358
727
|
const checkReport = createReleaseCheckReport({
|
|
359
728
|
passport,
|
|
360
729
|
artifactEvidence,
|
|
730
|
+
publishEvidence: publishEvidenceMeta.value,
|
|
361
731
|
impact,
|
|
362
732
|
agentIndex,
|
|
363
733
|
productMechanism,
|
|
@@ -414,9 +784,73 @@ function resolveSiblingJson(basePath, relativePath) {
|
|
|
414
784
|
return readJsonFile(candidate);
|
|
415
785
|
}
|
|
416
786
|
|
|
787
|
+
function artifactDigestValue(artifact = {}) {
|
|
788
|
+
return optionalString(artifact.digest || artifact.sha256 || artifact.integrity || artifact.shasum);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function equivalentDigest(left, right) {
|
|
792
|
+
if (!left || !right) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
return left === right || left === `sha256:${right}` || `sha256:${left}` === right;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function artifactEvidenceKeys(artifact = {}) {
|
|
799
|
+
const name = optionalString(artifact.name);
|
|
800
|
+
if (!name) {
|
|
801
|
+
return [];
|
|
802
|
+
}
|
|
803
|
+
const group = optionalString(artifact.group);
|
|
804
|
+
const kind = optionalString(artifact.kind);
|
|
805
|
+
const ref = optionalString(artifact.ref || artifact.version);
|
|
806
|
+
const keys = [];
|
|
807
|
+
if (group || kind || ref) {
|
|
808
|
+
keys.push(["structured", group, kind, name, ref].join("\0"));
|
|
809
|
+
}
|
|
810
|
+
if (kind || ref) {
|
|
811
|
+
keys.push(["structured", "", kind, name, ref].join("\0"));
|
|
812
|
+
}
|
|
813
|
+
if (kind) {
|
|
814
|
+
keys.push(["structured", "", kind, name, ""].join("\0"));
|
|
815
|
+
}
|
|
816
|
+
keys.push(["name", name].join("\0"));
|
|
817
|
+
return keys;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function indexEvidenceArtifacts(artifacts = []) {
|
|
821
|
+
const index = new Map();
|
|
822
|
+
for (const artifact of artifacts) {
|
|
823
|
+
for (const key of artifactEvidenceKeys(artifact)) {
|
|
824
|
+
const entries = index.get(key) || [];
|
|
825
|
+
entries.push(artifact);
|
|
826
|
+
index.set(key, entries);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return index;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function findEvidenceForArtifact(artifact, evidenceIndex) {
|
|
833
|
+
const artifactDigest = artifactDigestValue(artifact);
|
|
834
|
+
for (const key of artifactEvidenceKeys(artifact)) {
|
|
835
|
+
const entries = evidenceIndex.get(key) || [];
|
|
836
|
+
if (entries.length === 0) {
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const matchingDigest = entries.find((entry) => equivalentDigest(artifactDigest, artifactDigestValue(entry)));
|
|
840
|
+
if (matchingDigest) {
|
|
841
|
+
return matchingDigest;
|
|
842
|
+
}
|
|
843
|
+
if (!key.startsWith("name\0") || entries.length === 1) {
|
|
844
|
+
return entries[0];
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return undefined;
|
|
848
|
+
}
|
|
849
|
+
|
|
417
850
|
export function createReleaseCheckReport({
|
|
418
851
|
passport,
|
|
419
852
|
artifactEvidence,
|
|
853
|
+
publishEvidence,
|
|
420
854
|
impact,
|
|
421
855
|
agentIndex,
|
|
422
856
|
productMechanism,
|
|
@@ -437,27 +871,146 @@ export function createReleaseCheckReport({
|
|
|
437
871
|
issues.push(issue("warning", "release.sourceSha", "release.sourceSha is recommended"));
|
|
438
872
|
}
|
|
439
873
|
const artifacts = Array.isArray(passport?.artifacts) ? passport.artifacts : [];
|
|
440
|
-
const
|
|
874
|
+
const normalizedPublishEvidence = normalizePublishEvidence(publishEvidence);
|
|
875
|
+
const publishEvidenceSupplied =
|
|
876
|
+
Boolean(passport?.evidence?.publishEvidence) ||
|
|
877
|
+
(publishEvidence && typeof publishEvidence === "object" && Object.keys(publishEvidence).length > 0);
|
|
878
|
+
const evidenceArtifacts = [
|
|
879
|
+
...(Array.isArray(artifactEvidence?.artifacts) ? artifactEvidence.artifacts : []),
|
|
880
|
+
...(normalizedPublishEvidence?.artifacts || []),
|
|
881
|
+
];
|
|
882
|
+
if (publishEvidenceSupplied || passport?.packageSet) {
|
|
883
|
+
const checkPublishField = (value, label) => {
|
|
884
|
+
if (!value) {
|
|
885
|
+
issues.push(issue("error", `publishEvidence.${label}`, `publishEvidence.${label} is required`));
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
if (Number(normalizedPublishEvidence?.schema) !== 1) {
|
|
889
|
+
issues.push(issue("error", "publishEvidence.schema", "publishEvidence.schema must be 1"));
|
|
890
|
+
}
|
|
891
|
+
checkPublishField(normalizedPublishEvidence?.version, "version");
|
|
892
|
+
checkPublishField(normalizedPublishEvidence?.channel, "channel");
|
|
893
|
+
checkPublishField(normalizedPublishEvidence?.sourceSha, "sourceSha");
|
|
894
|
+
checkPublishField(normalizedPublishEvidence?.releaseSha, "releaseSha");
|
|
895
|
+
checkPublishField(normalizedPublishEvidence?.targetRef, "targetRef");
|
|
896
|
+
checkPublishField(normalizedPublishEvidence?.releaseMaterialSha, "releaseMaterialSha");
|
|
897
|
+
checkPublishField(normalizedPublishEvidence?.publishToolingSha, "publishToolingSha");
|
|
898
|
+
if (!Array.isArray(normalizedPublishEvidence?.artifacts) || normalizedPublishEvidence.artifacts.length === 0) {
|
|
899
|
+
issues.push(issue("error", "publishEvidence.artifacts", "publishEvidence.artifacts must include published artifacts"));
|
|
900
|
+
}
|
|
901
|
+
if (passport?.release?.sourceSha && normalizedPublishEvidence?.sourceSha && passport.release.sourceSha !== normalizedPublishEvidence.sourceSha) {
|
|
902
|
+
issues.push(issue("error", "publishEvidence.sourceSha.mismatch", "publishEvidence.sourceSha must match passport.release.sourceSha"));
|
|
903
|
+
}
|
|
904
|
+
if (passport?.release?.releaseSha && normalizedPublishEvidence?.releaseSha && passport.release.releaseSha !== normalizedPublishEvidence.releaseSha) {
|
|
905
|
+
issues.push(issue("error", "publishEvidence.releaseSha.mismatch", "publishEvidence.releaseSha must match passport.release.releaseSha"));
|
|
906
|
+
}
|
|
907
|
+
if (passport?.release?.targetRef && normalizedPublishEvidence?.targetRef && passport.release.targetRef !== normalizedPublishEvidence.targetRef) {
|
|
908
|
+
issues.push(issue("error", "publishEvidence.targetRef.mismatch", "publishEvidence.targetRef must match passport.release.targetRef"));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
441
911
|
if (artifacts.length === 0) {
|
|
442
912
|
issues.push(issue("error", "artifacts.empty", "release passport must list at least one artifact"));
|
|
443
913
|
}
|
|
444
|
-
const
|
|
914
|
+
const evidenceIndex = indexEvidenceArtifacts(evidenceArtifacts);
|
|
445
915
|
for (const artifact of artifacts) {
|
|
446
916
|
if (!artifact.name) {
|
|
447
917
|
issues.push(issue("error", "artifact.name", "artifact name is required"));
|
|
448
918
|
continue;
|
|
449
919
|
}
|
|
450
|
-
const evidence =
|
|
920
|
+
const evidence = findEvidenceForArtifact(artifact, evidenceIndex);
|
|
451
921
|
if (!evidence) {
|
|
452
922
|
issues.push(issue("error", "artifact.evidence.missing", `artifact ${artifact.name} is missing evidence`));
|
|
453
923
|
continue;
|
|
454
924
|
}
|
|
455
|
-
|
|
456
|
-
|
|
925
|
+
const evidenceDigest = artifactDigestValue(evidence);
|
|
926
|
+
if (!evidenceDigest) {
|
|
927
|
+
issues.push(issue("error", "artifact.digest", `artifact ${artifact.name} must have a digest`));
|
|
457
928
|
}
|
|
458
929
|
if (artifact.sha256 && evidence.sha256 && artifact.sha256 !== evidence.sha256) {
|
|
459
930
|
issues.push(issue("error", "artifact.sha256.mismatch", `artifact ${artifact.name} digest differs between passport and evidence`));
|
|
460
931
|
}
|
|
932
|
+
if (artifact.digest && evidenceDigest && artifact.digest !== evidenceDigest && artifact.digest !== `sha256:${evidenceDigest}`) {
|
|
933
|
+
issues.push(issue("error", "artifact.digest.mismatch", `artifact ${artifact.name} digest differs between passport and evidence`));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (passport?.packageSet) {
|
|
937
|
+
const main = passport.packageSet.main || {};
|
|
938
|
+
const checkPackageEntry = (entry, label, role) => {
|
|
939
|
+
if (!entry.name || !entry.version) {
|
|
940
|
+
issues.push(issue("error", `${label}.identity`, `${label} must include name and version`));
|
|
941
|
+
}
|
|
942
|
+
if (!entry.distTag) {
|
|
943
|
+
issues.push(issue("error", `${label}.distTag`, `${label} must include distTag`));
|
|
944
|
+
}
|
|
945
|
+
if (!entry.digest) {
|
|
946
|
+
issues.push(issue("error", `${label}.digest`, `${label} must include digest`));
|
|
947
|
+
}
|
|
948
|
+
if (entry.name && entry.version) {
|
|
949
|
+
const artifact = findEvidenceForArtifact({
|
|
950
|
+
group: "node",
|
|
951
|
+
kind: "npm",
|
|
952
|
+
name: entry.name,
|
|
953
|
+
ref: entry.version,
|
|
954
|
+
digest: entry.digest,
|
|
955
|
+
}, evidenceIndex);
|
|
956
|
+
if (!artifact) {
|
|
957
|
+
issues.push(issue("error", `${label}.artifact`, `${label} must have matching npm artifact evidence`, {
|
|
958
|
+
role,
|
|
959
|
+
name: entry.name,
|
|
960
|
+
version: entry.version,
|
|
961
|
+
}));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
checkPackageEntry(main, "packageSet.main", "main");
|
|
966
|
+
const platforms = Array.isArray(passport.packageSet.platforms) ? passport.packageSet.platforms : [];
|
|
967
|
+
if (platforms.length < 3) {
|
|
968
|
+
issues.push(issue("error", "packageSet.platforms", "packageSet must include at least three platform packages"));
|
|
969
|
+
}
|
|
970
|
+
for (const [index, entry] of platforms.entries()) {
|
|
971
|
+
checkPackageEntry(entry, `packageSet.platforms[${index}]`, "platform");
|
|
972
|
+
}
|
|
973
|
+
if (!Array.isArray(passport?.publish?.packages) || passport.publish.packages.length !== 1 + platforms.length) {
|
|
974
|
+
issues.push(issue("error", "publish.packages", "publish.packages must summarize main and platform packages"));
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (passport?.trustedPublishing) {
|
|
978
|
+
if (!passport.trustedPublishing.provider) {
|
|
979
|
+
issues.push(issue("error", "trustedPublishing.provider", "trustedPublishing.provider is required"));
|
|
980
|
+
}
|
|
981
|
+
if (passport.trustedPublishing.auth !== "trusted-publishing") {
|
|
982
|
+
issues.push(issue("error", "trustedPublishing.auth", "trustedPublishing.auth must be trusted-publishing"));
|
|
983
|
+
}
|
|
984
|
+
if (passport.trustedPublishing.enabled !== true) {
|
|
985
|
+
issues.push(issue("error", "trustedPublishing.enabled", "trusted publishing evidence must be enabled"));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (passport?.transaction) {
|
|
989
|
+
if (!passport.transaction.state) {
|
|
990
|
+
issues.push(issue("error", "transaction.state", "transaction.state is required"));
|
|
991
|
+
} else if (passport.transaction.state !== "complete") {
|
|
992
|
+
issues.push(issue("error", "transaction.state", "release passport transaction state must be complete"));
|
|
993
|
+
}
|
|
994
|
+
if (!passport.transaction.exactTag) {
|
|
995
|
+
issues.push(issue("error", "transaction.exactTag", "transaction.exactTag is required"));
|
|
996
|
+
}
|
|
997
|
+
if (!passport.transaction.releaseSha) {
|
|
998
|
+
issues.push(issue("error", "transaction.releaseSha", "transaction.releaseSha is required"));
|
|
999
|
+
}
|
|
1000
|
+
if (!passport.transaction.releaseMaterialSha) {
|
|
1001
|
+
issues.push(issue("error", "transaction.releaseMaterialSha", "transaction.releaseMaterialSha is required"));
|
|
1002
|
+
}
|
|
1003
|
+
if (!passport.transaction.stateRef) {
|
|
1004
|
+
issues.push(issue("error", "transaction.stateRef", "transaction.stateRef is required"));
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (passport?.anchorManifest) {
|
|
1008
|
+
if (!passport.anchorManifest.sha256) {
|
|
1009
|
+
issues.push(issue("error", "anchorManifest.sha256", "anchorManifest.sha256 is required"));
|
|
1010
|
+
}
|
|
1011
|
+
if (!passport.anchorManifest.fields || typeof passport.anchorManifest.fields !== "object" || Array.isArray(passport.anchorManifest.fields)) {
|
|
1012
|
+
issues.push(issue("error", "anchorManifest.fields", "anchorManifest.fields must be an object"));
|
|
1013
|
+
}
|
|
461
1014
|
}
|
|
462
1015
|
if (!passport?.runnerPolicy?.productionDefault) {
|
|
463
1016
|
issues.push(issue("warning", "runnerPolicy.productionDefault", "runner policy should record the production default"));
|
|
@@ -472,6 +1025,15 @@ export function createReleaseCheckReport({
|
|
|
472
1025
|
completeness: {
|
|
473
1026
|
artifactCount: artifacts.length,
|
|
474
1027
|
evidenceArtifactCount: evidenceArtifacts.length,
|
|
1028
|
+
packageSetPresent: Boolean(passport?.packageSet),
|
|
1029
|
+
trustedPublishingPresent: Boolean(passport?.trustedPublishing),
|
|
1030
|
+
transactionPresent: Boolean(passport?.transaction),
|
|
1031
|
+
anchorManifestPresent: Boolean(passport?.anchorManifest),
|
|
1032
|
+
buildSummaryPresent: Boolean(passport?.buildSummary),
|
|
1033
|
+
platformArtifactManifestCount: Array.isArray(passport?.platformArtifactManifests)
|
|
1034
|
+
? passport.platformArtifactManifests.length
|
|
1035
|
+
: 0,
|
|
1036
|
+
distTagPromotionEvidencePresent: Boolean(passport?.distTagPromotion),
|
|
475
1037
|
impactPresent: Boolean(impact),
|
|
476
1038
|
agentIndexPresent: Boolean(agentIndex),
|
|
477
1039
|
productMechanismPresent: Boolean(productMechanism),
|
|
@@ -514,6 +1076,7 @@ export async function readJsonFromLocation(location) {
|
|
|
514
1076
|
export async function verifyReleasePassport({
|
|
515
1077
|
passportLocation,
|
|
516
1078
|
artifactEvidenceLocation = "",
|
|
1079
|
+
publishEvidenceLocation = "",
|
|
517
1080
|
impactLocation = "",
|
|
518
1081
|
agentIndexLocation = "",
|
|
519
1082
|
productMechanismLocation = "",
|
|
@@ -524,6 +1087,10 @@ export async function verifyReleasePassport({
|
|
|
524
1087
|
artifactEvidenceLocation
|
|
525
1088
|
? await readJsonFromLocation(artifactEvidenceLocation)
|
|
526
1089
|
: resolveSiblingJson(basePath, passport.evidence?.artifactEvidence) || {};
|
|
1090
|
+
const publishEvidence =
|
|
1091
|
+
publishEvidenceLocation
|
|
1092
|
+
? await readJsonFromLocation(publishEvidenceLocation)
|
|
1093
|
+
: resolveSiblingJson(basePath, passport.evidence?.publishEvidence) || {};
|
|
527
1094
|
const impact =
|
|
528
1095
|
impactLocation
|
|
529
1096
|
? await readJsonFromLocation(impactLocation)
|
|
@@ -539,6 +1106,7 @@ export async function verifyReleasePassport({
|
|
|
539
1106
|
return createReleaseCheckReport({
|
|
540
1107
|
passport,
|
|
541
1108
|
artifactEvidence,
|
|
1109
|
+
publishEvidence,
|
|
542
1110
|
impact,
|
|
543
1111
|
agentIndex,
|
|
544
1112
|
productMechanism,
|