@kungfu-tech/buildchain 4.0.1-alpha.9 → 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.
- package/README.md +9 -6
- package/actions/v4-release-candidate-promote/README.md +3 -0
- package/architecture/agent-change-map.md +1015 -0
- package/architecture/ci-lane-change-budget.json +70 -1
- package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
- package/architecture/internal-capabilities.json +156 -11
- package/architecture/maintainability-debt.json +3229 -0
- package/architecture/maintainability-policy.json +480 -129
- package/architecture/v3-core-mechanism-inventory.json +4 -3
- package/architecture/v3-v4-capability-closure.md +59 -0
- package/architecture/v3-v4-live-capability-inventory.json +102404 -0
- package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
- package/architecture/v4-runtime-semantic-closure.json +710 -0
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/agent-index.json +1 -0
- package/dist/site/artifact-schemas.json +2 -0
- package/dist/site/buildchain-contract.json +123 -32
- package/dist/site/buildchain-site.json +96 -28
- package/dist/site/capability-registry.json +6 -6
- package/dist/site/cli-registry.json +5 -2
- package/dist/site/controller-registry.json +41 -5
- package/dist/site/kfd-claims.json +170 -21
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +1474 -215
- package/dist/site/page-registry.json +85 -17
- package/dist/site/public-surface-audit.json +85 -20
- package/dist/site/publication-authority-registry.json +28 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +5 -0
- package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +94 -18
- package/docs/MAP.md +1 -1
- package/docs/cli-reference.md +4 -4
- package/docs/dev-delivery-warrant.md +12 -9
- package/docs/node-api-reference.md +291 -206
- package/docs/publish-transaction.md +11 -4
- package/docs/release-candidate.md +7 -0
- package/docs/v4-adopter-delivery.md +23 -4
- package/docs/v4-declarative-release-tail-migration.md +43 -0
- package/package.json +8 -3
- package/packages/core/adopter-delivery-passport.js +36 -0
- package/packages/core/buildchain-contract.js +31 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/dev-delivery-warrant-settlement.js +239 -3
- package/packages/core/dev-delivery-warrant-state.js +15 -2
- package/packages/core/dev-delivery-warrant.js +21 -4
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/index.js +1 -0
- package/packages/core/paper-scaffold-content.js +401 -0
- package/packages/core/paper.js +12 -378
- package/packages/core/publication-control-plane-audit.js +135 -111
- package/packages/core/publication-rehearsal-projection.js +104 -0
- package/packages/core/publication-rehearsal-runtime.js +190 -0
- package/packages/core/release-candidate-recovery.js +6 -5
- package/packages/core/release-passport.js +82 -184
- package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
- package/packages/core/v4-floating-consumer-policy.js +3 -2
- package/packages/core/v4-floating-consumer-release-passport.js +134 -15
- package/packages/core/v4-publication-qualification.js +222 -0
- package/scripts/artifact-signing-controller-core.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +17 -17
- package/scripts/buildchain-cli-help.mjs +3 -0
- package/scripts/check-internal-architecture.mjs +23 -3
- package/scripts/check-inventory.mjs +23 -5
- package/scripts/check-maintainability.mjs +145 -4
- package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
- package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -3
- package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
- package/scripts/dev-delivery-warrant-options.mjs +27 -11
- package/scripts/dev-delivery-warrant.mjs +45 -5
- package/scripts/dev-pr-auto-merge.mjs +25 -11
- package/scripts/dev-pr-delivery-warrant.mjs +44 -0
- package/scripts/dev-pr-prequeue-guard.mjs +0 -18
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-agent-change-map.mjs +118 -0
- package/scripts/generate-release-candidate-passport.mjs +26 -1
- package/scripts/generate-site-bundle.mjs +6 -0
- package/scripts/maintainability-domain-metrics.mjs +237 -0
- package/scripts/maintainability-governance.mjs +473 -0
- package/scripts/maintainability-metrics.mjs +28 -6
- package/scripts/project-cut-merge-queue-admission.mjs +394 -0
- package/scripts/promotion-routing-evidence.mjs +35 -6
- package/scripts/release-candidate-resolver.mjs +44 -0
- package/scripts/release-line-policy.mjs +25 -15
- package/scripts/release-tail.mjs +19 -2
- package/scripts/resume-from-candidate-run.mjs +106 -103
- package/scripts/run-lifecycle-core.mjs +341 -376
- package/scripts/site-capability-metadata.mjs +4 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v3-v4-capability-catalog.mjs +439 -0
- package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
- 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:
|
|
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-
|
|
501
|
-
|
|
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
|
},
|
package/packages/core/index.js
CHANGED
|
@@ -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
|
+
}
|