@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.
@@ -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
- artifacts: artifactEvidence.artifacts.map((asset) => ({
300
- name: asset.name,
301
- platform: asset.platform,
302
- sha256: asset.sha256,
303
- evidence: artifactEvidencePath,
304
- url: asset.url,
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 evidenceArtifacts = Array.isArray(artifactEvidence?.artifacts) ? artifactEvidence.artifacts : [];
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 evidenceByName = new Map(evidenceArtifacts.map((artifact) => [artifact.name, artifact]));
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 = evidenceByName.get(artifact.name);
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
- if (!/^[0-9a-f]{64}$/i.test(String(evidence.sha256 || ""))) {
456
- issues.push(issue("error", "artifact.sha256", `artifact ${artifact.name} must have a sha256 digest`));
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,