@kungfu-tech/buildchain 4.0.0 → 4.0.1-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 (69) hide show
  1. package/AGENTS.md +13 -5
  2. package/actions/promote-buildchain-ref/README.md +11 -6
  3. package/architecture/internal-capabilities.json +11 -2
  4. package/architecture/maintainability-policy.json +149 -17
  5. package/architecture/v4-floating-consumer-policy.json +75 -0
  6. package/architecture/v4-floating-consumer-policy.md +32 -0
  7. package/architecture/v4-runtime-ref-resume-authority.json +64 -0
  8. package/architecture/v4-stage-capsule-qualification.json +2 -2
  9. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  10. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +53 -0
  11. package/contracts/fixtures/v4-runtime-ref-resume-authority-v1/scenario.json +15 -0
  12. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +105 -0
  13. package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +235 -0
  14. package/dist/site/buildchain-contract.json +101 -31
  15. package/dist/site/buildchain-site.json +73 -27
  16. package/dist/site/capability-registry.json +3 -3
  17. package/dist/site/controller-registry.json +62 -6
  18. package/dist/site/kfd-claims.json +68 -13
  19. package/dist/site/kfd-upstream-aggregate.json +1 -1
  20. package/dist/site/manual-registry.json +6 -6
  21. package/dist/site/node-api-registry.json +799 -127
  22. package/dist/site/page-registry.json +63 -17
  23. package/dist/site/public-surface-audit.json +49 -17
  24. package/dist/site/publication-registry.json +4 -4
  25. package/dist/site/release-provenance.json +2 -0
  26. package/dist/site/site-manifest.json +10 -10
  27. package/dist/site/workflow-registry.json +46 -18
  28. package/docs/MAP.md +2 -0
  29. package/docs/dev-delivery-warrant.md +19 -1
  30. package/docs/lifecycle-protocol.md +41 -0
  31. package/docs/node-api-reference.md +207 -134
  32. package/docs/reusable-build-surface.md +41 -0
  33. package/docs/runtime-train-validation.md +10 -0
  34. package/docs/v4-runtime-ref-resume-authority.md +69 -0
  35. package/docs/versioning.md +1 -0
  36. package/package.json +6 -4
  37. package/packages/core/artifact-signing.js +61 -0
  38. package/packages/core/buildchain-config.js +66 -1
  39. package/packages/core/dev-delivery-proof.js +37 -3
  40. package/packages/core/dev-delivery-warrant.js +3 -3
  41. package/packages/core/index.js +2 -0
  42. package/packages/core/publication-authority.js +5 -5
  43. package/packages/core/release-candidate.js +79 -19
  44. package/packages/core/release-passport.js +239 -33
  45. package/packages/core/v4-floating-consumer-evidence.js +324 -0
  46. package/packages/core/v4-floating-consumer-policy.js +446 -0
  47. package/packages/core/v4-floating-consumer-release-passport.js +126 -0
  48. package/packages/core/v4-runtime-ref-resume-authority.js +625 -0
  49. package/packages/core/v4-runtime-selector-persistence.js +228 -0
  50. package/packages/core/workflow-yaml-contract.js +48 -0
  51. package/scripts/audit-publication-control-plane.mjs +31 -31
  52. package/scripts/check-inventory.mjs +1 -1
  53. package/scripts/check-v4-floating-consumer-policy-contract.mjs +198 -0
  54. package/scripts/check-v4-public-dogfood-contract.mjs +6 -11
  55. package/scripts/dev-delivery-source-proof-reuse.mjs +631 -0
  56. package/scripts/dev-pr-auto-merge.mjs +37 -48
  57. package/scripts/dev-pr-delivery-warrant.mjs +205 -2
  58. package/scripts/dev-pr-prequeue-guard.mjs +399 -0
  59. package/scripts/ensure-github-release.mjs +3 -3
  60. package/scripts/generate-channel-build-workflow.mjs +3 -0
  61. package/scripts/generate-channel-promotion-workflow.mjs +112 -12
  62. package/scripts/generate-release-candidate-passport.mjs +41 -33
  63. package/scripts/init-repo.mjs +5 -1
  64. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  65. package/scripts/npm-publish-transaction.mjs +101 -89
  66. package/scripts/resume-from-candidate-run.mjs +11 -14
  67. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  68. package/scripts/site-capability-metadata.mjs +2 -0
  69. package/scripts/v4-consumer-policy.mjs +231 -0
@@ -0,0 +1,631 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { pathToFileURL } from "node:url";
7
+ import {
8
+ classifyDevDeliveryDelta,
9
+ createSourceQualificationProofV2,
10
+ devDeliveryContentRoot,
11
+ verifySourceQualificationProof,
12
+ } from "../packages/core/dev-delivery-warrant.js";
13
+ import { validateControllerReceipt } from "../packages/core/controller-evidence.js";
14
+
15
+ const REUSE_DECISION_SCHEMA =
16
+ "kungfu.buildchain.source-qualification-reuse-decision/v1";
17
+ const SHA = /^[0-9a-f]{40}$/u;
18
+
19
+ function flag(args, name, fallback = "") {
20
+ const index = args.indexOf(`--${name}`);
21
+ return index === -1 ? fallback : args[index + 1] || "";
22
+ }
23
+
24
+ function required(value, label) {
25
+ const normalized = String(value || "").trim();
26
+ if (!normalized) throw new Error(`${label} is required`);
27
+ return normalized;
28
+ }
29
+
30
+ function exactSha(value, label) {
31
+ const normalized = required(value, label).toLowerCase();
32
+ if (!SHA.test(normalized))
33
+ throw new Error(`${label} must be an exact 40-hex Git SHA`);
34
+ return normalized;
35
+ }
36
+
37
+ function jsonList(value, label) {
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(value || "[]");
41
+ } catch (error) {
42
+ throw new Error(`${label} must be a JSON array: ${error.message}`);
43
+ }
44
+ if (!Array.isArray(parsed) || parsed.length === 0)
45
+ throw new Error(`${label} must be a non-empty JSON array`);
46
+ return [
47
+ ...new Set(
48
+ parsed.map((entry) => String(entry || "").trim()).filter(Boolean),
49
+ ),
50
+ ].sort();
51
+ }
52
+
53
+ function readJson(filePath, label) {
54
+ try {
55
+ return JSON.parse(fs.readFileSync(required(filePath, label), "utf8"));
56
+ } catch (error) {
57
+ throw new Error(`could not read ${label}: ${error.message}`);
58
+ }
59
+ }
60
+
61
+ function writeJson(filePath, value) {
62
+ const target = path.resolve(required(filePath, "output"));
63
+ fs.mkdirSync(path.dirname(target), { recursive: true });
64
+ fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`);
65
+ }
66
+
67
+ function git(args, { cwd = process.cwd(), encoding = "utf8" } = {}) {
68
+ const result = spawnSync("git", args, {
69
+ cwd,
70
+ encoding,
71
+ maxBuffer: 64 * 1024 * 1024,
72
+ });
73
+ if (result.status !== 0)
74
+ throw new Error(
75
+ `git ${args.join(" ")} failed: ${String(result.stderr || "").trim()}`,
76
+ );
77
+ return encoding === null ? result.stdout : String(result.stdout || "").trim();
78
+ }
79
+
80
+ function sha256(bytes) {
81
+ return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
82
+ }
83
+
84
+ function fileFact(filePath, workspace) {
85
+ const absolute = path.resolve(filePath);
86
+ return {
87
+ path: path
88
+ .relative(path.resolve(workspace), absolute)
89
+ .split(path.sep)
90
+ .join("/"),
91
+ size: fs.statSync(absolute).size,
92
+ sha256: sha256(fs.readFileSync(absolute)),
93
+ };
94
+ }
95
+
96
+ function gitPathRoot(ref, paths, label, cwd) {
97
+ const files = paths.map((filePath) => {
98
+ const normalized = filePath.replace(/^\.\//u, "");
99
+ if (!normalized || normalized.includes("\n"))
100
+ throw new Error(`${label} contains an invalid path`);
101
+ return {
102
+ path: normalized,
103
+ blob: exactSha(
104
+ git(["rev-parse", `${ref}:${normalized}`], { cwd }),
105
+ `${label} blob for ${normalized}`,
106
+ ),
107
+ };
108
+ });
109
+ return devDeliveryContentRoot({
110
+ schema: "kungfu.buildchain.git-path-set/v1",
111
+ label,
112
+ files,
113
+ });
114
+ }
115
+
116
+ function exactMergeGroupBinding(input = {}) {
117
+ const cwd = path.resolve(input.cwd || process.cwd());
118
+ const mergeGroupHead = exactSha(input.mergeGroupHead, "mergeGroupHead");
119
+ const expectedTree = exactSha(input.mergeGroupTree, "mergeGroupTree");
120
+ const currentBase = exactSha(input.currentBase, "currentBase");
121
+ const sourceHead = exactSha(input.sourceHead, "sourceHead");
122
+ const actualTree = exactSha(
123
+ git(["rev-parse", `${mergeGroupHead}^{tree}`], { cwd }),
124
+ "mergeGroupTree",
125
+ );
126
+ if (actualTree !== expectedTree)
127
+ return { ok: false, reason: "merge-group-tree-mismatch" };
128
+ const [commit, ...parents] = git(
129
+ ["rev-list", "--parents", "-n", "1", mergeGroupHead],
130
+ { cwd },
131
+ ).split(/\s+/u);
132
+ if (commit !== mergeGroupHead) {
133
+ return { ok: false, reason: "merge-group-parent-mismatch" };
134
+ }
135
+ if (
136
+ parents.length === 2 &&
137
+ parents[0] === currentBase &&
138
+ parents[1] === sourceHead
139
+ ) {
140
+ return {
141
+ ok: true,
142
+ mergeGroupHead,
143
+ mergeGroupTree: actualTree,
144
+ parents,
145
+ compositionMode: "two-parent-merge",
146
+ replayedCommitTrees: [],
147
+ };
148
+ }
149
+
150
+ const linearRange = (head) => {
151
+ try {
152
+ git(["merge-base", "--is-ancestor", currentBase, head], { cwd });
153
+ } catch {
154
+ return null;
155
+ }
156
+ const commits = git(
157
+ ["rev-list", "--reverse", "--topo-order", `${currentBase}..${head}`],
158
+ { cwd },
159
+ )
160
+ .split(/\s+/u)
161
+ .filter(Boolean);
162
+ if (commits.length === 0) return null;
163
+ let expectedParent = currentBase;
164
+ const trees = [];
165
+ for (const rangeCommit of commits) {
166
+ const [observedCommit, ...rangeParents] = git(
167
+ ["rev-list", "--parents", "-n", "1", rangeCommit],
168
+ { cwd },
169
+ ).split(/\s+/u);
170
+ if (
171
+ observedCommit !== rangeCommit ||
172
+ rangeParents.length !== 1 ||
173
+ rangeParents[0] !== expectedParent
174
+ ) {
175
+ return null;
176
+ }
177
+ trees.push(
178
+ exactSha(
179
+ git(["rev-parse", `${rangeCommit}^{tree}`], { cwd }),
180
+ "replayedCommitTree",
181
+ ),
182
+ );
183
+ expectedParent = rangeCommit;
184
+ }
185
+ return trees;
186
+ };
187
+
188
+ const sourceTrees = linearRange(sourceHead);
189
+ const mergeGroupTrees = linearRange(mergeGroupHead);
190
+ if (
191
+ !sourceTrees ||
192
+ !mergeGroupTrees ||
193
+ sourceTrees.length !== mergeGroupTrees.length ||
194
+ sourceTrees.some((tree, index) => tree !== mergeGroupTrees[index])
195
+ ) {
196
+ return { ok: false, reason: "merge-group-parent-mismatch" };
197
+ }
198
+ return {
199
+ ok: true,
200
+ mergeGroupHead,
201
+ mergeGroupTree: actualTree,
202
+ parents,
203
+ compositionMode: "linear-replay",
204
+ replayedCommitTrees: mergeGroupTrees,
205
+ };
206
+ }
207
+
208
+ export function sourceQualificationPredicates(input = {}) {
209
+ const cwd = path.resolve(input.cwd || process.cwd());
210
+ const repository = required(input.repository, "repository");
211
+ const protectedBase = required(input.protectedBase, "protectedBase");
212
+ const qualifiedBase = exactSha(input.qualifiedBase, "qualifiedBase");
213
+ const sourceHead = exactSha(input.sourceHead, "sourceHead");
214
+ const sourceTree = exactSha(
215
+ git(["rev-parse", `${sourceHead}^{tree}`], { cwd }),
216
+ "sourceTree",
217
+ );
218
+ const policyPaths = jsonList(input.policyPaths, "policy paths");
219
+ const closurePaths = jsonList(input.closurePaths, "closure paths");
220
+ const dependencyPaths = jsonList(input.dependencyPaths, "dependency paths");
221
+ const requiredContexts = jsonList(
222
+ input.requiredContexts,
223
+ "required contexts",
224
+ );
225
+ const affectedPaths = git(
226
+ ["diff", "--name-only", "--no-renames", `${qualifiedBase}...${sourceHead}`],
227
+ { cwd },
228
+ )
229
+ .split("\n")
230
+ .map((entry) => entry.trim())
231
+ .filter(Boolean)
232
+ .sort();
233
+ const patch = git(
234
+ [
235
+ "diff",
236
+ "--binary",
237
+ "--full-index",
238
+ "--no-ext-diff",
239
+ `${qualifiedBase}...${sourceHead}`,
240
+ ],
241
+ {
242
+ cwd,
243
+ encoding: null,
244
+ },
245
+ );
246
+ const runtimeRef = required(input.runtimeRef, "runtimeRef");
247
+ const runtimeSha = exactSha(input.runtimeSha, "runtimeSha");
248
+ const contractDigest = required(input.contractDigest, "contractDigest");
249
+ const nodeVersion = required(input.nodeVersion, "nodeVersion");
250
+ return {
251
+ repository,
252
+ protectedBase,
253
+ qualifiedBase,
254
+ sourceHead,
255
+ sourceTree,
256
+ sourceIdentityRoot: devDeliveryContentRoot({
257
+ schema: "kungfu.buildchain.source-identity/v1",
258
+ repository,
259
+ protectedBase,
260
+ qualifiedBase,
261
+ sourceHead,
262
+ sourceTree,
263
+ }),
264
+ sourcePatchRoot: sha256(patch),
265
+ planRoot: devDeliveryContentRoot({
266
+ schema: "kungfu.buildchain.source-qualification-plan/v1",
267
+ mode: "source",
268
+ workingDirectory: required(
269
+ input.workingDirectory || ".",
270
+ "workingDirectory",
271
+ ),
272
+ nodeVersion,
273
+ policyPaths,
274
+ closurePaths,
275
+ dependencyPaths,
276
+ requiredContexts,
277
+ }),
278
+ closureRoot: gitPathRoot(sourceHead, closurePaths, "source-closure", cwd),
279
+ dependencyRoot: gitPathRoot(
280
+ sourceHead,
281
+ dependencyPaths,
282
+ "source-dependencies",
283
+ cwd,
284
+ ),
285
+ toolchainRoot: devDeliveryContentRoot({
286
+ schema: "kungfu.buildchain.source-toolchain/v1",
287
+ runtimeRef,
288
+ runtimeSha,
289
+ contractDigest,
290
+ nodeVersion,
291
+ }),
292
+ policyRoot: gitPathRoot(sourceHead, policyPaths, "source-policy", cwd),
293
+ requiredContextRoot: devDeliveryContentRoot({
294
+ schema: "kungfu.buildchain.required-context-set/v1",
295
+ contexts: requiredContexts,
296
+ }),
297
+ affectedPaths,
298
+ };
299
+ }
300
+
301
+ export function sealSourceQualificationProof(input = {}) {
302
+ const receipt = readJson(input.controllerReceiptPath, "controller receipt");
303
+ const expectedSourceSha = exactSha(input.sourceHead, "sourceHead");
304
+ const expectedRuntimeSha = exactSha(input.runtimeSha, "runtimeSha");
305
+ const expectedRepository = required(input.repository, "repository");
306
+ const validation = validateControllerReceipt(receipt, {
307
+ expectedSourceSha,
308
+ expectedRuntimeSha,
309
+ });
310
+ if (
311
+ !validation.ok ||
312
+ !validation.qualifying ||
313
+ receipt.controller?.id !== "source-check" ||
314
+ receipt.source?.repository !== expectedRepository
315
+ ) {
316
+ throw new Error(
317
+ `controller receipt is not qualifying: ${validation.issues.join("; ") || receipt.status || "unknown"}`,
318
+ );
319
+ }
320
+ const predicates = sourceQualificationPredicates(input);
321
+ return createSourceQualificationProofV2({
322
+ ...predicates,
323
+ controllerReceiptRoot: receipt.digest,
324
+ sourceWorkflowRunId: input.sourceWorkflowRunId,
325
+ shardEvidenceRoots: [receipt.digest],
326
+ qualifiedAt: input.qualifiedAt,
327
+ });
328
+ }
329
+
330
+ function rootedDecision(body) {
331
+ return { ...body, decisionRoot: devDeliveryContentRoot(body) };
332
+ }
333
+
334
+ export function materializeReuseLifecycleEvidence({
335
+ decision,
336
+ sourceProofPath,
337
+ decisionPath,
338
+ manifestPath,
339
+ summaryPath,
340
+ workspace = process.cwd(),
341
+ repository = "",
342
+ sourceRef = "",
343
+ runId = "",
344
+ runAttempt = "",
345
+ platformId = process.platform,
346
+ platformName = platformId,
347
+ runnerOs = process.platform,
348
+ runnerArch = process.arch,
349
+ } = {}) {
350
+ if (!decision?.reusable || decision.action !== "reuse-source-qualification") {
351
+ throw new Error(
352
+ "reuse lifecycle evidence requires a reusable source-proof decision",
353
+ );
354
+ }
355
+ const files = [
356
+ fileFact(required(sourceProofPath, "source proof"), workspace),
357
+ fileFact(required(decisionPath, "reuse decision"), workspace),
358
+ ].sort((left, right) => left.path.localeCompare(right.path));
359
+ const summary = {
360
+ contract: "kungfu-buildchain-artifact-summary",
361
+ artifactName: "buildchain-check",
362
+ platform: String(platformId),
363
+ fileCount: files.length,
364
+ totalBytes: files.reduce((total, entry) => total + entry.size, 0),
365
+ digest: crypto
366
+ .createHash("sha256")
367
+ .update(JSON.stringify(files))
368
+ .digest("hex"),
369
+ };
370
+ const manifest = {
371
+ schemaVersion: 1,
372
+ contract: "kungfu-buildchain-artifact",
373
+ artifactName: "buildchain-check",
374
+ platform: {
375
+ id: String(platformId),
376
+ name: String(platformName),
377
+ os: String(runnerOs),
378
+ arch: String(runnerArch),
379
+ },
380
+ git: {
381
+ repository: String(repository),
382
+ sha: decision.mergeGroupHead,
383
+ ref: String(sourceRef),
384
+ runId: String(runId),
385
+ runAttempt: String(runAttempt),
386
+ },
387
+ lifecycle: {
388
+ stage: "check",
389
+ commandSource: "exact-source-proof-reuse",
390
+ executed: false,
391
+ satisfiedBy: decision.sourceProofRoot,
392
+ },
393
+ qualification: {
394
+ schema: REUSE_DECISION_SCHEMA,
395
+ authority: decision.finalAuthority,
396
+ sourceProofRoot: decision.sourceProofRoot,
397
+ decisionRoot: decision.decisionRoot,
398
+ sourceWorkflowRunId: decision.sourceWorkflowRunId,
399
+ sourceHead: decision.sourceHead,
400
+ qualifiedBase: decision.currentBase,
401
+ mergeGroupHead: decision.mergeGroupHead,
402
+ mergeGroupTree: decision.mergeGroupTree,
403
+ },
404
+ summary,
405
+ expectedArtifacts: {
406
+ ok: true,
407
+ source: "exact-source-proof-reuse",
408
+ checks: [
409
+ {
410
+ name: "source-proof-verification",
411
+ ok: true,
412
+ detail: decision.sourceProofRoot,
413
+ },
414
+ ],
415
+ },
416
+ files,
417
+ };
418
+ writeJson(manifestPath, manifest);
419
+ writeJson(summaryPath, summary);
420
+ return { manifest, summary };
421
+ }
422
+
423
+ export function verifySourceQualificationReuse(input = {}) {
424
+ const proof = readJson(input.sourceProofPath, "source proof");
425
+ const receipt = readJson(input.controllerReceiptPath, "controller receipt");
426
+ const expectedSourceSha = exactSha(input.sourceHead, "sourceHead");
427
+ const expectedRuntimeSha = exactSha(input.runtimeSha, "runtimeSha");
428
+ const expectedRepository = required(input.repository, "repository");
429
+ const receiptValidation = validateControllerReceipt(receipt, {
430
+ expectedSourceSha,
431
+ expectedRuntimeSha,
432
+ });
433
+ if (
434
+ !receiptValidation.ok ||
435
+ !receiptValidation.qualifying ||
436
+ receipt.controller?.id !== "source-check" ||
437
+ receipt.source?.repository !== expectedRepository
438
+ ) {
439
+ return rootedDecision({
440
+ schema: REUSE_DECISION_SCHEMA,
441
+ reusable: false,
442
+ action: "rerun-full-source-qualification",
443
+ reason: "producer-controller-receipt-not-qualifying",
444
+ });
445
+ }
446
+ const verification = verifySourceQualificationProof(proof, {
447
+ repository: required(input.repository, "repository"),
448
+ protectedBase: required(input.protectedBase, "protectedBase"),
449
+ qualifiedBase: exactSha(input.currentBase, "currentBase"),
450
+ sourceHead: exactSha(input.sourceHead, "sourceHead"),
451
+ sourceWorkflowRunId: Number(input.sourceWorkflowRunId),
452
+ controllerReceiptRoot: receipt.digest,
453
+ });
454
+ if (!verification.ok) {
455
+ return rootedDecision({
456
+ schema: REUSE_DECISION_SCHEMA,
457
+ reusable: false,
458
+ action: "rerun-full-source-qualification",
459
+ reason: verification.reason,
460
+ ...(verification.error ? { diagnostic: verification.error } : {}),
461
+ });
462
+ }
463
+ const current = sourceQualificationPredicates({
464
+ ...input,
465
+ qualifiedBase: input.currentBase,
466
+ });
467
+ const classification = classifyDevDeliveryDelta({
468
+ proof,
469
+ current: { ...current, graphKnown: true, changedPaths: [] },
470
+ });
471
+ if (!classification.reusable) {
472
+ return rootedDecision({
473
+ schema: REUSE_DECISION_SCHEMA,
474
+ reusable: false,
475
+ action: "rerun-full-source-qualification",
476
+ reason: classification.reason,
477
+ });
478
+ }
479
+ const mergeGroup = exactMergeGroupBinding(input);
480
+ if (!mergeGroup.ok) {
481
+ return rootedDecision({
482
+ schema: REUSE_DECISION_SCHEMA,
483
+ reusable: false,
484
+ action: "rerun-full-source-qualification",
485
+ reason: mergeGroup.reason,
486
+ });
487
+ }
488
+ return rootedDecision({
489
+ schema: REUSE_DECISION_SCHEMA,
490
+ reusable: true,
491
+ action: "reuse-source-qualification",
492
+ reason: "exact-source-proof",
493
+ sourceProofRoot: proof.proofRoot,
494
+ sourceWorkflowRunId: proof.sourceWorkflowRunId,
495
+ sourceHead: proof.sourceHead,
496
+ currentBase: proof.qualifiedBase,
497
+ mergeGroupHead: mergeGroup.mergeGroupHead,
498
+ mergeGroupTree: mergeGroup.mergeGroupTree,
499
+ mergeGroupParents: mergeGroup.parents,
500
+ mergeGroupCompositionMode: mergeGroup.compositionMode,
501
+ mergeGroupReplayTrees: mergeGroup.replayedCommitTrees,
502
+ predicateRoots: {
503
+ sourceIdentityRoot: proof.sourceIdentityRoot,
504
+ sourcePatchRoot: proof.sourcePatchRoot,
505
+ planRoot: proof.planRoot,
506
+ closureRoot: proof.closureRoot,
507
+ dependencyRoot: proof.dependencyRoot,
508
+ toolchainRoot: proof.toolchainRoot,
509
+ policyRoot: proof.policyRoot,
510
+ requiredContextRoot: proof.requiredContextRoot,
511
+ },
512
+ warrantBinding: "sourceProofRoot",
513
+ finalAuthority: "exact-merge-group-source-proof-verification",
514
+ verifiedAt: required(input.verifiedAt, "verifiedAt"),
515
+ });
516
+ }
517
+
518
+ function writeOutputs(values) {
519
+ const output = process.env.GITHUB_OUTPUT;
520
+ if (!output) return;
521
+ fs.appendFileSync(
522
+ output,
523
+ Object.entries(values)
524
+ .map(([key, value]) => `${key}=${String(value)}\n`)
525
+ .join(""),
526
+ );
527
+ }
528
+
529
+ function cliInput(args) {
530
+ return {
531
+ cwd: flag(args, "cwd", process.cwd()),
532
+ repository: flag(args, "repository", process.env.GITHUB_REPOSITORY),
533
+ protectedBase: flag(args, "branch", process.env.GITHUB_BASE_REF),
534
+ qualifiedBase: flag(args, "qualified-base"),
535
+ currentBase: flag(args, "current-base"),
536
+ sourceHead: flag(args, "source-head"),
537
+ sourceWorkflowRunId: flag(
538
+ args,
539
+ "source-workflow-run-id",
540
+ process.env.GITHUB_RUN_ID,
541
+ ),
542
+ runtimeRef: flag(args, "runtime-ref"),
543
+ runtimeSha: flag(args, "runtime-sha"),
544
+ contractDigest: flag(args, "contract-digest"),
545
+ nodeVersion: flag(args, "node-version", "24"),
546
+ workingDirectory: flag(args, "working-directory", "."),
547
+ policyPaths: flag(args, "policy-paths-json"),
548
+ closurePaths: flag(args, "closure-paths-json"),
549
+ dependencyPaths: flag(args, "dependency-paths-json"),
550
+ requiredContexts: flag(args, "required-contexts-json"),
551
+ controllerReceiptPath: flag(args, "controller-receipt"),
552
+ sourceProofPath: flag(args, "source-proof"),
553
+ mergeGroupHead: flag(args, "merge-group-head"),
554
+ mergeGroupTree: flag(args, "merge-group-tree"),
555
+ qualifiedAt: flag(args, "qualified-at"),
556
+ verifiedAt: flag(args, "verified-at"),
557
+ outputPath: flag(args, "output"),
558
+ manifestPath: flag(args, "manifest-output"),
559
+ summaryPath: flag(args, "summary-output"),
560
+ };
561
+ }
562
+
563
+ export function sourceProofReuseCli(args = process.argv.slice(2)) {
564
+ const [command = "", ...rest] = args;
565
+ const input = cliInput(rest);
566
+ if (command === "seal") {
567
+ const proof = sealSourceQualificationProof(input);
568
+ writeJson(input.outputPath, proof);
569
+ writeOutputs({ "proof-root": proof.proofRoot, "proof-reused": "false" });
570
+ return proof;
571
+ }
572
+ if (command === "verify") {
573
+ let decision;
574
+ try {
575
+ decision = verifySourceQualificationReuse(input);
576
+ } catch (error) {
577
+ decision = rootedDecision({
578
+ schema: REUSE_DECISION_SCHEMA,
579
+ reusable: false,
580
+ action: "rerun-full-source-qualification",
581
+ reason: "unverifiable-proof",
582
+ diagnostic: String(error.message || error),
583
+ });
584
+ }
585
+ writeJson(input.outputPath, decision);
586
+ if (decision.reusable && input.manifestPath && input.summaryPath) {
587
+ materializeReuseLifecycleEvidence({
588
+ decision,
589
+ sourceProofPath: input.sourceProofPath,
590
+ decisionPath: input.outputPath,
591
+ manifestPath: input.manifestPath,
592
+ summaryPath: input.summaryPath,
593
+ workspace: input.cwd,
594
+ repository: input.repository,
595
+ sourceRef: process.env.GITHUB_REF,
596
+ runId: process.env.GITHUB_RUN_ID,
597
+ runAttempt: process.env.GITHUB_RUN_ATTEMPT,
598
+ platformId: process.env.RUNNER_OS || process.platform,
599
+ platformName:
600
+ process.env.RUNNER_NAME || process.env.RUNNER_OS || process.platform,
601
+ runnerOs: process.env.RUNNER_OS || process.platform,
602
+ runnerArch: process.env.RUNNER_ARCH || process.arch,
603
+ });
604
+ }
605
+ writeOutputs({
606
+ reuse: String(decision.reusable),
607
+ "proof-root": decision.sourceProofRoot || "",
608
+ reason: decision.reason,
609
+ "decision-root": decision.decisionRoot,
610
+ });
611
+ return decision;
612
+ }
613
+ throw new Error(
614
+ "usage: dev-delivery-source-proof-reuse.mjs <seal|verify> [options]",
615
+ );
616
+ }
617
+
618
+ if (
619
+ process.argv[1] &&
620
+ import.meta.url === pathToFileURL(process.argv[1]).href
621
+ ) {
622
+ try {
623
+ const result = sourceProofReuseCli();
624
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
625
+ } catch (error) {
626
+ console.error(
627
+ `::error::${String(error.message || error).replace(/\r?\n/gu, "%0A")}`,
628
+ );
629
+ process.exitCode = 1;
630
+ }
631
+ }