@kungfu-tech/buildchain 4.0.1-alpha.8 → 4.0.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 (98) hide show
  1. package/README.md +9 -6
  2. package/actions/v4-release-candidate-promote/README.md +3 -0
  3. package/architecture/agent-change-map.md +1015 -0
  4. package/architecture/ci-lane-change-budget.json +70 -1
  5. package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
  6. package/architecture/internal-capabilities.json +156 -11
  7. package/architecture/maintainability-debt.json +3229 -0
  8. package/architecture/maintainability-policy.json +480 -129
  9. package/architecture/v3-core-mechanism-inventory.json +4 -3
  10. package/architecture/v3-v4-capability-closure.md +59 -0
  11. package/architecture/v3-v4-live-capability-inventory.json +102404 -0
  12. package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
  13. package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
  14. package/architecture/v4-runtime-semantic-closure.json +710 -0
  15. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  16. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  17. package/contracts/v4-publication-qualification-v1.schema.json +51 -0
  18. package/dist/site/agent-index.json +1 -0
  19. package/dist/site/artifact-schemas.json +2 -0
  20. package/dist/site/buildchain-contract.json +123 -32
  21. package/dist/site/buildchain-site.json +96 -28
  22. package/dist/site/capability-registry.json +6 -6
  23. package/dist/site/cli-registry.json +5 -2
  24. package/dist/site/controller-registry.json +41 -5
  25. package/dist/site/kfd-claims.json +170 -21
  26. package/dist/site/kfd-upstream-aggregate.json +1 -1
  27. package/dist/site/manual-registry.json +6 -6
  28. package/dist/site/node-api-registry.json +1474 -215
  29. package/dist/site/page-registry.json +85 -17
  30. package/dist/site/public-surface-audit.json +85 -20
  31. package/dist/site/publication-authority-registry.json +28 -1
  32. package/dist/site/publication-registry.json +4 -4
  33. package/dist/site/release-provenance.json +5 -0
  34. package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
  35. package/dist/site/site-manifest.json +10 -10
  36. package/dist/site/workflow-registry.json +94 -18
  37. package/docs/MAP.md +1 -1
  38. package/docs/cli-reference.md +4 -4
  39. package/docs/dev-delivery-warrant.md +12 -9
  40. package/docs/node-api-reference.md +291 -206
  41. package/docs/publish-transaction.md +11 -4
  42. package/docs/release-candidate.md +7 -0
  43. package/docs/v4-adopter-delivery.md +23 -4
  44. package/docs/v4-declarative-release-tail-migration.md +43 -0
  45. package/package.json +8 -3
  46. package/packages/core/adopter-delivery-passport.js +36 -0
  47. package/packages/core/buildchain-contract.js +31 -1
  48. package/packages/core/buildchain-publication-authority.js +1 -0
  49. package/packages/core/dev-delivery-warrant-settlement.js +239 -3
  50. package/packages/core/dev-delivery-warrant-state.js +15 -2
  51. package/packages/core/dev-delivery-warrant.js +21 -4
  52. package/packages/core/github-governance-authority.js +4 -4
  53. package/packages/core/index.js +1 -0
  54. package/packages/core/paper-scaffold-content.js +401 -0
  55. package/packages/core/paper.js +12 -378
  56. package/packages/core/publication-control-plane-audit.js +135 -111
  57. package/packages/core/publication-rehearsal-projection.js +104 -0
  58. package/packages/core/publication-rehearsal-runtime.js +190 -0
  59. package/packages/core/release-candidate-recovery.js +6 -5
  60. package/packages/core/release-passport.js +82 -184
  61. package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
  62. package/packages/core/v4-floating-consumer-policy.js +3 -2
  63. package/packages/core/v4-floating-consumer-release-passport.js +134 -15
  64. package/packages/core/v4-publication-qualification.js +222 -0
  65. package/scripts/artifact-signing-controller-core.mjs +1 -1
  66. package/scripts/audit-publication-control-plane.mjs +17 -17
  67. package/scripts/buildchain-cli-help.mjs +3 -0
  68. package/scripts/check-internal-architecture.mjs +23 -3
  69. package/scripts/check-inventory.mjs +23 -5
  70. package/scripts/check-maintainability.mjs +145 -4
  71. package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
  72. package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
  73. package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -3
  74. package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
  75. package/scripts/dev-delivery-warrant-options.mjs +27 -11
  76. package/scripts/dev-delivery-warrant.mjs +45 -5
  77. package/scripts/dev-pr-auto-merge.mjs +25 -11
  78. package/scripts/dev-pr-delivery-warrant.mjs +44 -0
  79. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  80. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  81. package/scripts/generate-agent-change-map.mjs +118 -0
  82. package/scripts/generate-release-candidate-passport.mjs +26 -1
  83. package/scripts/generate-site-bundle.mjs +6 -0
  84. package/scripts/maintainability-domain-metrics.mjs +237 -0
  85. package/scripts/maintainability-governance.mjs +473 -0
  86. package/scripts/maintainability-metrics.mjs +28 -6
  87. package/scripts/project-cut-merge-queue-admission.mjs +394 -0
  88. package/scripts/promotion-routing-evidence.mjs +35 -6
  89. package/scripts/release-candidate-resolver.mjs +44 -0
  90. package/scripts/release-line-policy.mjs +25 -15
  91. package/scripts/release-tail.mjs +19 -2
  92. package/scripts/resume-from-candidate-run.mjs +106 -103
  93. package/scripts/run-lifecycle-core.mjs +341 -376
  94. package/scripts/site-capability-metadata.mjs +4 -0
  95. package/scripts/stable-candidate-qualification.mjs +9 -10
  96. package/scripts/v3-v4-capability-catalog.mjs +439 -0
  97. package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
  98. package/scripts/v4-declarative-promotion-admission.mjs +42 -0
@@ -11,7 +11,9 @@ import {
11
11
  createCancelQueuedDevDeliveryCandidate,
12
12
  } from "./dev-delivery-warrant-cancellation.js";
13
13
  import {
14
+ createDevDeliveryTerminalEvidenceReconciler,
14
15
  DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA,
16
+ DEV_DELIVERY_TERMINAL_EVIDENCE_CORRECTION_SCHEMA,
15
17
  createDevDeliveryTerminalSettler,
16
18
  normalizeProviderFailureAuthorityBinding,
17
19
  } from "./dev-delivery-warrant-settlement.js";
@@ -91,6 +93,7 @@ export {
91
93
  };
92
94
  export { DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA };
93
95
  export { DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA };
96
+ export { DEV_DELIVERY_TERMINAL_EVIDENCE_CORRECTION_SCHEMA };
94
97
 
95
98
  export function selectDevDeliveryWarrant(
96
99
  queueInput,
@@ -437,6 +440,10 @@ export function closeDevDeliveryWarrant(
437
440
  ...(failureAuthority || {}),
438
441
  };
439
442
  queue.activeWarrant = null;
443
+ candidate.terminal.successorWake = createDevDeliverySuccessorWake(
444
+ queue,
445
+ currentTime,
446
+ );
440
447
  return { candidate, active };
441
448
  },
442
449
  currentTime,
@@ -453,10 +460,7 @@ export function closeDevDeliveryWarrant(
453
460
  ...(failureAuthority || {}),
454
461
  expectedOldStateRoot: transaction.expectedOldStateRoot,
455
462
  nextStateRoot: transaction.after.stateRoot,
456
- successorWake: createDevDeliverySuccessorWake(
457
- transaction.after,
458
- currentTime,
459
- ),
463
+ successorWake: transaction.result.candidate.terminal.successorWake,
460
464
  nextAction: "Select the next queued candidate, if any.",
461
465
  };
462
466
  return {
@@ -514,6 +518,11 @@ export const settleDevDeliveryTerminalEvent = createDevDeliveryTerminalSettler({
514
518
  cancelQueued: cancelQueuedDevDeliveryCandidate,
515
519
  terminalStates: TERMINAL_STATES,
516
520
  });
521
+ export const reconcileDevDeliveryTerminalEvidence =
522
+ createDevDeliveryTerminalEvidenceReconciler({
523
+ normalizeQueue: normalizeDevDeliveryQueue,
524
+ transition,
525
+ });
517
526
  export function observeDevDeliveryQueue(
518
527
  queueInput,
519
528
  { now = new Date().toISOString(), allowLegacyV3Readback = false } = {},
@@ -543,6 +552,14 @@ export function observeDevDeliveryQueue(
543
552
  ) || null
544
553
  : null,
545
554
  states,
555
+ pendingSuccessorWakes: queue.candidates
556
+ .filter((candidate) => candidate.terminal?.successorWake)
557
+ .map((candidate) => ({
558
+ pullRequestNumber: candidate.pullRequestNumber,
559
+ sourceHead: candidate.sourceHead,
560
+ outcome: candidate.status,
561
+ successorWake: candidate.terminal.successorWake,
562
+ })),
546
563
  queued: queued.map((entry, index) => ({
547
564
  position: index + 1,
548
565
  candidateId: entry.candidate.candidateId,
@@ -110,6 +110,8 @@ const PUBLIC_REPOSITORY_TARGETS = Object.freeze({
110
110
  "site-libkungfu-dev": [
111
111
  target("main", [check("web-surface / Record web-surface controller receipt")], false),
112
112
  ],
113
+ taolu: ["dev", "alpha", "release"].map((family) => target(`${family}/v1/v1.0`,
114
+ [check("build / Summarize build contract"), check("stage-capsule / Reconcile exact three-platform evidence")], false)),
113
115
  });
114
116
  const PUBLIC_REPOSITORIES = Object.freeze(Object.keys(PUBLIC_REPOSITORY_TARGETS).sort());
115
117
  const PROTECTED_AUTHORITY_PATHS = Object.freeze([
@@ -497,10 +499,8 @@ export function createBuildchainGithubGovernanceAuthority() {
497
499
  privateRepositoryPolicy: "excluded-from-managed-zone",
498
500
  unknownRepositoryPolicy: "non-authoritative-until-explicit-admission",
499
501
  baseline: {
500
- observedOn: "2026-07-30",
501
- repositoryCount: 16,
502
- publicCount: 16,
503
- privateCount: 0,
502
+ observedOn: "2026-08-28", repositoryCount: 17,
503
+ publicCount: 17, privateCount: 0,
504
504
  authoritativePublicTargetCount: Object.values(PUBLIC_REPOSITORY_TARGETS)
505
505
  .reduce((count, targets) => count + targets.length, 0),
506
506
  },
@@ -646,6 +646,7 @@ export {
646
646
  export * from "./release-propagation.js";
647
647
  export * from "./v4-floating-consumer-policy.js";
648
648
  export * from "./v4-runtime-ref-resume-authority.js";
649
+ export * from "./v4-publication-qualification.js";
649
650
 
650
651
  export {
651
652
  RELEASE_ACTIVATION_CONTRACT,
@@ -1,3 +1,11 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ gitResult,
5
+ gitValue,
6
+ normalizeRepository,
7
+ sha256Text,
8
+ } from "./paper-repository.js";
1
9
  function jsonText(value) {
2
10
  return `${JSON.stringify(value, null, 2)}\n`;
3
11
  }
@@ -161,3 +169,396 @@ Replace this section with the reviewed paper content.
161
169
  \\end{document}
162
170
  `;
163
171
  }
172
+ function planPaperScaffold(
173
+ runtime,
174
+ {
175
+ cwd = process.cwd(),
176
+ buildchainRoot = process.cwd(),
177
+ buildchainVersion = "",
178
+ buildchainRef = "v3",
179
+ buildchainSha = "",
180
+ name = path.basename(path.resolve(cwd)),
181
+ title = "",
182
+ packageName = "",
183
+ repository = "",
184
+ version = "0.1.0-alpha.0",
185
+ siteBaseUrl = "",
186
+ } = {},
187
+ ) {
188
+ const resolvedCwd = path.resolve(cwd);
189
+ const normalizedName = String(name || "").trim();
190
+ if (!normalizedName) throw new Error("paper scaffold requires --name");
191
+ const normalizedPackage = runtime.normalizePackageName(packageName);
192
+ const normalizedRepository = normalizeRepository(repository);
193
+ if (!normalizedRepository) {
194
+ throw new Error("paper scaffold requires --repository <owner/repo>");
195
+ }
196
+ const normalizedVersion = String(version || "")
197
+ .trim()
198
+ .replace(/^v/, "");
199
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(normalizedVersion)) {
200
+ throw new Error("paper scaffold --version must be semver");
201
+ }
202
+ const runtimeIdentity = runtime.buildchainPackageIdentity(
203
+ buildchainRoot,
204
+ buildchainVersion,
205
+ );
206
+ const runtimeSha =
207
+ buildchainSha ||
208
+ runtime.resolvePaperRuntimeGitSha(buildchainRoot, runtimeIdentity.version);
209
+ if (!runtime.GIT_SHA_PATTERN.test(runtimeSha)) {
210
+ throw new Error(
211
+ "paper scaffold cannot resolve the exact Buildchain runtime SHA; pass buildchainSha or use a published Buildchain package with npm gitHead provenance",
212
+ );
213
+ }
214
+ const files = runtime.scaffoldFiles({
215
+ buildchainRoot,
216
+ buildchainVersion: runtimeIdentity.version,
217
+ buildchainRef,
218
+ buildchainSha: runtimeSha,
219
+ cwd: resolvedCwd,
220
+ name: normalizedName,
221
+ title: title || normalizedName,
222
+ packageName: normalizedPackage,
223
+ repository: normalizedRepository,
224
+ version: normalizedVersion,
225
+ siteBaseUrl,
226
+ });
227
+ const changes = [...files.entries()].map(([relativePath, content]) => {
228
+ const filePath = path.resolve(resolvedCwd, relativePath);
229
+ const exists = fs.existsSync(filePath);
230
+ if (!exists) {
231
+ return {
232
+ path: relativePath,
233
+ action: "create",
234
+ sha256: sha256Text(content),
235
+ content,
236
+ };
237
+ }
238
+ if (!fs.statSync(filePath).isFile()) {
239
+ return {
240
+ path: relativePath,
241
+ action: "conflict",
242
+ reason: "path-exists-and-is-not-a-file",
243
+ sha256: sha256Text(content),
244
+ content,
245
+ };
246
+ }
247
+ const current = fs.readFileSync(filePath, "utf8");
248
+ return current === content
249
+ ? {
250
+ path: relativePath,
251
+ action: "unchanged",
252
+ sha256: sha256Text(content),
253
+ content,
254
+ }
255
+ : {
256
+ path: relativePath,
257
+ action: "conflict",
258
+ reason: "existing-content-differs",
259
+ currentSha256: sha256Text(current),
260
+ sha256: sha256Text(content),
261
+ content,
262
+ };
263
+ });
264
+ const publicChanges = changes.map(({ content: _content, ...entry }) => entry);
265
+ const conflicts = publicChanges.filter(
266
+ (entry) => entry.action === "conflict",
267
+ );
268
+ const result = {
269
+ schemaVersion: 1,
270
+ contract: runtime.PAPER_SCAFFOLD_CONTRACT,
271
+ ok: conflicts.length === 0,
272
+ cwd: resolvedCwd,
273
+ dryRun: true,
274
+ identity: {
275
+ project: normalizedName,
276
+ title: title || normalizedName,
277
+ package: normalizedPackage,
278
+ repository: normalizedRepository,
279
+ version: normalizedVersion,
280
+ },
281
+ buildchain: {
282
+ version: runtimeIdentity.version,
283
+ ref: runtimeSha,
284
+ resolvedSha: runtimeSha,
285
+ },
286
+ summary: {
287
+ create: publicChanges.filter((entry) => entry.action === "create").length,
288
+ unchanged: publicChanges.filter((entry) => entry.action === "unchanged")
289
+ .length,
290
+ conflict: conflicts.length,
291
+ },
292
+ changes: publicChanges,
293
+ conflicts,
294
+ nextActions:
295
+ conflicts.length > 0
296
+ ? [
297
+ {
298
+ id: "resolve-scaffold-conflicts",
299
+ command: "",
300
+ description:
301
+ "Resolve the listed semantic conflicts; scaffold never overwrites them.",
302
+ },
303
+ ]
304
+ : [
305
+ {
306
+ id: "write-scaffold",
307
+ command: "buildchain paper scaffold --write <same arguments>",
308
+ description:
309
+ "Write only missing files after the conflict-free plan is reviewed.",
310
+ },
311
+ ],
312
+ };
313
+ Object.defineProperty(result, "_plannedFiles", {
314
+ value: changes,
315
+ enumerable: false,
316
+ });
317
+ return result;
318
+ }
319
+
320
+ function writePaperScaffold(runtime, plan) {
321
+ if (!plan || plan.contract !== runtime.PAPER_SCAFFOLD_CONTRACT) {
322
+ throw new Error("paper scaffold plan contract mismatch");
323
+ }
324
+ if (!plan.ok || plan.conflicts.length > 0) {
325
+ return {
326
+ ...plan,
327
+ dryRun: false,
328
+ written: [],
329
+ ok: false,
330
+ errorCode: "paper-scaffold-conflict",
331
+ };
332
+ }
333
+ const resolvedCwd = path.resolve(plan.cwd);
334
+ const creates = plan._plannedFiles.filter(
335
+ (entry) => entry.action === "create",
336
+ );
337
+ for (const entry of creates) {
338
+ const target = path.resolve(resolvedCwd, entry.path);
339
+ if (fs.existsSync(target)) {
340
+ const current = fs.statSync(target).isFile()
341
+ ? fs.readFileSync(target, "utf8")
342
+ : undefined;
343
+ if (current !== entry.content) {
344
+ throw new Error(
345
+ `paper scaffold race detected at ${entry.path}; no file was overwritten`,
346
+ );
347
+ }
348
+ continue;
349
+ }
350
+ }
351
+ const written = [];
352
+ for (const entry of creates) {
353
+ const target = path.resolve(resolvedCwd, entry.path);
354
+ if (fs.existsSync(target)) continue;
355
+ fs.mkdirSync(path.dirname(target), { recursive: true });
356
+ fs.writeFileSync(target, entry.content, { flag: "wx" });
357
+ written.push(entry.path);
358
+ }
359
+ return {
360
+ ...plan,
361
+ ok: true,
362
+ dryRun: false,
363
+ written,
364
+ idempotent: written.length === 0,
365
+ nextActions: [
366
+ {
367
+ id: "paper-preflight",
368
+ command: `buildchain paper preflight --cwd ${JSON.stringify(resolvedCwd)} --json`,
369
+ description:
370
+ "Verify the generated repository before any external mutation.",
371
+ },
372
+ ],
373
+ };
374
+ }
375
+
376
+ function planPaperMigration(
377
+ runtime,
378
+ {
379
+ cwd = process.cwd(),
380
+ buildchainRoot = process.cwd(),
381
+ buildchainVersion = "",
382
+ buildchainSha = "",
383
+ } = {},
384
+ ) {
385
+ const resolvedCwd = path.resolve(cwd);
386
+ const repositoryRoot = gitValue(resolvedCwd, [
387
+ "rev-parse",
388
+ "--show-toplevel",
389
+ ]);
390
+ const repositoryPrefix = gitValue(resolvedCwd, [
391
+ "rev-parse",
392
+ "--show-prefix",
393
+ ]).replace(/\/+$/u, "");
394
+ if (!repositoryRoot || repositoryPrefix) {
395
+ throw new Error("paper migration must target the exact repository root");
396
+ }
397
+ const source = {
398
+ head: gitValue(resolvedCwd, ["rev-parse", "HEAD"]),
399
+ clean: gitResult(resolvedCwd, ["status", "--porcelain"]).stdout === "",
400
+ };
401
+ if (!runtime.GIT_SHA_PATTERN.test(source.head)) {
402
+ throw new Error("paper migration requires a committed Git source");
403
+ }
404
+ const plannedFiles = [
405
+ ...runtime.migrationFiles({
406
+ cwd: resolvedCwd,
407
+ buildchainRoot,
408
+ buildchainVersion,
409
+ buildchainSha,
410
+ }),
411
+ ].map(([relativePath, content]) => {
412
+ const target = path.resolve(resolvedCwd, relativePath);
413
+ if (fs.existsSync(target) && !fs.statSync(target).isFile()) {
414
+ return {
415
+ path: relativePath,
416
+ action: "conflict",
417
+ currentSha256: "",
418
+ sha256: sha256Text(content),
419
+ content,
420
+ };
421
+ }
422
+ const current = fs.existsSync(target)
423
+ ? fs.readFileSync(target, "utf8")
424
+ : undefined;
425
+ return {
426
+ path: relativePath,
427
+ action:
428
+ current === undefined
429
+ ? "create"
430
+ : current === content
431
+ ? "unchanged"
432
+ : "update",
433
+ currentSha256: current === undefined ? "" : sha256Text(current),
434
+ sha256: sha256Text(content),
435
+ content,
436
+ };
437
+ });
438
+ const changes = plannedFiles.map(({ content: _content, ...entry }) => entry);
439
+ const conflicts = changes.filter((entry) => entry.action === "conflict");
440
+ const ok = source.clean && conflicts.length === 0;
441
+ const result = {
442
+ schemaVersion: 1,
443
+ contract: runtime.PAPER_MIGRATION_CONTRACT,
444
+ ok,
445
+ cwd: resolvedCwd,
446
+ dryRun: true,
447
+ source,
448
+ summary: {
449
+ create: changes.filter((entry) => entry.action === "create").length,
450
+ update: changes.filter((entry) => entry.action === "update").length,
451
+ unchanged: changes.filter((entry) => entry.action === "unchanged").length,
452
+ conflict: conflicts.length,
453
+ },
454
+ changes,
455
+ conflicts,
456
+ nextActions: !source.clean
457
+ ? [
458
+ {
459
+ id: "commit-source",
460
+ command: "git status --short",
461
+ description:
462
+ "Migration only rewrites Buildchain-owned control files from a clean committed source.",
463
+ },
464
+ ]
465
+ : conflicts.length > 0
466
+ ? [
467
+ {
468
+ id: "resolve-migration-conflicts",
469
+ command: "",
470
+ description:
471
+ "Resolve non-file targets; migration never replaces a directory or special path.",
472
+ },
473
+ ]
474
+ : [
475
+ {
476
+ id: "write-migration",
477
+ command: "buildchain paper migrate --write --json",
478
+ description:
479
+ "Write the reviewed Buildchain-owned control files without changing paper content or publication configuration.",
480
+ },
481
+ {
482
+ id: "refresh-pnpm-lock",
483
+ command: "pnpm install --lockfile-only",
484
+ description:
485
+ "Bind the exact Buildchain semantic dependency into pnpm-lock.yaml after the reviewed package update.",
486
+ },
487
+ ],
488
+ };
489
+ Object.defineProperty(result, "_plannedFiles", {
490
+ value: plannedFiles,
491
+ enumerable: false,
492
+ });
493
+ return result;
494
+ }
495
+
496
+ function writePaperMigration(runtime, plan) {
497
+ if (!plan || plan.contract !== runtime.PAPER_MIGRATION_CONTRACT) {
498
+ throw new Error("paper migration plan contract mismatch");
499
+ }
500
+ if (!plan.ok) {
501
+ return {
502
+ ...plan,
503
+ dryRun: false,
504
+ written: [],
505
+ updated: [],
506
+ ok: false,
507
+ errorCode: "paper-migration-blocked",
508
+ };
509
+ }
510
+ const written = [];
511
+ const updated = [];
512
+ for (const entry of plan._plannedFiles) {
513
+ if (entry.action === "unchanged") continue;
514
+ const target = path.resolve(plan.cwd, entry.path);
515
+ const exists = fs.existsSync(target);
516
+ const current =
517
+ exists && fs.statSync(target).isFile()
518
+ ? fs.readFileSync(target, "utf8")
519
+ : undefined;
520
+ const currentSha256 = current === undefined ? "" : sha256Text(current);
521
+ if (
522
+ (entry.action === "create" && exists) ||
523
+ (entry.action === "update" && currentSha256 !== entry.currentSha256)
524
+ ) {
525
+ throw new Error(
526
+ `paper migration race detected at ${entry.path}; no stale plan was applied`,
527
+ );
528
+ }
529
+ }
530
+ for (const entry of plan._plannedFiles) {
531
+ if (entry.action === "unchanged") continue;
532
+ const target = path.resolve(plan.cwd, entry.path);
533
+ fs.mkdirSync(path.dirname(target), { recursive: true });
534
+ fs.writeFileSync(target, entry.content, {
535
+ flag: entry.action === "create" ? "wx" : "w",
536
+ });
537
+ (entry.action === "create" ? written : updated).push(entry.path);
538
+ }
539
+ return {
540
+ ...plan,
541
+ ok: true,
542
+ dryRun: false,
543
+ written,
544
+ updated,
545
+ idempotent: written.length === 0 && updated.length === 0,
546
+ nextActions: [
547
+ {
548
+ id: "paper-preflight",
549
+ command: `buildchain paper preflight --cwd ${JSON.stringify(plan.cwd)} --offline --json`,
550
+ description:
551
+ "Verify the migrated repository before any external mutation.",
552
+ },
553
+ ],
554
+ };
555
+ }
556
+
557
+ export function createPaperScaffoldOperations(runtime) {
558
+ return {
559
+ planPaperMigration: (options) => planPaperMigration(runtime, options),
560
+ planPaperScaffold: (options) => planPaperScaffold(runtime, options),
561
+ writePaperMigration: (plan) => writePaperMigration(runtime, plan),
562
+ writePaperScaffold: (plan) => writePaperScaffold(runtime, plan),
563
+ };
564
+ }