@kungfu-tech/buildchain 4.0.1-alpha.5 → 4.0.1-alpha.55
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/release-tail/README.md +8 -0
- 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 +256 -0
- package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
- package/architecture/internal-capabilities.json +220 -15
- package/architecture/maintainability-debt.json +3229 -0
- package/architecture/maintainability-policy.json +489 -132
- package/architecture/release-tail-contract-inventory.json +5 -2
- 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-adopter-delivery-parity.json +32 -5
- package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-runtime-semantic-closure.json +710 -0
- package/bin/buildchain.mjs +7 -1
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +1 -0
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-qualification-v1.schema.json +51 -0
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/agent-index.json +4 -0
- package/dist/site/artifact-schemas.json +8 -0
- package/dist/site/buildchain-contract.json +125 -34
- package/dist/site/buildchain-site.json +308 -33
- package/dist/site/capability-registry.json +13 -12
- package/dist/site/cli-registry.json +57 -2
- package/dist/site/controller-registry.json +41 -5
- package/dist/site/kfd-claims.json +438 -24
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +22 -7
- package/dist/site/node-api-registry.json +12843 -9314
- package/dist/site/page-registry.json +288 -21
- package/dist/site/public-surface-audit.json +281 -21
- package/dist/site/publication-authority-registry.json +66 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +20 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +19 -11
- package/dist/site/workflow-registry.json +195 -17
- package/docs/MAP.md +4 -2
- package/docs/cli-reference.md +70 -2
- package/docs/cli.md +8 -0
- package/docs/dev-delivery-warrant.md +34 -10
- package/docs/node-api-reference.md +524 -279
- package/docs/publish-transaction.md +11 -4
- package/docs/release-candidate.md +7 -0
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +152 -0
- package/docs/v4-declarative-release-tail-migration.md +43 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/package.json +24 -4
- package/packages/core/adopter-delivery-passport.js +295 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/buildchain-agent-manuals.js +1 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +31 -1
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/buildchain-publication-authority.js +2 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-delivery-native-proof.js +43 -27
- package/packages/core/dev-delivery-warrant-settlement.js +239 -3
- package/packages/core/dev-delivery-warrant-state.js +46 -10
- package/packages/core/dev-delivery-warrant.js +29 -7
- package/packages/core/github-governance-authority.js +5 -4
- package/packages/core/index.js +6 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -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/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +71 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +95 -170
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
- package/packages/core/v4-floating-consumer-evidence.js +18 -4
- package/packages/core/v4-floating-consumer-policy.js +139 -35
- package/packages/core/v4-floating-consumer-release-passport.js +154 -8
- package/packages/core/v4-publication-qualification.js +222 -0
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/packages/core/workflow-yaml-contract.js +24 -2
- package/scripts/artifact-signing-controller-core.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +17 -17
- package/scripts/buildchain-cli-help.mjs +13 -0
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-internal-architecture.mjs +23 -3
- package/scripts/check-inventory.mjs +24 -6
- 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 +30 -4
- package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/dev-delivery-warrant-options.mjs +27 -11
- package/scripts/dev-delivery-warrant-store.mjs +10 -6
- package/scripts/dev-delivery-warrant.mjs +50 -6
- package/scripts/dev-pr-auto-merge.mjs +25 -11
- package/scripts/dev-pr-delivery-warrant.mjs +45 -0
- package/scripts/dev-pr-prequeue-guard.mjs +0 -18
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-agent-change-map.mjs +118 -0
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-release-candidate-passport.mjs +177 -0
- package/scripts/generate-site-bundle.mjs +36 -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-channel-router.mjs +4 -4
- package/scripts/promotion-routing-evidence.mjs +35 -6
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-candidate-resolver.mjs +44 -0
- package/scripts/release-line-policy.mjs +25 -15
- package/scripts/release-tail.mjs +68 -3
- package/scripts/resume-from-candidate-run.mjs +595 -65
- package/scripts/run-lifecycle-core.mjs +341 -376
- package/scripts/site-capability-metadata.mjs +21 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v3-v4-capability-catalog.mjs +439 -0
- package/scripts/v4-consumer-policy.mjs +4 -0
- package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
- package/scripts/v4-declarative-promotion-admission.mjs +42 -0
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ADOPTER_PROTOCOL_DRIVER_INTERFACE,
|
|
3
|
+
adopterDeliveryGateDigest,
|
|
4
|
+
defineAdopterProtocolDriver,
|
|
5
|
+
} from "./adopter-delivery-gate.js";
|
|
6
|
+
import {
|
|
7
|
+
KFD_LEGACY_SUPPORT_MATRIX_CONTRACT,
|
|
8
|
+
validateKfdAdopterManifestGate,
|
|
9
|
+
validateKfdLegacySupportMatrixProjection,
|
|
10
|
+
} from "./kfd-adopter-manifest.js";
|
|
11
|
+
|
|
12
|
+
export const LEGACY_KFD_ADOPTER_PROTOCOL_ID =
|
|
13
|
+
"buildchain.kfd-adopter/legacy-support-matrix";
|
|
14
|
+
export const LEGACY_KFD_ADOPTER_PROTOCOL_VERSION = "1.0.0";
|
|
15
|
+
export const LEGACY_KFD_ADOPTER_DRIVER_REPORT_CONTRACT =
|
|
16
|
+
"kungfu-buildchain-legacy-kfd-adopter-driver-report";
|
|
17
|
+
|
|
18
|
+
function issue(code, path, message) {
|
|
19
|
+
return { code, path, message };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizedIssues(entries) {
|
|
23
|
+
return (entries ?? []).map(({ code, path, message }) => ({
|
|
24
|
+
code,
|
|
25
|
+
path: path ? `/${path.replaceAll(".", "/")}` : "/declaration",
|
|
26
|
+
message,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function sameJson(left, right) {
|
|
31
|
+
try {
|
|
32
|
+
return adopterDeliveryGateDigest(left) === adopterDeliveryGateDigest(right);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function bindingIssues(request, manifest) {
|
|
39
|
+
const issues = [];
|
|
40
|
+
if (
|
|
41
|
+
request.project.instanceId !== manifest?.manifestId ||
|
|
42
|
+
request.project.adopterId !== manifest?.adopter?.id
|
|
43
|
+
) {
|
|
44
|
+
issues.push(
|
|
45
|
+
issue(
|
|
46
|
+
"delivery-legacy-project-binding-mismatch",
|
|
47
|
+
"/project",
|
|
48
|
+
"Delivery project identity must match the standard adopter manifest.",
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (!sameJson(request.artifact, manifest?.adopter?.artifact)) {
|
|
53
|
+
issues.push(
|
|
54
|
+
issue(
|
|
55
|
+
"delivery-legacy-artifact-binding-mismatch",
|
|
56
|
+
"/artifact",
|
|
57
|
+
"Delivery artifact must match the standard adopter manifest artifact.",
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return issues;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function invalidContextResult(issues) {
|
|
65
|
+
const report = {
|
|
66
|
+
schemaVersion: 1,
|
|
67
|
+
contract: LEGACY_KFD_ADOPTER_DRIVER_REPORT_CONTRACT,
|
|
68
|
+
valid: false,
|
|
69
|
+
qualifying: false,
|
|
70
|
+
selfCertified: false,
|
|
71
|
+
independentlyCertified: false,
|
|
72
|
+
manifestRoot: null,
|
|
73
|
+
manifestGateRoot: null,
|
|
74
|
+
legacyProjectionRoot: null,
|
|
75
|
+
legacyProjection: null,
|
|
76
|
+
adapterIssues: issues,
|
|
77
|
+
nonClaims: [
|
|
78
|
+
"The legacy support matrix is a one-way projection, not declaration authority.",
|
|
79
|
+
"Buildchain carries the existing standard manifest decision and does not certify KFD adoption.",
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
valid: false,
|
|
84
|
+
report,
|
|
85
|
+
reportRoot: adopterDeliveryGateDigest(report),
|
|
86
|
+
issues,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createLegacyKfdAdopterProtocolDriver() {
|
|
91
|
+
return defineAdopterProtocolDriver({
|
|
92
|
+
interface: ADOPTER_PROTOCOL_DRIVER_INTERFACE,
|
|
93
|
+
id: LEGACY_KFD_ADOPTER_PROTOCOL_ID,
|
|
94
|
+
version: LEGACY_KFD_ADOPTER_PROTOCOL_VERSION,
|
|
95
|
+
verify({ request, context }) {
|
|
96
|
+
const manifest = context?.adopterManifest;
|
|
97
|
+
const manifestGate = context?.adopterManifestGate;
|
|
98
|
+
if (!manifest || !manifestGate) {
|
|
99
|
+
return invalidContextResult([
|
|
100
|
+
issue(
|
|
101
|
+
"delivery-legacy-verification-context-invalid",
|
|
102
|
+
"/protocol",
|
|
103
|
+
"The exact standard adopter manifest and manifest gate are required.",
|
|
104
|
+
),
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const source = manifest?.adopter?.artifact?.coordinate?.match(
|
|
109
|
+
/^([^@\s]+)@([0-9a-f]{40})$/,
|
|
110
|
+
);
|
|
111
|
+
const gateValidation = validateKfdAdopterManifestGate(manifestGate, {
|
|
112
|
+
expectedAdopterId: manifest?.adopter?.id,
|
|
113
|
+
expectedSourceRepository: source?.[1] ?? "",
|
|
114
|
+
expectedSourceSha: source?.[2] ?? "",
|
|
115
|
+
checkedAt: manifestGate?.checkedAt,
|
|
116
|
+
});
|
|
117
|
+
const projectionValidation = validateKfdLegacySupportMatrixProjection(
|
|
118
|
+
request.declaration,
|
|
119
|
+
{ manifest, manifestGate },
|
|
120
|
+
);
|
|
121
|
+
const adapterIssues = [
|
|
122
|
+
...bindingIssues(request, manifest),
|
|
123
|
+
...normalizedIssues(gateValidation.issues),
|
|
124
|
+
...normalizedIssues(projectionValidation.issues),
|
|
125
|
+
];
|
|
126
|
+
if (
|
|
127
|
+
request.declaration?.contract !== KFD_LEGACY_SUPPORT_MATRIX_CONTRACT
|
|
128
|
+
) {
|
|
129
|
+
adapterIssues.push(
|
|
130
|
+
issue(
|
|
131
|
+
"delivery-legacy-contract-invalid",
|
|
132
|
+
"/declaration/contract",
|
|
133
|
+
`Legacy declaration must use ${KFD_LEGACY_SUPPORT_MATRIX_CONTRACT}.`,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const valid =
|
|
139
|
+
gateValidation.valid &&
|
|
140
|
+
projectionValidation.valid &&
|
|
141
|
+
adapterIssues.length === 0;
|
|
142
|
+
const report = {
|
|
143
|
+
schemaVersion: 1,
|
|
144
|
+
contract: LEGACY_KFD_ADOPTER_DRIVER_REPORT_CONTRACT,
|
|
145
|
+
valid,
|
|
146
|
+
qualifying: false,
|
|
147
|
+
selfCertified: false,
|
|
148
|
+
independentlyCertified: false,
|
|
149
|
+
manifestRoot: manifestGate?.authority?.manifestRoot ?? null,
|
|
150
|
+
manifestGateRoot: manifestGate?.gateRoot ?? null,
|
|
151
|
+
legacyProjectionRoot: adopterDeliveryGateDigest(request.declaration),
|
|
152
|
+
legacyProjection: structuredClone(request.declaration),
|
|
153
|
+
adapterIssues,
|
|
154
|
+
nonClaims: [
|
|
155
|
+
"The legacy support matrix is a one-way projection, not declaration authority.",
|
|
156
|
+
"Buildchain carries the existing standard manifest decision and does not certify KFD adoption.",
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
valid,
|
|
161
|
+
report,
|
|
162
|
+
reportRoot: adopterDeliveryGateDigest(report),
|
|
163
|
+
issues: adapterIssues,
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -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
|
+
}
|