@kungfu-tech/buildchain 3.0.3-alpha.0 → 3.0.3-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +32 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +19 -1
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +41 -241
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +0 -45
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +161 -42
  19. package/dist/site/buildchain-site.json +180 -84
  20. package/dist/site/capability-registry.json +6 -6
  21. package/dist/site/cli-registry.json +932 -98
  22. package/dist/site/controller-registry.json +97 -5
  23. package/dist/site/kfd-claims.json +316 -23
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +371 -56
  27. package/dist/site/page-registry.json +155 -59
  28. package/dist/site/public-surface-audit.json +286 -24
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +570 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +35 -12
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +143 -12
  37. package/docs/consumer-issue-reporting.md +19 -1
  38. package/docs/github-governance-authority.md +2 -2
  39. package/docs/lifecycle-protocol.md +20 -2
  40. package/docs/migration-inventory.md +24 -5
  41. package/docs/ownership.md +19 -1
  42. package/docs/publication-artifacts.md +185 -18
  43. package/docs/publication-authority.md +4 -4
  44. package/docs/publish-transaction.md +78 -31
  45. package/docs/release-candidate.md +24 -0
  46. package/docs/release-flow.md +50 -32
  47. package/docs/release-governance.md +31 -31
  48. package/docs/release-passport.md +4 -0
  49. package/docs/release-propagation.md +57 -12
  50. package/docs/reusable-build-surface.md +95 -27
  51. package/docs/runtime-train-validation.md +20 -2
  52. package/docs/stable-candidate-patrol.md +6 -6
  53. package/docs/toolkit-observability.md +22 -0
  54. package/docs/versioning.md +6 -4
  55. package/docs/web-surface-deployments.md +62 -0
  56. package/package.json +7 -5
  57. package/packages/core/README.md +61 -4
  58. package/packages/core/buildchain-contract.js +8 -4
  59. package/packages/core/cache-evidence.js +1 -0
  60. package/packages/core/diagnostics.js +105 -30
  61. package/packages/core/github-governance-authority.js +10 -25
  62. package/packages/core/index.js +40 -0
  63. package/packages/core/paper-fleet.js +260 -0
  64. package/packages/core/paper-repository.js +210 -0
  65. package/packages/core/paper-work.js +365 -0
  66. package/packages/core/paper.js +3180 -0
  67. package/packages/core/public-surface-audit.js +26 -75
  68. package/packages/core/public-surface-cli.js +117 -0
  69. package/packages/core/publication-artifact.js +1 -6
  70. package/packages/core/publication-authority.js +1 -0
  71. package/packages/core/publication-reproducibility.js +947 -0
  72. package/packages/core/publication-sealed-bundle.js +190 -0
  73. package/packages/core/publish-transaction.js +147 -17
  74. package/packages/core/release-candidate.js +234 -0
  75. package/packages/core/release-passport.js +483 -201
  76. package/packages/core/release-propagation.js +175 -1
  77. package/scripts/auditable-demo.mjs +119 -5
  78. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  79. package/scripts/aws-macos-jit-core.mjs +378 -0
  80. package/scripts/aws-macos-jit.mjs +123 -0
  81. package/scripts/aws-runner-burst-core.mjs +248 -0
  82. package/scripts/aws-runner-burst.mjs +79 -0
  83. package/scripts/aws-windows-jit-core.mjs +374 -0
  84. package/scripts/aws-windows-jit.mjs +121 -0
  85. package/scripts/build-contract-core.mjs +69 -6
  86. package/scripts/build-standalone-binary.mjs +23 -0
  87. package/scripts/buildchain-cli-help.mjs +256 -0
  88. package/scripts/buildchain-contract-lock.mjs +1 -1
  89. package/scripts/check-internal-architecture.mjs +69 -2
  90. package/scripts/check-inventory.mjs +69 -2
  91. package/scripts/check-javascript-syntax.mjs +31 -0
  92. package/scripts/check-maintainability.mjs +371 -0
  93. package/scripts/compiler-cache-evidence.mjs +221 -0
  94. package/scripts/dev-alpha-candidate-patrol.mjs +268 -43
  95. package/scripts/finalize-native-artifact-signing-result.mjs +75 -4
  96. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  97. package/scripts/generate-release-candidate-passport.mjs +4 -0
  98. package/scripts/generate-site-bundle.mjs +68 -188
  99. package/scripts/import-artifact-signing-results.mjs +64 -5
  100. package/scripts/inspect-artifact-signing-requests.mjs +7 -0
  101. package/scripts/maintainability-metrics.mjs +339 -0
  102. package/scripts/npm-publish-transaction.mjs +57 -6
  103. package/scripts/paper-work-fleet-cli.mjs +563 -0
  104. package/scripts/paper.mjs +631 -0
  105. package/scripts/promotion-channel-router.mjs +16 -4
  106. package/scripts/publication-reproducibility.mjs +62 -0
  107. package/scripts/release-propagation.mjs +39 -0
  108. package/scripts/resolve-build-contract.mjs +6 -0
  109. package/scripts/route-offline-runners.mjs +236 -0
  110. package/scripts/run-lifecycle-core.mjs +62 -1
  111. package/scripts/runtime-ref-core.mjs +23 -2
  112. package/scripts/seal-artifact-signing-requests.mjs +13 -6
  113. package/scripts/site-capability-metadata.mjs +202 -0
  114. package/scripts/stable-candidate-qualification.mjs +42 -0
  115. package/scripts/web-surface-core.mjs +272 -86
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  export const RELEASE_PROPAGATION_GRAPH_CONTRACT = "kungfu-buildchain-release-propagation-graph";
6
6
  export const RELEASE_PROPAGATION_PLAN_CONTRACT = "kungfu-buildchain-release-propagation-plan";
7
7
  export const RELEASE_PROPAGATION_LOCK_CONTRACT = "kungfu-buildchain-release-propagation-lock";
8
+ export const RELEASE_PROPAGATION_RECEIPT_CONTRACT = "kungfu-buildchain-release-propagation-receipt";
8
9
 
9
10
  const SUPPORTED_CHANNELS = new Set(["alpha", "release"]);
10
11
  const SUPPORTED_CHANNEL_POLICIES = new Set(["preserve", "explicit"]);
@@ -31,6 +32,49 @@ function sha256Json(value) {
31
32
  return crypto.createHash("sha256").update(stableJson(value)).digest("hex");
32
33
  }
33
34
 
35
+ function releaseVersion(upstreamRelease) {
36
+ return upstreamRelease.package?.version || upstreamRelease.publicationArtifact?.version || "";
37
+ }
38
+
39
+ function safeBranchPart(value) {
40
+ const normalized = String(value || "")
41
+ .trim()
42
+ .toLowerCase()
43
+ .replaceAll(/[^a-z0-9._-]+/g, "-")
44
+ .replaceAll(/^-+|-+$/g, "");
45
+ if (!normalized) {
46
+ throw new Error(`release propagation branch component is empty: ${value}`);
47
+ }
48
+ return normalized;
49
+ }
50
+
51
+ function releaseIdentity(upstreamRelease) {
52
+ const version = releaseVersion(upstreamRelease);
53
+ if (!version) {
54
+ throw new Error("upstream release does not expose a package or publication version");
55
+ }
56
+ return {
57
+ repository: upstreamRelease.repository,
58
+ version,
59
+ channel: upstreamRelease.channel,
60
+ };
61
+ }
62
+
63
+ function propagationIdentity({ upstreamRelease, targetNode }) {
64
+ return {
65
+ release: releaseIdentity(upstreamRelease),
66
+ downstreamRepository: targetNode.repository,
67
+ };
68
+ }
69
+
70
+ function propagationBranch({ upstreamRelease, targetNode }) {
71
+ const identity = propagationIdentity({ upstreamRelease, targetNode });
72
+ const digest = sha256Json(identity).slice(0, 12);
73
+ const repository = safeBranchPart(upstreamRelease.repository.replace("/", "-"));
74
+ const version = safeBranchPart(releaseVersion(upstreamRelease));
75
+ return `buildchain/release-propagation/${repository}/${version}-${upstreamRelease.channel}-${digest}`;
76
+ }
77
+
34
78
  function assertPlainObject(value, label) {
35
79
  if (!value || typeof value !== "object" || Array.isArray(value)) {
36
80
  throw new Error(`${label} must be an object`);
@@ -305,6 +349,8 @@ export function createReleasePropagationLock({
305
349
  upstreamRelease,
306
350
  downstreamChannel,
307
351
  } = {}) {
352
+ const identity = propagationIdentity({ upstreamRelease, targetNode });
353
+ const propagationKey = sha256Json(identity);
308
354
  const lock = {
309
355
  schemaVersion: 1,
310
356
  contract: RELEASE_PROPAGATION_LOCK_CONTRACT,
@@ -331,6 +377,9 @@ export function createReleasePropagationLock({
331
377
  edge: edge.id,
332
378
  channelPolicy: edge.channelPolicy,
333
379
  channelMap: edge.channelMap,
380
+ releaseIdentity: identity.release,
381
+ propagationKey,
382
+ branch: propagationBranch({ upstreamRelease, targetNode }),
334
383
  exact: true,
335
384
  floatingTags: false,
336
385
  },
@@ -374,6 +423,8 @@ export function planReleasePropagation({ graph: graphInput, upstreamRelease: rel
374
423
  channel: downstreamChannel,
375
424
  baseRef: lock.downstream.baseRef,
376
425
  lockPath: lock.downstream.lockPath,
426
+ propagationKey: lock.propagation.propagationKey,
427
+ branch: lock.propagation.branch,
377
428
  lock,
378
429
  });
379
430
  }
@@ -387,6 +438,7 @@ export function planReleasePropagation({ graph: graphInput, upstreamRelease: rel
387
438
  targetCount: targets.length,
388
439
  channels: [...new Set(targets.map((target) => target.channel))],
389
440
  repositories: targets.map((target) => target.repository),
441
+ releaseIdentity: releaseIdentity(upstreamRelease),
390
442
  },
391
443
  };
392
444
  }
@@ -411,13 +463,135 @@ export function writeReleasePropagationLock({ plan, target = "", cwd = process.c
411
463
  }
412
464
  const selected = matches[0];
413
465
  const outputPath = path.resolve(cwd, output || selected.lockPath);
466
+ const rendered = stableJson(selected.lock);
467
+ const previous = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, "utf8") : "";
414
468
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
415
- fs.writeFileSync(outputPath, stableJson(selected.lock));
469
+ if (previous !== rendered) {
470
+ fs.writeFileSync(outputPath, rendered);
471
+ }
416
472
  return {
417
473
  target: selected.target,
418
474
  repository: selected.repository,
419
475
  channel: selected.channel,
420
476
  path: outputPath,
421
477
  lockSha256: selected.lock.lockSha256,
478
+ propagationKey: selected.propagationKey || selected.lock.propagation?.propagationKey || "",
479
+ branch: selected.branch || selected.lock.propagation?.branch || "",
480
+ status: previous === rendered ? "reused" : "written",
481
+ changed: previous !== rendered,
482
+ };
483
+ }
484
+
485
+ const RECEIPT_VISIBILITY_STATES = new Set(["pending", "visible", "failed", "not-requested"]);
486
+ const RECEIPT_PR_STATES = new Set(["planned", "created", "updated", "reused", "no-change", "failed"]);
487
+
488
+ function receiptVisibilityState(value, label) {
489
+ const state = optionalString(value) || "pending";
490
+ if (!RECEIPT_VISIBILITY_STATES.has(state)) {
491
+ throw new Error(`${label} must be pending, visible, failed, or not-requested`);
492
+ }
493
+ return state;
494
+ }
495
+
496
+ function receiptPrOutcome(value = {}) {
497
+ const outcome = assertPlainObject(value, "release propagation PR outcome");
498
+ const state = optionalString(outcome.state) || "planned";
499
+ if (!RECEIPT_PR_STATES.has(state)) {
500
+ throw new Error("release propagation PR outcome state is unsupported");
501
+ }
502
+ return {
503
+ state,
504
+ number: outcome.number === undefined || outcome.number === null || outcome.number === ""
505
+ ? null
506
+ : Number(outcome.number),
507
+ url: optionalString(outcome.url),
508
+ branch: assertString(outcome.branch, "release propagation PR outcome branch"),
509
+ };
510
+ }
511
+
512
+ export function createReleasePropagationReceipt({
513
+ plan,
514
+ target = "",
515
+ lockResult,
516
+ prOutcome,
517
+ stagingState = "pending",
518
+ productionState = "not-requested",
519
+ observedAt = "",
520
+ } = {}) {
521
+ const selectedPlan = assertPlainObject(plan, "plan");
522
+ const matches = selectedPlan.targets.filter((entry) =>
523
+ !target || entry.target === target || entry.repository === target);
524
+ if (matches.length !== 1) {
525
+ throw new Error(`expected exactly one propagation target, found ${matches.length}`);
526
+ }
527
+ const selected = matches[0];
528
+ const result = assertPlainObject(lockResult, "release propagation lock result");
529
+ if (result.lockSha256 !== selected.lock.lockSha256) {
530
+ throw new Error("release propagation receipt lock result does not match the plan");
531
+ }
532
+ const propagationKey = selected.propagationKey || selected.lock.propagation?.propagationKey || "";
533
+ if (result.propagationKey && result.propagationKey !== propagationKey) {
534
+ throw new Error("release propagation receipt key does not match the lock result");
535
+ }
536
+ const branch = selected.branch || selected.lock.propagation?.branch || "";
537
+ const normalizedPr = receiptPrOutcome({ ...prOutcome, branch: prOutcome?.branch || branch });
538
+ if (normalizedPr.branch !== branch) {
539
+ throw new Error("release propagation PR branch does not match the exact plan");
540
+ }
541
+ const upstreamRelease = selectedPlan.upstreamRelease;
542
+ const release = releaseIdentity(upstreamRelease);
543
+ const states = {
544
+ "package-published": {
545
+ state: upstreamRelease.package ? "complete" : "not-applicable",
546
+ evidence: upstreamRelease.package
547
+ ? {
548
+ name: upstreamRelease.package.name,
549
+ version: upstreamRelease.package.version,
550
+ integrity: upstreamRelease.package.integrity,
551
+ }
552
+ : null,
553
+ },
554
+ "alpha-complete": {
555
+ state: release.channel === "alpha" ? "complete" : "not-applicable",
556
+ evidence: release.channel === "alpha"
557
+ ? {
558
+ releasePassportSha256: upstreamRelease.releasePassport.sha256,
559
+ tag: upstreamRelease.tag,
560
+ }
561
+ : null,
562
+ },
563
+ "staging-visible": {
564
+ state: receiptVisibilityState(stagingState, "stagingState"),
565
+ },
566
+ "production-visible": {
567
+ state: receiptVisibilityState(productionState, "productionState"),
568
+ },
569
+ };
570
+ const body = {
571
+ schemaVersion: 1,
572
+ contract: RELEASE_PROPAGATION_RECEIPT_CONTRACT,
573
+ propagationKey,
574
+ observedAt: optionalString(observedAt),
575
+ release,
576
+ upstream: {
577
+ repository: upstreamRelease.repository,
578
+ tag: upstreamRelease.tag,
579
+ sourceSha: upstreamRelease.sourceSha,
580
+ releasePassport: upstreamRelease.releasePassport,
581
+ },
582
+ downstream: {
583
+ repository: selected.repository,
584
+ channel: selected.channel,
585
+ baseRef: selected.baseRef,
586
+ lockPath: selected.lockPath,
587
+ lockSha256: result.lockSha256,
588
+ lockWriteState: result.status || (result.changed === false ? "reused" : "written"),
589
+ pullRequest: normalizedPr,
590
+ },
591
+ states,
592
+ };
593
+ return {
594
+ ...body,
595
+ receiptSha256: sha256Json(body),
422
596
  };
423
597
  }
@@ -423,6 +423,38 @@ function copyFile(source, target) {
423
423
  fs.copyFileSync(source, target);
424
424
  }
425
425
 
426
+ function parseAdapterArguments(value = "[]") {
427
+ let parsed;
428
+ try {
429
+ parsed = JSON.parse(value);
430
+ } catch {
431
+ invariant(false, "adapter arguments must be valid JSON");
432
+ }
433
+ invariant(
434
+ Array.isArray(parsed) && parsed.length <= 32,
435
+ "adapter arguments must be an array with at most 32 entries",
436
+ );
437
+ const reserved = new Set([
438
+ "--artifact-root",
439
+ "--output",
440
+ "--source-coordinate",
441
+ ]);
442
+ for (const [index, argument] of parsed.entries()) {
443
+ invariant(
444
+ typeof argument === "string"
445
+ && argument.length > 0
446
+ && argument.length <= 256
447
+ && !/[\0\r\n]/.test(argument),
448
+ `adapter argument ${index} is invalid`,
449
+ );
450
+ invariant(
451
+ !reserved.has(argument),
452
+ `adapter argument ${index} attempts to override a reserved coordinate`,
453
+ );
454
+ }
455
+ return parsed;
456
+ }
457
+
426
458
  function runAdapter(values) {
427
459
  const sourceRoot = path.resolve(required(values, "--source-root"));
428
460
  const artifactRoot = path.resolve(required(values, "--artifact-root"));
@@ -430,6 +462,9 @@ function runAdapter(values) {
430
462
  const diagnostics = path.resolve(required(values, "--diagnostics"));
431
463
  const sourceCoordinate = path.resolve(required(values, "--source-coordinate"));
432
464
  const adapterRelative = required(values, "--adapter");
465
+ const adapterArguments = parseAdapterArguments(
466
+ values["--adapter-arguments-json"] || "[]",
467
+ );
433
468
  const adapter = resolveInside(sourceRoot, adapterRelative, "adapter path");
434
469
  const metadata = fs.lstatSync(adapter);
435
470
  invariant(metadata.isFile() && !metadata.isSymbolicLink(), "adapter must be a regular non-symlink file");
@@ -456,7 +491,15 @@ function runAdapter(values) {
456
491
  try {
457
492
  const result = spawnSync(
458
493
  adapter,
459
- ["--artifact-root", artifactRoot, "--output", output, "--source-coordinate", sourceCoordinate],
494
+ [
495
+ "--artifact-root",
496
+ artifactRoot,
497
+ "--output",
498
+ output,
499
+ "--source-coordinate",
500
+ sourceCoordinate,
501
+ ...adapterArguments,
502
+ ],
460
503
  { cwd: sourceRoot, env: environment, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 },
461
504
  );
462
505
  fs.writeFileSync(path.join(diagnostics, "adapter.stdout.log"), result.stdout || "");
@@ -473,6 +516,8 @@ function runAdapter(values) {
473
516
  schema: "buildchain.auditable-demo-adapter-execution/v1",
474
517
  path: adapterRelative,
475
518
  sha256: sha256(readRegular(adapter, "adapter", 4 * 1024 * 1024)),
519
+ arguments: adapterArguments,
520
+ argumentsRoot: sha256(Buffer.from(stableJson(adapterArguments))),
476
521
  exitCode: 0,
477
522
  });
478
523
  } finally {
@@ -490,8 +535,8 @@ function prepareSmoke(values) {
490
535
  const scene = {
491
536
  schema: "build-images.demo-scene/v1",
492
537
  id: identifier,
493
- width: 640,
494
- height: 360,
538
+ width: 1280,
539
+ height: 720,
495
540
  fps: 5,
496
541
  durationMs: 1000,
497
542
  title: `${normalized.scene.title.slice(0, 96)} gate smoke`,
@@ -838,6 +883,38 @@ function constraintsForAdditionalRendition(entry, policy) {
838
883
  throw new Error(`additional rendition MIME type is not allowed: ${entry.mimeType}`);
839
884
  }
840
885
 
886
+ function expectedRenditionDimensions(rule, scene) {
887
+ if (rule.dimensions === undefined) {
888
+ return {
889
+ policy: "scene-exact",
890
+ width: scene.width,
891
+ height: scene.height,
892
+ };
893
+ }
894
+ exactKeys(
895
+ rule.dimensions,
896
+ ["policy", "width", "height"],
897
+ [],
898
+ `${rule.path}.dimensions`,
899
+ );
900
+ invariant(
901
+ rule.dimensions.policy === "exact-downscale-same-aspect",
902
+ `${rule.path} dimension policy is unsupported`,
903
+ );
904
+ const width = integer(rule.dimensions.width, 1, 16384, `${rule.path}.dimensions.width`);
905
+ const height = integer(rule.dimensions.height, 1, 16384, `${rule.path}.dimensions.height`);
906
+ invariant(width <= scene.width && height <= scene.height, `${rule.path} dimensions would upscale the scene`);
907
+ invariant(
908
+ width * scene.height === height * scene.width,
909
+ `${rule.path} dimensions drift from the scene aspect ratio`,
910
+ );
911
+ return {
912
+ policy: rule.dimensions.policy,
913
+ width,
914
+ height,
915
+ };
916
+ }
917
+
841
918
  function qualifyRendererOutput(renderOutput, manifest, scene, profileId, inspectMedia, inspectionRoot = "") {
842
919
  const loaded = loadMediaProfile(profileId);
843
920
  const { profile, catalog } = loaded;
@@ -887,12 +964,16 @@ function qualifyRendererOutput(renderOutput, manifest, scene, profileId, inspect
887
964
  }
888
965
  const facts = profile.mode === "web-delivery" ? inspectMedia(target) : {};
889
966
  if (profile.mode === "web-delivery") {
967
+ const expectedDimensions = expectedRenditionDimensions(rule, scene);
890
968
  invariant(facts && typeof facts === "object", `${rule.path} inspection is missing`);
891
969
  invariant(facts.container === rule.container, `${rule.path} container mismatch`);
892
970
  invariant(facts.videoCodec === rule.videoCodec, `${rule.path} video codec mismatch`);
893
971
  if (rule.pixelFormat) invariant(facts.pixelFormat === rule.pixelFormat, `${rule.path} pixel format mismatch`);
894
972
  invariant(facts.audioStreams === rule.audioStreams, `${rule.path} audio stream policy failed`);
895
- invariant(facts.width === scene.width && facts.height === scene.height, `${rule.path} dimensions mismatch`);
973
+ invariant(
974
+ facts.width === expectedDimensions.width && facts.height === expectedDimensions.height,
975
+ `${rule.path} dimensions mismatch`,
976
+ );
896
977
  if (facts.durationMs > 0) {
897
978
  invariant(
898
979
  Math.abs(facts.durationMs - scene.durationMs) <= catalog.qualification.durationToleranceMs,
@@ -916,6 +997,9 @@ function qualifyRendererOutput(renderOutput, manifest, scene, profileId, inspect
916
997
  root: declared.root,
917
998
  bytes: declared.bytes,
918
999
  maximumBytes: rule.maximumBytes || 0,
1000
+ dimensionPolicy: profile.mode === "web-delivery"
1001
+ ? expectedRenditionDimensions(rule, scene).policy
1002
+ : "not-qualified",
919
1003
  ...facts,
920
1004
  });
921
1005
  }
@@ -986,12 +1070,28 @@ function finalizeGate(values) {
986
1070
  const rendererImage = required(values, "--renderer-image");
987
1071
  const adapterRelative = required(values, "--adapter");
988
1072
  const sourceSha = required(values, "--source-sha");
1073
+ const mediaProfile = values["--media-profile"] || "archive-v1";
989
1074
  invariant(/^[0-9a-f]{40}$/.test(sourceSha), "source SHA must be exact");
990
1075
  const normalized = validateAdapterOutput(adapterOutput);
991
- verifyRendererOutput(smokeOutput, rendererImage, {
1076
+ const selectedProfile = loadMediaProfile(mediaProfile).profile;
1077
+ const mediaInspectionPath = values["--media-inspection"]
1078
+ ? path.resolve(values["--media-inspection"])
1079
+ : "";
1080
+ const mediaInspection = selectedProfile.mode === "web-delivery"
1081
+ ? loadMediaInspection(
1082
+ required({ "--media-inspection": mediaInspectionPath }, "--media-inspection"),
1083
+ smokeOutput,
1084
+ rendererImage,
1085
+ )
1086
+ : null;
1087
+ const verifiedSmoke = verifyRendererOutput(smokeOutput, rendererImage, {
992
1088
  scene: path.join(smokeInput, "scene.json"),
993
1089
  transcript: path.join(smokeInput, "complete-transcript.txt"),
994
1090
  projection: path.join(smokeInput, "public-projection.json"),
1091
+ }, {
1092
+ mediaProfile,
1093
+ inspectMedia: mediaInspection?.inspectMedia,
1094
+ inspectionRoot: mediaInspection?.inspectionRoot || "",
995
1095
  });
996
1096
  const sourceCoordinate = validateSourceCoordinate(readJson(sourceCoordinatePath, "source artifact coordinate"));
997
1097
  invariant(sourceCoordinate.sourceSha === sourceSha, "source artifact coordinate SHA mismatch");
@@ -1025,10 +1125,16 @@ function finalizeGate(values) {
1025
1125
  adapter: {
1026
1126
  path: adapterRelative,
1027
1127
  sha256: readJson(path.join(diagnostics, "adapter.json"), "adapter execution").sha256,
1128
+ argumentsRoot: readJson(
1129
+ path.join(diagnostics, "adapter.json"),
1130
+ "adapter execution",
1131
+ ).argumentsRoot,
1028
1132
  },
1029
1133
  renderer: {
1030
1134
  image: rendererImage,
1031
1135
  smokeManifestRoot: sha256(readRegular(path.join(smokeOutput, "manifest.json"), "smoke manifest")),
1136
+ mediaProfile,
1137
+ mediaQualificationRoot: verifiedSmoke.qualification.qualificationRoot,
1032
1138
  },
1033
1139
  qualifiedInputs: {
1034
1140
  transcript: sha256(readRegular(path.join(output, "complete-transcript.txt"), "transcript")),
@@ -1060,12 +1166,18 @@ function verifyGate(values) {
1060
1166
  const expectedRoot = required(values, "--expected-root");
1061
1167
  const expectedImage = required(values, "--renderer-image");
1062
1168
  const expectedSourceSha = required(values, "--source-sha");
1169
+ const expectedMediaProfile = values["--media-profile"] || "archive-v1";
1063
1170
  invariant(DIGEST_PATTERN.test(expectedRoot), "expected gate root must be sha256");
1064
1171
  invariant(verifyChecksums(bundle) === expectedRoot, "gate bundle root mismatch");
1065
1172
  const receipt = readJson(path.join(bundle, "gate-receipt.json"), "gate receipt");
1066
1173
  invariant(receipt.schema === "buildchain.auditable-demo-gate/v1" && receipt.status === "passed", "gate did not pass");
1067
1174
  invariant(receipt.sourceSha === expectedSourceSha, "gate source SHA mismatch");
1068
1175
  invariant(receipt.renderer?.image === expectedImage, "gate renderer image mismatch");
1176
+ invariant(receipt.renderer?.mediaProfile === expectedMediaProfile, "gate media profile mismatch");
1177
+ invariant(
1178
+ DIGEST_PATTERN.test(receipt.renderer?.mediaQualificationRoot || ""),
1179
+ "gate media qualification root is invalid",
1180
+ );
1069
1181
  const normalized = validateAdapterOutput(bundle, false);
1070
1182
  invariant(
1071
1183
  receipt.qualifiedInputs?.transcript === sha256(readRegular(path.join(bundle, "complete-transcript.txt"), "transcript"))
@@ -1113,6 +1225,7 @@ function finalizeMedia(values) {
1113
1225
  "--expected-root": gateRoot,
1114
1226
  "--renderer-image": rendererImage,
1115
1227
  "--source-sha": sourceSha,
1228
+ "--media-profile": mediaProfile,
1116
1229
  });
1117
1230
  const terminalCapturePath = path.join(gateBundle, "terminal-capture.json");
1118
1231
  const verifiedRenderer = verifyRendererOutput(renderOutput, rendererImage, {
@@ -1199,6 +1312,7 @@ export {
1199
1312
  inspectIsoBmffFastStart,
1200
1313
  inspectMediaFile,
1201
1314
  inspectRendererMedia,
1315
+ parseAdapterArguments,
1202
1316
  qualifyMediaFixture,
1203
1317
  prepareSmoke,
1204
1318
  runAdapter,