@kungfu-tech/buildchain 3.0.3 → 3.0.4-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -0
- package/dist/site/buildchain-contract.json +8 -8
- package/dist/site/buildchain-site.json +7 -7
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +18 -0
- package/dist/site/kfd-claims.json +19 -7
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +2 -2
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +14 -4
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/docs/cli.md +22 -7
- package/package.json +1 -1
- package/packages/core/paper-agent-entry.js +361 -0
- package/packages/core/paper-repository.js +2 -0
- package/packages/core/paper-scaffold-content.js +163 -0
- package/packages/core/paper-work.js +35 -0
- package/packages/core/paper.js +151 -175
- package/scripts/aggregate-build-summary.mjs +67 -4
- package/scripts/auditable-demo-renditions.mjs +131 -0
- package/scripts/auditable-demo.mjs +88 -91
- package/scripts/buildchain-cli-help.mjs +1 -0
- package/scripts/paper-agent-cli.mjs +35 -0
- package/scripts/paper-work-fleet-cli.mjs +14 -4
- package/scripts/paper.mjs +15 -2
- package/scripts/site-capability-metadata.mjs +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const TERMINAL_CAPTURE_NON_AUTHORITIES = [
|
|
5
|
+
"first-party-identity",
|
|
6
|
+
"system-identity",
|
|
7
|
+
"kfd-compliance",
|
|
8
|
+
"product-system-metadata",
|
|
9
|
+
"package-metadata",
|
|
10
|
+
"registry-history",
|
|
11
|
+
"scan-output",
|
|
12
|
+
"standalone-generation",
|
|
13
|
+
];
|
|
14
|
+
const RENDITION_SET_NON_AUTHORITIES = [
|
|
15
|
+
"publication-authority",
|
|
16
|
+
"runtime-authority",
|
|
17
|
+
...TERMINAL_CAPTURE_NON_AUTHORITIES,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export function validateTerminalCapture(value, scene, helpers) {
|
|
21
|
+
const { decodeBase64, digestPattern, exactKeys, integer, invariant, maxBytes, maxEvents, text } = helpers;
|
|
22
|
+
exactKeys(
|
|
23
|
+
value,
|
|
24
|
+
["schema", "command", "dimensions", "durationMs", "encoding", "events", "completion", "exitCode", "authority"],
|
|
25
|
+
[],
|
|
26
|
+
"terminalCapture",
|
|
27
|
+
);
|
|
28
|
+
invariant(value.schema === "kungfu.terminal-capture/v1", "unsupported terminal capture schema");
|
|
29
|
+
text(value.command, 1, 160, "terminalCapture.command");
|
|
30
|
+
exactKeys(value.dimensions, ["columns", "rows"], [], "terminalCapture.dimensions");
|
|
31
|
+
integer(value.dimensions.columns, 80, 200, "terminalCapture.dimensions.columns");
|
|
32
|
+
integer(value.dimensions.rows, 24, 80, "terminalCapture.dimensions.rows");
|
|
33
|
+
const durationMs = integer(value.durationMs, 500, 60000, "terminalCapture.durationMs");
|
|
34
|
+
invariant(
|
|
35
|
+
durationMs <= scene.durationMs && scene.durationMs - durationMs <= 2000,
|
|
36
|
+
"terminal capture duration must end within two seconds of the scene",
|
|
37
|
+
);
|
|
38
|
+
invariant(value.encoding === "base64", "terminalCapture.encoding must be base64");
|
|
39
|
+
invariant(
|
|
40
|
+
Array.isArray(value.events) && value.events.length > 0 && value.events.length <= maxEvents,
|
|
41
|
+
`terminalCapture.events must contain 1 through ${maxEvents} events`,
|
|
42
|
+
);
|
|
43
|
+
let previousAtMs = -1;
|
|
44
|
+
let totalBytes = 0;
|
|
45
|
+
for (const [index, event] of value.events.entries()) {
|
|
46
|
+
exactKeys(event, ["atMs", "data"], [], `terminalCapture.events[${index}]`);
|
|
47
|
+
const atMs = integer(event.atMs, 0, durationMs - 1, `terminalCapture.events[${index}].atMs`);
|
|
48
|
+
invariant(atMs >= previousAtMs, "terminal capture event timestamps must be monotonic");
|
|
49
|
+
invariant(index > 0 || atMs === 0, "the first terminal capture event must start at zero");
|
|
50
|
+
previousAtMs = atMs;
|
|
51
|
+
totalBytes += decodeBase64(event.data, `terminalCapture.events[${index}].data`).length;
|
|
52
|
+
invariant(totalBytes <= maxBytes, "terminal capture exceeds the 4 MiB byte bound");
|
|
53
|
+
}
|
|
54
|
+
exactKeys(value.completion, ["schema", "status", "reportRoot", "eventCount"], [], "terminalCapture.completion");
|
|
55
|
+
invariant(
|
|
56
|
+
value.completion.schema === "kungfu.agent-work-lab.tui-autoplay/v1"
|
|
57
|
+
&& value.completion.status === "qualified"
|
|
58
|
+
&& digestPattern.test(value.completion.reportRoot),
|
|
59
|
+
"terminal capture completion sentinel is not a qualified Agent Work Lab autoplay",
|
|
60
|
+
);
|
|
61
|
+
integer(value.completion.eventCount, 1, 100_000, "terminalCapture.completion.eventCount");
|
|
62
|
+
invariant(value.exitCode === 0, "terminal capture exitCode must be zero");
|
|
63
|
+
exactKeys(value.authority, ["classification", "grants", "nonAuthorities"], [], "terminalCapture.authority");
|
|
64
|
+
invariant(value.authority.classification === "volatile-terminal-observation", "terminal capture authority classification must remain observation-only");
|
|
65
|
+
invariant(Array.isArray(value.authority.grants) && value.authority.grants.length === 0, "terminal capture must not grant authority");
|
|
66
|
+
invariant(
|
|
67
|
+
JSON.stringify(value.authority.nonAuthorities) === JSON.stringify(TERMINAL_CAPTURE_NON_AUTHORITIES),
|
|
68
|
+
"terminal capture must declare every identity and metadata non-authority",
|
|
69
|
+
);
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validateRenditionSet(output, helpers) {
|
|
74
|
+
const { decodeUtf8, exactKeys, invariant, maxBytes, readJson, readRegular, sha256, validateProjection, validateScene } = helpers;
|
|
75
|
+
const manifestPath = path.join(output, "rendition-set.json");
|
|
76
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
77
|
+
const value = readJson(manifestPath, "rendition set");
|
|
78
|
+
exactKeys(value, ["schema", "renditions", "authority"], [], "renditionSet");
|
|
79
|
+
invariant(value.schema === "kungfu.auditable-demo.rendition-set/v1", "unsupported rendition set schema");
|
|
80
|
+
exactKeys(value.authority, ["classification", "grants", "nonAuthorities"], [], "renditionSet.authority");
|
|
81
|
+
invariant(
|
|
82
|
+
value.authority.classification === "capture-routing-metadata"
|
|
83
|
+
&& Array.isArray(value.authority.grants)
|
|
84
|
+
&& value.authority.grants.length === 0
|
|
85
|
+
&& JSON.stringify(value.authority.nonAuthorities) === JSON.stringify(RENDITION_SET_NON_AUTHORITIES),
|
|
86
|
+
"rendition set authority boundary is invalid",
|
|
87
|
+
);
|
|
88
|
+
invariant(Array.isArray(value.renditions) && value.renditions.length === 2, "rendition set must contain exactly two captures");
|
|
89
|
+
const expected = [
|
|
90
|
+
{
|
|
91
|
+
id: "1080p", role: "primary", transcript: "complete-transcript.txt",
|
|
92
|
+
projection: "public-projection.json", scene: "scene.json", terminalCapture: "terminal-capture.json",
|
|
93
|
+
width: 1920, height: 1080,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "720p", role: "responsive", transcript: "complete-transcript-720p.txt",
|
|
97
|
+
projection: "public-projection-720p.json", scene: "scene-720p.json", terminalCapture: "terminal-capture-720p.json",
|
|
98
|
+
width: 1280, height: 720,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
const normalized = value.renditions.map((entry, index) => {
|
|
102
|
+
const label = `renditionSet.renditions[${index}]`;
|
|
103
|
+
exactKeys(entry, ["id", "role", "transcript", "projection", "scene", "terminalCapture", "captureRoot"], [], label);
|
|
104
|
+
const declaration = expected[index];
|
|
105
|
+
for (const key of ["id", "role", "transcript", "projection", "scene", "terminalCapture"]) {
|
|
106
|
+
invariant(entry[key] === declaration[key], `${label}.${key} is not the exact native rendition contract`);
|
|
107
|
+
}
|
|
108
|
+
const transcriptBytes = readRegular(path.join(output, entry.transcript), `${label} transcript`, 4 * 1024 * 1024);
|
|
109
|
+
const transcript = decodeUtf8(transcriptBytes, `${label} transcript`).replace(/\r\n/g, "\n");
|
|
110
|
+
invariant(transcript.trim().length > 0, `${label} transcript must not be empty`);
|
|
111
|
+
const lines = transcript.endsWith("\n") ? transcript.slice(0, -1).split("\n") : transcript.split("\n");
|
|
112
|
+
const scene = validateScene(readJson(path.join(output, entry.scene), `${label} scene`));
|
|
113
|
+
invariant(scene.width === declaration.width && scene.height === declaration.height, `${label} scene dimensions are not native`);
|
|
114
|
+
const projection = validateProjection(readJson(path.join(output, entry.projection), `${label} projection`), scene, lines.length);
|
|
115
|
+
const captureBytes = readRegular(path.join(output, entry.terminalCapture), `${label} terminal capture`, maxBytes);
|
|
116
|
+
const capture = validateTerminalCapture(JSON.parse(decodeUtf8(captureBytes, `${label} terminal capture`)), scene, helpers);
|
|
117
|
+
invariant(entry.captureRoot === sha256(captureBytes), `${label}.captureRoot mismatch`);
|
|
118
|
+
return { ...entry, transcript, lines, scene, projection, capture };
|
|
119
|
+
});
|
|
120
|
+
invariant(normalized[0].captureRoot !== normalized[1].captureRoot, "native rendition capture roots must be distinct");
|
|
121
|
+
invariant(
|
|
122
|
+
JSON.stringify(normalized[0].capture.dimensions) !== JSON.stringify(normalized[1].capture.dimensions),
|
|
123
|
+
"native rendition PTY dimensions must be distinct",
|
|
124
|
+
);
|
|
125
|
+
invariant(
|
|
126
|
+
normalized[0].projection.evidenceClass === normalized[1].projection.evidenceClass
|
|
127
|
+
&& normalized[0].projection.claimBoundary === normalized[1].projection.claimBoundary,
|
|
128
|
+
"native rendition evidence boundaries must match",
|
|
129
|
+
);
|
|
130
|
+
return { schema: value.schema, renditions: normalized, authority: value.authority };
|
|
131
|
+
}
|
|
@@ -7,6 +7,10 @@ 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
14
|
|
|
11
15
|
const UTF8 = new TextDecoder("utf-8", { fatal: true });
|
|
12
16
|
const IMAGE_PATTERN = /^[a-z0-9][a-z0-9./_-]*@sha256:[0-9a-f]{64}$/;
|
|
@@ -22,19 +26,16 @@ const REQUIRED_ADAPTER_FILES = [
|
|
|
22
26
|
"public-projection.json",
|
|
23
27
|
"scene.json",
|
|
24
28
|
];
|
|
25
|
-
const OPTIONAL_ADAPTER_FILES = [
|
|
29
|
+
const OPTIONAL_ADAPTER_FILES = [
|
|
30
|
+
"terminal-capture.json",
|
|
31
|
+
"complete-transcript-720p.txt",
|
|
32
|
+
"public-projection-720p.json",
|
|
33
|
+
"scene-720p.json",
|
|
34
|
+
"terminal-capture-720p.json",
|
|
35
|
+
"rendition-set.json",
|
|
36
|
+
];
|
|
26
37
|
const MAX_TERMINAL_CAPTURE_BYTES = 4 * 1024 * 1024;
|
|
27
38
|
const MAX_TERMINAL_CAPTURE_EVENTS = 10_000;
|
|
28
|
-
const TERMINAL_CAPTURE_NON_AUTHORITIES = [
|
|
29
|
-
"first-party-identity",
|
|
30
|
-
"system-identity",
|
|
31
|
-
"kfd-compliance",
|
|
32
|
-
"product-system-metadata",
|
|
33
|
-
"package-metadata",
|
|
34
|
-
"registry-history",
|
|
35
|
-
"scan-output",
|
|
36
|
-
"standalone-generation",
|
|
37
|
-
];
|
|
38
39
|
|
|
39
40
|
function invariant(condition, message) {
|
|
40
41
|
if (!condition) throw new Error(message);
|
|
@@ -249,80 +250,11 @@ function validateProjection(value, scene, transcriptLineCount) {
|
|
|
249
250
|
};
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
|
|
253
|
-
exactKeys
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"command",
|
|
258
|
-
"dimensions",
|
|
259
|
-
"durationMs",
|
|
260
|
-
"encoding",
|
|
261
|
-
"events",
|
|
262
|
-
"completion",
|
|
263
|
-
"exitCode",
|
|
264
|
-
"authority",
|
|
265
|
-
],
|
|
266
|
-
[],
|
|
267
|
-
"terminalCapture",
|
|
268
|
-
);
|
|
269
|
-
invariant(value.schema === "kungfu.terminal-capture/v1", "unsupported terminal capture schema");
|
|
270
|
-
text(value.command, 1, 160, "terminalCapture.command");
|
|
271
|
-
exactKeys(value.dimensions, ["columns", "rows"], [], "terminalCapture.dimensions");
|
|
272
|
-
integer(value.dimensions.columns, 80, 200, "terminalCapture.dimensions.columns");
|
|
273
|
-
integer(value.dimensions.rows, 24, 80, "terminalCapture.dimensions.rows");
|
|
274
|
-
const durationMs = integer(value.durationMs, 500, 60000, "terminalCapture.durationMs");
|
|
275
|
-
invariant(
|
|
276
|
-
durationMs <= scene.durationMs && scene.durationMs - durationMs <= 2000,
|
|
277
|
-
"terminal capture duration must end within two seconds of the scene",
|
|
278
|
-
);
|
|
279
|
-
invariant(value.encoding === "base64", "terminalCapture.encoding must be base64");
|
|
280
|
-
invariant(
|
|
281
|
-
Array.isArray(value.events)
|
|
282
|
-
&& value.events.length > 0
|
|
283
|
-
&& value.events.length <= MAX_TERMINAL_CAPTURE_EVENTS,
|
|
284
|
-
`terminalCapture.events must contain 1 through ${MAX_TERMINAL_CAPTURE_EVENTS} events`,
|
|
285
|
-
);
|
|
286
|
-
let previousAtMs = -1;
|
|
287
|
-
let totalBytes = 0;
|
|
288
|
-
for (const [index, event] of value.events.entries()) {
|
|
289
|
-
exactKeys(event, ["atMs", "data"], [], `terminalCapture.events[${index}]`);
|
|
290
|
-
const atMs = integer(event.atMs, 0, durationMs - 1, `terminalCapture.events[${index}].atMs`);
|
|
291
|
-
invariant(atMs >= previousAtMs, "terminal capture event timestamps must be monotonic");
|
|
292
|
-
invariant(index > 0 || atMs === 0, "the first terminal capture event must start at zero");
|
|
293
|
-
previousAtMs = atMs;
|
|
294
|
-
totalBytes += decodeBase64(event.data, `terminalCapture.events[${index}].data`).length;
|
|
295
|
-
invariant(totalBytes <= MAX_TERMINAL_CAPTURE_BYTES, "terminal capture exceeds the 4 MiB byte bound");
|
|
296
|
-
}
|
|
297
|
-
exactKeys(
|
|
298
|
-
value.completion,
|
|
299
|
-
["schema", "status", "reportRoot", "eventCount"],
|
|
300
|
-
[],
|
|
301
|
-
"terminalCapture.completion",
|
|
302
|
-
);
|
|
303
|
-
invariant(
|
|
304
|
-
value.completion.schema === "kungfu.agent-work-lab.tui-autoplay/v1"
|
|
305
|
-
&& value.completion.status === "qualified"
|
|
306
|
-
&& DIGEST_PATTERN.test(value.completion.reportRoot),
|
|
307
|
-
"terminal capture completion sentinel is not a qualified Agent Work Lab autoplay",
|
|
308
|
-
);
|
|
309
|
-
integer(value.completion.eventCount, 1, 100_000, "terminalCapture.completion.eventCount");
|
|
310
|
-
invariant(value.exitCode === 0, "terminal capture exitCode must be zero");
|
|
311
|
-
exactKeys(value.authority, ["classification", "grants", "nonAuthorities"], [], "terminalCapture.authority");
|
|
312
|
-
invariant(
|
|
313
|
-
value.authority.classification === "volatile-terminal-observation",
|
|
314
|
-
"terminal capture authority classification must remain observation-only",
|
|
315
|
-
);
|
|
316
|
-
invariant(
|
|
317
|
-
Array.isArray(value.authority.grants) && value.authority.grants.length === 0,
|
|
318
|
-
"terminal capture must not grant authority",
|
|
319
|
-
);
|
|
320
|
-
invariant(
|
|
321
|
-
JSON.stringify(value.authority.nonAuthorities) === JSON.stringify(TERMINAL_CAPTURE_NON_AUTHORITIES),
|
|
322
|
-
"terminal capture must declare every identity and metadata non-authority",
|
|
323
|
-
);
|
|
324
|
-
return value;
|
|
325
|
-
}
|
|
253
|
+
const RENDITION_VALIDATION_HELPERS = {
|
|
254
|
+
decodeBase64, decodeUtf8, digestPattern: DIGEST_PATTERN, exactKeys, integer, invariant,
|
|
255
|
+
maxBytes: MAX_TERMINAL_CAPTURE_BYTES, maxEvents: MAX_TERMINAL_CAPTURE_EVENTS,
|
|
256
|
+
readJson, readRegular, sha256, text, validateProjection, validateScene,
|
|
257
|
+
};
|
|
326
258
|
|
|
327
259
|
function validateSourceCoordinate(value) {
|
|
328
260
|
exactKeys(
|
|
@@ -379,8 +311,17 @@ function validateAdapterOutput(output, strict = true) {
|
|
|
379
311
|
);
|
|
380
312
|
const terminalCapturePath = path.join(output, "terminal-capture.json");
|
|
381
313
|
const terminalCapture = fs.existsSync(terminalCapturePath)
|
|
382
|
-
? validateTerminalCapture(
|
|
314
|
+
? validateTerminalCapture(
|
|
315
|
+
readJson(terminalCapturePath, "terminal capture"),
|
|
316
|
+
scene,
|
|
317
|
+
RENDITION_VALIDATION_HELPERS,
|
|
318
|
+
)
|
|
383
319
|
: null;
|
|
320
|
+
const renditionSet = validateRenditionSet(output, RENDITION_VALIDATION_HELPERS);
|
|
321
|
+
invariant(
|
|
322
|
+
!renditionSet || terminalCapture,
|
|
323
|
+
"rendition set requires the primary terminal capture",
|
|
324
|
+
);
|
|
384
325
|
if (strict) {
|
|
385
326
|
const allowed = new Set([...REQUIRED_ADAPTER_FILES, ...OPTIONAL_ADAPTER_FILES]);
|
|
386
327
|
for (const member of listFiles(output)) invariant(allowed.has(member), `undeclared adapter output: ${member}`);
|
|
@@ -391,6 +332,7 @@ function validateAdapterOutput(output, strict = true) {
|
|
|
391
332
|
scene,
|
|
392
333
|
projection,
|
|
393
334
|
terminalCapture,
|
|
335
|
+
renditionSet,
|
|
394
336
|
};
|
|
395
337
|
}
|
|
396
338
|
|
|
@@ -1047,6 +989,21 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
1047
989
|
const observed = manifest.inputs?.[key]?.root;
|
|
1048
990
|
invariant(observed === sha256(readRegular(filePath, `${key} input`)), `renderer ${key} input root mismatch`);
|
|
1049
991
|
}
|
|
992
|
+
if (expectedInputs.renditionSet) {
|
|
993
|
+
invariant(
|
|
994
|
+
manifest.derivation?.policy === "independent-native-frame-sets/v1",
|
|
995
|
+
"renderer did not use independent native frame sets",
|
|
996
|
+
);
|
|
997
|
+
invariant(
|
|
998
|
+
Array.isArray(manifest.inputs?.renditions)
|
|
999
|
+
&& manifest.inputs.renditions.length === 2
|
|
1000
|
+
&& manifest.inputs.renditions[0]?.role === "primary"
|
|
1001
|
+
&& manifest.inputs.renditions[1]?.role === "responsive"
|
|
1002
|
+
&& manifest.inputs.renditions[0]?.terminalCapture?.root
|
|
1003
|
+
!== manifest.inputs.renditions[1]?.terminalCapture?.root,
|
|
1004
|
+
"renderer native rendition inputs are not independently bound",
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1050
1007
|
const probe = readJson(path.join(renderOutput, "media-probe.json"), "media probe");
|
|
1051
1008
|
invariant(probe.schema === "build-images.demo-media-probe/v1" && probe.passed === true, "renderer media probe failed");
|
|
1052
1009
|
const qualification = qualifyRendererOutput(
|
|
@@ -1099,11 +1056,10 @@ function finalizeGate(values) {
|
|
|
1099
1056
|
fs.writeFileSync(path.join(output, "complete-transcript.txt"), normalized.transcript);
|
|
1100
1057
|
writeJson(path.join(output, "scene.json"), normalized.scene);
|
|
1101
1058
|
writeJson(path.join(output, "public-projection.json"), normalized.projection);
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
path.join(adapterOutput,
|
|
1105
|
-
|
|
1106
|
-
);
|
|
1059
|
+
for (const name of OPTIONAL_ADAPTER_FILES) {
|
|
1060
|
+
if (fs.existsSync(path.join(adapterOutput, name))) {
|
|
1061
|
+
copyFile(path.join(adapterOutput, name), path.join(output, name));
|
|
1062
|
+
}
|
|
1107
1063
|
}
|
|
1108
1064
|
copyFile(sourceCoordinatePath, path.join(output, "source-artifact.json"));
|
|
1109
1065
|
copyFile(path.join(diagnostics, "adapter.json"), path.join(output, "adapter.json"));
|
|
@@ -1148,6 +1104,22 @@ function finalizeGate(values) {
|
|
|
1148
1104
|
},
|
|
1149
1105
|
}
|
|
1150
1106
|
: {}),
|
|
1107
|
+
...(normalized.renditionSet
|
|
1108
|
+
? {
|
|
1109
|
+
renditionSet: {
|
|
1110
|
+
schema: normalized.renditionSet.schema,
|
|
1111
|
+
root: sha256(readRegular(path.join(output, "rendition-set.json"), "rendition set")),
|
|
1112
|
+
renditions: normalized.renditionSet.renditions.map((rendition) => ({
|
|
1113
|
+
id: rendition.id,
|
|
1114
|
+
role: rendition.role,
|
|
1115
|
+
captureRoot: rendition.captureRoot,
|
|
1116
|
+
sceneRoot: sha256(readRegular(path.join(output, rendition.scene), `${rendition.id} scene`)),
|
|
1117
|
+
transcriptRoot: sha256(readRegular(path.join(output, rendition.transcript), `${rendition.id} transcript`)),
|
|
1118
|
+
projectionRoot: sha256(readRegular(path.join(output, rendition.projection), `${rendition.id} projection`)),
|
|
1119
|
+
})),
|
|
1120
|
+
},
|
|
1121
|
+
}
|
|
1122
|
+
: {}),
|
|
1151
1123
|
evidenceClass: normalized.projection.evidenceClass,
|
|
1152
1124
|
claimBoundary: normalized.projection.claimBoundary,
|
|
1153
1125
|
},
|
|
@@ -1198,6 +1170,27 @@ function verifyGate(values) {
|
|
|
1198
1170
|
"gate terminal capture root mismatch",
|
|
1199
1171
|
);
|
|
1200
1172
|
}
|
|
1173
|
+
const qualifiedRenditionSet = receipt.qualifiedInputs?.renditionSet;
|
|
1174
|
+
invariant(
|
|
1175
|
+
Boolean(qualifiedRenditionSet) === Boolean(normalized.renditionSet),
|
|
1176
|
+
"gate rendition set presence drifted",
|
|
1177
|
+
);
|
|
1178
|
+
if (normalized.renditionSet) {
|
|
1179
|
+
invariant(
|
|
1180
|
+
qualifiedRenditionSet.schema === normalized.renditionSet.schema
|
|
1181
|
+
&& qualifiedRenditionSet.root === sha256(readRegular(path.join(bundle, "rendition-set.json"), "rendition set"))
|
|
1182
|
+
&& JSON.stringify(qualifiedRenditionSet.renditions)
|
|
1183
|
+
=== JSON.stringify(normalized.renditionSet.renditions.map((rendition) => ({
|
|
1184
|
+
id: rendition.id,
|
|
1185
|
+
role: rendition.role,
|
|
1186
|
+
captureRoot: rendition.captureRoot,
|
|
1187
|
+
sceneRoot: sha256(readRegular(path.join(bundle, rendition.scene), `${rendition.id} scene`)),
|
|
1188
|
+
transcriptRoot: sha256(readRegular(path.join(bundle, rendition.transcript), `${rendition.id} transcript`)),
|
|
1189
|
+
projectionRoot: sha256(readRegular(path.join(bundle, rendition.projection), `${rendition.id} projection`)),
|
|
1190
|
+
}))),
|
|
1191
|
+
"gate native rendition roots mismatch",
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1201
1194
|
invariant(receipt.qualifiedInputs.evidenceClass === normalized.projection.evidenceClass, "gate evidence class drifted");
|
|
1202
1195
|
}
|
|
1203
1196
|
|
|
@@ -1228,6 +1221,7 @@ function finalizeMedia(values) {
|
|
|
1228
1221
|
"--media-profile": mediaProfile,
|
|
1229
1222
|
});
|
|
1230
1223
|
const terminalCapturePath = path.join(gateBundle, "terminal-capture.json");
|
|
1224
|
+
const renditionSetPath = path.join(gateBundle, "rendition-set.json");
|
|
1231
1225
|
const verifiedRenderer = verifyRendererOutput(renderOutput, rendererImage, {
|
|
1232
1226
|
scene: path.join(gateBundle, "scene.json"),
|
|
1233
1227
|
transcript: path.join(gateBundle, "complete-transcript.txt"),
|
|
@@ -1235,6 +1229,9 @@ function finalizeMedia(values) {
|
|
|
1235
1229
|
...(fs.existsSync(terminalCapturePath)
|
|
1236
1230
|
? { terminalCapture: terminalCapturePath }
|
|
1237
1231
|
: {}),
|
|
1232
|
+
...(fs.existsSync(renditionSetPath)
|
|
1233
|
+
? { renditionSet: renditionSetPath }
|
|
1234
|
+
: {}),
|
|
1238
1235
|
}, {
|
|
1239
1236
|
mediaProfile,
|
|
1240
1237
|
inspectMedia: mediaInspection?.inspectMedia,
|
|
@@ -199,6 +199,7 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
199
199
|
buildchain paper work submit [--cwd <dir>] [--title <title>] [--body <body>] [--execute] [--json]
|
|
200
200
|
buildchain paper fleet audit [--root <dir>] [--offline] [--json]
|
|
201
201
|
buildchain paper fleet update [--root <dir>] [--write] [--json]
|
|
202
|
+
buildchain paper agent verify [--cwd <dir>] [--offline] [--json]
|
|
202
203
|
buildchain paper preflight [--cwd <dir>] [--offline] [--json]
|
|
203
204
|
buildchain paper bootstrap npm [--cwd <dir>] [--execute]
|
|
204
205
|
[--confirm-public-package <name>] [--json]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { collectPaperPreflight } from "../packages/core/paper.js";
|
|
2
|
+
|
|
3
|
+
function readFlag(args, name, fallback = "") {
|
|
4
|
+
const index = args.indexOf(`--${name}`);
|
|
5
|
+
if (index === -1) return fallback;
|
|
6
|
+
return args[index + 1] || "";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function runPaperAgentCli({
|
|
10
|
+
command,
|
|
11
|
+
subcommand,
|
|
12
|
+
args,
|
|
13
|
+
cwd,
|
|
14
|
+
buildchainRoot,
|
|
15
|
+
buildchainVersion,
|
|
16
|
+
buildchainRef,
|
|
17
|
+
buildchainSha,
|
|
18
|
+
}) {
|
|
19
|
+
if (command !== "agent" || subcommand !== "verify") {
|
|
20
|
+
return { handled: false, result: null };
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
handled: true,
|
|
24
|
+
result: collectPaperPreflight({
|
|
25
|
+
cwd,
|
|
26
|
+
buildchainRoot,
|
|
27
|
+
buildchainVersion,
|
|
28
|
+
buildchainRef: readFlag(args, "buildchain-ref", buildchainRef),
|
|
29
|
+
buildchainSha,
|
|
30
|
+
registry: readFlag(args, "registry", "https://registry.npmjs.org/"),
|
|
31
|
+
offline: args.includes("--offline"),
|
|
32
|
+
agentEntryMode: "local",
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -368,12 +368,13 @@ function githubGovernance(cwd, repository) {
|
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
function runWorkStart({ args, cwd }) {
|
|
371
|
+
function runWorkStart({ args, cwd, buildchainSha }) {
|
|
372
372
|
const topic = args[0]?.startsWith("--") ? "" : args[0] || "";
|
|
373
373
|
const plan = createPaperWorkStartPlan({
|
|
374
374
|
cwd,
|
|
375
375
|
topic,
|
|
376
376
|
branch: readFlag(args, "branch"),
|
|
377
|
+
buildchainSha,
|
|
377
378
|
});
|
|
378
379
|
return args.includes("--execute") ? executePaperWorkStart(plan) : plan;
|
|
379
380
|
}
|
|
@@ -382,7 +383,11 @@ function runScaffold(options) {
|
|
|
382
383
|
const plan = planPaperScaffold({
|
|
383
384
|
cwd: options.cwd,
|
|
384
385
|
buildchainRoot: options.buildchainRoot,
|
|
385
|
-
buildchainVersion:
|
|
386
|
+
buildchainVersion: readFlag(
|
|
387
|
+
options.args,
|
|
388
|
+
"buildchain-version",
|
|
389
|
+
options.buildchainVersion,
|
|
390
|
+
),
|
|
386
391
|
buildchainRef: readFlag(
|
|
387
392
|
options.args,
|
|
388
393
|
"buildchain-ref",
|
|
@@ -405,7 +410,11 @@ function runMigration(options) {
|
|
|
405
410
|
const plan = planPaperMigration({
|
|
406
411
|
cwd: options.cwd,
|
|
407
412
|
buildchainRoot: options.buildchainRoot,
|
|
408
|
-
buildchainVersion:
|
|
413
|
+
buildchainVersion: readFlag(
|
|
414
|
+
options.args,
|
|
415
|
+
"buildchain-version",
|
|
416
|
+
options.buildchainVersion,
|
|
417
|
+
),
|
|
409
418
|
buildchainSha: options.buildchainSha,
|
|
410
419
|
});
|
|
411
420
|
return options.args.some((entry) => ["--write", "--execute"].includes(entry))
|
|
@@ -413,7 +422,7 @@ function runMigration(options) {
|
|
|
413
422
|
: plan;
|
|
414
423
|
}
|
|
415
424
|
|
|
416
|
-
function runWorkSubmit({ args, cwd }) {
|
|
425
|
+
function runWorkSubmit({ args, cwd, buildchainSha }) {
|
|
417
426
|
const repository = collectPaperStatus({ cwd }).identity.repository;
|
|
418
427
|
const branch = commandResult("git", ["branch", "--show-current"], {
|
|
419
428
|
cwd,
|
|
@@ -426,6 +435,7 @@ function runWorkSubmit({ args, cwd }) {
|
|
|
426
435
|
cwd,
|
|
427
436
|
pullRequests: observation.rows,
|
|
428
437
|
pullRequestObservation: observation,
|
|
438
|
+
buildchainSha,
|
|
429
439
|
});
|
|
430
440
|
return args.includes("--execute") ? executeWorkSubmit(plan, args) : plan;
|
|
431
441
|
}
|
package/scripts/paper.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
PAPER_SCAFFOLD_CONTRACT,
|
|
14
14
|
PAPER_STATUS_CONTRACT,
|
|
15
15
|
collectPaperPreflight,
|
|
16
|
+
resolvePaperBuildchainSha,
|
|
16
17
|
collectPaperStatus,
|
|
17
18
|
createPaperAlphaPlan,
|
|
18
19
|
createPaperBuildPlan,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
printPaperWorkFleetSummary,
|
|
25
26
|
runPaperWorkFleetCli,
|
|
26
27
|
} from "./paper-work-fleet-cli.mjs";
|
|
28
|
+
import { runPaperAgentCli } from "./paper-agent-cli.mjs";
|
|
27
29
|
|
|
28
30
|
function usage() {
|
|
29
31
|
return `Usage:
|
|
@@ -38,6 +40,7 @@ function usage() {
|
|
|
38
40
|
[--execute] [--json]
|
|
39
41
|
buildchain paper fleet audit [--root <dir>] [--offline] [--json]
|
|
40
42
|
buildchain paper fleet update [--root <dir>] [--write] [--json]
|
|
43
|
+
buildchain paper agent verify [--cwd <dir>] [--offline] [--json]
|
|
41
44
|
buildchain paper preflight [--cwd <dir>] [--offline] [--json]
|
|
42
45
|
buildchain paper bootstrap npm [--cwd <dir>] [--package <name>]
|
|
43
46
|
[--repository <owner/repo>] [--workflow <filename>]
|
|
@@ -455,6 +458,7 @@ export async function runPaperCli(
|
|
|
455
458
|
buildchainSha = "",
|
|
456
459
|
} = {},
|
|
457
460
|
) {
|
|
461
|
+
buildchainSha = resolvePaperBuildchainSha(buildchainRoot, buildchainSha);
|
|
458
462
|
const [command = "", maybeSubcommand = "", ...rest] = args;
|
|
459
463
|
const json = hasFlag(args, "json");
|
|
460
464
|
try {
|
|
@@ -476,8 +480,12 @@ export async function runPaperCli(
|
|
|
476
480
|
buildchainRef,
|
|
477
481
|
buildchainSha,
|
|
478
482
|
});
|
|
483
|
+
// prettier-ignore
|
|
484
|
+
const paperAgent = runPaperAgentCli({ command, subcommand: maybeSubcommand, args: effectiveArgs, cwd, buildchainRoot, buildchainVersion, buildchainRef, buildchainSha });
|
|
479
485
|
let result;
|
|
480
|
-
if (
|
|
486
|
+
if (paperAgent.handled) {
|
|
487
|
+
result = paperAgent.result;
|
|
488
|
+
} else if (workFleet.handled) {
|
|
481
489
|
result = workFleet.result;
|
|
482
490
|
} else if (command === "preflight") {
|
|
483
491
|
result = collectPaperPreflight({
|
|
@@ -492,6 +500,11 @@ export async function runPaperCli(
|
|
|
492
500
|
"https://registry.npmjs.org/",
|
|
493
501
|
),
|
|
494
502
|
offline: hasFlag(effectiveArgs, "offline"),
|
|
503
|
+
agentEntryMode: hasFlag(effectiveArgs, "ci")
|
|
504
|
+
? "ci"
|
|
505
|
+
: hasFlag(effectiveArgs, "agent-entry")
|
|
506
|
+
? "local"
|
|
507
|
+
: "contract",
|
|
495
508
|
});
|
|
496
509
|
} else if (command === "bootstrap" && maybeSubcommand === "npm") {
|
|
497
510
|
result = executePaperNpmBootstrap({
|
|
@@ -577,7 +590,7 @@ export async function runPaperCli(
|
|
|
577
590
|
: plan;
|
|
578
591
|
} else {
|
|
579
592
|
throw new Error(
|
|
580
|
-
"usage: buildchain paper <scaffold|migrate|work start|work submit|fleet audit|fleet update|preflight|bootstrap npm|build|alpha|status|resume> ...",
|
|
593
|
+
"usage: buildchain paper <scaffold|migrate|work start|work submit|fleet audit|fleet update|agent verify|preflight|bootstrap npm|build|alpha|status|resume> ...",
|
|
581
594
|
);
|
|
582
595
|
}
|
|
583
596
|
printResult(result, json);
|
|
@@ -97,6 +97,7 @@ export function cliCommandMeta(id) {
|
|
|
97
97
|
"portable-cache-receipt": { group: "observability-diagnostics", purpose: "Seal exact, compatible, miss, or corrupt provider evidence against one cache plan." },
|
|
98
98
|
"npm-dry-run": { group: "release-passport-trust", purpose: "Verify npm publish shape before a release transaction." },
|
|
99
99
|
paper: { group: "reusable-build", purpose: "Inspect the unified paper repository and publication command families." },
|
|
100
|
+
"paper-agent": { group: "getting-started", purpose: "Verify the mandatory managed Paper agent entry contract on an existing work branch." },
|
|
100
101
|
"paper-alpha": { group: "release-passport-trust", purpose: "Plan or open the protected dev-to-alpha paper publication PR without direct publication." },
|
|
101
102
|
"paper-bootstrap-npm": { group: "release-passport-trust", purpose: "Dry-run or explicitly bootstrap the public npm package and bind GitHub Trusted Publishing." },
|
|
102
103
|
"paper-build": { group: "reusable-build", purpose: "Plan or execute the existing two-clean-build reproducibility gate for a paper." },
|
|
@@ -199,4 +200,3 @@ export function nodeApiMeta(exportName) {
|
|
|
199
200
|
}
|
|
200
201
|
return { capabilityGroup: capabilityGroup(meta.group), summary: meta.summary, audience: ["developer", "agent"], maturity: "stable" };
|
|
201
202
|
}
|
|
202
|
-
|