@kungfu-tech/buildchain 3.0.2-alpha.7 → 3.0.2-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -2
  2. package/contracts/auditable-demo-media-profiles-v1.json +168 -0
  3. package/contracts/evidence/auditable-demo-web-delivery-v1.json +103 -0
  4. package/contracts/fixtures/auditable-demo-web-delivery-v1/complete-transcript.txt +2 -0
  5. package/contracts/fixtures/auditable-demo-web-delivery-v1/public-projection.json +16 -0
  6. package/contracts/fixtures/auditable-demo-web-delivery-v1/scene.json +12 -0
  7. package/dist/site/buildchain-contract.json +44 -26
  8. package/dist/site/buildchain-site.json +42 -32
  9. package/dist/site/capability-registry.json +3 -3
  10. package/dist/site/controller-registry.json +11 -3
  11. package/dist/site/kfd-claims.json +74 -8
  12. package/dist/site/kfd-upstream-aggregate.json +1 -1
  13. package/dist/site/manual-registry.json +7 -7
  14. package/dist/site/node-api-registry.json +44 -5
  15. package/dist/site/page-registry.json +30 -20
  16. package/dist/site/public-surface-audit.json +38 -10
  17. package/dist/site/publication-authority-registry.json +26 -1
  18. package/dist/site/publication-registry.json +4 -4
  19. package/dist/site/release-provenance.json +3 -0
  20. package/dist/site/site-manifest.json +11 -11
  21. package/dist/site/workflow-registry.json +41 -7
  22. package/docs/MAP.md +2 -0
  23. package/docs/auditable-demo.md +55 -3
  24. package/docs/dev-alpha-candidate-patrol.md +9 -0
  25. package/docs/github-artifact-attestation.md +1 -1
  26. package/docs/release-governance.md +32 -0
  27. package/docs/reusable-build-surface.md +73 -58
  28. package/docs/runtime-train-validation.md +21 -0
  29. package/docs/versioning.md +1 -0
  30. package/package.json +5 -1
  31. package/packages/core/artifact-signing-result.js +228 -0
  32. package/packages/core/artifact-signing.js +412 -0
  33. package/packages/core/buildchain-config.js +58 -0
  34. package/packages/core/buildchain-contract.js +8 -0
  35. package/packages/core/buildchain-publication-authority.js +1 -0
  36. package/packages/core/detached-artifact-signature.js +121 -0
  37. package/packages/core/github-governance-authority.js +6 -0
  38. package/packages/core/index.js +27 -0
  39. package/scripts/auditable-demo.mjs +491 -22
  40. package/scripts/buildchain-channel-router.mjs +8 -2
  41. package/scripts/check-inventory.mjs +5 -0
  42. package/scripts/dev-alpha-candidate-patrol.mjs +18 -0
  43. package/scripts/dispatch-artifact-signing-authority.mjs +152 -0
  44. package/scripts/finalize-native-artifact-signing-result.mjs +96 -0
  45. package/scripts/generate-site-bundle.mjs +3 -0
  46. package/scripts/import-artifact-signing-results.mjs +76 -0
  47. package/scripts/inspect-artifact-signing-requests.mjs +101 -0
  48. package/scripts/materialize-artifact-signing-request.mjs +66 -0
  49. package/scripts/merge-artifact-signing-results.mjs +76 -0
  50. package/scripts/release-line-policy.mjs +27 -0
  51. package/scripts/runtime-ref-core.mjs +10 -6
  52. package/scripts/seal-artifact-signing-requests.mjs +368 -0
  53. package/scripts/sign-detached-artifact-requests.mjs +237 -0
  54. package/scripts/verify-artifact-signing-results.mjs +99 -0
@@ -262,6 +262,33 @@ export {
262
262
  verifyArtifactVerificationEnvelope,
263
263
  } from "./artifact-verification-envelope.js";
264
264
 
265
+ export {
266
+ ARTIFACT_SIGNING_AUTHORITY_CONTRACT,
267
+ ARTIFACT_SIGNING_RECEIPT_CONTRACT,
268
+ ARTIFACT_SIGNING_REQUEST_CONTRACT,
269
+ artifactSigningDigest,
270
+ createArtifactSigningReceipt,
271
+ createArtifactSigningRequest,
272
+ listArtifactSigningProfiles,
273
+ resolveArtifactSigningProfile,
274
+ validateArtifactSigningReceipt,
275
+ validateArtifactSigningRequest,
276
+ } from "./artifact-signing.js";
277
+
278
+ export {
279
+ ARTIFACT_SIGNING_RESULT_CONTRACT,
280
+ artifactSigningEvidenceDigest,
281
+ createArtifactSigningResult,
282
+ validateArtifactSigningResult,
283
+ verifyArtifactSigningResultFiles,
284
+ } from "./artifact-signing-result.js";
285
+
286
+ export {
287
+ DETACHED_ARTIFACT_SIGNATURE_CONTRACT,
288
+ signDetachedArtifactRequest,
289
+ verifyDetachedArtifactSignature,
290
+ } from "./detached-artifact-signature.js";
291
+
265
292
  export {
266
293
  BUILDCHAIN_CONTRACT_LOCK,
267
294
  BUILDCHAIN_RUNTIME_CONTRACT_WORLD,
@@ -11,6 +11,12 @@ import { fileURLToPath } from "node:url";
11
11
  const UTF8 = new TextDecoder("utf-8", { fatal: true });
12
12
  const IMAGE_PATTERN = /^[a-z0-9][a-z0-9./_-]*@sha256:[0-9a-f]{64}$/;
13
13
  const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
14
+ const MAX_BUNDLE_MEMBER_BYTES = 8 * 1024 * 1024;
15
+ const SCRIPT_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
16
+ const MEDIA_PROFILE_CATALOG = path.resolve(
17
+ SCRIPT_DIRECTORY,
18
+ "../contracts/auditable-demo-media-profiles-v1.json",
19
+ );
14
20
  const REQUIRED_ADAPTER_FILES = [
15
21
  "complete-transcript.txt",
16
22
  "public-projection.json",
@@ -119,7 +125,10 @@ function verifyChecksums(root, checksumName = "checksums.sha256") {
119
125
  const target = resolveInside(root, member, "checksum member");
120
126
  invariant(!declared.has(member), `duplicate checksum member: ${member}`);
121
127
  declared.add(member);
122
- invariant(sha256(readRegular(target, member)).slice(7) === match[1], `checksum mismatch: ${member}`);
128
+ invariant(
129
+ sha256(readRegular(target, member, MAX_BUNDLE_MEMBER_BYTES)).slice(7) === match[1],
130
+ `checksum mismatch: ${member}`,
131
+ );
123
132
  }
124
133
  const actual = listFiles(root).filter((name) => name !== checksumName);
125
134
  invariant(
@@ -392,40 +401,465 @@ function prepareSmoke(values) {
392
401
  writeJson(path.join(output, "public-projection.json"), projection);
393
402
  }
394
403
 
395
- function verifyRendererOutput(renderOutput, expectedImage, expectedInputs) {
404
+ function semanticRoot(value) {
405
+ return sha256(Buffer.from(stableJson(value)));
406
+ }
407
+
408
+ function validateBudgetBasis(entry, label) {
409
+ exactKeys(
410
+ entry.budgetBasis,
411
+ ["evidence", "observedPath", "observedBytes", "multiplier", "rounding"],
412
+ [],
413
+ `${label}.budgetBasis`,
414
+ );
415
+ invariant(
416
+ entry.budgetBasis.evidence === "contracts/evidence/auditable-demo-web-delivery-v1.json",
417
+ `${label}.budgetBasis.evidence is unsupported`,
418
+ );
419
+ text(entry.budgetBasis.observedPath, 1, 128, `${label}.budgetBasis.observedPath`);
420
+ const observedBytes = integer(
421
+ entry.budgetBasis.observedBytes,
422
+ 1,
423
+ MAX_BUNDLE_MEMBER_BYTES,
424
+ `${label}.budgetBasis.observedBytes`,
425
+ );
426
+ const multiplier = integer(entry.budgetBasis.multiplier, 1, 64, `${label}.budgetBasis.multiplier`);
427
+ invariant(entry.budgetBasis.rounding === "next-power-of-two", `${label}.budgetBasis.rounding is unsupported`);
428
+ const expected = 2 ** Math.ceil(Math.log2(observedBytes * multiplier));
429
+ invariant(entry.maximumBytes === expected, `${label}.maximumBytes does not match its measured budget basis`);
430
+ }
431
+
432
+ function loadMediaProfile(profileId) {
433
+ const catalog = readJson(MEDIA_PROFILE_CATALOG, "auditable demo media profile catalog");
434
+ invariant(
435
+ catalog.schema === "buildchain.auditable-demo-media-profiles/v1",
436
+ "unsupported media profile catalog",
437
+ );
438
+ invariant(catalog.profiles && typeof catalog.profiles === "object", "media profile catalog is invalid");
439
+ const seen = new Set();
440
+ const resolve = (identifier) => {
441
+ invariant(!seen.has(identifier), `media profile inheritance cycle: ${identifier}`);
442
+ const declared = catalog.profiles[identifier];
443
+ invariant(declared && typeof declared === "object", `unsupported media profile: ${identifier}`);
444
+ seen.add(identifier);
445
+ const inherited = declared.extends ? resolve(declared.extends) : {
446
+ mode: "archive",
447
+ renditions: [],
448
+ singletonRoles: [],
449
+ additionalRenditions: null,
450
+ };
451
+ seen.delete(identifier);
452
+ const byPath = new Map(inherited.renditions.map((entry) => [entry.path, entry]));
453
+ for (const entry of declared.renditions || []) {
454
+ invariant(entry && typeof entry === "object" && typeof entry.path === "string", `${identifier} rendition is invalid`);
455
+ byPath.set(entry.path, { ...(byPath.get(entry.path) || {}), ...entry });
456
+ }
457
+ return {
458
+ mode: declared.mode || inherited.mode,
459
+ renditions: [...byPath.values()],
460
+ singletonRoles: [...new Set([...(inherited.singletonRoles || []), ...(declared.singletonRoles || [])])],
461
+ additionalRenditions: declared.additionalRenditions || inherited.additionalRenditions || null,
462
+ };
463
+ };
464
+ const resolved = resolve(profileId);
465
+ const profile = { id: profileId, ...resolved };
466
+ for (const [index, entry] of profile.renditions.entries()) {
467
+ if (profile.mode === "web-delivery") {
468
+ invariant(entry.budgetBasis, `${profileId}.renditions[${index}].budgetBasis is required`);
469
+ validateBudgetBasis(entry, `${profileId}.renditions[${index}]`);
470
+ }
471
+ }
472
+ return {
473
+ catalog,
474
+ catalogRoot: semanticRoot(catalog),
475
+ profile,
476
+ profileRoot: semanticRoot(profile),
477
+ };
478
+ }
479
+
480
+ function inspectIsoBmffFastStart(filePath) {
481
+ const bytes = readRegular(filePath, "MP4 rendition", MAX_BUNDLE_MEMBER_BYTES);
482
+ let offset = 0;
483
+ let moovOffset = -1;
484
+ let mdatOffset = -1;
485
+ let ftypSeen = false;
486
+ while (offset + 8 <= bytes.length) {
487
+ let size = bytes.readUInt32BE(offset);
488
+ const type = bytes.toString("ascii", offset + 4, offset + 8);
489
+ let headerSize = 8;
490
+ if (size === 1) {
491
+ invariant(offset + 16 <= bytes.length, "MP4 extended box header is truncated");
492
+ const extended = bytes.readBigUInt64BE(offset + 8);
493
+ invariant(extended <= BigInt(Number.MAX_SAFE_INTEGER), "MP4 box size is too large");
494
+ size = Number(extended);
495
+ headerSize = 16;
496
+ } else if (size === 0) {
497
+ size = bytes.length - offset;
498
+ }
499
+ invariant(size >= headerSize && offset + size <= bytes.length, "MP4 box layout is invalid");
500
+ if (type === "ftyp") ftypSeen = true;
501
+ if (type === "moov" && moovOffset === -1) moovOffset = offset;
502
+ if (type === "mdat" && mdatOffset === -1) mdatOffset = offset;
503
+ offset += size;
504
+ }
505
+ invariant(offset === bytes.length && ftypSeen, "MP4 top-level boxes are incomplete");
506
+ if (moovOffset === -1 || mdatOffset === -1) return "missing-moov-or-mdat";
507
+ return moovOffset < mdatOffset ? "moov-before-mdat" : "mdat-before-moov";
508
+ }
509
+
510
+ function rationalNumber(value) {
511
+ const match = /^([0-9]+)\/([0-9]+)$/.exec(String(value || ""));
512
+ if (!match || Number(match[2]) === 0) return 0;
513
+ return Number(match[1]) / Number(match[2]);
514
+ }
515
+
516
+ function inspectMediaFile(filePath) {
517
+ const result = spawnSync(
518
+ "ffprobe",
519
+ [
520
+ "-v", "error",
521
+ "-show_entries", "format=format_name,duration:stream=codec_type,codec_name,pix_fmt,width,height,avg_frame_rate",
522
+ "-of", "json",
523
+ filePath,
524
+ ],
525
+ { encoding: "utf8", maxBuffer: 1024 * 1024 },
526
+ );
527
+ invariant(!result.error && result.status === 0, `ffprobe failed for ${path.basename(filePath)}`);
528
+ let parsed;
529
+ try {
530
+ parsed = JSON.parse(result.stdout);
531
+ } catch {
532
+ throw new Error(`ffprobe returned invalid JSON for ${path.basename(filePath)}`);
533
+ }
534
+ const streams = Array.isArray(parsed.streams) ? parsed.streams : [];
535
+ const videos = streams.filter((entry) => entry.codec_type === "video");
536
+ const audioStreams = streams.filter((entry) => entry.codec_type === "audio").length;
537
+ invariant(videos.length === 1, `${path.basename(filePath)} must contain exactly one video or image stream`);
538
+ const video = videos[0];
539
+ const formatNames = String(parsed.format?.format_name || "").split(",");
540
+ let container = formatNames.includes("mp4") ? "mp4" : "";
541
+ if (formatNames.includes("webm")) container = "webm";
542
+ if (formatNames.includes("gif") || video.codec_name === "gif") container = "gif";
543
+ if (video.codec_name === "png") container = "png";
544
+ if (video.codec_name === "webp") container = "webp";
545
+ if (video.codec_name === "av1" && formatNames.includes("avif")) container = "avif";
546
+ const duration = Number(parsed.format?.duration || 0);
547
+ return {
548
+ container,
549
+ videoCodec: String(video.codec_name || ""),
550
+ pixelFormat: String(video.pix_fmt || ""),
551
+ width: Number(video.width || 0),
552
+ height: Number(video.height || 0),
553
+ durationMs: Number.isFinite(duration) ? Math.round(duration * 1000) : 0,
554
+ frameRate: rationalNumber(video.avg_frame_rate),
555
+ audioStreams,
556
+ progressiveDownload: container === "mp4" ? inspectIsoBmffFastStart(filePath) : "not-applicable",
557
+ };
558
+ }
559
+
560
+ function inspectRendererMedia(values) {
561
+ const renderOutput = path.resolve(required(values, "--render-output"));
562
+ const output = path.resolve(required(values, "--output"));
563
+ const rendererImage = required(values, "--renderer-image");
564
+ invariant(IMAGE_PATTERN.test(rendererImage), "renderer image must use an immutable sha256 coordinate");
565
+ invariant(!fs.existsSync(output), "media inspection output must not already exist");
566
+ const manifest = readJson(path.join(renderOutput, "manifest.json"), "renderer manifest");
567
+ invariant(manifest.schema === "build-images.auditable-demo-render/v1", "unexpected renderer manifest schema");
568
+ invariant(manifest.renderer?.image === rendererImage, "renderer manifest image coordinate mismatch");
569
+ const members = Object.keys(manifest.outputs || {})
570
+ .filter((name) => name !== "media-probe.json")
571
+ .sort()
572
+ .map((name) => {
573
+ const target = resolveInside(renderOutput, name, "media inspection member");
574
+ const bytes = readRegular(target, name, MAX_BUNDLE_MEMBER_BYTES);
575
+ return {
576
+ path: name,
577
+ root: sha256(bytes),
578
+ bytes: bytes.length,
579
+ facts: inspectMediaFile(target),
580
+ };
581
+ });
582
+ const body = {
583
+ schema: "buildchain.auditable-demo-media-inspection/v1",
584
+ rendererImage,
585
+ members,
586
+ };
587
+ writeJson(output, { ...body, inspectionRoot: semanticRoot(body) });
588
+ }
589
+
590
+ function qualifyMediaFixture(values) {
591
+ const renderOutput = path.resolve(required(values, "--render-output"));
592
+ const output = path.resolve(required(values, "--output"));
593
+ const rendererImage = required(values, "--renderer-image");
594
+ const rendererSourceRepository = required(values, "--renderer-source-repository");
595
+ const rendererSourceRef = required(values, "--renderer-source-ref");
596
+ const rendererSourceSha = required(values, "--renderer-source-sha");
597
+ const mediaProfile = required(values, "--media-profile");
598
+ invariant(IMAGE_PATTERN.test(rendererImage), "renderer image must use an immutable sha256 coordinate");
599
+ invariant(/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/.test(rendererSourceRepository), "renderer source repository is invalid");
600
+ invariant(/^refs\/tags\/[a-zA-Z0-9._-]+$/.test(rendererSourceRef), "renderer source ref must be an exact tag ref");
601
+ invariant(/^[0-9a-f]{40}$/.test(rendererSourceSha), "renderer source SHA must be exact");
602
+ invariant(!fs.existsSync(output), "media fixture evidence output must not already exist");
603
+ const mediaInspection = loadMediaInspection(
604
+ path.resolve(required(values, "--media-inspection")),
605
+ renderOutput,
606
+ rendererImage,
607
+ );
608
+ const verified = verifyRendererOutput(renderOutput, rendererImage, {
609
+ scene: path.join(renderOutput, "scene.json"),
610
+ transcript: path.join(renderOutput, "complete-transcript.txt"),
611
+ projection: path.join(renderOutput, "public-projection.json"),
612
+ }, {
613
+ mediaProfile,
614
+ inspectMedia: mediaInspection.inspectMedia,
615
+ inspectionRoot: mediaInspection.inspectionRoot,
616
+ });
617
+ const body = {
618
+ schema: "buildchain.auditable-demo-media-profile-fixture/v1",
619
+ renderer: {
620
+ image: rendererImage,
621
+ sourceRepository: rendererSourceRepository,
622
+ sourceRef: rendererSourceRef,
623
+ sourceSha: rendererSourceSha,
624
+ },
625
+ inputs: Object.fromEntries(
626
+ ["complete-transcript.txt", "public-projection.json", "scene.json"].map((name) => [
627
+ name,
628
+ sha256(readRegular(path.join(renderOutput, name), `fixture ${name}`)),
629
+ ]),
630
+ ),
631
+ rendererManifestRoot: sha256(readRegular(path.join(renderOutput, "manifest.json"), "renderer manifest")),
632
+ qualification: verified.qualification,
633
+ };
634
+ writeJson(output, { ...body, evidenceRoot: semanticRoot(body) });
635
+ }
636
+
637
+ function loadMediaInspection(filePath, renderOutput, rendererImage) {
638
+ const value = readJson(filePath, "media inspection");
639
+ exactKeys(value, ["schema", "rendererImage", "members", "inspectionRoot"], [], "mediaInspection");
640
+ invariant(value.schema === "buildchain.auditable-demo-media-inspection/v1", "unsupported media inspection schema");
641
+ invariant(value.rendererImage === rendererImage, "media inspection renderer image mismatch");
642
+ invariant(Array.isArray(value.members), "mediaInspection.members must be an array");
643
+ const body = {
644
+ schema: value.schema,
645
+ rendererImage: value.rendererImage,
646
+ members: value.members,
647
+ };
648
+ invariant(value.inspectionRoot === semanticRoot(body), "media inspection root mismatch");
649
+ const byPath = new Map();
650
+ for (const [index, entry] of value.members.entries()) {
651
+ exactKeys(entry, ["path", "root", "bytes", "facts"], [], `mediaInspection.members[${index}]`);
652
+ text(entry.path, 1, 256, `mediaInspection.members[${index}].path`);
653
+ invariant(DIGEST_PATTERN.test(entry.root), `mediaInspection.members[${index}].root is invalid`);
654
+ integer(entry.bytes, 1, MAX_BUNDLE_MEMBER_BYTES, `mediaInspection.members[${index}].bytes`);
655
+ exactKeys(
656
+ entry.facts,
657
+ [
658
+ "container",
659
+ "videoCodec",
660
+ "pixelFormat",
661
+ "width",
662
+ "height",
663
+ "durationMs",
664
+ "frameRate",
665
+ "audioStreams",
666
+ "progressiveDownload",
667
+ ],
668
+ [],
669
+ `mediaInspection.members[${index}].facts`,
670
+ );
671
+ const facts = {
672
+ container: text(entry.facts.container, 1, 32, `mediaInspection.members[${index}].facts.container`),
673
+ videoCodec: text(entry.facts.videoCodec, 1, 32, `mediaInspection.members[${index}].facts.videoCodec`),
674
+ pixelFormat: text(entry.facts.pixelFormat, 0, 32, `mediaInspection.members[${index}].facts.pixelFormat`),
675
+ width: integer(entry.facts.width, 1, 16384, `mediaInspection.members[${index}].facts.width`),
676
+ height: integer(entry.facts.height, 1, 16384, `mediaInspection.members[${index}].facts.height`),
677
+ durationMs: integer(entry.facts.durationMs, 0, 3_600_000, `mediaInspection.members[${index}].facts.durationMs`),
678
+ frameRate: entry.facts.frameRate,
679
+ audioStreams: integer(entry.facts.audioStreams, 0, 64, `mediaInspection.members[${index}].facts.audioStreams`),
680
+ progressiveDownload: text(
681
+ entry.facts.progressiveDownload,
682
+ 1,
683
+ 32,
684
+ `mediaInspection.members[${index}].facts.progressiveDownload`,
685
+ ),
686
+ };
687
+ invariant(
688
+ Number.isFinite(facts.frameRate) && facts.frameRate >= 0 && facts.frameRate <= 240,
689
+ `mediaInspection.members[${index}].facts.frameRate is out of range`,
690
+ );
691
+ invariant(!byPath.has(entry.path), `duplicate media inspection member: ${entry.path}`);
692
+ const target = resolveInside(renderOutput, entry.path, "media inspection member");
693
+ const bytes = readRegular(target, entry.path, MAX_BUNDLE_MEMBER_BYTES);
694
+ invariant(entry.root === sha256(bytes), `media inspection member root mismatch: ${entry.path}`);
695
+ invariant(entry.bytes === bytes.length, `media inspection member byte count mismatch: ${entry.path}`);
696
+ byPath.set(entry.path, facts);
697
+ }
698
+ return {
699
+ inspectionRoot: value.inspectionRoot,
700
+ inspectMedia: (target) => {
701
+ const facts = byPath.get(path.relative(renderOutput, target).split(path.sep).join("/"));
702
+ invariant(facts, `media inspection facts are missing: ${path.basename(target)}`);
703
+ return facts;
704
+ },
705
+ };
706
+ }
707
+
708
+ function constraintsForAdditionalRendition(entry, policy) {
709
+ const common = {
710
+ path: entry.path,
711
+ role: entry.role,
712
+ mimeType: entry.mimeType,
713
+ maximumBytes: policy.maximumBytesByMimeType[entry.mimeType],
714
+ audioStreams: 0,
715
+ };
716
+ if (entry.mimeType === "video/mp4") {
717
+ return { ...common, container: "mp4", videoCodec: "h264", pixelFormat: "yuv420p", progressiveDownload: "moov-before-mdat" };
718
+ }
719
+ if (entry.mimeType === "video/webm") {
720
+ return { ...common, container: "webm", videoCodec: "vp9", pixelFormat: "yuv420p" };
721
+ }
722
+ if (entry.mimeType === "image/webp") return { ...common, container: "webp", videoCodec: "webp" };
723
+ if (entry.mimeType === "image/avif") return { ...common, container: "avif", videoCodec: "av1" };
724
+ throw new Error(`additional rendition MIME type is not allowed: ${entry.mimeType}`);
725
+ }
726
+
727
+ function qualifyRendererOutput(renderOutput, manifest, scene, profileId, inspectMedia, inspectionRoot = "") {
728
+ const loaded = loadMediaProfile(profileId);
729
+ const { profile, catalog } = loaded;
730
+ const outputs = manifest.outputs || {};
731
+ const rules = profile.renditions.map((entry) => ({ ...entry }));
732
+ const knownPaths = new Set(rules.map((entry) => entry.path));
733
+ const declaration = manifest.webDelivery;
734
+ if (declaration !== undefined) {
735
+ exactKeys(declaration, ["schema", "renditions"], [], "manifest.webDelivery");
736
+ invariant(
737
+ declaration.schema === "build-images.auditable-demo-web-delivery/v1",
738
+ "unsupported renderer web-delivery declaration",
739
+ );
740
+ invariant(Array.isArray(declaration.renditions), "manifest.webDelivery.renditions must be an array");
741
+ for (const [index, entry] of declaration.renditions.entries()) {
742
+ exactKeys(entry, ["path", "role", "mimeType"], [], `manifest.webDelivery.renditions[${index}]`);
743
+ invariant(!knownPaths.has(entry.path), `renderer web-delivery path is already profile-owned: ${entry.path}`);
744
+ const policy = profile.additionalRenditions;
745
+ invariant(policy, `media profile ${profileId} does not admit additional renditions`);
746
+ invariant(policy.allowedRoles.includes(entry.role), `additional rendition role is not allowed: ${entry.role}`);
747
+ invariant(policy.allowedMimeTypes.includes(entry.mimeType), `additional rendition MIME type is not allowed: ${entry.mimeType}`);
748
+ const maximumBytes = policy.maximumBytesByMimeType?.[entry.mimeType];
749
+ integer(maximumBytes, 1, MAX_BUNDLE_MEMBER_BYTES, `media profile ${profileId} additional ${entry.mimeType} budget`);
750
+ rules.push(constraintsForAdditionalRendition(entry, policy));
751
+ knownPaths.add(entry.path);
752
+ }
753
+ }
754
+ for (const name of Object.keys(outputs)) {
755
+ if (name !== "media-probe.json") invariant(knownPaths.has(name), `unbound renderer output: ${name}`);
756
+ }
757
+ const singletonRoles = new Set(profile.singletonRoles || []);
758
+ const observedRoles = new Set();
759
+ const renditions = [];
760
+ for (const rule of rules) {
761
+ const declared = outputs[rule.path];
762
+ invariant(declared && typeof declared === "object", `required renderer output is missing: ${rule.path}`);
763
+ const target = resolveInside(renderOutput, rule.path, "renderer output path");
764
+ const bytes = readRegular(target, rule.path, MAX_BUNDLE_MEMBER_BYTES);
765
+ invariant(declared.root === sha256(bytes), `renderer manifest root mismatch: ${rule.path}`);
766
+ invariant(declared.bytes === bytes.length, `renderer manifest byte count mismatch: ${rule.path}`);
767
+ if (rule.maximumBytes !== undefined) {
768
+ invariant(bytes.length <= rule.maximumBytes, `${rule.path} byte budget exceeded`);
769
+ }
770
+ if (singletonRoles.has(rule.role)) {
771
+ invariant(!observedRoles.has(rule.role), `duplicate singleton role: ${rule.role}`);
772
+ observedRoles.add(rule.role);
773
+ }
774
+ const facts = profile.mode === "web-delivery" ? inspectMedia(target) : {};
775
+ if (profile.mode === "web-delivery") {
776
+ invariant(facts && typeof facts === "object", `${rule.path} inspection is missing`);
777
+ invariant(facts.container === rule.container, `${rule.path} container mismatch`);
778
+ invariant(facts.videoCodec === rule.videoCodec, `${rule.path} video codec mismatch`);
779
+ if (rule.pixelFormat) invariant(facts.pixelFormat === rule.pixelFormat, `${rule.path} pixel format mismatch`);
780
+ invariant(facts.audioStreams === rule.audioStreams, `${rule.path} audio stream policy failed`);
781
+ invariant(facts.width === scene.width && facts.height === scene.height, `${rule.path} dimensions mismatch`);
782
+ if (facts.durationMs > 0) {
783
+ invariant(
784
+ Math.abs(facts.durationMs - scene.durationMs) <= catalog.qualification.durationToleranceMs,
785
+ `${rule.path} duration mismatch`,
786
+ );
787
+ }
788
+ if (rule.frameRatePolicy === "scene-exact") {
789
+ invariant(Math.abs(facts.frameRate - scene.fps) < 0.001, `${rule.path} frame rate mismatch`);
790
+ }
791
+ if (rule.progressiveDownload) {
792
+ invariant(
793
+ facts.progressiveDownload === rule.progressiveDownload,
794
+ `${rule.path} progressive download evidence mismatch`,
795
+ );
796
+ }
797
+ }
798
+ renditions.push({
799
+ path: rule.path,
800
+ role: rule.role,
801
+ mimeType: rule.mimeType,
802
+ root: declared.root,
803
+ bytes: declared.bytes,
804
+ maximumBytes: rule.maximumBytes || 0,
805
+ ...facts,
806
+ });
807
+ }
808
+ const body = {
809
+ schema: "buildchain.auditable-demo-media-qualification/v1",
810
+ profile: {
811
+ id: profileId,
812
+ mode: profile.mode,
813
+ catalogRoot: loaded.catalogRoot,
814
+ profileRoot: loaded.profileRoot,
815
+ },
816
+ inspectionRoot,
817
+ renditions,
818
+ nonClaims: catalog.qualification.nonClaims,
819
+ };
820
+ return { ...body, qualificationRoot: semanticRoot(body) };
821
+ }
822
+
823
+ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, options = {}) {
396
824
  invariant(IMAGE_PATTERN.test(expectedImage), "renderer image must use an immutable sha256 coordinate");
397
- const expectedMembers = [
825
+ const fixedMembers = [
398
826
  "checksums.sha256",
399
827
  "complete-transcript.txt",
400
- "demo.gif",
401
- "demo.mp4",
402
- "demo.webm",
403
828
  "manifest.json",
404
- "media-probe.json",
405
- "poster.png",
406
829
  "public-projection.json",
407
830
  "scene.json",
408
831
  ];
409
- invariant(
410
- JSON.stringify(listFiles(renderOutput)) === JSON.stringify(expectedMembers),
411
- "renderer output member set is not exact",
412
- );
413
832
  verifyChecksums(renderOutput);
414
833
  const manifest = readJson(path.join(renderOutput, "manifest.json"), "renderer manifest");
415
834
  invariant(manifest.schema === "build-images.auditable-demo-render/v1", "unexpected renderer manifest schema");
416
835
  invariant(manifest.renderer?.image === expectedImage, "renderer manifest image coordinate mismatch");
417
- invariant(
418
- JSON.stringify(Object.keys(manifest.outputs || {}).sort()) ===
419
- JSON.stringify(["demo.gif", "demo.mp4", "demo.webm", "media-probe.json", "poster.png"]),
420
- "renderer manifest output set is not exact",
421
- );
836
+ const outputNames = Object.keys(manifest.outputs || {}).sort();
837
+ invariant(outputNames.includes("media-probe.json"), "renderer manifest must declare media-probe.json");
838
+ const expectedMembers = [...new Set([...fixedMembers, ...outputNames])].sort();
839
+ invariant(JSON.stringify(listFiles(renderOutput)) === JSON.stringify(expectedMembers), "renderer output member set is not exact");
840
+ for (const name of outputNames) {
841
+ invariant(!name.includes("\\") && !name.split("/").includes(".."), `renderer output path is invalid: ${name}`);
842
+ const target = resolveInside(renderOutput, name, "renderer manifest output");
843
+ const bytes = readRegular(target, name, MAX_BUNDLE_MEMBER_BYTES);
844
+ const declared = manifest.outputs[name];
845
+ invariant(declared?.root === sha256(bytes), `renderer manifest root mismatch: ${name}`);
846
+ invariant(declared?.bytes === bytes.length, `renderer manifest byte count mismatch: ${name}`);
847
+ }
422
848
  for (const [key, filePath] of Object.entries(expectedInputs)) {
423
849
  const observed = manifest.inputs?.[key]?.root;
424
850
  invariant(observed === sha256(readRegular(filePath, `${key} input`)), `renderer ${key} input root mismatch`);
425
851
  }
426
852
  const probe = readJson(path.join(renderOutput, "media-probe.json"), "media probe");
427
853
  invariant(probe.schema === "build-images.demo-media-probe/v1" && probe.passed === true, "renderer media probe failed");
428
- return { manifest, probe };
854
+ const qualification = qualifyRendererOutput(
855
+ renderOutput,
856
+ manifest,
857
+ readJson(path.join(renderOutput, "scene.json"), "renderer scene"),
858
+ options.mediaProfile || "archive-v1",
859
+ options.inspectMedia || inspectMediaFile,
860
+ options.inspectionRoot || "",
861
+ );
862
+ return { manifest, probe, qualification };
429
863
  }
430
864
 
431
865
  function finalizeGate(values) {
@@ -521,16 +955,32 @@ function finalizeMedia(values) {
521
955
  const rendererImage = required(values, "--renderer-image");
522
956
  const gateRoot = required(values, "--gate-root");
523
957
  const sourceSha = required(values, "--source-sha");
958
+ const mediaProfile = values["--media-profile"] || "archive-v1";
959
+ const selectedProfile = loadMediaProfile(mediaProfile).profile;
960
+ const mediaInspectionPath = values["--media-inspection"]
961
+ ? path.resolve(values["--media-inspection"])
962
+ : "";
963
+ const mediaInspection = selectedProfile.mode === "web-delivery"
964
+ ? loadMediaInspection(
965
+ required({ "--media-inspection": mediaInspectionPath }, "--media-inspection"),
966
+ renderOutput,
967
+ rendererImage,
968
+ )
969
+ : null;
524
970
  verifyGate({
525
971
  "--bundle": gateBundle,
526
972
  "--expected-root": gateRoot,
527
973
  "--renderer-image": rendererImage,
528
974
  "--source-sha": sourceSha,
529
975
  });
530
- verifyRendererOutput(renderOutput, rendererImage, {
976
+ const verifiedRenderer = verifyRendererOutput(renderOutput, rendererImage, {
531
977
  scene: path.join(gateBundle, "scene.json"),
532
978
  transcript: path.join(gateBundle, "complete-transcript.txt"),
533
979
  projection: path.join(gateBundle, "public-projection.json"),
980
+ }, {
981
+ mediaProfile,
982
+ inspectMedia: mediaInspection?.inspectMedia,
983
+ inspectionRoot: mediaInspection?.inspectionRoot || "",
534
984
  });
535
985
  ensureEmptyDirectory(output, "media bundle");
536
986
  for (const name of listFiles(renderOutput)) {
@@ -538,19 +988,30 @@ function finalizeMedia(values) {
538
988
  copyFile(path.join(renderOutput, name), path.join(output, destination));
539
989
  }
540
990
  copyFile(path.join(gateBundle, "gate-receipt.json"), path.join(output, "gate-receipt.json"));
541
- writeJson(path.join(output, "media-receipt.json"), {
542
- schema: "buildchain.auditable-demo-media/v1",
991
+ if (mediaInspectionPath) copyFile(mediaInspectionPath, path.join(output, "media-inspection.json"));
992
+ const commonReceipt = {
543
993
  status: "passed",
544
994
  sourceSha,
545
995
  qualifiedGateRoot: gateRoot,
546
996
  rendererImage,
547
997
  rendererManifestRoot: sha256(readRegular(path.join(renderOutput, "manifest.json"), "renderer manifest")),
548
- });
998
+ };
999
+ const mediaReceipt = selectedProfile.mode === "archive"
1000
+ ? { schema: "buildchain.auditable-demo-media/v1", ...commonReceipt }
1001
+ : {
1002
+ schema: "buildchain.auditable-demo-media/v2",
1003
+ ...commonReceipt,
1004
+ qualification: verifiedRenderer.qualification,
1005
+ qualificationRoot: verifiedRenderer.qualification.qualificationRoot,
1006
+ };
1007
+ writeJson(path.join(output, "media-receipt.json"), mediaReceipt);
549
1008
  const root = writeChecksums(output);
550
1009
  const artifactName = `auditable-demo-media-${sourceSha.slice(0, 12)}-${root.slice(7, 23)}`;
551
1010
  appendOutputs(values["--github-output"], {
552
1011
  "media-root": root,
553
1012
  "media-artifact-name": artifactName,
1013
+ "media-profile": mediaProfile,
1014
+ "media-qualification-root": verifiedRenderer.qualification.qualificationRoot,
554
1015
  });
555
1016
  process.stdout.write(stableJson({ status: "passed", root, artifactName }));
556
1017
  }
@@ -568,6 +1029,10 @@ function main(argv) {
568
1029
  return verifyGate(values);
569
1030
  case "finalize-media":
570
1031
  return finalizeMedia(values);
1032
+ case "inspect-media":
1033
+ return inspectRendererMedia(values);
1034
+ case "qualify-media-fixture":
1035
+ return qualifyMediaFixture(values);
571
1036
  default:
572
1037
  throw new Error(`unknown command: ${command || "<empty>"}`);
573
1038
  }
@@ -586,6 +1051,10 @@ if (invokedDirectly) {
586
1051
  export {
587
1052
  finalizeGate,
588
1053
  finalizeMedia,
1054
+ inspectIsoBmffFastStart,
1055
+ inspectMediaFile,
1056
+ inspectRendererMedia,
1057
+ qualifyMediaFixture,
589
1058
  prepareSmoke,
590
1059
  runAdapter,
591
1060
  sha256,
@@ -9,6 +9,7 @@ const STABLE_PUBLISH_CHANNELS = new Set(["release", "major"]);
9
9
  const OFFICIAL_REF = /^v(\d+)(?:\.\d+)?(?:-alpha)?$/;
10
10
  const EXACT_SHA = /^[0-9a-f]{40}$/i;
11
11
  const TRAIN_REF = /^(?:refs\/heads\/)?train\/v(\d+)\/v\d+\.\d+\/[A-Za-z0-9._/-]+$/;
12
+ const AUTHORITY_REF = /^(?:refs\/heads\/)?authority\/v(\d+)\/v\d+\.\d+\/[A-Za-z0-9._/-]+$/;
12
13
  const SEMVER_TAG = /^refs\/tags\/v?\d+\.\d+\.\d+(?:-([0-9A-Za-z.-]+))?$/;
13
14
 
14
15
  function normalized(value) {
@@ -36,6 +37,8 @@ function majorFrom(value) {
36
37
  if (official) return Number(official[1]);
37
38
  const train = text.match(TRAIN_REF);
38
39
  if (train) return Number(train[1]);
40
+ const authority = text.match(AUTHORITY_REF);
41
+ if (authority) return Number(authority[1]);
39
42
  const embedded = text.match(/(?:^|\/)v(\d+)(?:$|[./-])/);
40
43
  if (embedded) return Number(embedded[1]);
41
44
  const version = text.match(/^(\d+)\.\d+\.\d+/);
@@ -57,6 +60,9 @@ function classifyRequestedRef(value) {
57
60
  if (TRAIN_REF.test(value) || TRAIN_REF.test(ref)) {
58
61
  return { kind: "override", ref: normalized(value).replace(/^refs\/heads\//, "") };
59
62
  }
63
+ if (AUTHORITY_REF.test(value) || AUTHORITY_REF.test(ref)) {
64
+ return { kind: "override", ref: normalized(value).replace(/^refs\/heads\//, "") };
65
+ }
60
66
  const official = ref.match(OFFICIAL_REF);
61
67
  if (official) {
62
68
  return {
@@ -65,7 +71,7 @@ function classifyRequestedRef(value) {
65
71
  major: Number(official[1]),
66
72
  };
67
73
  }
68
- throw new Error("buildchain-ref must be an official vN/vN.M channel, a train ref, or an exact 40-character SHA");
74
+ throw new Error("buildchain-ref must be an official vN/vN.M channel, a train ref, an authority ref, or an exact 40-character SHA");
69
75
  }
70
76
 
71
77
  function selected(channel, major, source, reason) {
@@ -100,7 +106,7 @@ export function resolveBuildchainChannel({
100
106
  throw new Error(`buildchain-channel=${channel} conflicts with buildchain-ref=${explicitRef.ref}`);
101
107
  }
102
108
  if (channel !== "auto" && explicitRef.kind === "override") {
103
- throw new Error("train and exact-SHA buildchain-ref overrides require buildchain-channel=auto");
109
+ throw new Error("train, authority, and exact-SHA buildchain-ref overrides require buildchain-channel=auto");
104
110
  }
105
111
  return {
106
112
  channel: explicitRef.kind,