@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.
Files changed (72) hide show
  1. package/README.md +4 -4
  2. package/actions/promote-buildchain-ref/README.md +8 -0
  3. package/bin/buildchain.mjs +13 -1
  4. package/contracts/auditable-demo-scenario-v1.schema.json +52 -0
  5. package/dist/site/buildchain-contract.json +47 -27
  6. package/dist/site/buildchain-site.json +165 -44
  7. package/dist/site/capability-registry.json +3 -3
  8. package/dist/site/cli-registry.json +40 -4
  9. package/dist/site/controller-registry.json +20 -4
  10. package/dist/site/kfd-claims.json +140 -19
  11. package/dist/site/kfd-upstream-aggregate.json +9 -9
  12. package/dist/site/manual-registry.json +9 -9
  13. package/dist/site/node-api-registry.json +1161 -180
  14. package/dist/site/page-registry.json +152 -31
  15. package/dist/site/public-surface-audit.json +386 -19
  16. package/dist/site/publication-authority-registry.json +61 -1
  17. package/dist/site/publication-registry.json +4 -4
  18. package/dist/site/release-provenance.json +1 -0
  19. package/dist/site/site-manifest.json +13 -13
  20. package/dist/site/workflow-registry.json +151 -13
  21. package/docs/MAP.md +2 -0
  22. package/docs/auditable-demo.md +58 -11
  23. package/docs/aws-us-elastic-runner-burst-plane.md +114 -80
  24. package/docs/cli-reference.md +154 -0
  25. package/docs/dev-alpha-candidate-patrol.md +13 -5
  26. package/docs/dev-delivery-warrant.md +158 -0
  27. package/docs/node-api-reference.md +54 -15
  28. package/docs/publication-authority.md +11 -0
  29. package/docs/release-candidate.md +19 -2
  30. package/docs/release-governance.md +60 -1
  31. package/docs/reusable-build-surface.md +11 -1
  32. package/docs/shifu-gate-profiles.md +12 -1
  33. package/docs/versioning.md +2 -0
  34. package/package.json +4 -2
  35. package/packages/core/buildchain-publication-authority.js +3 -1
  36. package/packages/core/channel-candidate.js +2 -21
  37. package/packages/core/channel-promotion-baseline.js +199 -0
  38. package/packages/core/dev-delivery-candidate-identity.js +94 -0
  39. package/packages/core/dev-delivery-common.js +73 -0
  40. package/packages/core/dev-delivery-proof.js +252 -0
  41. package/packages/core/dev-delivery-warrant-cancellation.js +94 -0
  42. package/packages/core/dev-delivery-warrant-settlement.js +73 -0
  43. package/packages/core/dev-delivery-warrant.js +591 -0
  44. package/scripts/auditable-demo-bundle-verification.mjs +148 -0
  45. package/scripts/auditable-demo-platform.mjs +86 -50
  46. package/scripts/auditable-demo-presentation.mjs +83 -0
  47. package/scripts/auditable-demo-renditions.mjs +264 -0
  48. package/scripts/auditable-demo.mjs +24 -30
  49. package/scripts/aws-windows-jit-campaign-core.mjs +7 -8
  50. package/scripts/aws-windows-jit-controller.mjs +1 -0
  51. package/scripts/aws-windows-jit-core.mjs +1 -1
  52. package/scripts/build-contract-core.mjs +58 -3
  53. package/scripts/buildchain-cli-help.mjs +8 -0
  54. package/scripts/buildchain-patrol.mjs +9 -0
  55. package/scripts/check-inventory.mjs +1 -0
  56. package/scripts/dev-alpha-candidate-patrol.mjs +45 -48
  57. package/scripts/dev-delivery-proof.mjs +193 -0
  58. package/scripts/dev-delivery-warrant.mjs +426 -0
  59. package/scripts/dev-pr-auto-merge.mjs +497 -55
  60. package/scripts/dev-pr-delivery-warrant.mjs +209 -0
  61. package/scripts/dispatch-artifact-signing-authority.mjs +2 -4
  62. package/scripts/gate-profile-core.mjs +24 -0
  63. package/scripts/generate-site-bundle.mjs +2 -2
  64. package/scripts/git-fetch-process-tree.mjs +142 -0
  65. package/scripts/lifecycle-substage-evidence.mjs +274 -0
  66. package/scripts/locked-source-checkout.mjs +6 -3
  67. package/scripts/resolve-artifact-transfer-mode.mjs +9 -0
  68. package/scripts/resolve-build-contract.mjs +7 -0
  69. package/scripts/route-offline-runners.mjs +1 -0
  70. package/scripts/run-lifecycle-core.mjs +9 -9
  71. package/scripts/shifu-gate-profile.mjs +10 -16
  72. 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, MAX_BUNDLE_MEMBER_BYTES)).slice(7) === match[1],
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 = readJson(path.join(renderOutput, "manifest.json"), "renderer 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(readRegular(path.join(renderOutput, "manifest.json"), "renderer manifest")),
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 = readJson(path.join(renderOutput, "manifest.json"), "renderer 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
- if (expectedInputs.renditionSet) {
1003
- invariant(
1004
- manifest.derivation?.policy === "independent-native-frame-sets/v1",
1005
- "renderer did not use independent native frame sets",
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(readRegular(path.join(renderOutput, "manifest.json"), "renderer manifest")),
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
- reservationUsd * WINDOWS_EC2_JIT.maxConcurrentInstances;
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: 80,
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 = parseJsonArray(customPlatformsJson, "platforms-json").map(
817
- normalizePlatform,
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",