@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.
- package/AGENTS.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +32 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +19 -1
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +41 -241
- 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 +0 -45
- 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 +161 -42
- package/dist/site/buildchain-site.json +180 -84
- package/dist/site/capability-registry.json +6 -6
- package/dist/site/cli-registry.json +932 -98
- package/dist/site/controller-registry.json +97 -5
- package/dist/site/kfd-claims.json +316 -23
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +371 -56
- package/dist/site/page-registry.json +155 -59
- package/dist/site/public-surface-audit.json +286 -24
- 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 +570 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +35 -12
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +143 -12
- package/docs/consumer-issue-reporting.md +19 -1
- package/docs/github-governance-authority.md +2 -2
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +185 -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 +31 -31
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +95 -27
- package/docs/runtime-train-validation.md +20 -2
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +6 -4
- package/docs/web-surface-deployments.md +62 -0
- package/package.json +7 -5
- package/packages/core/README.md +61 -4
- package/packages/core/buildchain-contract.js +8 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +10 -25
- package/packages/core/index.js +40 -0
- package/packages/core/paper-fleet.js +260 -0
- package/packages/core/paper-repository.js +210 -0
- package/packages/core/paper-work.js +365 -0
- package/packages/core/paper.js +3180 -0
- package/packages/core/public-surface-audit.js +26 -75
- package/packages/core/public-surface-cli.js +117 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -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 +483 -201
- package/packages/core/release-propagation.js +175 -1
- package/scripts/auditable-demo.mjs +119 -5
- 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-cli-help.mjs +256 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +69 -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 +268 -43
- package/scripts/finalize-native-artifact-signing-result.mjs +75 -4
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +68 -188
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +7 -0
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/paper-work-fleet-cli.mjs +563 -0
- package/scripts/paper.mjs +631 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +23 -2
- package/scripts/seal-artifact-signing-requests.mjs +13 -6
- package/scripts/site-capability-metadata.mjs +202 -0
- package/scripts/stable-candidate-qualification.mjs +42 -0
- 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
|
-
|
|
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
|
-
[
|
|
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:
|
|
494
|
-
height:
|
|
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(
|
|
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
|
-
|
|
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,
|