@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.
- package/AGENTS.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +20 -2
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +56 -2
- package/bin/internal/command-registry.mjs +82 -0
- package/bin/internal/trust-release-cli.mjs +1 -0
- package/contracts/auditable-demo-media-profiles-v1.json +116 -1
- package/contracts/buildchain-v2-residuals-v1.json +146 -0
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
- package/dist/site/buildchain-contract.json +188 -46
- package/dist/site/buildchain-site.json +181 -90
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/cli-registry.json +860 -98
- package/dist/site/controller-registry.json +118 -5
- package/dist/site/kfd-claims.json +233 -19
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +374 -59
- package/dist/site/page-registry.json +156 -65
- package/dist/site/public-surface-audit.json +231 -22
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/site-manifest.json +24 -24
- package/dist/site/workflow-registry.json +575 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +54 -13
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +119 -12
- package/docs/consumer-issue-reporting.md +20 -2
- package/docs/dev-alpha-candidate-patrol.md +24 -10
- package/docs/github-governance-authority.md +24 -31
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/observed-evidence-patrol.md +28 -12
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +158 -18
- package/docs/publication-authority.md +4 -4
- package/docs/publish-transaction.md +78 -31
- package/docs/release-candidate.md +24 -0
- package/docs/release-flow.md +50 -32
- package/docs/release-governance.md +39 -32
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +218 -90
- package/docs/runtime-train-validation.md +20 -2
- package/docs/shifu-gate-profiles.md +7 -1
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +46 -24
- package/docs/web-surface-deployments.md +82 -1
- package/package.json +7 -5
- package/packages/core/README.md +60 -4
- package/packages/core/artifact-signing.js +1 -0
- package/packages/core/buildchain-contract.js +9 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/controller-evidence.js +2 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +25 -17
- package/packages/core/index.js +40 -0
- package/packages/core/paper.js +3226 -0
- package/packages/core/public-surface-audit.js +3 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-control-plane-audit.js +28 -0
- package/packages/core/publication-reproducibility.js +947 -0
- package/packages/core/publication-sealed-bundle.js +190 -0
- package/packages/core/publish-transaction.js +147 -17
- package/packages/core/release-candidate.js +234 -0
- package/packages/core/release-passport.js +519 -228
- package/packages/core/release-propagation.js +175 -1
- package/packages/core/stable-release-gate.js +4 -1
- package/scripts/artifact-signing-delegation.mjs +268 -0
- package/scripts/audit-github-governance.mjs +32 -13
- package/scripts/audit-publication-control-plane.mjs +17 -8
- package/scripts/auditable-demo.mjs +266 -7
- package/scripts/aws-codebuild-toolchain.mjs +285 -0
- package/scripts/aws-macos-jit-core.mjs +378 -0
- package/scripts/aws-macos-jit.mjs +123 -0
- package/scripts/aws-runner-burst-core.mjs +248 -0
- package/scripts/aws-runner-burst.mjs +79 -0
- package/scripts/aws-windows-jit-core.mjs +374 -0
- package/scripts/aws-windows-jit.mjs +121 -0
- package/scripts/build-contract-core.mjs +69 -6
- package/scripts/build-standalone-binary.mjs +23 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/buildchain-patrol.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +70 -2
- package/scripts/check-javascript-syntax.mjs +31 -0
- package/scripts/check-maintainability.mjs +371 -0
- package/scripts/compiler-cache-evidence.mjs +221 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
- package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
- package/scripts/gate-profile-core.mjs +6 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +84 -18
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +52 -14
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/observed-evidence.mjs +151 -33
- package/scripts/paper.mjs +645 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/reconcile-github-governance.mjs +8 -1
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +36 -4
- package/scripts/seal-artifact-signing-requests.mjs +64 -12
- package/scripts/stable-candidate-qualification.mjs +50 -0
- package/scripts/verify-artifact-signing-results.mjs +26 -1
- package/scripts/web-surface-core.mjs +272 -86
- 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 {
|
|
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
|
-
[
|
|
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:
|
|
380
|
-
height:
|
|
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(
|
|
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
|
-
|
|
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,
|