@kungfu-tech/buildchain 3.0.2 → 3.0.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +20 -2
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +56 -2
  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 +146 -0
  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 +188 -46
  19. package/dist/site/buildchain-site.json +181 -90
  20. package/dist/site/capability-registry.json +5 -5
  21. package/dist/site/cli-registry.json +860 -98
  22. package/dist/site/controller-registry.json +118 -5
  23. package/dist/site/kfd-claims.json +233 -19
  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 +374 -59
  27. package/dist/site/page-registry.json +156 -65
  28. package/dist/site/public-surface-audit.json +231 -22
  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 +575 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +54 -13
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +119 -12
  37. package/docs/consumer-issue-reporting.md +20 -2
  38. package/docs/dev-alpha-candidate-patrol.md +24 -10
  39. package/docs/github-governance-authority.md +24 -31
  40. package/docs/lifecycle-protocol.md +20 -2
  41. package/docs/migration-inventory.md +24 -5
  42. package/docs/observed-evidence-patrol.md +28 -12
  43. package/docs/ownership.md +19 -1
  44. package/docs/publication-artifacts.md +158 -18
  45. package/docs/publication-authority.md +4 -4
  46. package/docs/publish-transaction.md +78 -31
  47. package/docs/release-candidate.md +24 -0
  48. package/docs/release-flow.md +50 -32
  49. package/docs/release-governance.md +39 -32
  50. package/docs/release-passport.md +4 -0
  51. package/docs/release-propagation.md +57 -12
  52. package/docs/reusable-build-surface.md +218 -90
  53. package/docs/runtime-train-validation.md +20 -2
  54. package/docs/shifu-gate-profiles.md +7 -1
  55. package/docs/stable-candidate-patrol.md +6 -6
  56. package/docs/toolkit-observability.md +22 -0
  57. package/docs/versioning.md +46 -24
  58. package/docs/web-surface-deployments.md +82 -1
  59. package/package.json +7 -5
  60. package/packages/core/README.md +60 -4
  61. package/packages/core/artifact-signing.js +1 -0
  62. package/packages/core/buildchain-contract.js +9 -4
  63. package/packages/core/cache-evidence.js +1 -0
  64. package/packages/core/controller-evidence.js +2 -0
  65. package/packages/core/diagnostics.js +105 -30
  66. package/packages/core/github-governance-authority.js +25 -17
  67. package/packages/core/index.js +40 -0
  68. package/packages/core/paper.js +3226 -0
  69. package/packages/core/public-surface-audit.js +3 -0
  70. package/packages/core/publication-artifact.js +1 -6
  71. package/packages/core/publication-authority.js +1 -0
  72. package/packages/core/publication-control-plane-audit.js +28 -0
  73. package/packages/core/publication-reproducibility.js +947 -0
  74. package/packages/core/publication-sealed-bundle.js +190 -0
  75. package/packages/core/publish-transaction.js +147 -17
  76. package/packages/core/release-candidate.js +234 -0
  77. package/packages/core/release-passport.js +519 -228
  78. package/packages/core/release-propagation.js +175 -1
  79. package/packages/core/stable-release-gate.js +4 -1
  80. package/scripts/artifact-signing-delegation.mjs +268 -0
  81. package/scripts/audit-github-governance.mjs +32 -13
  82. package/scripts/audit-publication-control-plane.mjs +17 -8
  83. package/scripts/auditable-demo.mjs +266 -7
  84. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  85. package/scripts/aws-macos-jit-core.mjs +378 -0
  86. package/scripts/aws-macos-jit.mjs +123 -0
  87. package/scripts/aws-runner-burst-core.mjs +248 -0
  88. package/scripts/aws-runner-burst.mjs +79 -0
  89. package/scripts/aws-windows-jit-core.mjs +374 -0
  90. package/scripts/aws-windows-jit.mjs +121 -0
  91. package/scripts/build-contract-core.mjs +69 -6
  92. package/scripts/build-standalone-binary.mjs +23 -0
  93. package/scripts/buildchain-contract-lock.mjs +1 -1
  94. package/scripts/buildchain-patrol.mjs +1 -1
  95. package/scripts/check-internal-architecture.mjs +69 -2
  96. package/scripts/check-inventory.mjs +70 -2
  97. package/scripts/check-javascript-syntax.mjs +31 -0
  98. package/scripts/check-maintainability.mjs +371 -0
  99. package/scripts/compiler-cache-evidence.mjs +221 -0
  100. package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
  101. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  102. package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
  103. package/scripts/gate-profile-core.mjs +6 -1
  104. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  105. package/scripts/generate-release-candidate-passport.mjs +4 -0
  106. package/scripts/generate-site-bundle.mjs +84 -18
  107. package/scripts/import-artifact-signing-results.mjs +64 -5
  108. package/scripts/inspect-artifact-signing-requests.mjs +52 -14
  109. package/scripts/maintainability-metrics.mjs +339 -0
  110. package/scripts/npm-publish-transaction.mjs +57 -6
  111. package/scripts/observed-evidence.mjs +151 -33
  112. package/scripts/paper.mjs +645 -0
  113. package/scripts/promotion-channel-router.mjs +16 -4
  114. package/scripts/publication-reproducibility.mjs +62 -0
  115. package/scripts/reconcile-github-governance.mjs +8 -1
  116. package/scripts/release-propagation.mjs +39 -0
  117. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  118. package/scripts/resolve-build-contract.mjs +6 -0
  119. package/scripts/route-offline-runners.mjs +236 -0
  120. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  121. package/scripts/run-lifecycle-core.mjs +62 -1
  122. package/scripts/runtime-ref-core.mjs +36 -4
  123. package/scripts/seal-artifact-signing-requests.mjs +64 -12
  124. package/scripts/stable-candidate-qualification.mjs +50 -0
  125. package/scripts/verify-artifact-signing-results.mjs +26 -1
  126. package/scripts/web-surface-core.mjs +272 -86
  127. package/scripts/web-surface-production-decision.mjs +19 -3
@@ -22,6 +22,19 @@ const REQUIRED_ADAPTER_FILES = [
22
22
  "public-projection.json",
23
23
  "scene.json",
24
24
  ];
25
+ const OPTIONAL_ADAPTER_FILES = ["terminal-capture.json"];
26
+ const MAX_TERMINAL_CAPTURE_BYTES = 4 * 1024 * 1024;
27
+ const MAX_TERMINAL_CAPTURE_EVENTS = 10_000;
28
+ const TERMINAL_CAPTURE_NON_AUTHORITIES = [
29
+ "first-party-identity",
30
+ "system-identity",
31
+ "kfd-compliance",
32
+ "product-system-metadata",
33
+ "package-metadata",
34
+ "registry-history",
35
+ "scan-output",
36
+ "standalone-generation",
37
+ ];
25
38
 
26
39
  function invariant(condition, message) {
27
40
  if (!condition) throw new Error(message);
@@ -155,6 +168,19 @@ function text(value, minimum, maximum, label) {
155
168
  return value;
156
169
  }
157
170
 
171
+ function decodeBase64(value, label) {
172
+ invariant(
173
+ typeof value === "string"
174
+ && value.length > 0
175
+ && value.length <= MAX_TERMINAL_CAPTURE_BYTES * 2
176
+ && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value),
177
+ `${label} must be canonical base64`,
178
+ );
179
+ const decoded = Buffer.from(value, "base64");
180
+ invariant(decoded.toString("base64") === value, `${label} must be canonical base64`);
181
+ return decoded;
182
+ }
183
+
158
184
  function validateScene(value) {
159
185
  exactKeys(
160
186
  value,
@@ -223,6 +249,81 @@ function validateProjection(value, scene, transcriptLineCount) {
223
249
  };
224
250
  }
225
251
 
252
+ function validateTerminalCapture(value, scene) {
253
+ exactKeys(
254
+ value,
255
+ [
256
+ "schema",
257
+ "command",
258
+ "dimensions",
259
+ "durationMs",
260
+ "encoding",
261
+ "events",
262
+ "completion",
263
+ "exitCode",
264
+ "authority",
265
+ ],
266
+ [],
267
+ "terminalCapture",
268
+ );
269
+ invariant(value.schema === "kungfu.terminal-capture/v1", "unsupported terminal capture schema");
270
+ text(value.command, 1, 160, "terminalCapture.command");
271
+ exactKeys(value.dimensions, ["columns", "rows"], [], "terminalCapture.dimensions");
272
+ integer(value.dimensions.columns, 80, 200, "terminalCapture.dimensions.columns");
273
+ integer(value.dimensions.rows, 24, 80, "terminalCapture.dimensions.rows");
274
+ const durationMs = integer(value.durationMs, 500, 60000, "terminalCapture.durationMs");
275
+ invariant(
276
+ durationMs <= scene.durationMs && scene.durationMs - durationMs <= 2000,
277
+ "terminal capture duration must end within two seconds of the scene",
278
+ );
279
+ invariant(value.encoding === "base64", "terminalCapture.encoding must be base64");
280
+ invariant(
281
+ Array.isArray(value.events)
282
+ && value.events.length > 0
283
+ && value.events.length <= MAX_TERMINAL_CAPTURE_EVENTS,
284
+ `terminalCapture.events must contain 1 through ${MAX_TERMINAL_CAPTURE_EVENTS} events`,
285
+ );
286
+ let previousAtMs = -1;
287
+ let totalBytes = 0;
288
+ for (const [index, event] of value.events.entries()) {
289
+ exactKeys(event, ["atMs", "data"], [], `terminalCapture.events[${index}]`);
290
+ const atMs = integer(event.atMs, 0, durationMs - 1, `terminalCapture.events[${index}].atMs`);
291
+ invariant(atMs >= previousAtMs, "terminal capture event timestamps must be monotonic");
292
+ invariant(index > 0 || atMs === 0, "the first terminal capture event must start at zero");
293
+ previousAtMs = atMs;
294
+ totalBytes += decodeBase64(event.data, `terminalCapture.events[${index}].data`).length;
295
+ invariant(totalBytes <= MAX_TERMINAL_CAPTURE_BYTES, "terminal capture exceeds the 4 MiB byte bound");
296
+ }
297
+ exactKeys(
298
+ value.completion,
299
+ ["schema", "status", "reportRoot", "eventCount"],
300
+ [],
301
+ "terminalCapture.completion",
302
+ );
303
+ invariant(
304
+ value.completion.schema === "kungfu.agent-work-lab.tui-autoplay/v1"
305
+ && value.completion.status === "qualified"
306
+ && DIGEST_PATTERN.test(value.completion.reportRoot),
307
+ "terminal capture completion sentinel is not a qualified Agent Work Lab autoplay",
308
+ );
309
+ integer(value.completion.eventCount, 1, 100_000, "terminalCapture.completion.eventCount");
310
+ invariant(value.exitCode === 0, "terminal capture exitCode must be zero");
311
+ exactKeys(value.authority, ["classification", "grants", "nonAuthorities"], [], "terminalCapture.authority");
312
+ invariant(
313
+ value.authority.classification === "volatile-terminal-observation",
314
+ "terminal capture authority classification must remain observation-only",
315
+ );
316
+ invariant(
317
+ Array.isArray(value.authority.grants) && value.authority.grants.length === 0,
318
+ "terminal capture must not grant authority",
319
+ );
320
+ invariant(
321
+ JSON.stringify(value.authority.nonAuthorities) === JSON.stringify(TERMINAL_CAPTURE_NON_AUTHORITIES),
322
+ "terminal capture must declare every identity and metadata non-authority",
323
+ );
324
+ return value;
325
+ }
326
+
226
327
  function validateSourceCoordinate(value) {
227
328
  exactKeys(
228
329
  value,
@@ -276,11 +377,21 @@ function validateAdapterOutput(output, strict = true) {
276
377
  scene,
277
378
  lines.length,
278
379
  );
380
+ const terminalCapturePath = path.join(output, "terminal-capture.json");
381
+ const terminalCapture = fs.existsSync(terminalCapturePath)
382
+ ? validateTerminalCapture(readJson(terminalCapturePath, "terminal capture"), scene)
383
+ : null;
279
384
  if (strict) {
280
- const allowed = new Set(REQUIRED_ADAPTER_FILES);
385
+ const allowed = new Set([...REQUIRED_ADAPTER_FILES, ...OPTIONAL_ADAPTER_FILES]);
281
386
  for (const member of listFiles(output)) invariant(allowed.has(member), `undeclared adapter output: ${member}`);
282
387
  }
283
- return { transcript: transcript.endsWith("\n") ? transcript : `${transcript}\n`, lines, scene, projection };
388
+ return {
389
+ transcript: transcript.endsWith("\n") ? transcript : `${transcript}\n`,
390
+ lines,
391
+ scene,
392
+ projection,
393
+ terminalCapture,
394
+ };
284
395
  }
285
396
 
286
397
  function parseArguments(argv) {
@@ -312,6 +423,38 @@ function copyFile(source, target) {
312
423
  fs.copyFileSync(source, target);
313
424
  }
314
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
+
315
458
  function runAdapter(values) {
316
459
  const sourceRoot = path.resolve(required(values, "--source-root"));
317
460
  const artifactRoot = path.resolve(required(values, "--artifact-root"));
@@ -319,6 +462,9 @@ function runAdapter(values) {
319
462
  const diagnostics = path.resolve(required(values, "--diagnostics"));
320
463
  const sourceCoordinate = path.resolve(required(values, "--source-coordinate"));
321
464
  const adapterRelative = required(values, "--adapter");
465
+ const adapterArguments = parseAdapterArguments(
466
+ values["--adapter-arguments-json"] || "[]",
467
+ );
322
468
  const adapter = resolveInside(sourceRoot, adapterRelative, "adapter path");
323
469
  const metadata = fs.lstatSync(adapter);
324
470
  invariant(metadata.isFile() && !metadata.isSymbolicLink(), "adapter must be a regular non-symlink file");
@@ -345,7 +491,15 @@ function runAdapter(values) {
345
491
  try {
346
492
  const result = spawnSync(
347
493
  adapter,
348
- ["--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
+ ],
349
503
  { cwd: sourceRoot, env: environment, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 },
350
504
  );
351
505
  fs.writeFileSync(path.join(diagnostics, "adapter.stdout.log"), result.stdout || "");
@@ -355,10 +509,15 @@ function runAdapter(values) {
355
509
  fs.writeFileSync(path.join(output, "complete-transcript.txt"), normalized.transcript);
356
510
  writeJson(path.join(output, "scene.json"), normalized.scene);
357
511
  writeJson(path.join(output, "public-projection.json"), normalized.projection);
512
+ if (normalized.terminalCapture) {
513
+ writeJson(path.join(output, "terminal-capture.json"), normalized.terminalCapture);
514
+ }
358
515
  writeJson(path.join(diagnostics, "adapter.json"), {
359
516
  schema: "buildchain.auditable-demo-adapter-execution/v1",
360
517
  path: adapterRelative,
361
518
  sha256: sha256(readRegular(adapter, "adapter", 4 * 1024 * 1024)),
519
+ arguments: adapterArguments,
520
+ argumentsRoot: sha256(Buffer.from(stableJson(adapterArguments))),
362
521
  exitCode: 0,
363
522
  });
364
523
  } finally {
@@ -376,8 +535,8 @@ function prepareSmoke(values) {
376
535
  const scene = {
377
536
  schema: "build-images.demo-scene/v1",
378
537
  id: identifier,
379
- width: 640,
380
- height: 360,
538
+ width: 1280,
539
+ height: 720,
381
540
  fps: 5,
382
541
  durationMs: 1000,
383
542
  title: `${normalized.scene.title.slice(0, 96)} gate smoke`,
@@ -724,6 +883,38 @@ function constraintsForAdditionalRendition(entry, policy) {
724
883
  throw new Error(`additional rendition MIME type is not allowed: ${entry.mimeType}`);
725
884
  }
726
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
+
727
918
  function qualifyRendererOutput(renderOutput, manifest, scene, profileId, inspectMedia, inspectionRoot = "") {
728
919
  const loaded = loadMediaProfile(profileId);
729
920
  const { profile, catalog } = loaded;
@@ -773,12 +964,16 @@ function qualifyRendererOutput(renderOutput, manifest, scene, profileId, inspect
773
964
  }
774
965
  const facts = profile.mode === "web-delivery" ? inspectMedia(target) : {};
775
966
  if (profile.mode === "web-delivery") {
967
+ const expectedDimensions = expectedRenditionDimensions(rule, scene);
776
968
  invariant(facts && typeof facts === "object", `${rule.path} inspection is missing`);
777
969
  invariant(facts.container === rule.container, `${rule.path} container mismatch`);
778
970
  invariant(facts.videoCodec === rule.videoCodec, `${rule.path} video codec mismatch`);
779
971
  if (rule.pixelFormat) invariant(facts.pixelFormat === rule.pixelFormat, `${rule.path} pixel format mismatch`);
780
972
  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`);
973
+ invariant(
974
+ facts.width === expectedDimensions.width && facts.height === expectedDimensions.height,
975
+ `${rule.path} dimensions mismatch`,
976
+ );
782
977
  if (facts.durationMs > 0) {
783
978
  invariant(
784
979
  Math.abs(facts.durationMs - scene.durationMs) <= catalog.qualification.durationToleranceMs,
@@ -802,6 +997,9 @@ function qualifyRendererOutput(renderOutput, manifest, scene, profileId, inspect
802
997
  root: declared.root,
803
998
  bytes: declared.bytes,
804
999
  maximumBytes: rule.maximumBytes || 0,
1000
+ dimensionPolicy: profile.mode === "web-delivery"
1001
+ ? expectedRenditionDimensions(rule, scene).policy
1002
+ : "not-qualified",
805
1003
  ...facts,
806
1004
  });
807
1005
  }
@@ -872,12 +1070,28 @@ function finalizeGate(values) {
872
1070
  const rendererImage = required(values, "--renderer-image");
873
1071
  const adapterRelative = required(values, "--adapter");
874
1072
  const sourceSha = required(values, "--source-sha");
1073
+ const mediaProfile = values["--media-profile"] || "archive-v1";
875
1074
  invariant(/^[0-9a-f]{40}$/.test(sourceSha), "source SHA must be exact");
876
1075
  const normalized = validateAdapterOutput(adapterOutput);
877
- 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, {
878
1088
  scene: path.join(smokeInput, "scene.json"),
879
1089
  transcript: path.join(smokeInput, "complete-transcript.txt"),
880
1090
  projection: path.join(smokeInput, "public-projection.json"),
1091
+ }, {
1092
+ mediaProfile,
1093
+ inspectMedia: mediaInspection?.inspectMedia,
1094
+ inspectionRoot: mediaInspection?.inspectionRoot || "",
881
1095
  });
882
1096
  const sourceCoordinate = validateSourceCoordinate(readJson(sourceCoordinatePath, "source artifact coordinate"));
883
1097
  invariant(sourceCoordinate.sourceSha === sourceSha, "source artifact coordinate SHA mismatch");
@@ -885,6 +1099,12 @@ function finalizeGate(values) {
885
1099
  fs.writeFileSync(path.join(output, "complete-transcript.txt"), normalized.transcript);
886
1100
  writeJson(path.join(output, "scene.json"), normalized.scene);
887
1101
  writeJson(path.join(output, "public-projection.json"), normalized.projection);
1102
+ if (normalized.terminalCapture) {
1103
+ copyFile(
1104
+ path.join(adapterOutput, "terminal-capture.json"),
1105
+ path.join(output, "terminal-capture.json"),
1106
+ );
1107
+ }
888
1108
  copyFile(sourceCoordinatePath, path.join(output, "source-artifact.json"));
889
1109
  copyFile(path.join(diagnostics, "adapter.json"), path.join(output, "adapter.json"));
890
1110
  for (const name of listFiles(smokeOutput)) {
@@ -905,15 +1125,29 @@ function finalizeGate(values) {
905
1125
  adapter: {
906
1126
  path: adapterRelative,
907
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,
908
1132
  },
909
1133
  renderer: {
910
1134
  image: rendererImage,
911
1135
  smokeManifestRoot: sha256(readRegular(path.join(smokeOutput, "manifest.json"), "smoke manifest")),
1136
+ mediaProfile,
1137
+ mediaQualificationRoot: verifiedSmoke.qualification.qualificationRoot,
912
1138
  },
913
1139
  qualifiedInputs: {
914
1140
  transcript: sha256(readRegular(path.join(output, "complete-transcript.txt"), "transcript")),
915
1141
  projection: sha256(readRegular(path.join(output, "public-projection.json"), "projection")),
916
1142
  scene: sha256(readRegular(path.join(output, "scene.json"), "scene")),
1143
+ ...(normalized.terminalCapture
1144
+ ? {
1145
+ terminalCapture: {
1146
+ schema: normalized.terminalCapture.schema,
1147
+ root: sha256(readRegular(path.join(output, "terminal-capture.json"), "terminal capture")),
1148
+ },
1149
+ }
1150
+ : {}),
917
1151
  evidenceClass: normalized.projection.evidenceClass,
918
1152
  claimBoundary: normalized.projection.claimBoundary,
919
1153
  },
@@ -932,12 +1166,18 @@ function verifyGate(values) {
932
1166
  const expectedRoot = required(values, "--expected-root");
933
1167
  const expectedImage = required(values, "--renderer-image");
934
1168
  const expectedSourceSha = required(values, "--source-sha");
1169
+ const expectedMediaProfile = values["--media-profile"] || "archive-v1";
935
1170
  invariant(DIGEST_PATTERN.test(expectedRoot), "expected gate root must be sha256");
936
1171
  invariant(verifyChecksums(bundle) === expectedRoot, "gate bundle root mismatch");
937
1172
  const receipt = readJson(path.join(bundle, "gate-receipt.json"), "gate receipt");
938
1173
  invariant(receipt.schema === "buildchain.auditable-demo-gate/v1" && receipt.status === "passed", "gate did not pass");
939
1174
  invariant(receipt.sourceSha === expectedSourceSha, "gate source SHA mismatch");
940
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
+ );
941
1181
  const normalized = validateAdapterOutput(bundle, false);
942
1182
  invariant(
943
1183
  receipt.qualifiedInputs?.transcript === sha256(readRegular(path.join(bundle, "complete-transcript.txt"), "transcript"))
@@ -945,6 +1185,19 @@ function verifyGate(values) {
945
1185
  && receipt.qualifiedInputs?.scene === sha256(readRegular(path.join(bundle, "scene.json"), "scene")),
946
1186
  "gate qualified input roots mismatch",
947
1187
  );
1188
+ const qualifiedCapture = receipt.qualifiedInputs?.terminalCapture;
1189
+ invariant(
1190
+ Boolean(qualifiedCapture) === Boolean(normalized.terminalCapture),
1191
+ "gate terminal capture presence drifted",
1192
+ );
1193
+ if (normalized.terminalCapture) {
1194
+ invariant(
1195
+ qualifiedCapture.schema === normalized.terminalCapture.schema
1196
+ && qualifiedCapture.root
1197
+ === sha256(readRegular(path.join(bundle, "terminal-capture.json"), "terminal capture")),
1198
+ "gate terminal capture root mismatch",
1199
+ );
1200
+ }
948
1201
  invariant(receipt.qualifiedInputs.evidenceClass === normalized.projection.evidenceClass, "gate evidence class drifted");
949
1202
  }
950
1203
 
@@ -972,11 +1225,16 @@ function finalizeMedia(values) {
972
1225
  "--expected-root": gateRoot,
973
1226
  "--renderer-image": rendererImage,
974
1227
  "--source-sha": sourceSha,
1228
+ "--media-profile": mediaProfile,
975
1229
  });
1230
+ const terminalCapturePath = path.join(gateBundle, "terminal-capture.json");
976
1231
  const verifiedRenderer = verifyRendererOutput(renderOutput, rendererImage, {
977
1232
  scene: path.join(gateBundle, "scene.json"),
978
1233
  transcript: path.join(gateBundle, "complete-transcript.txt"),
979
1234
  projection: path.join(gateBundle, "public-projection.json"),
1235
+ ...(fs.existsSync(terminalCapturePath)
1236
+ ? { terminalCapture: terminalCapturePath }
1237
+ : {}),
980
1238
  }, {
981
1239
  mediaProfile,
982
1240
  inspectMedia: mediaInspection?.inspectMedia,
@@ -1054,6 +1312,7 @@ export {
1054
1312
  inspectIsoBmffFastStart,
1055
1313
  inspectMediaFile,
1056
1314
  inspectRendererMedia,
1315
+ parseAdapterArguments,
1057
1316
  qualifyMediaFixture,
1058
1317
  prepareSmoke,
1059
1318
  runAdapter,