@kungfu-tech/buildchain 3.0.6-alpha.0 → 3.0.6-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/README.md +4 -4
- package/actions/promote-buildchain-ref/README.md +8 -0
- package/bin/buildchain.mjs +13 -1
- package/contracts/auditable-demo-scenario-v1.schema.json +52 -0
- package/dist/site/buildchain-contract.json +47 -27
- package/dist/site/buildchain-site.json +165 -44
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/cli-registry.json +40 -4
- package/dist/site/controller-registry.json +20 -4
- package/dist/site/kfd-claims.json +140 -19
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +9 -9
- package/dist/site/node-api-registry.json +1161 -180
- package/dist/site/page-registry.json +152 -31
- package/dist/site/public-surface-audit.json +386 -19
- package/dist/site/publication-authority-registry.json +61 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/site-manifest.json +13 -13
- package/dist/site/workflow-registry.json +151 -13
- package/docs/MAP.md +2 -0
- package/docs/auditable-demo.md +58 -11
- package/docs/aws-us-elastic-runner-burst-plane.md +114 -80
- package/docs/cli-reference.md +154 -0
- package/docs/dev-alpha-candidate-patrol.md +13 -5
- package/docs/dev-delivery-warrant.md +158 -0
- package/docs/node-api-reference.md +54 -15
- package/docs/publication-authority.md +11 -0
- package/docs/release-candidate.md +19 -2
- package/docs/release-governance.md +60 -1
- package/docs/reusable-build-surface.md +11 -1
- package/docs/shifu-gate-profiles.md +12 -1
- package/docs/versioning.md +2 -0
- package/package.json +4 -2
- package/packages/core/buildchain-publication-authority.js +3 -1
- package/packages/core/channel-candidate.js +2 -21
- package/packages/core/channel-promotion-baseline.js +199 -0
- package/packages/core/dev-delivery-candidate-identity.js +94 -0
- package/packages/core/dev-delivery-common.js +73 -0
- package/packages/core/dev-delivery-proof.js +252 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +94 -0
- package/packages/core/dev-delivery-warrant-settlement.js +73 -0
- package/packages/core/dev-delivery-warrant.js +591 -0
- package/scripts/auditable-demo-bundle-verification.mjs +148 -0
- package/scripts/auditable-demo-platform.mjs +86 -50
- package/scripts/auditable-demo-presentation.mjs +83 -0
- package/scripts/auditable-demo-renditions.mjs +264 -0
- package/scripts/auditable-demo.mjs +24 -30
- package/scripts/aws-windows-jit-campaign-core.mjs +7 -8
- package/scripts/aws-windows-jit-controller.mjs +1 -0
- package/scripts/aws-windows-jit-core.mjs +1 -1
- package/scripts/build-contract-core.mjs +58 -3
- package/scripts/buildchain-cli-help.mjs +8 -0
- package/scripts/buildchain-patrol.mjs +9 -0
- package/scripts/check-inventory.mjs +1 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +45 -48
- package/scripts/dev-delivery-proof.mjs +193 -0
- package/scripts/dev-delivery-warrant.mjs +426 -0
- package/scripts/dev-pr-auto-merge.mjs +497 -55
- package/scripts/dev-pr-delivery-warrant.mjs +209 -0
- package/scripts/dispatch-artifact-signing-authority.mjs +2 -4
- package/scripts/gate-profile-core.mjs +24 -0
- package/scripts/generate-site-bundle.mjs +2 -2
- package/scripts/git-fetch-process-tree.mjs +142 -0
- package/scripts/lifecycle-substage-evidence.mjs +274 -0
- package/scripts/locked-source-checkout.mjs +6 -3
- package/scripts/resolve-artifact-transfer-mode.mjs +9 -0
- package/scripts/resolve-build-contract.mjs +7 -0
- package/scripts/route-offline-runners.mjs +1 -0
- package/scripts/run-lifecycle-core.mjs +9 -9
- package/scripts/shifu-gate-profile.mjs +10 -16
- package/scripts/site-capability-metadata.mjs +14 -0
|
@@ -16,6 +16,270 @@ const RENDITION_SET_NON_AUTHORITIES = [
|
|
|
16
16
|
"runtime-authority",
|
|
17
17
|
...TERMINAL_CAPTURE_NON_AUTHORITIES,
|
|
18
18
|
];
|
|
19
|
+
const MAX_BUNDLE_MEMBER_BYTES = 8 * 1024 * 1024;
|
|
20
|
+
const MAX_LONG_FORM_RENDERER_MANIFEST_BYTES = 32 * 1024 * 1024;
|
|
21
|
+
const PRESENTATION_FRAMED = "presentation-framed";
|
|
22
|
+
const TERMINAL_FILL = "terminal-fill";
|
|
23
|
+
const GEOMETRY_TOLERANCE = 0.001;
|
|
24
|
+
|
|
25
|
+
function closeEnough(left, right, tolerance = GEOMETRY_TOLERANCE) {
|
|
26
|
+
return Number.isFinite(left) && Number.isFinite(right) && Math.abs(left - right) <= tolerance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateContentViewport(composition, expected, expectedMode, label, helpers) {
|
|
30
|
+
const { exactKeys, invariant } = helpers;
|
|
31
|
+
const viewport = composition.contentViewport;
|
|
32
|
+
exactKeys(viewport, ["x", "y", "width", "height", "fillRatio"], [], `${label}.contentViewport`);
|
|
33
|
+
invariant(
|
|
34
|
+
[viewport.x, viewport.y, viewport.width, viewport.height, viewport.fillRatio].every(Number.isFinite),
|
|
35
|
+
`${label} content viewport is malformed or out of bounds`,
|
|
36
|
+
);
|
|
37
|
+
invariant(
|
|
38
|
+
viewport.x >= 0 && viewport.y >= 0 && viewport.width > 0 && viewport.height > 0,
|
|
39
|
+
`${label} content viewport is malformed or out of bounds`,
|
|
40
|
+
);
|
|
41
|
+
invariant(
|
|
42
|
+
viewport.x + viewport.width <= expected.width + GEOMETRY_TOLERANCE
|
|
43
|
+
&& viewport.y + viewport.height <= expected.height + GEOMETRY_TOLERANCE,
|
|
44
|
+
`${label} content viewport is malformed or out of bounds`,
|
|
45
|
+
);
|
|
46
|
+
invariant(
|
|
47
|
+
viewport.fillRatio > 0
|
|
48
|
+
&& viewport.fillRatio <= 1
|
|
49
|
+
&& closeEnough(viewport.fillRatio, (viewport.width * viewport.height) / (expected.width * expected.height), 0.000001),
|
|
50
|
+
`${label} content viewport is malformed or out of bounds`,
|
|
51
|
+
);
|
|
52
|
+
if (expectedMode === TERMINAL_FILL) {
|
|
53
|
+
invariant(
|
|
54
|
+
closeEnough(viewport.x, 0)
|
|
55
|
+
&& closeEnough(viewport.y, 0)
|
|
56
|
+
&& closeEnough(viewport.width, expected.width)
|
|
57
|
+
&& closeEnough(viewport.height, expected.height)
|
|
58
|
+
&& closeEnough(viewport.fillRatio, 1, 0.000001),
|
|
59
|
+
`${label} does not provide a full-frame terminal viewport`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function validateTerminalGeometry(composition, expected, expectedMode, label, helpers) {
|
|
65
|
+
const { exactKeys, invariant } = helpers;
|
|
66
|
+
const geometry = composition.terminalGeometry;
|
|
67
|
+
if (expected.columns == null || expected.rows == null) {
|
|
68
|
+
invariant(geometry === null, `${label} unexpectedly declares terminal cell geometry`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
exactKeys(
|
|
72
|
+
geometry,
|
|
73
|
+
["columns", "rows", "cellWidth", "cellHeight", "fontSize", "lineHeight", "layout"],
|
|
74
|
+
[],
|
|
75
|
+
`${label}.terminalGeometry`,
|
|
76
|
+
);
|
|
77
|
+
invariant(
|
|
78
|
+
geometry.columns === expected.columns && geometry.rows === expected.rows,
|
|
79
|
+
`${label} terminal cell geometry is malformed or rendition-mismatched`,
|
|
80
|
+
);
|
|
81
|
+
invariant(
|
|
82
|
+
[geometry.cellWidth, geometry.cellHeight, geometry.fontSize, geometry.lineHeight]
|
|
83
|
+
.every((value) => Number.isFinite(value) && value > 0),
|
|
84
|
+
`${label} terminal cell geometry is malformed or rendition-mismatched`,
|
|
85
|
+
);
|
|
86
|
+
invariant(
|
|
87
|
+
geometry.layout === (expectedMode === TERMINAL_FILL ? "exact-grid" : "presentation-flow"),
|
|
88
|
+
`${label} terminal cell geometry is malformed or rendition-mismatched`,
|
|
89
|
+
);
|
|
90
|
+
if (expectedMode === TERMINAL_FILL) {
|
|
91
|
+
invariant(
|
|
92
|
+
closeEnough(geometry.cellWidth * geometry.columns, expected.width)
|
|
93
|
+
&& closeEnough(geometry.cellHeight * geometry.rows, expected.height)
|
|
94
|
+
&& closeEnough(geometry.lineHeight, geometry.cellHeight),
|
|
95
|
+
`${label} terminal cell geometry does not resolve to the full frame`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validateCompositionFrameSet(frameSet, expected, expectedMode, multiple, index, helpers) {
|
|
101
|
+
const { exactKeys, invariant } = helpers;
|
|
102
|
+
const label = `renderer composition frame set ${index}`;
|
|
103
|
+
invariant(frameSet && typeof frameSet === "object", `${label} is missing`);
|
|
104
|
+
invariant(
|
|
105
|
+
multiple
|
|
106
|
+
? frameSet.id === expected.id
|
|
107
|
+
&& frameSet.role === expected.role
|
|
108
|
+
&& frameSet.width === expected.width
|
|
109
|
+
&& frameSet.height === expected.height
|
|
110
|
+
: frameSet.width === expected.width && frameSet.height === expected.height,
|
|
111
|
+
multiple
|
|
112
|
+
? `${label} does not match the requested rendition`
|
|
113
|
+
: `${label} dimensions do not match the requested scene`,
|
|
114
|
+
);
|
|
115
|
+
const composition = frameSet.composition;
|
|
116
|
+
exactKeys(composition, ["mode", "contentViewport", "terminalGeometry"], [], `${label}.composition`);
|
|
117
|
+
invariant(composition.mode === expectedMode, `${label} mode drifted from the requested scene`);
|
|
118
|
+
validateContentViewport(composition, expected, expectedMode, label, helpers);
|
|
119
|
+
validateTerminalGeometry(composition, expected, expectedMode, label, helpers);
|
|
120
|
+
return composition;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function validateRendererComposition(manifest, renditions, helpers) {
|
|
124
|
+
const { exactKeys, invariant } = helpers;
|
|
125
|
+
invariant(Array.isArray(renditions) && renditions.length >= 1, "renderer composition requires declared renditions");
|
|
126
|
+
const expectedMode = renditions[0].compositionMode ?? PRESENTATION_FRAMED;
|
|
127
|
+
invariant(
|
|
128
|
+
(expectedMode === PRESENTATION_FRAMED || expectedMode === TERMINAL_FILL)
|
|
129
|
+
&& renditions.every((entry) => (entry.compositionMode ?? PRESENTATION_FRAMED) === expectedMode),
|
|
130
|
+
"requested rendition composition modes do not match",
|
|
131
|
+
);
|
|
132
|
+
const version = String(manifest.renderer?.contractVersion || "").split(".").map(Number);
|
|
133
|
+
invariant(
|
|
134
|
+
version.length === 3 && version.every(Number.isInteger) && version[0] === 1,
|
|
135
|
+
"renderer contract version is unsupported",
|
|
136
|
+
);
|
|
137
|
+
const supportsCompositionEvidence = version[1] >= 4;
|
|
138
|
+
if (!supportsCompositionEvidence) {
|
|
139
|
+
invariant(expectedMode === PRESENTATION_FRAMED, "renderer contract does not support composition evidence");
|
|
140
|
+
const sourceFrames = manifest.derivation?.sourceFrames;
|
|
141
|
+
const frameSets = manifest.derivation?.sourceFrameSets;
|
|
142
|
+
invariant(
|
|
143
|
+
manifest.policy?.compositionMode === undefined
|
|
144
|
+
&& sourceFrames?.composition === undefined
|
|
145
|
+
&& (!Array.isArray(frameSets) || frameSets.every((entry) => entry?.composition === undefined)),
|
|
146
|
+
"legacy renderer contract cannot declare composition evidence",
|
|
147
|
+
);
|
|
148
|
+
return { mode: expectedMode, frameSets: [], evidence: "legacy-presentation-default" };
|
|
149
|
+
}
|
|
150
|
+
invariant(manifest.policy?.compositionMode === expectedMode, "renderer composition policy drifted from the requested scene");
|
|
151
|
+
const sourceFrames = manifest.derivation?.sourceFrames;
|
|
152
|
+
const frameSets = renditions.length === 1
|
|
153
|
+
? [sourceFrames]
|
|
154
|
+
: manifest.derivation?.sourceFrameSets;
|
|
155
|
+
invariant(Array.isArray(frameSets) && frameSets.length === renditions.length, "renderer composition frame-set evidence is missing");
|
|
156
|
+
const compositions = frameSets.map((frameSet, index) => validateCompositionFrameSet(
|
|
157
|
+
frameSet,
|
|
158
|
+
renditions[index],
|
|
159
|
+
expectedMode,
|
|
160
|
+
renditions.length > 1,
|
|
161
|
+
index,
|
|
162
|
+
helpers,
|
|
163
|
+
));
|
|
164
|
+
if (renditions.length > 1) {
|
|
165
|
+
invariant(
|
|
166
|
+
JSON.stringify(sourceFrames?.composition) === JSON.stringify(frameSets[0].composition),
|
|
167
|
+
"renderer primary composition evidence drifted between sourceFrames and sourceFrameSets",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return { mode: expectedMode, frameSets: compositions };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function rendererCompositionRenditions(expectedInputs, primaryScene, helpers) {
|
|
174
|
+
const { invariant, readJson } = helpers;
|
|
175
|
+
if (expectedInputs.renditionSet) {
|
|
176
|
+
const set = validateRenditionSet(path.dirname(expectedInputs.renditionSet), helpers);
|
|
177
|
+
invariant(set, "renderer expected rendition set is missing");
|
|
178
|
+
return set.renditions.map((rendition) => ({
|
|
179
|
+
id: rendition.id,
|
|
180
|
+
role: rendition.role,
|
|
181
|
+
width: rendition.scene.width,
|
|
182
|
+
height: rendition.scene.height,
|
|
183
|
+
columns: rendition.capture.dimensions.columns,
|
|
184
|
+
rows: rendition.capture.dimensions.rows,
|
|
185
|
+
compositionMode: rendition.scene.compositionMode,
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
const terminal = expectedInputs.terminalCapture
|
|
189
|
+
? validateTerminalCapture(
|
|
190
|
+
readJson(expectedInputs.terminalCapture, "renderer expected terminal capture"),
|
|
191
|
+
primaryScene,
|
|
192
|
+
helpers,
|
|
193
|
+
)
|
|
194
|
+
: null;
|
|
195
|
+
return [{
|
|
196
|
+
width: primaryScene.width,
|
|
197
|
+
height: primaryScene.height,
|
|
198
|
+
columns: terminal?.dimensions.columns ?? null,
|
|
199
|
+
rows: terminal?.dimensions.rows ?? null,
|
|
200
|
+
compositionMode: primaryScene.compositionMode,
|
|
201
|
+
}];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function validateRendererCompositionInputs(manifest, expectedInputs, helpers) {
|
|
205
|
+
const { invariant, readJson, validateScene } = helpers;
|
|
206
|
+
if (expectedInputs.renditionSet) {
|
|
207
|
+
invariant(
|
|
208
|
+
manifest.derivation?.policy === "independent-native-frame-sets/v1",
|
|
209
|
+
"renderer did not use independent native frame sets",
|
|
210
|
+
);
|
|
211
|
+
invariant(
|
|
212
|
+
Array.isArray(manifest.inputs?.renditions)
|
|
213
|
+
&& manifest.inputs.renditions.length === 2
|
|
214
|
+
&& manifest.inputs.renditions[0]?.role === "primary"
|
|
215
|
+
&& manifest.inputs.renditions[1]?.role === "responsive"
|
|
216
|
+
&& manifest.inputs.renditions[0]?.terminalCapture?.root
|
|
217
|
+
!== manifest.inputs.renditions[1]?.terminalCapture?.root,
|
|
218
|
+
"renderer native rendition inputs are not independently bound",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const primaryScene = validateScene(readJson(expectedInputs.scene, "renderer expected scene"));
|
|
222
|
+
return validateRendererComposition(
|
|
223
|
+
manifest,
|
|
224
|
+
rendererCompositionRenditions(expectedInputs, primaryScene, helpers),
|
|
225
|
+
helpers,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function validateLongFormManifestRendition(entry, declaration, index, helpers) {
|
|
230
|
+
const { digestPattern, invariant, maxBytes, maxEvents } = helpers;
|
|
231
|
+
const scene = entry?.scene?.path;
|
|
232
|
+
const capture = entry?.terminalCapture;
|
|
233
|
+
invariant(
|
|
234
|
+
entry?.id === declaration.id
|
|
235
|
+
&& entry?.role === declaration.role
|
|
236
|
+
&& scene?.durationClass === "long-form"
|
|
237
|
+
&& scene?.width === declaration.width
|
|
238
|
+
&& scene?.height === declaration.height
|
|
239
|
+
&& Number.isInteger(scene?.durationMs)
|
|
240
|
+
&& scene.durationMs >= 500
|
|
241
|
+
&& scene.durationMs <= 180000
|
|
242
|
+
&& Number.isInteger(scene?.fps)
|
|
243
|
+
&& scene.fps >= 1
|
|
244
|
+
&& scene.fps <= 10
|
|
245
|
+
&& capture?.schema === "kungfu.terminal-capture/v1"
|
|
246
|
+
&& digestPattern.test(capture?.root)
|
|
247
|
+
&& Number.isInteger(capture?.durationMs)
|
|
248
|
+
&& capture.durationMs >= 500
|
|
249
|
+
&& capture.durationMs <= scene.durationMs
|
|
250
|
+
&& scene.durationMs - capture.durationMs <= 2000
|
|
251
|
+
&& Number.isInteger(capture?.events)
|
|
252
|
+
&& capture.events >= 1
|
|
253
|
+
&& capture.events <= maxEvents
|
|
254
|
+
&& Number.isInteger(capture?.bytes)
|
|
255
|
+
&& capture.bytes >= 1
|
|
256
|
+
&& capture.bytes <= maxBytes,
|
|
257
|
+
`oversized renderer manifest rendition ${index} is not bounded long-form evidence`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function readRendererManifest(filePath, helpers) {
|
|
262
|
+
const { decodeUtf8, invariant, readRegular } = helpers;
|
|
263
|
+
const bytes = readRegular(filePath, "renderer manifest", MAX_LONG_FORM_RENDERER_MANIFEST_BYTES);
|
|
264
|
+
let manifest;
|
|
265
|
+
try {
|
|
266
|
+
manifest = JSON.parse(decodeUtf8(bytes, "renderer manifest"));
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof SyntaxError) throw new Error("renderer manifest must be valid JSON");
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
invariant(manifest.schema === "build-images.auditable-demo-render/v1", "unexpected renderer manifest schema");
|
|
272
|
+
if (bytes.length > MAX_BUNDLE_MEMBER_BYTES) {
|
|
273
|
+
const renditions = manifest.inputs?.renditions;
|
|
274
|
+
invariant(Array.isArray(renditions) && renditions.length === 2, "oversized renderer manifest requires exactly two bounded long-form native renditions");
|
|
275
|
+
const expected = [
|
|
276
|
+
{ id: "1080p", role: "primary", width: 1920, height: 1080 },
|
|
277
|
+
{ id: "720p", role: "responsive", width: 1280, height: 720 },
|
|
278
|
+
];
|
|
279
|
+
renditions.forEach((entry, index) => validateLongFormManifestRendition(entry, expected[index], index, helpers));
|
|
280
|
+
}
|
|
281
|
+
return { bytes, manifest };
|
|
282
|
+
}
|
|
19
283
|
export function validateTerminalCapture(value, scene, helpers) {
|
|
20
284
|
const { decodeBase64, digestPattern, exactKeys, integer, invariant, maxBytes, maxEvents, text } = helpers;
|
|
21
285
|
exactKeys(
|
|
@@ -7,10 +7,7 @@ import os from "node:os";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import {
|
|
11
|
-
validateRenditionSet,
|
|
12
|
-
validateTerminalCapture,
|
|
13
|
-
} from "./auditable-demo-renditions.mjs";
|
|
10
|
+
import { readRendererManifest, validateRendererCompositionInputs, validateRenditionSet, validateTerminalCapture } from "./auditable-demo-renditions.mjs";
|
|
14
11
|
|
|
15
12
|
const UTF8 = new TextDecoder("utf-8", { fatal: true });
|
|
16
13
|
const IMAGE_PATTERN = /^[a-z0-9][a-z0-9./_-]*@sha256:[0-9a-f]{64}$/;
|
|
@@ -130,7 +127,7 @@ function writeChecksums(root, checksumName = "checksums.sha256") {
|
|
|
130
127
|
return sha256(Buffer.from(bytes));
|
|
131
128
|
}
|
|
132
129
|
|
|
133
|
-
function verifyChecksums(root, checksumName = "checksums.sha256") {
|
|
130
|
+
function verifyChecksums(root, checksumName = "checksums.sha256", options = {}) {
|
|
134
131
|
const bytes = readRegular(path.join(root, checksumName), checksumName);
|
|
135
132
|
const text = decodeUtf8(bytes, checksumName);
|
|
136
133
|
invariant(text.endsWith("\n"), `${checksumName} must end with a newline`);
|
|
@@ -143,8 +140,11 @@ function verifyChecksums(root, checksumName = "checksums.sha256") {
|
|
|
143
140
|
const target = resolveInside(root, member, "checksum member");
|
|
144
141
|
invariant(!declared.has(member), `duplicate checksum member: ${member}`);
|
|
145
142
|
declared.add(member);
|
|
143
|
+
const maximumBytes = options.allowLongFormRendererManifest && member === "manifest.json"
|
|
144
|
+
? 32 * 1024 * 1024
|
|
145
|
+
: MAX_BUNDLE_MEMBER_BYTES;
|
|
146
146
|
invariant(
|
|
147
|
-
sha256(readRegular(target, member,
|
|
147
|
+
sha256(readRegular(target, member, maximumBytes)).slice(7) === match[1],
|
|
148
148
|
`checksum mismatch: ${member}`,
|
|
149
149
|
);
|
|
150
150
|
}
|
|
@@ -190,7 +190,7 @@ function validateScene(value) {
|
|
|
190
190
|
exactKeys(
|
|
191
191
|
value,
|
|
192
192
|
["schema", "id", "width", "height", "fps", "durationMs", "title"],
|
|
193
|
-
["durationClass", "commandLabel", "background", "accent"],
|
|
193
|
+
["durationClass", "compositionMode", "commandLabel", "background", "accent"],
|
|
194
194
|
"scene",
|
|
195
195
|
);
|
|
196
196
|
invariant(value.schema === "build-images.demo-scene/v1", "unsupported scene schema");
|
|
@@ -199,6 +199,11 @@ function validateScene(value) {
|
|
|
199
199
|
integer(value.height, 360, 1080, "scene.height");
|
|
200
200
|
const durationClass = value.durationClass ?? "standard";
|
|
201
201
|
invariant(durationClass === "standard" || durationClass === "long-form", "scene.durationClass is invalid");
|
|
202
|
+
const compositionMode = value.compositionMode ?? "presentation-framed";
|
|
203
|
+
invariant(
|
|
204
|
+
compositionMode === "presentation-framed" || compositionMode === "terminal-fill",
|
|
205
|
+
"scene.compositionMode is invalid",
|
|
206
|
+
);
|
|
202
207
|
const maximumDurationMs = durationClass === "long-form" ? LONG_FORM_MAX_DURATION_MS : STANDARD_MAX_DURATION_MS;
|
|
203
208
|
const maximumFps = durationClass === "long-form" ? LONG_FORM_MAX_FPS : 30;
|
|
204
209
|
integer(value.fps, 1, maximumFps, "scene.fps");
|
|
@@ -216,6 +221,7 @@ function validateScene(value) {
|
|
|
216
221
|
height: value.height,
|
|
217
222
|
fps: value.fps,
|
|
218
223
|
...(value.durationClass === undefined ? {} : { durationClass }),
|
|
224
|
+
compositionMode,
|
|
219
225
|
durationMs: value.durationMs,
|
|
220
226
|
title: value.title,
|
|
221
227
|
commandLabel: value.commandLabel ?? "",
|
|
@@ -674,8 +680,7 @@ function inspectRendererMedia(values) {
|
|
|
674
680
|
const rendererImage = required(values, "--renderer-image");
|
|
675
681
|
invariant(IMAGE_PATTERN.test(rendererImage), "renderer image must use an immutable sha256 coordinate");
|
|
676
682
|
invariant(!fs.existsSync(output), "media inspection output must not already exist");
|
|
677
|
-
const manifest =
|
|
678
|
-
invariant(manifest.schema === "build-images.auditable-demo-render/v1", "unexpected renderer manifest schema");
|
|
683
|
+
const { manifest } = readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS);
|
|
679
684
|
invariant(manifest.renderer?.image === rendererImage, "renderer manifest image coordinate mismatch");
|
|
680
685
|
const members = Object.keys(manifest.outputs || {})
|
|
681
686
|
.filter((name) => name !== "media-probe.json")
|
|
@@ -739,7 +744,7 @@ function qualifyMediaFixture(values) {
|
|
|
739
744
|
sha256(readRegular(path.join(renderOutput, name), `fixture ${name}`)),
|
|
740
745
|
]),
|
|
741
746
|
),
|
|
742
|
-
rendererManifestRoot: sha256(
|
|
747
|
+
rendererManifestRoot: sha256(readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS).bytes),
|
|
743
748
|
qualification: verified.qualification,
|
|
744
749
|
};
|
|
745
750
|
writeJson(output, { ...body, evidenceRoot: semanticRoot(body) });
|
|
@@ -979,9 +984,8 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
979
984
|
"public-projection.json",
|
|
980
985
|
"scene.json",
|
|
981
986
|
];
|
|
982
|
-
verifyChecksums(renderOutput);
|
|
983
|
-
const manifest =
|
|
984
|
-
invariant(manifest.schema === "build-images.auditable-demo-render/v1", "unexpected renderer manifest schema");
|
|
987
|
+
verifyChecksums(renderOutput, "checksums.sha256", { allowLongFormRendererManifest: true });
|
|
988
|
+
const { manifest } = readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS);
|
|
985
989
|
invariant(manifest.renderer?.image === expectedImage, "renderer manifest image coordinate mismatch");
|
|
986
990
|
const outputNames = Object.keys(manifest.outputs || {}).sort();
|
|
987
991
|
invariant(outputNames.includes("media-probe.json"), "renderer manifest must declare media-probe.json");
|
|
@@ -999,21 +1003,11 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
999
1003
|
const observed = manifest.inputs?.[key]?.root;
|
|
1000
1004
|
invariant(observed === sha256(readRegular(filePath, `${key} input`)), `renderer ${key} input root mismatch`);
|
|
1001
1005
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
invariant(
|
|
1008
|
-
Array.isArray(manifest.inputs?.renditions)
|
|
1009
|
-
&& manifest.inputs.renditions.length === 2
|
|
1010
|
-
&& manifest.inputs.renditions[0]?.role === "primary"
|
|
1011
|
-
&& manifest.inputs.renditions[1]?.role === "responsive"
|
|
1012
|
-
&& manifest.inputs.renditions[0]?.terminalCapture?.root
|
|
1013
|
-
!== manifest.inputs.renditions[1]?.terminalCapture?.root,
|
|
1014
|
-
"renderer native rendition inputs are not independently bound",
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1006
|
+
const composition = validateRendererCompositionInputs(
|
|
1007
|
+
manifest,
|
|
1008
|
+
expectedInputs,
|
|
1009
|
+
RENDITION_VALIDATION_HELPERS,
|
|
1010
|
+
);
|
|
1017
1011
|
const probe = readJson(path.join(renderOutput, "media-probe.json"), "media probe");
|
|
1018
1012
|
invariant(probe.schema === "build-images.demo-media-probe/v1" && probe.passed === true, "renderer media probe failed");
|
|
1019
1013
|
const qualification = qualifyRendererOutput(
|
|
@@ -1024,7 +1018,7 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
1024
1018
|
options.inspectMedia || inspectMediaFile,
|
|
1025
1019
|
options.inspectionRoot || "",
|
|
1026
1020
|
);
|
|
1027
|
-
return { manifest, probe, qualification };
|
|
1021
|
+
return { manifest, probe, qualification, composition };
|
|
1028
1022
|
}
|
|
1029
1023
|
|
|
1030
1024
|
function renditionInputRoots(root, renditions) {
|
|
@@ -1254,7 +1248,7 @@ function finalizeMedia(values) {
|
|
|
1254
1248
|
sourceSha,
|
|
1255
1249
|
qualifiedGateRoot: gateRoot,
|
|
1256
1250
|
rendererImage,
|
|
1257
|
-
rendererManifestRoot: sha256(
|
|
1251
|
+
rendererManifestRoot: sha256(readRendererManifest(path.join(renderOutput, "manifest.json"), RENDITION_VALIDATION_HELPERS).bytes),
|
|
1258
1252
|
};
|
|
1259
1253
|
const mediaReceipt = selectedProfile.mode === "archive"
|
|
1260
1254
|
? { schema: "buildchain.auditable-demo-media/v1", ...commonReceipt }
|
|
@@ -35,18 +35,15 @@ function tableName(value) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function number(value) {
|
|
38
|
-
return { N: String(value) };
|
|
38
|
+
return { N: String(Math.round(value * 100_000_000) / 100_000_000) };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function money(value, label) {
|
|
42
42
|
const normalized = String(value ?? "").trim();
|
|
43
|
-
if (!normalized) {
|
|
44
|
-
throw new Error(`${label} is required`);
|
|
45
|
-
}
|
|
43
|
+
if (!normalized) throw new Error(`${label} is required`);
|
|
46
44
|
const parsed = Number(normalized);
|
|
47
|
-
if (!Number.isFinite(parsed) || parsed < 0)
|
|
45
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
48
46
|
throw new Error(`${label} must be a non-negative finite number`);
|
|
49
|
-
}
|
|
50
47
|
return Math.round(parsed * 100_000_000) / 100_000_000;
|
|
51
48
|
}
|
|
52
49
|
|
|
@@ -90,9 +87,11 @@ export function createWindowsJitCampaignArmPlan(values = {}) {
|
|
|
90
87
|
);
|
|
91
88
|
const maxAcceptedInstances = acceptedInstances(values.maxAcceptedInstances);
|
|
92
89
|
const campaignReservationCeilingUsd = reservationUsd * maxAcceptedInstances;
|
|
93
|
-
const campaignSafetyCeilingUsd =
|
|
90
|
+
const campaignSafetyCeilingUsd = money(
|
|
94
91
|
campaignReservationCeilingUsd +
|
|
95
|
-
|
|
92
|
+
reservationUsd * WINDOWS_EC2_JIT.maxConcurrentInstances,
|
|
93
|
+
"campaignSafetyCeilingUsd",
|
|
94
|
+
);
|
|
96
95
|
const remainingPhaseBudgetUsd =
|
|
97
96
|
WINDOWS_EC2_JIT.budgetLimitUsd - phaseSpendBaselineUsd;
|
|
98
97
|
if (campaignSafetyCeilingUsd >= remainingPhaseBudgetUsd) {
|
|
@@ -164,6 +164,7 @@ function assertLivePreflight(plan, profile) {
|
|
|
164
164
|
throw new Error("Windows AMI identity or availability mismatch");
|
|
165
165
|
}
|
|
166
166
|
return {
|
|
167
|
+
budgetGuard: jsonResult(commandResult("/bin/bash", ["scripts/aws-windows-jit-operator.sh", "launch-gate", "--region", plan.aws.region, ...(profile ? ["--aws-profile", profile] : [])]), "provider Budget launch gate"),
|
|
167
168
|
runStatus: run.status,
|
|
168
169
|
jobStatus: job.status,
|
|
169
170
|
activeInstances: activeInstances.length,
|
|
@@ -12,7 +12,7 @@ export const WINDOWS_EC2_JIT = Object.freeze({
|
|
|
12
12
|
maximumInstanceLifetimeMinutes: 180,
|
|
13
13
|
maxConcurrentInstances: 1,
|
|
14
14
|
maxAcceptedInstances: 5,
|
|
15
|
-
budgetLimitUsd:
|
|
15
|
+
budgetLimitUsd: 110,
|
|
16
16
|
minimumSmokeJobs: 1,
|
|
17
17
|
minimumFullJobs: 3,
|
|
18
18
|
maximumCleanupLatencySeconds: 900,
|
|
@@ -3,7 +3,6 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { macosJitRunnerLabel } from "./aws-macos-jit-core.mjs";
|
|
5
5
|
import { windowsJitRunnerLabel } from "./aws-windows-jit-core.mjs";
|
|
6
|
-
|
|
7
6
|
export const RUNNER_PRESETS = Object.freeze({
|
|
8
7
|
"github-hosted": [
|
|
9
8
|
{ id: "linux-x64", name: "Linux x64", platform: "linux", runner: '["ubuntu-24.04"]', capabilities: ["node"] },
|
|
@@ -768,6 +767,55 @@ function platformIsLinux(platform) {
|
|
|
768
767
|
);
|
|
769
768
|
}
|
|
770
769
|
|
|
770
|
+
function platformUsesGitHubHostedRunner(platform, index) {
|
|
771
|
+
if (platform?.githubHosted !== undefined) {
|
|
772
|
+
if (typeof platform.githubHosted !== "boolean") {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`platforms-json[${index}].githubHosted must be a boolean`,
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
return platform.githubHosted;
|
|
778
|
+
}
|
|
779
|
+
if (String(platform?.provider || "").trim()) {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
const runnerLabels = parseJsonArray(
|
|
783
|
+
String(platform?.runner || "[]"),
|
|
784
|
+
`platforms-json[${index}].runner`,
|
|
785
|
+
).map((label) => String(label || "").toLowerCase());
|
|
786
|
+
if (runnerLabels.includes("self-hosted") || runnerLabels.length !== 1) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
return /^(ubuntu-(latest|20\.04|22\.04|24\.04|24\.04-arm)|windows-(latest|2019|2022|2025|11-arm)|macos-(latest|13|14|15|26)(-intel)?)$/.test(
|
|
790
|
+
runnerLabels[0],
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function bindRunnerHosting(platforms) {
|
|
795
|
+
return platforms.map((platform, index) => ({
|
|
796
|
+
...platform,
|
|
797
|
+
githubHosted: platformUsesGitHubHostedRunner(platform, index),
|
|
798
|
+
}));
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function runnerHostingSummary(platforms) {
|
|
802
|
+
const githubHostedPlatforms = platforms.filter(
|
|
803
|
+
(platform) => platform.githubHosted,
|
|
804
|
+
);
|
|
805
|
+
const relayPlatforms = platforms.filter((platform) => !platform.githubHosted);
|
|
806
|
+
return {
|
|
807
|
+
githubHostedPlatforms,
|
|
808
|
+
githubHostedPlatformsJson: JSON.stringify(githubHostedPlatforms),
|
|
809
|
+
githubHostedPlatformIdsJson: JSON.stringify(
|
|
810
|
+
githubHostedPlatforms.map((platform) => platform.id),
|
|
811
|
+
),
|
|
812
|
+
githubHostedPlatformCount: githubHostedPlatforms.length,
|
|
813
|
+
relayPlatforms,
|
|
814
|
+
relayPlatformsJson: JSON.stringify(relayPlatforms),
|
|
815
|
+
relayPlatformCount: relayPlatforms.length,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
771
819
|
function normalizePlatform(platform, index) {
|
|
772
820
|
const id = String(platform?.id || "").trim();
|
|
773
821
|
const name = String(platform?.name || id).trim();
|
|
@@ -793,7 +841,9 @@ function normalizePlatform(platform, index) {
|
|
|
793
841
|
if (platform?.platform !== undefined) normalized.platform = String(platform.platform || "").trim();
|
|
794
842
|
if (platform?.provider !== undefined) normalized.provider = String(platform.provider || "").trim();
|
|
795
843
|
if (platform?.project !== undefined) normalized.project = String(platform.project || "").trim();
|
|
844
|
+
if (platform?.githubHosted !== undefined) normalized.githubHosted = platform.githubHosted;
|
|
796
845
|
normalized.capabilities = capabilities.sort();
|
|
846
|
+
if (platform?.environment !== undefined) normalized.environment = platform.environment;
|
|
797
847
|
if (platform?.required === false) normalized.required = false;
|
|
798
848
|
return normalized;
|
|
799
849
|
}
|
|
@@ -813,8 +863,10 @@ export function resolveRunnerMatrix({
|
|
|
813
863
|
linuxContainerImage,
|
|
814
864
|
});
|
|
815
865
|
if (customPlatformsJson) {
|
|
816
|
-
const platforms =
|
|
817
|
-
|
|
866
|
+
const platforms = bindRunnerHosting(
|
|
867
|
+
parseJsonArray(customPlatformsJson, "platforms-json").map(
|
|
868
|
+
normalizePlatform,
|
|
869
|
+
),
|
|
818
870
|
);
|
|
819
871
|
if (platforms.length === 0) {
|
|
820
872
|
throw new Error("platforms-json must include at least one platform");
|
|
@@ -838,6 +890,7 @@ export function resolveRunnerMatrix({
|
|
|
838
890
|
containerPlatformsJson: JSON.stringify(containerPlatforms),
|
|
839
891
|
containerPlatformCount: containerPlatforms.length,
|
|
840
892
|
linuxContainer,
|
|
893
|
+
...runnerHostingSummary(platforms),
|
|
841
894
|
};
|
|
842
895
|
}
|
|
843
896
|
|
|
@@ -875,6 +928,7 @@ export function resolveRunnerMatrix({
|
|
|
875
928
|
runner: JSON.stringify(["self-hosted", "macOS", "ARM64", runnerLabel]),
|
|
876
929
|
}));
|
|
877
930
|
}
|
|
931
|
+
resolvedPlatforms = bindRunnerHosting(resolvedPlatforms);
|
|
878
932
|
const containerPlatforms = linuxContainer.enabled
|
|
879
933
|
? resolvedPlatforms.filter(platformIsLinux)
|
|
880
934
|
: [];
|
|
@@ -894,6 +948,7 @@ export function resolveRunnerMatrix({
|
|
|
894
948
|
containerPlatformsJson: JSON.stringify(containerPlatforms),
|
|
895
949
|
containerPlatformCount: containerPlatforms.length,
|
|
896
950
|
linuxContainer,
|
|
951
|
+
...runnerHostingSummary(resolvedPlatforms),
|
|
897
952
|
};
|
|
898
953
|
}
|
|
899
954
|
|
|
@@ -108,10 +108,18 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
108
108
|
buildchain inspect release --passport <file-or-url> [--json]
|
|
109
109
|
buildchain inspect artifact <subject> [--passport <file-or-url>] [--npm-registry <url>] [--json]
|
|
110
110
|
buildchain doctor [--cwd <dir>] [--require-publish-source-lock] [--json]
|
|
111
|
+
buildchain dev pr-admit --repository <owner/repo> --branch <dev/vN/vN.M>
|
|
112
|
+
--pull-request <n> --expected-head <sha>
|
|
113
|
+
[--execute] [--output <file>] [--json]
|
|
111
114
|
buildchain dev merge-queue --repository <owner/repo> --branch <dev/vN/vN.M>
|
|
112
115
|
[--from-config | --workflow <required-workflow.yml>...] [--cwd <dir>]
|
|
113
116
|
[--check-response-timeout-minutes <n>]
|
|
114
117
|
[--max-entries-to-build <n>] [--apply]
|
|
118
|
+
buildchain dev warrant <submit|select|heartbeat|recover|close|cancel-queued|observe>
|
|
119
|
+
--repository <owner/repo> --branch <dev/vN/vN.M>
|
|
120
|
+
[--execute] [--output <file>] [--json]
|
|
121
|
+
buildchain dev proof <source|verify-source|classify|replay|integration|verify-integration>
|
|
122
|
+
[--output <file>] [--json]
|
|
115
123
|
buildchain log <info|warn|error> --event <name> [--phase <phase>]
|
|
116
124
|
[--component <name>] [--source <name>] [--attribute key=value]...
|
|
117
125
|
[--path <jsonl>] [--json]
|
|
@@ -194,6 +194,15 @@ export async function runBuildchainPatrol(optionsInput = {}, clientInput) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
result.summary.plannedCount = result.planned.length;
|
|
197
|
+
const admissionActions = result.actions.flatMap((action) => action.result?.actions || []);
|
|
198
|
+
result.runKind = "cadence-patrol";
|
|
199
|
+
result.outcome = result.summary.evaluatedCount === 0
|
|
200
|
+
? "no-op-no-candidates"
|
|
201
|
+
: admissionActions.length === 0
|
|
202
|
+
? "no-op-all-skipped"
|
|
203
|
+
: "actions-present";
|
|
204
|
+
result.noOp = admissionActions.length === 0;
|
|
205
|
+
result.qualification = false;
|
|
197
206
|
result.ok = result.inspections.every((inspection) => inspection.ok !== false);
|
|
198
207
|
return result;
|
|
199
208
|
}
|
|
@@ -137,6 +137,7 @@ const requiredPaths = [
|
|
|
137
137
|
".github/workflows/release-line-bootstrap.yml",
|
|
138
138
|
".github/workflows/release-governance-reconcile.yml",
|
|
139
139
|
".github/workflows/dev-pr-auto-merge.yml",
|
|
140
|
+
".github/workflows/buildchain-dev-delivery.yml",
|
|
140
141
|
".github/workflows/buildchain-patrol.yml",
|
|
141
142
|
".github/workflows/patrol-daily.yml",
|
|
142
143
|
".github/workflows/patrol-weekly.yml",
|