@kungfu-tech/buildchain 3.0.2 → 3.0.3-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.
Files changed (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +20 -2
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +56 -2
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +146 -0
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +188 -46
  19. package/dist/site/buildchain-site.json +181 -90
  20. package/dist/site/capability-registry.json +5 -5
  21. package/dist/site/cli-registry.json +860 -98
  22. package/dist/site/controller-registry.json +118 -5
  23. package/dist/site/kfd-claims.json +233 -19
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +374 -59
  27. package/dist/site/page-registry.json +156 -65
  28. package/dist/site/public-surface-audit.json +231 -22
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +575 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +54 -13
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +119 -12
  37. package/docs/consumer-issue-reporting.md +20 -2
  38. package/docs/dev-alpha-candidate-patrol.md +24 -10
  39. package/docs/github-governance-authority.md +24 -31
  40. package/docs/lifecycle-protocol.md +20 -2
  41. package/docs/migration-inventory.md +24 -5
  42. package/docs/observed-evidence-patrol.md +28 -12
  43. package/docs/ownership.md +19 -1
  44. package/docs/publication-artifacts.md +158 -18
  45. package/docs/publication-authority.md +4 -4
  46. package/docs/publish-transaction.md +78 -31
  47. package/docs/release-candidate.md +24 -0
  48. package/docs/release-flow.md +50 -32
  49. package/docs/release-governance.md +39 -32
  50. package/docs/release-passport.md +4 -0
  51. package/docs/release-propagation.md +57 -12
  52. package/docs/reusable-build-surface.md +218 -90
  53. package/docs/runtime-train-validation.md +20 -2
  54. package/docs/shifu-gate-profiles.md +7 -1
  55. package/docs/stable-candidate-patrol.md +6 -6
  56. package/docs/toolkit-observability.md +22 -0
  57. package/docs/versioning.md +46 -24
  58. package/docs/web-surface-deployments.md +82 -1
  59. package/package.json +7 -5
  60. package/packages/core/README.md +60 -4
  61. package/packages/core/artifact-signing.js +1 -0
  62. package/packages/core/buildchain-contract.js +9 -4
  63. package/packages/core/cache-evidence.js +1 -0
  64. package/packages/core/controller-evidence.js +2 -0
  65. package/packages/core/diagnostics.js +105 -30
  66. package/packages/core/github-governance-authority.js +25 -17
  67. package/packages/core/index.js +40 -0
  68. package/packages/core/paper.js +3226 -0
  69. package/packages/core/public-surface-audit.js +3 -0
  70. package/packages/core/publication-artifact.js +1 -6
  71. package/packages/core/publication-authority.js +1 -0
  72. package/packages/core/publication-control-plane-audit.js +28 -0
  73. package/packages/core/publication-reproducibility.js +947 -0
  74. package/packages/core/publication-sealed-bundle.js +190 -0
  75. package/packages/core/publish-transaction.js +147 -17
  76. package/packages/core/release-candidate.js +234 -0
  77. package/packages/core/release-passport.js +519 -228
  78. package/packages/core/release-propagation.js +175 -1
  79. package/packages/core/stable-release-gate.js +4 -1
  80. package/scripts/artifact-signing-delegation.mjs +268 -0
  81. package/scripts/audit-github-governance.mjs +32 -13
  82. package/scripts/audit-publication-control-plane.mjs +17 -8
  83. package/scripts/auditable-demo.mjs +266 -7
  84. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  85. package/scripts/aws-macos-jit-core.mjs +378 -0
  86. package/scripts/aws-macos-jit.mjs +123 -0
  87. package/scripts/aws-runner-burst-core.mjs +248 -0
  88. package/scripts/aws-runner-burst.mjs +79 -0
  89. package/scripts/aws-windows-jit-core.mjs +374 -0
  90. package/scripts/aws-windows-jit.mjs +121 -0
  91. package/scripts/build-contract-core.mjs +69 -6
  92. package/scripts/build-standalone-binary.mjs +23 -0
  93. package/scripts/buildchain-contract-lock.mjs +1 -1
  94. package/scripts/buildchain-patrol.mjs +1 -1
  95. package/scripts/check-internal-architecture.mjs +69 -2
  96. package/scripts/check-inventory.mjs +70 -2
  97. package/scripts/check-javascript-syntax.mjs +31 -0
  98. package/scripts/check-maintainability.mjs +371 -0
  99. package/scripts/compiler-cache-evidence.mjs +221 -0
  100. package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
  101. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  102. package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
  103. package/scripts/gate-profile-core.mjs +6 -1
  104. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  105. package/scripts/generate-release-candidate-passport.mjs +4 -0
  106. package/scripts/generate-site-bundle.mjs +84 -18
  107. package/scripts/import-artifact-signing-results.mjs +64 -5
  108. package/scripts/inspect-artifact-signing-requests.mjs +52 -14
  109. package/scripts/maintainability-metrics.mjs +339 -0
  110. package/scripts/npm-publish-transaction.mjs +57 -6
  111. package/scripts/observed-evidence.mjs +151 -33
  112. package/scripts/paper.mjs +645 -0
  113. package/scripts/promotion-channel-router.mjs +16 -4
  114. package/scripts/publication-reproducibility.mjs +62 -0
  115. package/scripts/reconcile-github-governance.mjs +8 -1
  116. package/scripts/release-propagation.mjs +39 -0
  117. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  118. package/scripts/resolve-build-contract.mjs +6 -0
  119. package/scripts/route-offline-runners.mjs +236 -0
  120. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  121. package/scripts/run-lifecycle-core.mjs +62 -1
  122. package/scripts/runtime-ref-core.mjs +36 -4
  123. package/scripts/seal-artifact-signing-requests.mjs +64 -12
  124. package/scripts/stable-candidate-qualification.mjs +50 -0
  125. package/scripts/verify-artifact-signing-results.mjs +26 -1
  126. package/scripts/web-surface-core.mjs +272 -86
  127. package/scripts/web-surface-production-decision.mjs +19 -3
@@ -0,0 +1,645 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import {
7
+ PAPER_ALPHA_PLAN_CONTRACT,
8
+ PAPER_BUILD_PLAN_CONTRACT,
9
+ PAPER_MIGRATION_CONTRACT,
10
+ PAPER_NPM_BOOTSTRAP_CONTRACT,
11
+ PAPER_PREFLIGHT_CONTRACT,
12
+ PAPER_RESUME_PLAN_CONTRACT,
13
+ PAPER_SCAFFOLD_CONTRACT,
14
+ PAPER_STATUS_CONTRACT,
15
+ collectPaperPreflight,
16
+ collectPaperStatus,
17
+ createPaperAlphaPlan,
18
+ createPaperBuildPlan,
19
+ createPaperResumePlan,
20
+ executePaperNpmBootstrap,
21
+ planPaperMigration,
22
+ planPaperScaffold,
23
+ writePaperMigration,
24
+ writePaperScaffold,
25
+ } from "../packages/core/paper.js";
26
+ import { verifyPublicationReproducibility } from "../packages/core/publication-reproducibility.js";
27
+
28
+ function usage() {
29
+ return `Usage:
30
+ buildchain paper scaffold --package <name> --repository <owner/repo>
31
+ [--cwd <dir>] [--name <name>] [--title <title>]
32
+ [--version <semver>] [--site-base-url <url>]
33
+ [--buildchain-ref <ref>] [--write] [--json]
34
+ buildchain paper migrate [--cwd <dir>] [--write] [--json]
35
+ buildchain paper preflight [--cwd <dir>] [--offline] [--json]
36
+ buildchain paper bootstrap npm [--cwd <dir>] [--package <name>]
37
+ [--repository <owner/repo>] [--workflow <filename>]
38
+ [--bootstrap-version <version>] [--userconfig <path>]
39
+ [--offline] [--execute]
40
+ [--confirm-public-package <name>] [--json]
41
+ buildchain paper build [--cwd <dir>] [--source-sha <sha>]
42
+ [--no-toolchain-pull] [--keep-workspaces]
43
+ [--execute] [--json]
44
+ buildchain paper alpha [--cwd <dir>] [--source-ref <ref>] [--target-ref <ref>]
45
+ [--execute] [--json]
46
+ buildchain paper status [--cwd <dir>] [--json]
47
+ buildchain paper resume [--cwd <dir>] [--buildchain-ref <ref>]
48
+ [--execute] [--json]
49
+
50
+ Safety:
51
+ scaffold is a no-overwrite dry-run unless --write is present. migrate only
52
+ rewrites the five Buildchain-owned authority, workflow, lock, and version files.
53
+ npm bootstrap, Alpha PR creation, and resume dispatch never mutate externally
54
+ unless --execute is present. Real npm bootstrap additionally requires the
55
+ exact --confirm-public-package value.
56
+ `;
57
+ }
58
+
59
+ function readFlag(args, name, fallback = "") {
60
+ const index = args.indexOf(`--${name}`);
61
+ if (index === -1) return fallback;
62
+ return args[index + 1] || "";
63
+ }
64
+
65
+ function hasFlag(args, name) {
66
+ return args.includes(`--${name}`);
67
+ }
68
+
69
+ function printJson(value) {
70
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
71
+ }
72
+
73
+ function commandResult(command, args, { cwd, timeout = 60000 } = {}) {
74
+ const result = spawnSync(command, args, {
75
+ cwd,
76
+ env: process.env,
77
+ encoding: "utf8",
78
+ timeout,
79
+ maxBuffer: 2 * 1024 * 1024,
80
+ });
81
+ return {
82
+ ok: result.status === 0,
83
+ status: result.status ?? 1,
84
+ stdout: String(result.stdout || "").trim(),
85
+ stderr: String(result.stderr || "").trim(),
86
+ error: result.error?.message || "",
87
+ };
88
+ }
89
+
90
+ function publicScaffoldPlan(plan) {
91
+ return plan;
92
+ }
93
+
94
+ function humanSummary(result) {
95
+ if (result.contract === PAPER_SCAFFOLD_CONTRACT) {
96
+ process.stdout.write(
97
+ `paper scaffold: ${result.ok ? "ok" : "conflict"} (${result.dryRun ? "dry-run" : "write"})\n`,
98
+ );
99
+ process.stdout.write(
100
+ `create=${result.summary.create} unchanged=${result.summary.unchanged} conflict=${result.summary.conflict}\n`,
101
+ );
102
+ for (const entry of result.changes) {
103
+ process.stdout.write(`- ${entry.action}: ${entry.path}\n`);
104
+ }
105
+ return;
106
+ }
107
+ if (result.contract === PAPER_MIGRATION_CONTRACT) {
108
+ process.stdout.write(
109
+ `paper migrate: ${result.ok ? "ok" : "blocked"} (${result.dryRun ? "dry-run" : "write"})\n`,
110
+ );
111
+ process.stdout.write(
112
+ `create=${result.summary.create} update=${result.summary.update} unchanged=${result.summary.unchanged} conflict=${result.summary.conflict}\n`,
113
+ );
114
+ for (const entry of result.changes) {
115
+ process.stdout.write(`- ${entry.action}: ${entry.path}\n`);
116
+ }
117
+ return;
118
+ }
119
+ if (result.contract === PAPER_PREFLIGHT_CONTRACT) {
120
+ process.stdout.write(
121
+ `paper preflight: ${result.ok ? "ready" : "blocked"}\n`,
122
+ );
123
+ for (const check of result.checks) {
124
+ process.stdout.write(
125
+ `- ${check.status}: ${check.id}: ${check.message}\n`,
126
+ );
127
+ }
128
+ return;
129
+ }
130
+ if (result.contract === PAPER_STATUS_CONTRACT) {
131
+ process.stdout.write(`paper status: ${result.highestEvidenceState}\n`);
132
+ for (const entry of result.states) {
133
+ process.stdout.write(`- ${entry.status}: ${entry.id}: ${entry.reason}\n`);
134
+ }
135
+ return;
136
+ }
137
+ if (result.contract === PAPER_NPM_BOOTSTRAP_CONTRACT) {
138
+ process.stdout.write(
139
+ `paper bootstrap npm: ${result.ok ? result.publish.status : result.errorCode || "blocked"}\n`,
140
+ );
141
+ process.stdout.write(`package: ${result.package.name}\n`);
142
+ process.stdout.write(`trust: ${result.trust.status}\n`);
143
+ for (const url of result.trust.urls || []) {
144
+ process.stdout.write(`url: ${url}\n`);
145
+ }
146
+ return;
147
+ }
148
+ if (result.contract === PAPER_BUILD_PLAN_CONTRACT) {
149
+ process.stdout.write(
150
+ `paper build: ${result.dryRun ? "dry-run" : result.receipt?.status || "complete"}\n`,
151
+ );
152
+ process.stdout.write(`source: ${result.sourceSha}\n`);
153
+ if (result.receipt?.receiptDigest) {
154
+ process.stdout.write(`receipt: ${result.receipt.receiptDigest}\n`);
155
+ }
156
+ return;
157
+ }
158
+ if (result.contract === PAPER_ALPHA_PLAN_CONTRACT) {
159
+ process.stdout.write(
160
+ `paper alpha: ${result.dryRun ? "dry-run" : result.pr?.url || "submitted"}\n`,
161
+ );
162
+ process.stdout.write(`${result.source.ref} -> ${result.target.ref}\n`);
163
+ return;
164
+ }
165
+ if (result.contract === PAPER_RESUME_PLAN_CONTRACT) {
166
+ process.stdout.write(`paper resume: ${result.reason}\n`);
167
+ if (result.mutation?.command) {
168
+ process.stdout.write(`${result.mutation.command}\n`);
169
+ }
170
+ return;
171
+ }
172
+ printJson(result);
173
+ }
174
+
175
+ function printResult(result, json) {
176
+ if (json) printJson(result);
177
+ else humanSummary(result);
178
+ }
179
+
180
+ function githubPrRows({ cwd, repository, sourceRef, targetRef }) {
181
+ const query = commandResult(
182
+ "gh",
183
+ [
184
+ "pr",
185
+ "list",
186
+ "--repo",
187
+ repository,
188
+ "--head",
189
+ sourceRef,
190
+ "--base",
191
+ targetRef,
192
+ "--state",
193
+ "open",
194
+ "--json",
195
+ "number,url,headRefName,baseRefName",
196
+ "--limit",
197
+ "10",
198
+ ],
199
+ { cwd },
200
+ );
201
+ if (!query.ok) {
202
+ return {
203
+ ok: false,
204
+ rows: [],
205
+ errorCode: query.error ? "gh-unavailable" : "github-pr-query-failed",
206
+ };
207
+ }
208
+ try {
209
+ const rows = JSON.parse(query.stdout || "[]");
210
+ return { ok: true, rows: Array.isArray(rows) ? rows : [], errorCode: "" };
211
+ } catch {
212
+ return { ok: false, rows: [], errorCode: "github-pr-response-invalid" };
213
+ }
214
+ }
215
+
216
+ function githubBranchObservation({ cwd, repository, ref }) {
217
+ const query = commandResult(
218
+ "gh",
219
+ [
220
+ "api",
221
+ `repos/${repository}/git/ref/heads/${encodeURIComponent(ref)}`,
222
+ "--jq",
223
+ ".object.sha",
224
+ ],
225
+ { cwd },
226
+ );
227
+ const sha = query.stdout.trim();
228
+ return {
229
+ ok: query.ok && /^[0-9a-f]{40}$/.test(sha),
230
+ ref,
231
+ sha: /^[0-9a-f]{40}$/.test(sha) ? sha : "",
232
+ observedRef: `refs/heads/${ref}`,
233
+ observation: "github-api",
234
+ errorCode: query.ok
235
+ ? "github-ref-response-invalid"
236
+ : query.error
237
+ ? "gh-unavailable"
238
+ : "github-ref-query-failed",
239
+ };
240
+ }
241
+
242
+ function executeAlphaPlan(plan) {
243
+ if (!plan.ok) {
244
+ return {
245
+ ...plan,
246
+ ok: false,
247
+ dryRun: false,
248
+ errorCode: "paper-alpha-plan-invalid",
249
+ };
250
+ }
251
+ const source = githubBranchObservation({
252
+ cwd: plan.cwd,
253
+ repository: plan.repository,
254
+ ref: plan.source.ref,
255
+ });
256
+ if (!source.ok) {
257
+ return {
258
+ ...plan,
259
+ ok: false,
260
+ dryRun: false,
261
+ errorCode: source.errorCode,
262
+ nextActions: [
263
+ {
264
+ id: "resolve-source-ref",
265
+ command: "",
266
+ description:
267
+ "Resolve the exact protected dev source ref from GitHub before opening an Alpha PR.",
268
+ },
269
+ ],
270
+ };
271
+ }
272
+ const target = githubBranchObservation({
273
+ cwd: plan.cwd,
274
+ repository: plan.repository,
275
+ ref: plan.target.ref,
276
+ });
277
+ if (!target.ok) {
278
+ return {
279
+ ...plan,
280
+ source,
281
+ ok: false,
282
+ dryRun: false,
283
+ errorCode: target.errorCode,
284
+ nextActions: [
285
+ {
286
+ id: "resolve-target-ref",
287
+ command: "",
288
+ description:
289
+ "Resolve the exact protected Alpha target ref from GitHub before opening an Alpha PR.",
290
+ },
291
+ ],
292
+ };
293
+ }
294
+ plan = {
295
+ ...plan,
296
+ source,
297
+ target,
298
+ };
299
+ const existing = githubPrRows({
300
+ cwd: plan.cwd,
301
+ repository: plan.repository,
302
+ sourceRef: plan.source.ref,
303
+ targetRef: plan.target.ref,
304
+ });
305
+ if (!existing.ok) {
306
+ return {
307
+ ...plan,
308
+ ok: false,
309
+ dryRun: false,
310
+ errorCode: existing.errorCode,
311
+ };
312
+ }
313
+ if (existing.rows.length > 0) {
314
+ return {
315
+ ...plan,
316
+ ok: true,
317
+ dryRun: false,
318
+ reused: true,
319
+ pr: existing.rows[0],
320
+ nextActions: [
321
+ {
322
+ id: "review-alpha-pr",
323
+ command: "",
324
+ description:
325
+ "Complete the repository's independent protected PR review and merge gate.",
326
+ url: existing.rows[0].url,
327
+ },
328
+ ],
329
+ };
330
+ }
331
+ const title = `release(paper): publish ${plan.publicationVersion} alpha`;
332
+ const body = [
333
+ "## Summary",
334
+ "",
335
+ `Promote \`${plan.source.ref}\` to \`${plan.target.ref}\` through the protected Buildchain paper release flow.`,
336
+ "",
337
+ "## Safety",
338
+ "",
339
+ "- Build and verification remain credential-free.",
340
+ "- Exact artifact bytes are sealed before npm OIDC is available.",
341
+ "- This PR does not bypass review or publish directly.",
342
+ ].join("\n");
343
+ const created = commandResult(
344
+ "gh",
345
+ [
346
+ "pr",
347
+ "create",
348
+ "--repo",
349
+ plan.repository,
350
+ "--base",
351
+ plan.target.ref,
352
+ "--head",
353
+ plan.source.ref,
354
+ "--title",
355
+ title,
356
+ "--body",
357
+ body,
358
+ ],
359
+ { cwd: plan.cwd },
360
+ );
361
+ if (!created.ok) {
362
+ return {
363
+ ...plan,
364
+ ok: false,
365
+ dryRun: false,
366
+ errorCode: created.error ? "gh-unavailable" : "github-pr-create-failed",
367
+ };
368
+ }
369
+ const url =
370
+ created.stdout
371
+ .split(/\s+/)
372
+ .find((entry) => /^https:\/\/github\.com\//.test(entry)) || "";
373
+ return {
374
+ ...plan,
375
+ ok: true,
376
+ dryRun: false,
377
+ reused: false,
378
+ pr: {
379
+ url,
380
+ headRefName: plan.source.ref,
381
+ baseRefName: plan.target.ref,
382
+ },
383
+ nextActions: [
384
+ {
385
+ id: "review-alpha-pr",
386
+ command: "",
387
+ description:
388
+ "Complete the repository's independent protected PR review and merge gate.",
389
+ url,
390
+ },
391
+ ],
392
+ };
393
+ }
394
+
395
+ function executeResumePlan(plan, buildchainRef) {
396
+ if (!plan.resumable || !plan.transaction?.targetRef) {
397
+ return {
398
+ ...plan,
399
+ dryRun: false,
400
+ dispatched: false,
401
+ };
402
+ }
403
+ const args = [
404
+ "workflow",
405
+ "run",
406
+ ".github/workflows/paper-release.yml",
407
+ "--ref",
408
+ plan.transaction.targetRef,
409
+ ];
410
+ if (buildchainRef) {
411
+ args.push("-f", `buildchain-ref=${buildchainRef}`);
412
+ }
413
+ const dispatched = commandResult("gh", args, { cwd: plan.cwd });
414
+ if (!dispatched.ok) {
415
+ return {
416
+ ...plan,
417
+ ok: false,
418
+ dryRun: false,
419
+ dispatched: false,
420
+ errorCode: dispatched.error
421
+ ? "gh-unavailable"
422
+ : "github-workflow-dispatch-failed",
423
+ };
424
+ }
425
+ return {
426
+ ...plan,
427
+ ok: true,
428
+ dryRun: false,
429
+ dispatched: true,
430
+ nextActions: [
431
+ {
432
+ id: "inspect-resumed-transaction",
433
+ command: "buildchain paper status --json",
434
+ description:
435
+ "Wait for the protected workflow, then inspect the same transaction coordinates.",
436
+ },
437
+ ],
438
+ };
439
+ }
440
+
441
+ export async function runPaperCli(
442
+ args = [],
443
+ {
444
+ buildchainRoot = path.resolve(
445
+ path.dirname(fileURLToPath(import.meta.url)),
446
+ "..",
447
+ ),
448
+ buildchainVersion = "",
449
+ buildchainRef = "v3",
450
+ buildchainSha = "",
451
+ } = {},
452
+ ) {
453
+ const [command = "", maybeSubcommand = "", ...rest] = args;
454
+ const json = hasFlag(args, "json");
455
+ try {
456
+ if (!command || ["help", "--help", "-h"].includes(command)) {
457
+ process.stdout.write(usage());
458
+ return;
459
+ }
460
+ const effectiveArgs = maybeSubcommand.startsWith("--")
461
+ ? [maybeSubcommand, ...rest]
462
+ : rest;
463
+ const cwd = path.resolve(readFlag(effectiveArgs, "cwd", process.cwd()));
464
+ let result;
465
+ if (command === "scaffold") {
466
+ const plan = planPaperScaffold({
467
+ cwd,
468
+ buildchainRoot,
469
+ buildchainVersion,
470
+ buildchainRef: readFlag(effectiveArgs, "buildchain-ref", buildchainRef),
471
+ buildchainSha,
472
+ name: readFlag(effectiveArgs, "name", path.basename(cwd)),
473
+ title: readFlag(effectiveArgs, "title", ""),
474
+ packageName: readFlag(effectiveArgs, "package", ""),
475
+ repository: readFlag(effectiveArgs, "repository", ""),
476
+ version: readFlag(effectiveArgs, "version", "0.1.0-alpha.0"),
477
+ siteBaseUrl: readFlag(effectiveArgs, "site-base-url", ""),
478
+ });
479
+ result =
480
+ hasFlag(effectiveArgs, "write") || hasFlag(effectiveArgs, "execute")
481
+ ? writePaperScaffold(plan)
482
+ : publicScaffoldPlan(plan);
483
+ } else if (command === "migrate") {
484
+ const plan = planPaperMigration({
485
+ cwd,
486
+ buildchainRoot,
487
+ buildchainVersion,
488
+ buildchainSha,
489
+ });
490
+ result =
491
+ hasFlag(effectiveArgs, "write") || hasFlag(effectiveArgs, "execute")
492
+ ? writePaperMigration(plan)
493
+ : plan;
494
+ } else if (command === "preflight") {
495
+ result = collectPaperPreflight({
496
+ cwd,
497
+ buildchainRoot,
498
+ buildchainVersion,
499
+ buildchainRef: readFlag(effectiveArgs, "buildchain-ref", buildchainRef),
500
+ buildchainSha,
501
+ registry: readFlag(
502
+ effectiveArgs,
503
+ "registry",
504
+ "https://registry.npmjs.org/",
505
+ ),
506
+ offline: hasFlag(effectiveArgs, "offline"),
507
+ });
508
+ } else if (command === "status") {
509
+ result = collectPaperStatus({ cwd });
510
+ } else if (command === "bootstrap" && maybeSubcommand === "npm") {
511
+ result = executePaperNpmBootstrap({
512
+ cwd,
513
+ packageName: readFlag(effectiveArgs, "package", ""),
514
+ bootstrapVersion: readFlag(
515
+ effectiveArgs,
516
+ "bootstrap-version",
517
+ "0.0.0-bootstrap.0",
518
+ ),
519
+ registry: readFlag(
520
+ effectiveArgs,
521
+ "registry",
522
+ "https://registry.npmjs.org/",
523
+ ),
524
+ repository: readFlag(effectiveArgs, "repository", ""),
525
+ workflow: readFlag(effectiveArgs, "workflow", "paper-release.yml"),
526
+ environment: readFlag(effectiveArgs, "environment", ""),
527
+ execute: hasFlag(effectiveArgs, "execute"),
528
+ confirmedPackage: readFlag(effectiveArgs, "confirm-public-package", ""),
529
+ userconfig: readFlag(effectiveArgs, "userconfig", ""),
530
+ offline: hasFlag(effectiveArgs, "offline"),
531
+ });
532
+ } else if (command === "build") {
533
+ const plan = createPaperBuildPlan({
534
+ cwd,
535
+ sourceSha: readFlag(effectiveArgs, "source-sha", ""),
536
+ pullToolchain: !hasFlag(effectiveArgs, "no-toolchain-pull"),
537
+ });
538
+ if (!hasFlag(effectiveArgs, "execute")) {
539
+ result = plan;
540
+ } else {
541
+ const receipt = verifyPublicationReproducibility({
542
+ cwd,
543
+ sourceSha: plan.sourceSha,
544
+ promote: true,
545
+ keepWorkspaces: hasFlag(effectiveArgs, "keep-workspaces"),
546
+ pullToolchain: plan.pullToolchain,
547
+ packageName: readFlag(effectiveArgs, "package", ""),
548
+ });
549
+ result = {
550
+ ...plan,
551
+ ok: receipt.qualifying === true,
552
+ dryRun: false,
553
+ receipt,
554
+ nextActions: receipt.qualifying
555
+ ? [
556
+ {
557
+ id: "paper-status",
558
+ command: "buildchain paper status --json",
559
+ description:
560
+ "Inspect the qualifying reproducibility evidence and remaining publication gates.",
561
+ },
562
+ ]
563
+ : [
564
+ {
565
+ id: "repair-reproducibility",
566
+ command:
567
+ "buildchain paper build --execute --keep-workspaces --json",
568
+ description:
569
+ "Retain both clean workspaces and inspect the first byte difference.",
570
+ },
571
+ ],
572
+ };
573
+ }
574
+ } else if (command === "alpha") {
575
+ const plan = createPaperAlphaPlan({
576
+ cwd,
577
+ sourceRef: readFlag(effectiveArgs, "source-ref", ""),
578
+ targetRef: readFlag(effectiveArgs, "target-ref", ""),
579
+ });
580
+ result = hasFlag(effectiveArgs, "execute")
581
+ ? executeAlphaPlan(plan)
582
+ : plan;
583
+ } else if (command === "resume") {
584
+ const runtimeRef = readFlag(effectiveArgs, "buildchain-ref", "");
585
+ const plan = createPaperResumePlan({
586
+ cwd,
587
+ buildchainRef: runtimeRef,
588
+ });
589
+ result = hasFlag(effectiveArgs, "execute")
590
+ ? executeResumePlan(plan, runtimeRef)
591
+ : plan;
592
+ } else {
593
+ throw new Error(
594
+ "usage: buildchain paper <scaffold|migrate|preflight|bootstrap npm|build|alpha|status|resume> ...",
595
+ );
596
+ }
597
+ printResult(result, json);
598
+ if (result.ok === false) {
599
+ process.exitCode = 1;
600
+ }
601
+ return result;
602
+ } catch (error) {
603
+ if (!json) throw error;
604
+ const failure = {
605
+ schemaVersion: 1,
606
+ contract: "kungfu-buildchain-paper-error",
607
+ ok: false,
608
+ error: {
609
+ code: error.code || "paper-command-failed",
610
+ message: error.message,
611
+ },
612
+ nextActions: [],
613
+ };
614
+ printJson(failure);
615
+ process.exitCode = 1;
616
+ return failure;
617
+ }
618
+ }
619
+
620
+ if (
621
+ !process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT &&
622
+ process.argv[1] &&
623
+ import.meta.url === pathToFileURL(process.argv[1]).href
624
+ ) {
625
+ const packageJson = JSON.parse(
626
+ fs.readFileSync(
627
+ path.resolve(
628
+ path.dirname(fileURLToPath(import.meta.url)),
629
+ "..",
630
+ "package.json",
631
+ ),
632
+ "utf8",
633
+ ),
634
+ );
635
+ runPaperCli(process.argv.slice(2), {
636
+ buildchainRoot: path.resolve(
637
+ path.dirname(fileURLToPath(import.meta.url)),
638
+ "..",
639
+ ),
640
+ buildchainVersion: packageJson.version,
641
+ }).catch((error) => {
642
+ console.error(`paper: ${error.message}`);
643
+ process.exitCode = 1;
644
+ });
645
+ }
@@ -15,6 +15,12 @@ function normalized(value) {
15
15
  return String(value ?? "").trim();
16
16
  }
17
17
 
18
+ function sameExactSha(left, right) {
19
+ const first = normalized(left).toLowerCase();
20
+ const second = normalized(right).toLowerCase();
21
+ return /^[0-9a-f]{40}$/.test(first) && first === second;
22
+ }
23
+
18
24
  function targetIntent(targetRef, requestedPublicationChannel = "") {
19
25
  const ref = normalized(targetRef).replace(/^refs\/heads\//, "");
20
26
  const target = TARGETS.find((entry) => entry.pattern.test(ref));
@@ -36,6 +42,7 @@ export function resolvePromotionChannel({
36
42
  publicationChannel = "",
37
43
  targetRef = "",
38
44
  routerRef = "",
45
+ routerSha = "",
39
46
  packageVersion = "",
40
47
  } = {}) {
41
48
  const intent = targetIntent(targetRef, publicationChannel);
@@ -47,8 +54,10 @@ export function resolvePromotionChannel({
47
54
  routerRef,
48
55
  packageVersion,
49
56
  });
50
- const overrideUsed = selected.channel === "override";
51
- if (!overrideUsed && selected.channel !== intent.shellChannel) {
57
+ const trustedRouterPin = selected.channel === "override"
58
+ && sameExactSha(selected.buildchainRef, routerSha);
59
+ const overrideUsed = selected.channel === "override" && !trustedRouterPin;
60
+ if (!overrideUsed && !trustedRouterPin && selected.channel !== intent.shellChannel) {
52
61
  throw new Error(
53
62
  `promotion target ${intent.targetRef} requires ${intent.shellChannel} shell/runtime, got ${selected.channel}`,
54
63
  );
@@ -62,8 +71,10 @@ export function resolvePromotionChannel({
62
71
  shellRef,
63
72
  runtimeRef: selected.buildchainRef,
64
73
  overrideUsed,
65
- selectionSource: selected.selectionSource,
66
- reason: selected.reason,
74
+ selectionSource: trustedRouterPin ? "trusted-router-sha" : selected.selectionSource,
75
+ reason: trustedRouterPin
76
+ ? `explicit Buildchain runtime ref ${selected.buildchainRef} matches the reusable workflow SHA`
77
+ : selected.reason,
67
78
  };
68
79
  }
69
80
 
@@ -108,6 +119,7 @@ function main() {
108
119
  publicationChannel: args["publication-channel"],
109
120
  targetRef: args["target-ref"],
110
121
  routerRef: args["router-ref"],
122
+ routerSha: args["router-sha"],
111
123
  packageVersion: readPackageVersion(cwd),
112
124
  });
113
125
  if (process.env.GITHUB_OUTPUT) writeOutputs(process.env.GITHUB_OUTPUT, result);