@kungfu-tech/buildchain 3.0.4-alpha.0 → 3.0.4-alpha.10

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 (74) hide show
  1. package/AGENTS.md +14 -5
  2. package/README.md +18 -0
  3. package/bin/buildchain.mjs +136 -73
  4. package/bin/internal/trust-release-cli.mjs +15 -537
  5. package/bin/internal/trust-release-command-handlers.mjs +14 -0
  6. package/bin/internal/trust-release-inspection-handlers.mjs +175 -0
  7. package/bin/internal/trust-release-release-handlers.mjs +317 -0
  8. package/bin/internal/trust-release-verification-handlers.mjs +306 -0
  9. package/dist/site/buildchain-contract.json +36 -25
  10. package/dist/site/buildchain-site.json +1474 -61
  11. package/dist/site/capability-registry.json +8 -5
  12. package/dist/site/cli-registry.json +1869 -0
  13. package/dist/site/controller-registry.json +16 -3
  14. package/dist/site/kfd-claims.json +84 -10
  15. package/dist/site/kfd-upstream-aggregate.json +1 -1
  16. package/dist/site/manual-registry.json +49 -6
  17. package/dist/site/node-api-registry.json +17802 -11
  18. package/dist/site/page-registry.json +1441 -59
  19. package/dist/site/public-surface-audit.json +2863 -358
  20. package/dist/site/publication-registry.json +4 -4
  21. package/dist/site/release-model.json +7 -0
  22. package/dist/site/site-manifest.json +34 -10
  23. package/dist/site/workflow-registry.json +13 -5
  24. package/docs/MAP.md +21 -6
  25. package/docs/cli-reference.md +1936 -0
  26. package/docs/cli.md +15 -1
  27. package/docs/getting-started.md +167 -0
  28. package/docs/install.md +7 -8
  29. package/docs/node-api-reference.md +1952 -0
  30. package/docs/release-propagation.md +166 -8
  31. package/docs/site-bundle-contract.md +10 -4
  32. package/docs/versioning.md +5 -4
  33. package/package.json +8 -5
  34. package/packages/core/buildchain-agent-manuals.js +37 -0
  35. package/packages/core/buildchain-kfd-claims.js +4 -37
  36. package/packages/core/controller-evidence.js +8 -1
  37. package/packages/core/index.js +1 -13
  38. package/packages/core/paper-agent-entry.js +14 -11
  39. package/packages/core/paper-fleet.js +34 -7
  40. package/packages/core/paper-npm-bootstrap.js +492 -0
  41. package/packages/core/paper-repository.js +1 -0
  42. package/packages/core/paper.js +356 -674
  43. package/packages/core/public-surface-cli.js +12 -1
  44. package/packages/core/publication-package.js +7 -0
  45. package/packages/core/release-passport.js +67 -71
  46. package/packages/core/release-propagation-common.js +64 -0
  47. package/packages/core/release-propagation-execution-profile.js +59 -0
  48. package/packages/core/release-propagation-release.js +196 -0
  49. package/packages/core/release-propagation-stage-evidence.js +364 -0
  50. package/packages/core/release-propagation-work-capture.js +64 -0
  51. package/packages/core/release-propagation-work-constants.js +34 -0
  52. package/packages/core/release-propagation-work-control.js +203 -0
  53. package/packages/core/release-propagation-work-transitions.js +145 -0
  54. package/packages/core/release-propagation-work.js +517 -0
  55. package/packages/core/release-propagation.js +34 -158
  56. package/scripts/aggregate-build-summary.mjs +67 -4
  57. package/scripts/auditable-demo-renditions.mjs +131 -0
  58. package/scripts/auditable-demo.mjs +88 -91
  59. package/scripts/aws-windows-jit-controller-core.mjs +269 -0
  60. package/scripts/aws-windows-jit-controller.mjs +502 -0
  61. package/scripts/check-internal-architecture.mjs +178 -52
  62. package/scripts/check-inventory.mjs +2 -2
  63. package/scripts/check-maintainability.mjs +76 -17
  64. package/scripts/generate-public-reference.mjs +68 -0
  65. package/scripts/generate-site-bundle.mjs +35 -41
  66. package/scripts/maintainability-metrics.mjs +24 -4
  67. package/scripts/paper-work-fleet-cli.mjs +22 -3
  68. package/scripts/public-reference.mjs +557 -0
  69. package/scripts/release-propagation.mjs +126 -0
  70. package/scripts/resolve-artifact-transfer-mode.mjs +117 -0
  71. package/scripts/site-reference-registry.mjs +174 -0
  72. package/scripts/verify-golden-path.mjs +169 -0
  73. package/scripts/web-surface-core.mjs +46 -207
  74. package/scripts/web-surface-routing.mjs +286 -0
@@ -35,6 +35,7 @@ import {
35
35
  PAPER_AGENT_ENTRY_CONTRACT,
36
36
  collectPaperAgentEntry,
37
37
  paperAgentEntryFiles,
38
+ resolvePaperBuildchainSha,
38
39
  } from "./paper-agent-entry.js";
39
40
  import {
40
41
  managedPaperPackageJson,
@@ -44,6 +45,7 @@ import {
44
45
  scaffoldPackageJson,
45
46
  scaffoldReadme,
46
47
  } from "./paper-scaffold-content.js";
48
+ import { executePaperNpmBootstrap as executePaperNpmBootstrapOperation } from "./paper-npm-bootstrap.js";
47
49
 
48
50
  export { PAPER_PATHS, resolvePaperRepository } from "./paper-repository.js";
49
51
  export {
@@ -72,6 +74,7 @@ export {
72
74
  collectPaperFleetAudit,
73
75
  discoverPaperFleet,
74
76
  planPaperFleetUpdate,
77
+ paperFleetTransitionWorkspace,
75
78
  writePaperFleetUpdate,
76
79
  } from "./paper-fleet.js";
77
80
 
@@ -111,6 +114,7 @@ const PAPER_SCAFFOLD_PATHS = Object.freeze([
111
114
  PAPER_PATHS.buildWorkflow,
112
115
  PAPER_PATHS.verifyWorkflow,
113
116
  PAPER_PATHS.releaseWorkflow,
117
+ PAPER_PATHS.pnpmWorkspace,
114
118
  PAPER_PATHS.provisioningAuthority,
115
119
  "Makefile",
116
120
  "package.json",
@@ -132,6 +136,7 @@ const DEFAULT_TOOLCHAIN_COMMAND = "latexmk -pdf -outdir=_build paper/main.tex";
132
136
  const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/i;
133
137
  const GIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
134
138
  const PACKAGE_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
139
+ const BUILDCHAIN_PACKAGE_NAME = "@kungfu-tech/buildchain";
135
140
 
136
141
  function toPosix(value) {
137
142
  return String(value || "")
@@ -147,6 +152,66 @@ function jsonText(value) {
147
152
  return `${JSON.stringify(value, null, 2)}\n`;
148
153
  }
149
154
 
155
+ function paperPnpmWorkspace(current, buildchainVersion) {
156
+ const entry = `${BUILDCHAIN_PACKAGE_NAME}@${buildchainVersion}`;
157
+ const source = String(current || "");
158
+ const lines = source ? source.replace(/\r\n/g, "\n").split("\n") : [];
159
+ if (lines.at(-1) === "") lines.pop();
160
+ const keyLines = lines
161
+ .map((line, index) =>
162
+ /^minimumReleaseAgeExclude:\s*(?:#.*)?$/.test(line) ? index : -1,
163
+ )
164
+ .filter((index) => index >= 0);
165
+ const unsupportedKey = lines.some(
166
+ (line) =>
167
+ /^minimumReleaseAgeExclude\s*:/.test(line) &&
168
+ !/^minimumReleaseAgeExclude:\s*(?:#.*)?$/.test(line),
169
+ );
170
+ if (unsupportedKey || keyLines.length > 1) {
171
+ throw new Error(
172
+ "paper migration requires minimumReleaseAgeExclude to be one top-level block sequence",
173
+ );
174
+ }
175
+ if (keyLines.length === 0) {
176
+ const prefix = lines.length > 0 ? [...lines, ""] : [];
177
+ return `${[...prefix, "minimumReleaseAgeExclude:", ` - '${entry}'`].join(
178
+ "\n",
179
+ )}\n`;
180
+ }
181
+ const keyIndex = keyLines[0];
182
+ let blockEnd = lines.length;
183
+ for (let index = keyIndex + 1; index < lines.length; index += 1) {
184
+ if (/^[^\s#]/.test(lines[index])) {
185
+ blockEnd = index;
186
+ break;
187
+ }
188
+ }
189
+ const retained = [];
190
+ for (const line of lines.slice(keyIndex + 1, blockEnd)) {
191
+ if (!line.trim() || /^\s*#/.test(line)) {
192
+ retained.push(line);
193
+ continue;
194
+ }
195
+ const item = line.match(/^\s*-\s+(.+?)\s*(?:#.*)?$/);
196
+ if (!item) {
197
+ throw new Error(
198
+ "paper migration requires minimumReleaseAgeExclude to contain scalar package entries",
199
+ );
200
+ }
201
+ const value = item[1]
202
+ .trim()
203
+ .replace(/^'(.*)'$/, "$1")
204
+ .replace(/^"(.*)"$/, "$1");
205
+ if (!value.startsWith(`${BUILDCHAIN_PACKAGE_NAME}@`)) retained.push(line);
206
+ }
207
+ return `${[
208
+ ...lines.slice(0, keyIndex + 1),
209
+ ...retained,
210
+ ` - '${entry}'`,
211
+ ...lines.slice(blockEnd),
212
+ ].join("\n")}\n`;
213
+ }
214
+
150
215
  function existingFileFact(cwd, relativePath) {
151
216
  const filePath = path.resolve(cwd, relativePath);
152
217
  if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
@@ -177,8 +242,11 @@ function buildchainPackageIdentity(buildchainRoot, explicitVersion = "") {
177
242
  };
178
243
  }
179
244
 
180
- function runtimeGitSha(buildchainRoot, buildchainVersion = "") {
181
- const value = gitValue(buildchainRoot, ["rev-parse", "HEAD"]);
245
+ export function resolvePaperRuntimeGitSha(
246
+ buildchainRoot,
247
+ buildchainVersion = "",
248
+ ) {
249
+ const value = resolvePaperBuildchainSha(buildchainRoot);
182
250
  if (GIT_SHA_PATTERN.test(value)) return value;
183
251
  const identity = buildchainPackageIdentity(buildchainRoot, buildchainVersion);
184
252
  if (!identity.version) return "";
@@ -416,6 +484,7 @@ ${passportInput} secrets:
416
484
  BUILDCHAIN_GENERATED_WRITE_APP_CLIENT_ID: \${{ secrets.BUILDCHAIN_GENERATED_WRITE_APP_CLIENT_ID }}
417
485
  BUILDCHAIN_GENERATED_WRITE_APP_PRIVATE_KEY: \${{ secrets.BUILDCHAIN_GENERATED_WRITE_APP_PRIVATE_KEY }}
418
486
  BUILDCHAIN_GENERATED_WRITE_TOKEN: \${{ secrets.BUILDCHAIN_GENERATED_WRITE_TOKEN }}
487
+ BUILDCHAIN_PROMOTION_TOKEN: \${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}
419
488
  `;
420
489
  }
421
490
 
@@ -598,6 +667,7 @@ function scaffoldFiles({
598
667
  [PAPER_PATHS.buildWorkflow, buildWorkflow],
599
668
  [PAPER_PATHS.verifyWorkflow, verifyWorkflow],
600
669
  [PAPER_PATHS.releaseWorkflow, releaseWorkflow],
670
+ [PAPER_PATHS.pnpmWorkspace, paperPnpmWorkspace("", buildchainVersion)],
601
671
  [PAPER_PATHS.provisioningAuthority, jsonText(provisioningAuthority)],
602
672
  ...agentEntry,
603
673
  [
@@ -661,7 +731,8 @@ export function planPaperScaffold({
661
731
  buildchainVersion,
662
732
  );
663
733
  const runtimeSha =
664
- buildchainSha || runtimeGitSha(buildchainRoot, runtimeIdentity.version);
734
+ buildchainSha ||
735
+ resolvePaperRuntimeGitSha(buildchainRoot, runtimeIdentity.version);
665
736
  if (!GIT_SHA_PATTERN.test(runtimeSha)) {
666
737
  throw new Error(
667
738
  "paper scaffold cannot resolve the exact Buildchain runtime SHA; pass buildchainSha or use a published Buildchain package with npm gitHead provenance",
@@ -854,7 +925,8 @@ function migrationFiles({
854
925
  "paper publish package",
855
926
  );
856
927
  const runtimeSha =
857
- buildchainSha || runtimeGitSha(buildchainRoot, buildchainVersion);
928
+ buildchainSha ||
929
+ resolvePaperRuntimeGitSha(buildchainRoot, buildchainVersion);
858
930
  if (!GIT_SHA_PATTERN.test(runtimeSha)) {
859
931
  throw new Error("paper migration requires an exact Buildchain source SHA");
860
932
  }
@@ -917,12 +989,20 @@ function migrationFiles({
917
989
  currentPackage.value,
918
990
  runtimeIdentity.version,
919
991
  );
992
+ const pnpmWorkspacePath = path.resolve(cwd, PAPER_PATHS.pnpmWorkspace);
993
+ const pnpmWorkspace = paperPnpmWorkspace(
994
+ fs.existsSync(pnpmWorkspacePath)
995
+ ? fs.readFileSync(pnpmWorkspacePath, "utf8")
996
+ : "",
997
+ runtimeIdentity.version,
998
+ );
920
999
  const files = new Map([
921
1000
  [PAPER_PATHS.contractLock, contractLockText],
922
1001
  [PAPER_PATHS.versionPin, `${runtimeIdentity.version}\n`],
923
1002
  [PAPER_PATHS.buildWorkflow, buildWorkflow],
924
1003
  [PAPER_PATHS.verifyWorkflow, verifyWorkflow],
925
1004
  [PAPER_PATHS.releaseWorkflow, releaseWorkflow],
1005
+ [PAPER_PATHS.pnpmWorkspace, pnpmWorkspace],
926
1006
  [PAPER_PATHS.provisioningAuthority, jsonText(provisioningAuthority)],
927
1007
  ...agentEntry,
928
1008
  ["package.json", jsonText(packageJson)],
@@ -1224,8 +1304,7 @@ function releaseTransactionFacts(candidates, version) {
1224
1304
  };
1225
1305
  }
1226
1306
 
1227
- export function collectPaperStatus({ cwd = process.cwd() } = {}) {
1228
- const resolvedCwd = path.resolve(cwd);
1307
+ function collectPaperGovernanceFacts(resolvedCwd) {
1229
1308
  const configResult = paperConfig(resolvedCwd);
1230
1309
  const loaded = configResult.loaded;
1231
1310
  const publication = loaded?.config?.publication;
@@ -1266,7 +1345,13 @@ export function collectPaperStatus({ cwd = process.cwd() } = {}) {
1266
1345
  gitRepo.ok &&
1267
1346
  gitRepo.stdout === "true",
1268
1347
  );
1348
+ return {
1349
+ configResult, loaded, publication, packageName, version, configFact,
1350
+ scaffoldFacts, contractLockError, scaffoldOk, governedOk,
1351
+ };
1352
+ }
1269
1353
 
1354
+ function collectPaperEvidenceFacts(resolvedCwd, publication, version) {
1270
1355
  const admissionInputs = [PAPER_PATHS.capability, PAPER_PATHS.admission].map(
1271
1356
  (relativePath) => ({
1272
1357
  relativePath,
@@ -1324,6 +1409,82 @@ export function collectPaperStatus({ cwd = process.cwd() } = {}) {
1324
1409
  visibility.value,
1325
1410
  "production",
1326
1411
  );
1412
+ return {
1413
+ admitted, bootstrapValue, trustValue, content, reproducibility, deterministic,
1414
+ sealedOk, sealedError, candidates, transactionFacts, packagePublished,
1415
+ alphaComplete, stagingVisible, productionVisible,
1416
+ };
1417
+ }
1418
+
1419
+ function paperStatusBlockingNextActions({ stateMap, deterministic, transactionFacts }) {
1420
+ const actions = [];
1421
+ if (!stateMap.scaffolded.satisfied) {
1422
+ actions.push({
1423
+ id: "scaffold-paper",
1424
+ command: "buildchain paper scaffold --help",
1425
+ description: "Plan a no-overwrite Buildchain paper scaffold.",
1426
+ });
1427
+ } else if (!stateMap.governed.satisfied) {
1428
+ actions.push({
1429
+ id: "restore-governance",
1430
+ command: "buildchain paper preflight --json",
1431
+ description:
1432
+ "Repair the Git/contract-lock governance checks reported by preflight.",
1433
+ });
1434
+ } else if (!stateMap["content-ready"].satisfied) {
1435
+ actions.push({
1436
+ id: "complete-paper-content",
1437
+ command: "make check",
1438
+ description: "Add every declared paper source and metadata input.",
1439
+ });
1440
+ } else if (!deterministic) {
1441
+ actions.push({
1442
+ id: "build-reproducible-artifact",
1443
+ command: "buildchain paper build --execute --json",
1444
+ description:
1445
+ "Run the existing two-clean-build reproducibility gate and promote exact bytes.",
1446
+ });
1447
+ } else if (
1448
+ !stateMap.bootstrapped.satisfied ||
1449
+ !stateMap["trust-bound"].satisfied
1450
+ ) {
1451
+ actions.push({
1452
+ id: "bootstrap-npm",
1453
+ command: "buildchain paper bootstrap npm --json",
1454
+ description:
1455
+ "Inspect the dry-run npm bootstrap and Trusted Publishing handoff.",
1456
+ });
1457
+ } else if (!stateMap["alpha-complete"].satisfied) {
1458
+ actions.push({
1459
+ id: transactionFacts.selected ? "resume-alpha" : "start-alpha",
1460
+ command: transactionFacts.selected
1461
+ ? "buildchain paper resume --json"
1462
+ : "buildchain paper alpha --json",
1463
+ description: transactionFacts.selected
1464
+ ? "Resume the exact sealed release transaction."
1465
+ : "Plan the protected dev-to-alpha publication PR.",
1466
+ });
1467
+ }
1468
+ return actions;
1469
+ }
1470
+
1471
+ export function collectPaperStatus({ cwd = process.cwd() } = {}) {
1472
+ const resolvedCwd = path.resolve(cwd);
1473
+ const governance = collectPaperGovernanceFacts(resolvedCwd);
1474
+ const evidence = collectPaperEvidenceFacts(
1475
+ resolvedCwd,
1476
+ governance.publication,
1477
+ governance.version,
1478
+ );
1479
+ const {
1480
+ configResult, loaded, publication, packageName, version, configFact,
1481
+ scaffoldFacts, contractLockError, scaffoldOk, governedOk,
1482
+ } = governance;
1483
+ const {
1484
+ admitted, bootstrapValue, trustValue, content, reproducibility, deterministic,
1485
+ sealedOk, sealedError, candidates, transactionFacts, packagePublished,
1486
+ alphaComplete, stagingVisible, productionVisible,
1487
+ } = evidence;
1327
1488
 
1328
1489
  const states = [
1329
1490
  state(
@@ -1433,54 +1594,7 @@ export function collectPaperStatus({ cwd = process.cwd() } = {}) {
1433
1594
  const highestEvidenceState =
1434
1595
  [...states].reverse().find((entry) => entry.satisfied)?.id || "none";
1435
1596
  const stateMap = Object.fromEntries(states.map((entry) => [entry.id, entry]));
1436
- const blockingNextActions = [];
1437
- if (!stateMap.scaffolded.satisfied) {
1438
- blockingNextActions.push({
1439
- id: "scaffold-paper",
1440
- command: "buildchain paper scaffold --help",
1441
- description: "Plan a no-overwrite Buildchain paper scaffold.",
1442
- });
1443
- } else if (!stateMap.governed.satisfied) {
1444
- blockingNextActions.push({
1445
- id: "restore-governance",
1446
- command: "buildchain paper preflight --json",
1447
- description:
1448
- "Repair the Git/contract-lock governance checks reported by preflight.",
1449
- });
1450
- } else if (!stateMap["content-ready"].satisfied) {
1451
- blockingNextActions.push({
1452
- id: "complete-paper-content",
1453
- command: "make check",
1454
- description: "Add every declared paper source and metadata input.",
1455
- });
1456
- } else if (!deterministic) {
1457
- blockingNextActions.push({
1458
- id: "build-reproducible-artifact",
1459
- command: "buildchain paper build --execute --json",
1460
- description:
1461
- "Run the existing two-clean-build reproducibility gate and promote exact bytes.",
1462
- });
1463
- } else if (
1464
- !stateMap.bootstrapped.satisfied ||
1465
- !stateMap["trust-bound"].satisfied
1466
- ) {
1467
- blockingNextActions.push({
1468
- id: "bootstrap-npm",
1469
- command: "buildchain paper bootstrap npm --json",
1470
- description:
1471
- "Inspect the dry-run npm bootstrap and Trusted Publishing handoff.",
1472
- });
1473
- } else if (!stateMap["alpha-complete"].satisfied) {
1474
- blockingNextActions.push({
1475
- id: transactionFacts.selected ? "resume-alpha" : "start-alpha",
1476
- command: transactionFacts.selected
1477
- ? "buildchain paper resume --json"
1478
- : "buildchain paper alpha --json",
1479
- description: transactionFacts.selected
1480
- ? "Resume the exact sealed release transaction."
1481
- : "Plan the protected dev-to-alpha publication PR.",
1482
- });
1483
- }
1597
+ const blockingNextActions = paperStatusBlockingNextActions({ stateMap, deterministic, transactionFacts });
1484
1598
  return {
1485
1599
  schemaVersion: 1,
1486
1600
  contract: PAPER_STATUS_CONTRACT,
@@ -1734,7 +1848,8 @@ function runtimeFacts({
1734
1848
  version: identity.version,
1735
1849
  ref: buildchainRef,
1736
1850
  resolvedSha:
1737
- buildchainSha || runtimeGitSha(buildchainRoot, identity.version),
1851
+ buildchainSha ||
1852
+ resolvePaperRuntimeGitSha(buildchainRoot, identity.version),
1738
1853
  contract: contractWorld.contract,
1739
1854
  contractDigest: contractWorld.contractDigest,
1740
1855
  compatibilityDigest: contractWorld.compatibilityDigest,
@@ -2043,146 +2158,8 @@ function localToolchainObservation(cwd, publication) {
2043
2158
  };
2044
2159
  }
2045
2160
 
2046
- export function collectPaperPreflight({
2047
- cwd = process.cwd(),
2048
- buildchainRoot = process.cwd(),
2049
- buildchainVersion = "",
2050
- buildchainRef = "v3",
2051
- buildchainSha = "",
2052
- registry = NPM_REGISTRY,
2053
- offline = false,
2054
- agentEntryMode = "contract",
2055
- } = {}) {
2056
- const resolvedCwd = path.resolve(cwd);
2057
- const status = collectPaperStatus({ cwd: resolvedCwd });
2058
- const provisioning = validatePaperProvisioningAuthority(resolvedCwd);
2059
- const configResult = paperConfig(resolvedCwd);
2060
- let validation;
2061
- let validationError = "";
2062
- try {
2063
- validation = validateBuildchainConfig(resolvedCwd, {
2064
- requireLifecycleStages: ["verify"],
2065
- });
2066
- } catch (error) {
2067
- validationError = error.message;
2068
- }
2069
- const runtime = runtimeFacts({
2070
- buildchainRoot,
2071
- buildchainVersion,
2072
- buildchainRef: provisioning.value?.runtime?.ref || buildchainRef,
2073
- buildchainSha,
2074
- });
2075
- const agentEntry = collectPaperAgentEntry({
2076
- cwd: resolvedCwd,
2077
- buildchainSha: runtime.resolvedSha,
2078
- mode: agentEntryMode,
2079
- });
2080
- const lockPath = path.resolve(resolvedCwd, PAPER_PATHS.contractLock);
2081
- let lockEvaluation = {
2082
- status: "missing-lock",
2083
- compatible: false,
2084
- drift: false,
2085
- reasons: ["Buildchain contract lock is missing"],
2086
- };
2087
- try {
2088
- const lock = readBuildchainContractLock(lockPath);
2089
- if (lock) {
2090
- const current = runtimeContractWorld(buildchainRoot);
2091
- lockEvaluation = evaluateBuildchainContractLock({
2092
- lock,
2093
- current,
2094
- runtimeRef: runtime.ref,
2095
- runtimeSha: runtime.resolvedSha,
2096
- runtimeClass: /alpha/i.test(runtime.ref) ? "alpha" : "stable",
2097
- });
2098
- }
2099
- } catch (error) {
2100
- lockEvaluation = {
2101
- status: "invalid-lock",
2102
- compatible: false,
2103
- drift: false,
2104
- reasons: [error.message],
2105
- };
2106
- }
2107
- const source = {
2108
- repositoryRoot: gitValue(resolvedCwd, ["rev-parse", "--show-toplevel"]),
2109
- head: gitValue(resolvedCwd, ["rev-parse", "HEAD"]),
2110
- tree: gitValue(resolvedCwd, ["rev-parse", "HEAD^{tree}"]),
2111
- branch: gitValue(resolvedCwd, ["branch", "--show-current"]),
2112
- clean: gitResult(resolvedCwd, ["status", "--porcelain"]).stdout === "",
2113
- };
2114
- const packageName = status.identity.package;
2115
- const repository = status.identity.repository;
2116
- const expectedPublisher = expectedPaperTrustedPublisher(provisioning.value, {
2117
- repository,
2118
- workflow: path.posix.basename(PAPER_PATHS.releaseWorkflow),
2119
- });
2120
- const npm =
2121
- offline || !packageName
2122
- ? {
2123
- package: {
2124
- status: "unknown",
2125
- exists: null,
2126
- version: "",
2127
- registry,
2128
- errorCode: offline ? "offline" : "package-unresolved",
2129
- },
2130
- auth: {
2131
- status: "unknown",
2132
- authenticated: null,
2133
- identity: "",
2134
- errorCode: offline ? "offline" : "package-unresolved",
2135
- },
2136
- trust: {
2137
- status: "unknown",
2138
- configured: null,
2139
- publishers: [],
2140
- errorCode: offline ? "offline" : "package-unresolved",
2141
- },
2142
- }
2143
- : {
2144
- package: liveNpmPackageObservation(
2145
- packageName,
2146
- registry,
2147
- resolvedCwd,
2148
- ),
2149
- auth: liveNpmAuthObservation(registry, resolvedCwd),
2150
- trust: liveNpmTrustObservation(
2151
- packageName,
2152
- registry,
2153
- resolvedCwd,
2154
- expectedPublisher,
2155
- ),
2156
- };
2157
- const repositoryPermissions = offline
2158
- ? {
2159
- status: "unknown",
2160
- repository,
2161
- canWrite: null,
2162
- errorCode: "offline",
2163
- }
2164
- : liveRepositoryPermissionObservation(repository, resolvedCwd);
2165
- const repositoryActions = offline
2166
- ? {
2167
- status: "unknown",
2168
- defaultWorkflowPermissions: "",
2169
- canApprovePullRequestReviews: null,
2170
- errorCode: "offline",
2171
- }
2172
- : liveRepositoryActionsPolicyObservation(repository, resolvedCwd);
2173
- const generatedWriteAuthority = offline
2174
- ? {
2175
- status: "unknown",
2176
- configured: null,
2177
- mode: "",
2178
- errorCode: "offline",
2179
- }
2180
- : liveGeneratedWriteAuthorityObservation(repository, resolvedCwd);
2181
- const toolchain = localToolchainObservation(
2182
- resolvedCwd,
2183
- configResult.loaded?.config?.publication,
2184
- );
2185
- const checks = [
2161
+ function paperPreflightLocalChecks({ agentEntry, provisioning, validationError, source, toolchain, runtime, lockEvaluation }) {
2162
+ return [
2186
2163
  ...agentEntry.checks.map((entry) => ({
2187
2164
  ...entry,
2188
2165
  blocking: true,
@@ -2249,6 +2226,11 @@ export function collectPaperPreflight({
2249
2226
  ? `Buildchain runtime contract is ${lockEvaluation.status}.`
2250
2227
  : `Buildchain runtime contract is not admitted: ${(lockEvaluation.reasons || []).join("; ")}`,
2251
2228
  },
2229
+ ];
2230
+ }
2231
+
2232
+ function paperPreflightRepositoryChecks({ repositoryPermissions, repositoryActions, generatedWriteAuthority }) {
2233
+ return [
2252
2234
  {
2253
2235
  id: "repository.write-permission",
2254
2236
  status:
@@ -2302,6 +2284,11 @@ export function collectPaperPreflight({
2302
2284
  ? "No GitHub App or compatible narrow generated-write credential is configured."
2303
2285
  : `Generated-write authority is unknown (${generatedWriteAuthority.errorCode}).`,
2304
2286
  },
2287
+ ];
2288
+ }
2289
+
2290
+ function paperPreflightPublicationChecks({ npm, status }) {
2291
+ return [
2305
2292
  {
2306
2293
  id: "npm.package",
2307
2294
  status:
@@ -2358,24 +2345,19 @@ export function collectPaperPreflight({
2358
2345
  : `${status.conflicts.length} release-state conflict(s) require repair.`,
2359
2346
  },
2360
2347
  ];
2361
- const blockingChecks = checks.filter(
2362
- (entry) => entry.blocking && ["fail", "unknown"].includes(entry.status),
2363
- );
2364
- const externalMutationChecks = checks.filter(
2365
- (entry) =>
2366
- entry.scope === "external-mutation" &&
2367
- ["fail", "pending", "unknown"].includes(entry.status),
2368
- );
2369
- const nextActions = [];
2348
+ }
2349
+
2350
+ function paperPreflightNextActions({ validationError, source, lockEvaluation, npm, repositoryActions, generatedWriteAuthority, status }) {
2351
+ const actions = [];
2370
2352
  if (validationError) {
2371
- nextActions.push({
2353
+ actions.push({
2372
2354
  id: "repair-config",
2373
2355
  command: "buildchain validate --require-lifecycle-stages verify",
2374
2356
  description: validationError,
2375
2357
  });
2376
2358
  }
2377
2359
  if (!source.clean) {
2378
- nextActions.push({
2360
+ actions.push({
2379
2361
  id: "commit-source",
2380
2362
  command: "git status --short",
2381
2363
  description:
@@ -2383,7 +2365,7 @@ export function collectPaperPreflight({
2383
2365
  });
2384
2366
  }
2385
2367
  if (!lockEvaluation.compatible) {
2386
- nextActions.push({
2368
+ actions.push({
2387
2369
  id: "refresh-contract-lock",
2388
2370
  command: "buildchain paper scaffold --json",
2389
2371
  description:
@@ -2391,7 +2373,7 @@ export function collectPaperPreflight({
2391
2373
  });
2392
2374
  }
2393
2375
  if (npm.package.exists === false || npm.trust.configured === false) {
2394
- nextActions.push({
2376
+ actions.push({
2395
2377
  id: "bootstrap-npm",
2396
2378
  command: "buildchain paper bootstrap npm --json",
2397
2379
  description:
@@ -2403,7 +2385,7 @@ export function collectPaperPreflight({
2403
2385
  (repositoryActions.defaultWorkflowPermissions !== "read" ||
2404
2386
  repositoryActions.canApprovePullRequestReviews !== false)
2405
2387
  ) {
2406
- nextActions.push({
2388
+ actions.push({
2407
2389
  id: "constrain-repository-actions",
2408
2390
  command: "",
2409
2391
  description:
@@ -2411,7 +2393,7 @@ export function collectPaperPreflight({
2411
2393
  });
2412
2394
  }
2413
2395
  if (generatedWriteAuthority.configured === false) {
2414
- nextActions.push({
2396
+ actions.push({
2415
2397
  id: "configure-generated-write-authority",
2416
2398
  command: "",
2417
2399
  description:
@@ -2419,12 +2401,149 @@ export function collectPaperPreflight({
2419
2401
  });
2420
2402
  }
2421
2403
  if (status.deterministicBuild.status !== "qualifying") {
2422
- nextActions.push({
2404
+ actions.push({
2423
2405
  id: "build-paper",
2424
2406
  command: "buildchain paper build --execute --json",
2425
2407
  description: "Run the existing qualifying two-clean-build gate.",
2426
2408
  });
2427
2409
  }
2410
+ return actions;
2411
+ }
2412
+
2413
+ export function collectPaperPreflight({
2414
+ cwd = process.cwd(),
2415
+ buildchainRoot = process.cwd(),
2416
+ buildchainVersion = "",
2417
+ buildchainRef = "v3",
2418
+ buildchainSha = "",
2419
+ registry = NPM_REGISTRY,
2420
+ offline = false,
2421
+ agentEntryMode = "contract",
2422
+ } = {}) {
2423
+ const resolvedCwd = path.resolve(cwd);
2424
+ const status = collectPaperStatus({ cwd: resolvedCwd });
2425
+ const provisioning = validatePaperProvisioningAuthority(resolvedCwd);
2426
+ const configResult = paperConfig(resolvedCwd);
2427
+ let validation;
2428
+ let validationError = "";
2429
+ try {
2430
+ validation = validateBuildchainConfig(resolvedCwd, { requireLifecycleStages: ["verify"] });
2431
+ } catch (error) {
2432
+ validationError = error.message;
2433
+ }
2434
+ const runtime = runtimeFacts({
2435
+ buildchainRoot, buildchainVersion,
2436
+ buildchainRef: provisioning.value?.runtime?.ref || buildchainRef, buildchainSha,
2437
+ });
2438
+ const agentEntry = collectPaperAgentEntry({ cwd: resolvedCwd, buildchainSha: runtime.resolvedSha, mode: agentEntryMode });
2439
+ const lockPath = path.resolve(resolvedCwd, PAPER_PATHS.contractLock);
2440
+ let lockEvaluation = {
2441
+ status: "missing-lock",
2442
+ compatible: false,
2443
+ drift: false,
2444
+ reasons: ["Buildchain contract lock is missing"],
2445
+ };
2446
+ try {
2447
+ const lock = readBuildchainContractLock(lockPath);
2448
+ if (lock) {
2449
+ const current = runtimeContractWorld(buildchainRoot);
2450
+ lockEvaluation = evaluateBuildchainContractLock({
2451
+ lock,
2452
+ current,
2453
+ runtimeRef: runtime.ref,
2454
+ runtimeSha: runtime.resolvedSha,
2455
+ runtimeClass: /alpha/i.test(runtime.ref) ? "alpha" : "stable",
2456
+ });
2457
+ }
2458
+ } catch (error) {
2459
+ lockEvaluation = {
2460
+ status: "invalid-lock",
2461
+ compatible: false,
2462
+ drift: false,
2463
+ reasons: [error.message],
2464
+ };
2465
+ }
2466
+ const source = {
2467
+ repositoryRoot: gitValue(resolvedCwd, ["rev-parse", "--show-toplevel"]),
2468
+ head: gitValue(resolvedCwd, ["rev-parse", "HEAD"]),
2469
+ tree: gitValue(resolvedCwd, ["rev-parse", "HEAD^{tree}"]),
2470
+ branch: gitValue(resolvedCwd, ["branch", "--show-current"]),
2471
+ clean: gitResult(resolvedCwd, ["status", "--porcelain"]).stdout === "",
2472
+ };
2473
+ const packageName = status.identity.package;
2474
+ const repository = status.identity.repository;
2475
+ const expectedPublisher = expectedPaperTrustedPublisher(provisioning.value, {
2476
+ repository, workflow: path.posix.basename(PAPER_PATHS.releaseWorkflow),
2477
+ });
2478
+ const npm =
2479
+ offline || !packageName
2480
+ ? {
2481
+ package: {
2482
+ status: "unknown",
2483
+ exists: null,
2484
+ version: "",
2485
+ registry,
2486
+ errorCode: offline ? "offline" : "package-unresolved",
2487
+ },
2488
+ auth: {
2489
+ status: "unknown",
2490
+ authenticated: null,
2491
+ identity: "",
2492
+ errorCode: offline ? "offline" : "package-unresolved",
2493
+ },
2494
+ trust: {
2495
+ status: "unknown",
2496
+ configured: null,
2497
+ publishers: [],
2498
+ errorCode: offline ? "offline" : "package-unresolved",
2499
+ },
2500
+ }
2501
+ : {
2502
+ package: liveNpmPackageObservation(packageName, registry, resolvedCwd),
2503
+ auth: liveNpmAuthObservation(registry, resolvedCwd),
2504
+ trust: liveNpmTrustObservation(packageName, registry, resolvedCwd, expectedPublisher),
2505
+ };
2506
+ const repositoryPermissions = offline
2507
+ ? {
2508
+ status: "unknown",
2509
+ repository,
2510
+ canWrite: null,
2511
+ errorCode: "offline",
2512
+ }
2513
+ : liveRepositoryPermissionObservation(repository, resolvedCwd);
2514
+ const repositoryActions = offline
2515
+ ? {
2516
+ status: "unknown",
2517
+ defaultWorkflowPermissions: "",
2518
+ canApprovePullRequestReviews: null,
2519
+ errorCode: "offline",
2520
+ }
2521
+ : liveRepositoryActionsPolicyObservation(repository, resolvedCwd);
2522
+ const generatedWriteAuthority = offline
2523
+ ? {
2524
+ status: "unknown",
2525
+ configured: null,
2526
+ mode: "",
2527
+ errorCode: "offline",
2528
+ }
2529
+ : liveGeneratedWriteAuthorityObservation(repository, resolvedCwd);
2530
+ const toolchain = localToolchainObservation(resolvedCwd, configResult.loaded?.config?.publication);
2531
+ const checks = [
2532
+ ...paperPreflightLocalChecks({ agentEntry, provisioning, validationError, source, toolchain, runtime, lockEvaluation }),
2533
+ ...paperPreflightRepositoryChecks({ repositoryPermissions, repositoryActions, generatedWriteAuthority }),
2534
+ ...paperPreflightPublicationChecks({ npm, status }),
2535
+ ];
2536
+ const blockingChecks = checks.filter(
2537
+ (entry) => entry.blocking && ["fail", "unknown"].includes(entry.status),
2538
+ );
2539
+ const externalMutationChecks = checks.filter(
2540
+ (entry) =>
2541
+ entry.scope === "external-mutation" &&
2542
+ ["fail", "pending", "unknown"].includes(entry.status),
2543
+ );
2544
+ const nextActions = paperPreflightNextActions({
2545
+ validationError, source, lockEvaluation, npm, repositoryActions, generatedWriteAuthority, status,
2546
+ });
2428
2547
  return {
2429
2548
  schemaVersion: 1,
2430
2549
  contract: PAPER_PREFLIGHT_CONTRACT,
@@ -2718,467 +2837,30 @@ function bootstrapPackageShape({ packageName, version }) {
2718
2837
  };
2719
2838
  }
2720
2839
 
2721
- export function executePaperNpmBootstrap({
2722
- cwd = process.cwd(),
2723
- packageName = "",
2724
- bootstrapVersion = DEFAULT_BOOTSTRAP_VERSION,
2725
- registry = NPM_REGISTRY,
2726
- repository = "",
2727
- workflow = "paper-release.yml",
2728
- environment = "",
2729
- execute = false,
2730
- confirmedPackage = "",
2731
- userconfig = "",
2732
- offline = false,
2733
- } = {}) {
2734
- const resolvedCwd = path.resolve(cwd);
2735
- if (registry !== NPM_REGISTRY) {
2736
- throw new Error(
2737
- `paper npm bootstrap requires the official registry ${NPM_REGISTRY}`,
2738
- );
2739
- }
2740
- if (bootstrapVersion !== DEFAULT_BOOTSTRAP_VERSION) {
2741
- throw new Error(
2742
- `paper npm bootstrap version is fixed at ${DEFAULT_BOOTSTRAP_VERSION}`,
2743
- );
2744
- }
2745
- const configResult = paperConfig(resolvedCwd);
2746
- if (configResult.error) throw new Error(configResult.error);
2747
- const name = normalizePackageName(
2748
- packageName ||
2749
- configResult.loaded.config.publish?.package ||
2750
- configResult.loaded.config.publish?.mainPackage,
2751
- );
2752
- const repo = normalizeRepository(
2753
- repository || resolvePaperRepository(resolvedCwd),
2754
- );
2755
- const provisioning = validatePaperProvisioningAuthority(resolvedCwd);
2756
- if (!provisioning.valid) {
2757
- throw new Error(
2758
- `paper npm bootstrap requires a valid provisioning authority: ${provisioning.errors.join("; ")}`,
2759
- );
2760
- }
2761
- const expectedPublisher = expectedPaperTrustedPublisher(provisioning.value);
2762
- if (
2763
- provisioning.value.package?.name !== name ||
2764
- expectedPublisher.repository !== repo ||
2765
- expectedPublisher.workflow !== toPosix(workflow).replace(/^\/+/, "") ||
2766
- expectedPublisher.environment !== String(environment || "")
2767
- ) {
2768
- throw new Error(
2769
- "paper npm bootstrap coordinates differ from the exact provisioning authority",
2770
- );
2771
- }
2772
- if (execute && confirmedPackage !== name) {
2773
- throw new Error(
2774
- `real npm bootstrap requires --confirm-public-package ${name}`,
2775
- );
2776
- }
2777
- if (execute && offline) {
2778
- throw new Error("real npm bootstrap cannot run with --offline");
2779
- }
2780
- if (execute && (!repo || !workflow)) {
2781
- throw new Error(
2782
- "real npm bootstrap requires GitHub repository and workflow coordinates",
2783
- );
2784
- }
2785
- const packageObservation = offline
2786
- ? {
2787
- status: "unknown",
2788
- exists: null,
2789
- version: "",
2790
- registry,
2791
- errorCode: "offline",
2792
- }
2793
- : liveNpmPackageObservation(name, registry, resolvedCwd);
2794
- const auth = offline
2795
- ? {
2796
- status: "unknown",
2797
- authenticated: null,
2798
- identity: "",
2799
- errorCode: "offline",
2800
- }
2801
- : liveNpmAuthObservation(registry, resolvedCwd);
2802
- const plan = {
2803
- schemaVersion: 1,
2804
- contract: PAPER_NPM_BOOTSTRAP_CONTRACT,
2805
- ok: true,
2806
- dryRun: !execute,
2807
- externalMutation: execute,
2808
- package: {
2809
- name,
2810
- bootstrapVersion,
2811
- registry,
2812
- existsBefore: packageObservation.exists,
2813
- observedVersion: packageObservation.version,
2814
- },
2815
- repository: {
2816
- github: repo,
2817
- workflow,
2818
- environment,
2819
- },
2820
- authority: {
2821
- digest: provisioning.value.authorityDigest,
2822
- policyDigest: provisioning.value.policy.policyDigest,
2823
- },
2824
- auth: {
2825
- authenticated: auth.authenticated,
2826
- identity: auth.identity,
2827
- errorCode: auth.errorCode,
2828
- },
2829
- publish: {
2830
- status: packageObservation.exists === true ? "existing" : "planned",
2831
- distTag: "bootstrap",
2832
- access: "public",
2833
- },
2834
- trust: {
2835
- status: "planned",
2836
- urls: [],
2837
- },
2838
- receipts: [],
2839
- nextActions: execute
2840
- ? []
2841
- : [
2842
- {
2843
- id: "confirm-public-bootstrap",
2844
- command: `buildchain paper bootstrap npm --execute --confirm-public-package ${name} --json`,
2845
- description:
2846
- "After reviewing this dry-run, bootstrap the exact public package and configure GitHub Trusted Publishing.",
2847
- },
2848
- ],
2849
- };
2850
- if (!execute) {
2851
- const dryRunRoot = fs.mkdtempSync(
2852
- path.join(os.tmpdir(), "buildchain-paper-bootstrap-dry-run-"),
2853
- );
2854
- try {
2855
- fs.writeFileSync(
2856
- path.join(dryRunRoot, "package.json"),
2857
- jsonText(
2858
- bootstrapPackageShape({
2859
- packageName: name,
2860
- version: bootstrapVersion,
2861
- }),
2862
- ),
2863
- );
2864
- const configArgs = userconfig ? ["--userconfig", userconfig] : [];
2865
- const pack = commandResult(
2866
- "npm",
2867
- [
2868
- "pack",
2869
- "--dry-run",
2870
- "--json",
2871
- "--ignore-scripts",
2872
- `--registry=${registry}`,
2873
- ...configArgs,
2874
- ],
2875
- { cwd: dryRunRoot },
2876
- );
2877
- const publish = commandResult(
2878
- "npm",
2879
- [
2880
- "publish",
2881
- "--dry-run",
2882
- "--ignore-scripts",
2883
- "--access",
2884
- "public",
2885
- "--tag",
2886
- "bootstrap",
2887
- `--registry=${registry}`,
2888
- ...configArgs,
2889
- ],
2890
- { cwd: dryRunRoot },
2891
- );
2892
- return {
2893
- ...plan,
2894
- ok: pack.ok && publish.ok,
2895
- errorCode: !pack.ok
2896
- ? "npm-pack-dry-run-failed"
2897
- : !publish.ok
2898
- ? "npm-publish-dry-run-failed"
2899
- : "",
2900
- dryRunChecks: {
2901
- minimalPackageOnly: true,
2902
- pack: {
2903
- status: pack.ok ? "pass" : "fail",
2904
- entryCount: (() => {
2905
- const parsed = safeParseJson(pack.stdout);
2906
- const value = Array.isArray(parsed) ? parsed[0] : parsed;
2907
- return Array.isArray(value?.files) ? value.files.length : 0;
2908
- })(),
2909
- },
2910
- publish: {
2911
- status: publish.ok ? "pass" : "fail",
2912
- registry,
2913
- access: "public",
2914
- distTag: "bootstrap",
2915
- },
2916
- },
2917
- };
2918
- } finally {
2919
- fs.rmSync(dryRunRoot, { recursive: true, force: true });
2920
- }
2921
- }
2922
- if (packageObservation.exists === null) {
2923
- return {
2924
- ...plan,
2925
- ok: false,
2926
- errorCode: "npm-package-status-unknown",
2927
- publish: {
2928
- ...plan.publish,
2929
- status: "blocked",
2930
- },
2931
- trust: {
2932
- status: "blocked",
2933
- urls: [],
2934
- },
2935
- nextActions: [
2936
- {
2937
- id: "verify-npm-package",
2938
- command: `npm view ${name} version --json --registry=${registry}`,
2939
- description:
2940
- "Resolve whether the exact public package exists before any publish mutation.",
2941
- },
2942
- ],
2943
- };
2944
- }
2945
- if (auth.authenticated !== true) {
2946
- return {
2947
- ...plan,
2948
- ok: false,
2949
- errorCode: "npm-auth-required",
2950
- publish: {
2951
- ...plan.publish,
2952
- status: "blocked",
2953
- },
2954
- trust: {
2955
- status: "blocked",
2956
- urls: [],
2957
- },
2958
- nextActions: [
2959
- {
2960
- id: "authenticate-npm",
2961
- command: `npm whoami --registry=${registry}`,
2962
- description:
2963
- "Authenticate npm without sharing token contents, then rerun the exact bootstrap command.",
2964
- },
2965
- ],
2966
- };
2967
- }
2968
- const tempRoot = fs.mkdtempSync(
2969
- path.join(os.tmpdir(), "buildchain-paper-bootstrap-"),
2970
- );
2971
- try {
2972
- fs.writeFileSync(
2973
- path.join(tempRoot, "package.json"),
2974
- jsonText(
2975
- bootstrapPackageShape({ packageName: name, version: bootstrapVersion }),
2976
- ),
2977
- );
2978
- const configArgs = userconfig ? ["--userconfig", userconfig] : [];
2979
- const pack = commandResult(
2980
- "npm",
2981
- [
2982
- "pack",
2983
- "--dry-run",
2984
- "--json",
2985
- "--ignore-scripts",
2986
- `--registry=${registry}`,
2987
- ...configArgs,
2988
- ],
2989
- { cwd: tempRoot },
2990
- );
2991
- if (!pack.ok) {
2992
- return {
2993
- ...plan,
2994
- ok: false,
2995
- errorCode: "npm-pack-dry-run-failed",
2996
- publish: { ...plan.publish, status: "blocked" },
2997
- trust: { status: "blocked", urls: [] },
2998
- };
2999
- }
3000
- const publishDryRun = commandResult(
3001
- "npm",
3002
- [
3003
- "publish",
3004
- "--dry-run",
3005
- "--ignore-scripts",
3006
- "--access",
3007
- "public",
3008
- "--tag",
3009
- "bootstrap",
3010
- `--registry=${registry}`,
3011
- ...configArgs,
3012
- ],
3013
- { cwd: tempRoot },
3014
- );
3015
- if (!publishDryRun.ok) {
3016
- return {
3017
- ...plan,
3018
- ok: false,
3019
- errorCode: "npm-publish-dry-run-failed",
3020
- publish: { ...plan.publish, status: "blocked" },
3021
- trust: { status: "blocked", urls: [] },
3022
- };
3023
- }
3024
- let publishStatus =
3025
- packageObservation.exists === true ? "existing" : "published";
3026
- if (packageObservation.exists !== true) {
3027
- const published = commandResult(
3028
- "npm",
3029
- [
3030
- "publish",
3031
- "--ignore-scripts",
3032
- "--access",
3033
- "public",
3034
- "--tag",
3035
- "bootstrap",
3036
- `--registry=${registry}`,
3037
- ...configArgs,
3038
- ],
3039
- { cwd: tempRoot, timeout: 60000 },
3040
- );
3041
- if (!published.ok) {
3042
- const failure = {
3043
- ...plan,
3044
- ok: false,
3045
- errorCode: "npm-publish-failed",
3046
- publish: { ...plan.publish, status: "failed" },
3047
- trust: { status: "blocked", urls: [] },
3048
- };
3049
- failure.receipts = [
3050
- writePaperReceipt(resolvedCwd, PAPER_PATHS.npmBootstrap, failure),
3051
- ];
3052
- return failure;
3053
- }
3054
- }
3055
- const trustArgs = [
3056
- "trust",
3057
- "github",
3058
- name,
3059
- "--repo",
3060
- repo,
3061
- "--file",
3062
- workflow,
3063
- ...(environment ? ["--env", environment] : []),
3064
- "--allow-publish",
3065
- "--yes",
3066
- "--json",
3067
- `--registry=${registry}`,
3068
- ...configArgs,
3069
- ];
3070
- const trustCommand = commandResult("npm", trustArgs, {
3071
- cwd: resolvedCwd,
3072
- timeout: 60000,
3073
- });
3074
- const parsedTrust = safeParseJson(trustCommand.stdout);
3075
- const urls = [
3076
- ...extractUrls(
3077
- parsedTrust || `${trustCommand.stdout}\n${trustCommand.stderr}`,
3078
- ),
3079
- ];
3080
- const trustList =
3081
- trustCommand.ok && urls.length === 0
3082
- ? liveNpmTrustObservation(
3083
- name,
3084
- registry,
3085
- resolvedCwd,
3086
- expectedPublisher,
3087
- )
3088
- : {
3089
- status: "unknown",
3090
- configured: null,
3091
- publishers: [],
3092
- errorCode:
3093
- urls.length > 0 ? "web-action-required" : "npm-trust-failed",
3094
- };
3095
- const trustStatus =
3096
- trustCommand.ok && trustList.configured === true
3097
- ? "configured"
3098
- : urls.length > 0
3099
- ? "action-required"
3100
- : "failed";
3101
- const packageAfter = liveNpmPackageObservation(name, registry, resolvedCwd);
3102
- const packageVerified = packageAfter.exists === true;
3103
- const receipt = {
3104
- ...plan,
3105
- ok: trustStatus !== "failed" && packageVerified,
3106
- dryRun: false,
3107
- externalMutation: true,
3108
- errorCode:
3109
- trustStatus === "failed"
3110
- ? "npm-trust-failed"
3111
- : packageVerified
3112
- ? ""
3113
- : "npm-package-readback-failed",
3114
- package: {
3115
- ...plan.package,
3116
- existsAfter: packageAfter.exists,
3117
- observedVersionAfter: packageAfter.version,
3118
- readbackStatus: packageAfter.status,
3119
- },
3120
- publish: {
3121
- ...plan.publish,
3122
- status: publishStatus,
3123
- },
3124
- trust: {
3125
- status: trustStatus,
3126
- urls,
3127
- expectedPublisher,
3128
- exactBinding: trustList.exactBinding === true,
3129
- publishers: trustList.publishers,
3130
- },
3131
- nextActions:
3132
- trustStatus === "configured" && packageVerified
3133
- ? [
3134
- {
3135
- id: "paper-preflight",
3136
- command: "buildchain paper preflight --json",
3137
- description:
3138
- "Verify package existence and Trusted Publisher binding from live read-only sources.",
3139
- },
3140
- ]
3141
- : trustStatus === "action-required" && packageVerified
3142
- ? [
3143
- {
3144
- id: "complete-npm-web-action",
3145
- command: "",
3146
- description:
3147
- "Open the exact npm URL returned below, complete the web step, then rerun preflight.",
3148
- urls,
3149
- },
3150
- ]
3151
- : !packageVerified
3152
- ? [
3153
- {
3154
- id: "verify-npm-package-readback",
3155
- command: `npm view ${name} version --json --registry=${registry}`,
3156
- description:
3157
- "The mutation returned but the official registry did not prove package existence; do not infer success.",
3158
- },
3159
- ]
3160
- : [
3161
- {
3162
- id: "retry-npm-trust",
3163
- command: `buildchain paper bootstrap npm --execute --confirm-public-package ${name} --json`,
3164
- description:
3165
- "The package exists; retry only the idempotent Trusted Publisher configuration path.",
3166
- },
3167
- ],
3168
- };
3169
- receipt.receipts = [
3170
- writePaperReceipt(resolvedCwd, PAPER_PATHS.npmBootstrap, receipt),
3171
- ];
3172
- if (
3173
- packageVerified &&
3174
- (trustStatus === "configured" || trustStatus === "action-required")
3175
- ) {
3176
- receipt.receipts.push(
3177
- writePaperReceipt(resolvedCwd, PAPER_PATHS.npmTrust, receipt),
3178
- );
3179
- }
3180
- return receipt;
3181
- } finally {
3182
- fs.rmSync(tempRoot, { recursive: true, force: true });
3183
- }
2840
+ export function executePaperNpmBootstrap(options = {}) {
2841
+ return executePaperNpmBootstrapOperation(options, {
2842
+ DEFAULT_BOOTSTRAP_VERSION,
2843
+ NPM_REGISTRY,
2844
+ PAPER_NPM_BOOTSTRAP_CONTRACT,
2845
+ PAPER_PATHS,
2846
+ bootstrapPackageShape,
2847
+ commandResult,
2848
+ expectedPaperTrustedPublisher,
2849
+ extractUrls,
2850
+ fs,
2851
+ jsonText,
2852
+ liveNpmAuthObservation,
2853
+ liveNpmPackageObservation,
2854
+ liveNpmTrustObservation,
2855
+ normalizePackageName,
2856
+ normalizeRepository,
2857
+ os,
2858
+ paperConfig,
2859
+ path,
2860
+ resolvePaperRepository,
2861
+ safeParseJson,
2862
+ toPosix,
2863
+ validatePaperProvisioningAuthority,
2864
+ writePaperReceipt,
2865
+ });
3184
2866
  }