@kungfu-tech/buildchain 2.8.0 → 2.8.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/actions/promote-buildchain-ref/README.md +13 -0
- package/bin/buildchain.mjs +8 -0
- package/dist/site/agent-index.json +2 -1
- package/dist/site/artifact-schemas.json +1 -0
- package/dist/site/buildchain-contract.json +298 -0
- package/dist/site/release-provenance.json +2 -0
- package/docs/MAP.md +9 -0
- package/docs/cli.md +39 -0
- package/docs/migration-inventory.md +11 -0
- package/docs/release-candidate.md +38 -1
- package/docs/release-governance.md +18 -0
- package/docs/release-passport.md +126 -0
- package/docs/reusable-build-surface.md +79 -3
- package/docs/site-bundle-contract.md +6 -0
- package/docs/versioning.md +1 -1
- package/package.json +4 -2
- package/packages/core/buildchain-contract.js +524 -0
- package/packages/core/index.js +23 -0
- package/packages/core/kfd-gate.js +1064 -4
- package/packages/core/release-passport.js +298 -0
- package/scripts/buildchain-contract-lock.mjs +170 -0
- package/scripts/check-inventory.mjs +105 -3
- package/scripts/ensure-github-release.mjs +5 -5
- package/scripts/generate-site-bundle.mjs +4 -0
- package/scripts/release-candidate-resolver.mjs +1 -1
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export const BUILDCHAIN_RUNTIME_CONTRACT_WORLD = "kungfu-buildchain-runtime-contract-world";
|
|
6
|
+
export const BUILDCHAIN_CONTRACT_LOCK = "kungfu-buildchain-contract-lock";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_POLICY = "major-compatible";
|
|
9
|
+
const FLOATING_CLASSES = new Set(["stable", "alpha"]);
|
|
10
|
+
|
|
11
|
+
function optionalString(value) {
|
|
12
|
+
return value === undefined || value === null ? "" : String(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function stableJson(value) {
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
18
|
+
}
|
|
19
|
+
if (value && typeof value === "object") {
|
|
20
|
+
return `{${Object.keys(value)
|
|
21
|
+
.sort()
|
|
22
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
23
|
+
.join(",")}}`;
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function sha256Json(value) {
|
|
29
|
+
return crypto.createHash("sha256").update(stableJson(value)).digest("hex");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function sha256File(filePath) {
|
|
33
|
+
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readJson(filePath, fallback = undefined) {
|
|
37
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function maybeFileDigest(root, relPath) {
|
|
44
|
+
const filePath = path.join(root, relPath);
|
|
45
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isFile() ? `sha256:${sha256File(filePath)}` : "";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function surface(root, value) {
|
|
49
|
+
const breakingModel = {
|
|
50
|
+
id: value.id,
|
|
51
|
+
kind: value.kind,
|
|
52
|
+
contractVersion: value.contractVersion || 1,
|
|
53
|
+
requiredInputs: value.requiredInputs || [],
|
|
54
|
+
requiredOutputs: value.requiredOutputs || [],
|
|
55
|
+
breakingDefaults: value.breakingDefaults || {},
|
|
56
|
+
guarantees: value.guarantees || [],
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
contractVersion: 1,
|
|
60
|
+
stability: "stable",
|
|
61
|
+
additiveChanges: "optional inputs, optional outputs, diagnostics, and documentation may be added within the same major line",
|
|
62
|
+
...value,
|
|
63
|
+
breakingDigest: `sha256:${sha256Json(breakingModel)}`,
|
|
64
|
+
auditDigest: value.path ? maybeFileDigest(root, value.path) : "",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function majorLineFromPackageVersion(version = "") {
|
|
69
|
+
const match = String(version || "").match(/^(\d+)\./);
|
|
70
|
+
return match ? `v${match[1]}` : "v2";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createBuildchainContractWorld({ root = process.cwd(), packageJson = undefined } = {}) {
|
|
74
|
+
const pkg = packageJson || readJson(path.join(root, "package.json"), {});
|
|
75
|
+
const majorLine = majorLineFromPackageVersion(pkg.version);
|
|
76
|
+
const surfaces = [
|
|
77
|
+
surface(root, {
|
|
78
|
+
id: "reusable-build",
|
|
79
|
+
kind: "workflow",
|
|
80
|
+
path: ".github/workflows/.build.yml",
|
|
81
|
+
publicRef: `${pkg.repository ? "kungfu-systems/buildchain" : "buildchain"}/.github/workflows/.build.yml@${majorLine}`,
|
|
82
|
+
requiredInputs: [],
|
|
83
|
+
requiredOutputs: [
|
|
84
|
+
"buildchain-runtime-sha",
|
|
85
|
+
"publish-source-sha",
|
|
86
|
+
"build-summary-artifact",
|
|
87
|
+
"release-candidate-artifact",
|
|
88
|
+
],
|
|
89
|
+
breakingDefaults: {
|
|
90
|
+
buildchainRefDefault: "workflow-shell-ref-or-v2",
|
|
91
|
+
promoteOnlyHeavyBuildPolicy: "pr-stage-only",
|
|
92
|
+
},
|
|
93
|
+
optionalInputs: [
|
|
94
|
+
"buildchain-ref",
|
|
95
|
+
"runner-preset",
|
|
96
|
+
"platforms-json",
|
|
97
|
+
"release-candidate",
|
|
98
|
+
"artifact-transfer-mode",
|
|
99
|
+
"buildchain-contract-lock-path",
|
|
100
|
+
"buildchain-contract-drift-issue-mode",
|
|
101
|
+
],
|
|
102
|
+
guarantees: [
|
|
103
|
+
"runtime floating refs are resolved to immutable SHAs before matrix jobs",
|
|
104
|
+
"publish source locks are verified before heavy build jobs",
|
|
105
|
+
"release-candidate builds do not publish registry artifacts",
|
|
106
|
+
"contract drift is checked before heavy build jobs for stable floating refs",
|
|
107
|
+
],
|
|
108
|
+
}),
|
|
109
|
+
surface(root, {
|
|
110
|
+
id: "release-candidate-promote",
|
|
111
|
+
kind: "workflow",
|
|
112
|
+
path: ".github/workflows/release-candidate-promote.yml",
|
|
113
|
+
publicRef: `${pkg.repository ? "kungfu-systems/buildchain" : "buildchain"}/.github/workflows/release-candidate-promote.yml@${majorLine}`,
|
|
114
|
+
requiredInputs: ["channel"],
|
|
115
|
+
requiredOutputs: ["promoted-sha", "built-source-sha", "release-candidate-artifact"],
|
|
116
|
+
breakingDefaults: {
|
|
117
|
+
promoteOnlyReleaseCandidate: true,
|
|
118
|
+
requiredStatusCheck: "check",
|
|
119
|
+
},
|
|
120
|
+
optionalInputs: [
|
|
121
|
+
"buildchain-ref",
|
|
122
|
+
"release-candidate-workflow-file",
|
|
123
|
+
"release-candidate-workflow-name",
|
|
124
|
+
"publish-required-artifacts-json",
|
|
125
|
+
"release-passport-kfd-1-witness-jsons",
|
|
126
|
+
"release-passport-kfd-2-claim-jsons",
|
|
127
|
+
"release-passport-kfd-3-prebuild-witness-jsons",
|
|
128
|
+
"release-passport-kfd-3-artifact-witness-jsons",
|
|
129
|
+
"release-passport-kfd-3-artifact-verify-command",
|
|
130
|
+
"buildchain-contract-lock-path",
|
|
131
|
+
"buildchain-contract-drift-issue-mode",
|
|
132
|
+
],
|
|
133
|
+
guarantees: [
|
|
134
|
+
"promotion reuses PR-stage release-candidate artifacts",
|
|
135
|
+
"promotion does not run the heavy native build matrix",
|
|
136
|
+
"built source and promotion channel SHA are recorded separately",
|
|
137
|
+
"contract drift is checked before release-candidate resolution and publish",
|
|
138
|
+
"publish-gate source locks are created by the wrapper and enforced by promote-buildchain-ref before publish side effects",
|
|
139
|
+
],
|
|
140
|
+
}),
|
|
141
|
+
surface(root, {
|
|
142
|
+
id: "promote-buildchain-ref-action",
|
|
143
|
+
kind: "action",
|
|
144
|
+
path: "actions/promote-buildchain-ref/action.yml",
|
|
145
|
+
publicRef: `kungfu-systems/buildchain/actions/promote-buildchain-ref@${majorLine}`,
|
|
146
|
+
requiredInputs: ["token", "sha", "target-ref"],
|
|
147
|
+
requiredOutputs: ["sha"],
|
|
148
|
+
breakingDefaults: {
|
|
149
|
+
requireGovernance: false,
|
|
150
|
+
releasePassport: true,
|
|
151
|
+
},
|
|
152
|
+
optionalInputs: [
|
|
153
|
+
"publish-transaction",
|
|
154
|
+
"publish-required-artifacts-json",
|
|
155
|
+
"promote-only-release-candidate",
|
|
156
|
+
"release-passport-kfd-1-witness-jsons",
|
|
157
|
+
"release-passport-kfd-2-claim-jsons",
|
|
158
|
+
"release-passport-kfd-3-prebuild-witness-jsons",
|
|
159
|
+
"release-passport-kfd-3-artifact-witness-jsons",
|
|
160
|
+
"release-passport-kfd-3-artifact-verify-command",
|
|
161
|
+
],
|
|
162
|
+
guarantees: [
|
|
163
|
+
"protected release refs and durable release-state are finalized by Buildchain",
|
|
164
|
+
"release passport finalization is idempotent after publish side effects",
|
|
165
|
+
"publish transactions can require a resolved publish-gate source lock to prevent floating-ref drift",
|
|
166
|
+
],
|
|
167
|
+
}),
|
|
168
|
+
surface(root, {
|
|
169
|
+
id: "report-buildchain-issue-action",
|
|
170
|
+
kind: "action",
|
|
171
|
+
path: "actions/report-buildchain-issue/action.yml",
|
|
172
|
+
publicRef: `kungfu-systems/buildchain/actions/report-buildchain-issue@${majorLine}`,
|
|
173
|
+
requiredInputs: ["token"],
|
|
174
|
+
requiredOutputs: ["ok", "action", "issue-url", "fingerprint"],
|
|
175
|
+
breakingDefaults: {
|
|
176
|
+
failOnError: false,
|
|
177
|
+
mode: "create-or-comment",
|
|
178
|
+
},
|
|
179
|
+
optionalInputs: ["report-kind", "target-repository", "body-file", "comment-cooldown-hours"],
|
|
180
|
+
guarantees: [
|
|
181
|
+
"issue reporting is fail-soft by default",
|
|
182
|
+
"GitHub API 429 and 5xx failures are retried",
|
|
183
|
+
"missing issue permissions produce a copyable summary fallback",
|
|
184
|
+
],
|
|
185
|
+
}),
|
|
186
|
+
surface(root, {
|
|
187
|
+
id: "release-passport-schema",
|
|
188
|
+
kind: "schema",
|
|
189
|
+
path: "packages/core/release-passport.js",
|
|
190
|
+
requiredInputs: ["buildchain.release.json"],
|
|
191
|
+
requiredOutputs: ["check-report.json"],
|
|
192
|
+
breakingDefaults: {
|
|
193
|
+
schemaVersion: 1,
|
|
194
|
+
contract: "kungfu-buildchain-release-passport",
|
|
195
|
+
},
|
|
196
|
+
guarantees: [
|
|
197
|
+
"release passport verification fails closed for malformed required evidence",
|
|
198
|
+
"release-state SHA is recorded as a durable audit entrance",
|
|
199
|
+
],
|
|
200
|
+
}),
|
|
201
|
+
surface(root, {
|
|
202
|
+
id: "kfd-1-release-gate",
|
|
203
|
+
kind: "schema",
|
|
204
|
+
path: "packages/core/kfd-gate.js",
|
|
205
|
+
requiredInputs: ["KFD-1 witness JSON"],
|
|
206
|
+
requiredOutputs: ["kfd-1 release gate evidence"],
|
|
207
|
+
breakingDefaults: {
|
|
208
|
+
witnessContract: "kungfu-buildchain-kfd-1-witness-set",
|
|
209
|
+
releaseGateContract: "kungfu-buildchain-kfd-1-release-gate",
|
|
210
|
+
},
|
|
211
|
+
guarantees: [
|
|
212
|
+
"KFD-1 witnesses must include at least one artifact byte surface",
|
|
213
|
+
"artifact bytes are sha256 checked before passport finalization succeeds",
|
|
214
|
+
"KFD self contract witnesses record source/artifact hashes, self-hosting boundary, and responsibility state",
|
|
215
|
+
],
|
|
216
|
+
}),
|
|
217
|
+
surface(root, {
|
|
218
|
+
id: "kfd-2-release-trust-passport-audit",
|
|
219
|
+
kind: "schema",
|
|
220
|
+
path: "packages/core/release-passport.js",
|
|
221
|
+
requiredInputs: ["public release claim evidence"],
|
|
222
|
+
requiredOutputs: ["kfd-2 release trust passport audit"],
|
|
223
|
+
breakingDefaults: {
|
|
224
|
+
releaseTrustPassportContract: "kungfu-buildchain-kfd-2-release-trust-passport-audit",
|
|
225
|
+
},
|
|
226
|
+
guarantees: [
|
|
227
|
+
"public release claims must bind declared sources, machine evidence, hashes, artifacts, verification, audit boundary, responsibility, and residual risk",
|
|
228
|
+
"unbound public claims fail release passport verification",
|
|
229
|
+
"prose-only public claims downgrade the release trust passport audit",
|
|
230
|
+
],
|
|
231
|
+
}),
|
|
232
|
+
surface(root, {
|
|
233
|
+
id: "kfd-3-collaboration-interface-release-gate",
|
|
234
|
+
kind: "schema",
|
|
235
|
+
path: "packages/core/kfd-gate.js",
|
|
236
|
+
requiredInputs: [
|
|
237
|
+
"KFD-3 prebuild witness JSON",
|
|
238
|
+
"KFD-3 artifact witness JSON or verify command",
|
|
239
|
+
],
|
|
240
|
+
requiredOutputs: ["kfd-3 collaboration-interface release gate evidence"],
|
|
241
|
+
breakingDefaults: {
|
|
242
|
+
prebuildWitnessContract: "kungfu-buildchain-kfd-3-collaboration-interface-prebuild-witness",
|
|
243
|
+
artifactWitnessContract: "kungfu-buildchain-kfd-3-collaboration-interface-artifact-witness",
|
|
244
|
+
releaseGateContract: "kungfu-buildchain-kfd-3-collaboration-interface-release-gate",
|
|
245
|
+
},
|
|
246
|
+
guarantees: [
|
|
247
|
+
"KFD-3 pre-build witnesses must declare participant-facing public surfaces",
|
|
248
|
+
"artifact witnesses must not expose undeclared public participant-facing surfaces",
|
|
249
|
+
"collaborationInterface.digest mismatches fail passport verification",
|
|
250
|
+
"KFD repository self-verification can declare docs, schemas, standards metadata, package exports, and site-consumption contracts",
|
|
251
|
+
"KFD-3 passports expose releaseStatus, witness hashes, declared capability verification, reverse audit boundary, residual risk, and responsibility state",
|
|
252
|
+
],
|
|
253
|
+
}),
|
|
254
|
+
surface(root, {
|
|
255
|
+
id: "buildchain-cli",
|
|
256
|
+
kind: "cli",
|
|
257
|
+
path: "bin/buildchain.mjs",
|
|
258
|
+
requiredInputs: [],
|
|
259
|
+
requiredOutputs: [],
|
|
260
|
+
breakingDefaults: {
|
|
261
|
+
binary: "buildchain",
|
|
262
|
+
moduleSystem: "esm",
|
|
263
|
+
},
|
|
264
|
+
optionalInputs: [
|
|
265
|
+
"validate",
|
|
266
|
+
"lifecycle",
|
|
267
|
+
"collect github-release",
|
|
268
|
+
"verify release-passport",
|
|
269
|
+
"release-propagation",
|
|
270
|
+
"infra-contract",
|
|
271
|
+
],
|
|
272
|
+
guarantees: [
|
|
273
|
+
"CLI commands are stable within the major line unless the contract major changes",
|
|
274
|
+
],
|
|
275
|
+
}),
|
|
276
|
+
];
|
|
277
|
+
const base = {
|
|
278
|
+
schemaVersion: 1,
|
|
279
|
+
contract: BUILDCHAIN_RUNTIME_CONTRACT_WORLD,
|
|
280
|
+
product: {
|
|
281
|
+
name: "Buildchain",
|
|
282
|
+
package: pkg.name || "@kungfu-tech/buildchain",
|
|
283
|
+
version: pkg.version || "",
|
|
284
|
+
repository: pkg.repository?.url || pkg.repository || "https://github.com/kungfu-systems/buildchain",
|
|
285
|
+
},
|
|
286
|
+
majorLine,
|
|
287
|
+
compatibilityPolicy: DEFAULT_POLICY,
|
|
288
|
+
surfaces,
|
|
289
|
+
};
|
|
290
|
+
return finalizeBuildchainContractWorld(base);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function finalizeBuildchainContractWorld(contractWorld) {
|
|
294
|
+
const world = {
|
|
295
|
+
...contractWorld,
|
|
296
|
+
surfaces: (contractWorld.surfaces || []).map((entry) => ({ ...entry })),
|
|
297
|
+
};
|
|
298
|
+
const compatibilityModel = {
|
|
299
|
+
schemaVersion: world.schemaVersion,
|
|
300
|
+
contract: world.contract,
|
|
301
|
+
majorLine: world.majorLine,
|
|
302
|
+
surfaces: world.surfaces.map((entry) => ({
|
|
303
|
+
id: entry.id,
|
|
304
|
+
kind: entry.kind,
|
|
305
|
+
breakingDigest: entry.breakingDigest,
|
|
306
|
+
})),
|
|
307
|
+
};
|
|
308
|
+
const digestModel = {
|
|
309
|
+
...world,
|
|
310
|
+
contractDigest: undefined,
|
|
311
|
+
compatibilityDigest: undefined,
|
|
312
|
+
};
|
|
313
|
+
world.compatibilityDigest = `sha256:${sha256Json(compatibilityModel)}`;
|
|
314
|
+
world.contractDigest = `sha256:${sha256Json(digestModel)}`;
|
|
315
|
+
return world;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function createBuildchainContractLock({
|
|
319
|
+
buildchainRef = "v2",
|
|
320
|
+
resolvedSha = "",
|
|
321
|
+
contractWorld,
|
|
322
|
+
compatibilityPolicy = DEFAULT_POLICY,
|
|
323
|
+
acceptedAt = new Date().toISOString(),
|
|
324
|
+
} = {}) {
|
|
325
|
+
if (!contractWorld || contractWorld.contract !== BUILDCHAIN_RUNTIME_CONTRACT_WORLD) {
|
|
326
|
+
throw new Error("contractWorld must be a Buildchain runtime contract world");
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
schemaVersion: 1,
|
|
330
|
+
contract: BUILDCHAIN_CONTRACT_LOCK,
|
|
331
|
+
buildchain: {
|
|
332
|
+
ref: buildchainRef,
|
|
333
|
+
resolvedSha,
|
|
334
|
+
contract: contractWorld.contract,
|
|
335
|
+
contractDigest: contractWorld.contractDigest,
|
|
336
|
+
compatibilityDigest: contractWorld.compatibilityDigest,
|
|
337
|
+
majorLine: contractWorld.majorLine,
|
|
338
|
+
compatibilityPolicy,
|
|
339
|
+
acceptedAt,
|
|
340
|
+
surfaces: contractWorld.surfaces.map((entry) => ({
|
|
341
|
+
id: entry.id,
|
|
342
|
+
kind: entry.kind,
|
|
343
|
+
breakingDigest: entry.breakingDigest,
|
|
344
|
+
})),
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function readBuildchainContractWorld(filePath) {
|
|
350
|
+
const value = readJson(filePath);
|
|
351
|
+
if (!value || value.contract !== BUILDCHAIN_RUNTIME_CONTRACT_WORLD) {
|
|
352
|
+
throw new Error(`Buildchain contract world is missing or invalid: ${filePath}`);
|
|
353
|
+
}
|
|
354
|
+
return finalizeBuildchainContractWorld(value);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function readBuildchainContractLock(filePath) {
|
|
358
|
+
if (!filePath || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
const value = readJson(filePath);
|
|
362
|
+
if (!value || value.contract !== BUILDCHAIN_CONTRACT_LOCK) {
|
|
363
|
+
throw new Error(`Buildchain contract lock is missing or invalid: ${filePath}`);
|
|
364
|
+
}
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function surfaceMap(surfaces = []) {
|
|
369
|
+
return new Map(surfaces.map((entry) => [entry.id, entry]));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function evaluateBuildchainContractLock({
|
|
373
|
+
lock,
|
|
374
|
+
current,
|
|
375
|
+
runtimeRef = "",
|
|
376
|
+
runtimeSha = "",
|
|
377
|
+
runtimeClass = "",
|
|
378
|
+
compatibilityPolicy = "",
|
|
379
|
+
} = {}) {
|
|
380
|
+
if (!current || current.contract !== BUILDCHAIN_RUNTIME_CONTRACT_WORLD) {
|
|
381
|
+
throw new Error("current must be a Buildchain runtime contract world");
|
|
382
|
+
}
|
|
383
|
+
const floatingRuntime = FLOATING_CLASSES.has(runtimeClass);
|
|
384
|
+
if (!floatingRuntime) {
|
|
385
|
+
return {
|
|
386
|
+
ok: true,
|
|
387
|
+
status: "non-floating-runtime",
|
|
388
|
+
drift: false,
|
|
389
|
+
compatible: true,
|
|
390
|
+
issueRecommended: false,
|
|
391
|
+
reason: `runtime class ${runtimeClass || "unknown"} is not a stable floating ref`,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (!lock) {
|
|
395
|
+
return {
|
|
396
|
+
ok: true,
|
|
397
|
+
status: "missing-lock",
|
|
398
|
+
drift: false,
|
|
399
|
+
compatible: true,
|
|
400
|
+
issueRecommended: false,
|
|
401
|
+
reason: "consumer repository has no Buildchain contract lock",
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
const accepted = lock.buildchain || {};
|
|
405
|
+
const policy = compatibilityPolicy || accepted.compatibilityPolicy || DEFAULT_POLICY;
|
|
406
|
+
const shaDrift = !!accepted.resolvedSha && !!runtimeSha && accepted.resolvedSha !== runtimeSha;
|
|
407
|
+
const contractDrift = !!accepted.contractDigest && accepted.contractDigest !== current.contractDigest;
|
|
408
|
+
if (!shaDrift && !contractDrift) {
|
|
409
|
+
return {
|
|
410
|
+
ok: true,
|
|
411
|
+
status: "unchanged",
|
|
412
|
+
drift: false,
|
|
413
|
+
compatible: true,
|
|
414
|
+
issueRecommended: false,
|
|
415
|
+
policy,
|
|
416
|
+
accepted,
|
|
417
|
+
current: contractSummary(current, runtimeRef, runtimeSha),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const reasons = [];
|
|
421
|
+
if (accepted.contract !== current.contract) {
|
|
422
|
+
reasons.push(`contract changed from ${accepted.contract || "(unknown)"} to ${current.contract}`);
|
|
423
|
+
}
|
|
424
|
+
if (accepted.majorLine && accepted.majorLine !== current.majorLine) {
|
|
425
|
+
reasons.push(`major line changed from ${accepted.majorLine} to ${current.majorLine}`);
|
|
426
|
+
}
|
|
427
|
+
if (policy === "exact" && accepted.contractDigest !== current.contractDigest) {
|
|
428
|
+
reasons.push("exact policy requires the contract digest to remain unchanged");
|
|
429
|
+
}
|
|
430
|
+
if (!["major-compatible", "allow-additive", "exact"].includes(policy)) {
|
|
431
|
+
reasons.push(`unsupported compatibility policy: ${policy}`);
|
|
432
|
+
}
|
|
433
|
+
const currentSurfaces = surfaceMap(current.surfaces);
|
|
434
|
+
for (const oldSurface of accepted.surfaces || []) {
|
|
435
|
+
const nextSurface = currentSurfaces.get(oldSurface.id);
|
|
436
|
+
if (!nextSurface) {
|
|
437
|
+
reasons.push(`surface removed: ${oldSurface.id}`);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (nextSurface.breakingDigest !== oldSurface.breakingDigest) {
|
|
441
|
+
reasons.push(`surface breaking digest changed: ${oldSurface.id}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const compatible = reasons.length === 0;
|
|
445
|
+
return {
|
|
446
|
+
ok: compatible,
|
|
447
|
+
status: compatible ? "compatible-drift" : "breaking-drift",
|
|
448
|
+
drift: shaDrift || contractDrift,
|
|
449
|
+
shaDrift,
|
|
450
|
+
contractDrift,
|
|
451
|
+
compatible,
|
|
452
|
+
issueRecommended: true,
|
|
453
|
+
policy,
|
|
454
|
+
reasons,
|
|
455
|
+
accepted,
|
|
456
|
+
current: contractSummary(current, runtimeRef, runtimeSha),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function contractSummary(contractWorld, runtimeRef = "", runtimeSha = "") {
|
|
461
|
+
return {
|
|
462
|
+
ref: runtimeRef,
|
|
463
|
+
resolvedSha: runtimeSha,
|
|
464
|
+
contract: contractWorld.contract,
|
|
465
|
+
contractDigest: contractWorld.contractDigest,
|
|
466
|
+
compatibilityDigest: contractWorld.compatibilityDigest,
|
|
467
|
+
majorLine: contractWorld.majorLine,
|
|
468
|
+
surfaceCount: Array.isArray(contractWorld.surfaces) ? contractWorld.surfaces.length : 0,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function renderBuildchainContractDriftIssueBody({
|
|
473
|
+
repository = "",
|
|
474
|
+
workflow = "",
|
|
475
|
+
runUrl = "",
|
|
476
|
+
lockPath = "",
|
|
477
|
+
evaluation,
|
|
478
|
+
} = {}) {
|
|
479
|
+
const accepted = evaluation.accepted || {};
|
|
480
|
+
const current = evaluation.current || {};
|
|
481
|
+
const severity = evaluation.compatible ? "compatible" : "breaking";
|
|
482
|
+
return [
|
|
483
|
+
"# Buildchain contract drift",
|
|
484
|
+
"",
|
|
485
|
+
"## Summary",
|
|
486
|
+
"",
|
|
487
|
+
`Buildchain detected ${severity} contract drift for a floating runtime ref before expensive Buildchain work continued.`,
|
|
488
|
+
"",
|
|
489
|
+
"## Consumer",
|
|
490
|
+
"",
|
|
491
|
+
`- Repository: ${repository || "(unknown)"}`,
|
|
492
|
+
`- Workflow: ${workflow || "(unknown)"}`,
|
|
493
|
+
`- Run: ${runUrl || "(unknown)"}`,
|
|
494
|
+
`- Lock path: ${lockPath || "(unknown)"}`,
|
|
495
|
+
"",
|
|
496
|
+
"## Accepted Buildchain contract",
|
|
497
|
+
"",
|
|
498
|
+
`- Ref: ${accepted.ref || "(unknown)"}`,
|
|
499
|
+
`- SHA: ${accepted.resolvedSha || "(unknown)"}`,
|
|
500
|
+
`- Contract digest: ${accepted.contractDigest || "(unknown)"}`,
|
|
501
|
+
`- Compatibility digest: ${accepted.compatibilityDigest || "(unknown)"}`,
|
|
502
|
+
`- Policy: ${evaluation.policy || accepted.compatibilityPolicy || "(unknown)"}`,
|
|
503
|
+
"",
|
|
504
|
+
"## Current Buildchain contract",
|
|
505
|
+
"",
|
|
506
|
+
`- Ref: ${current.ref || "(unknown)"}`,
|
|
507
|
+
`- SHA: ${current.resolvedSha || "(unknown)"}`,
|
|
508
|
+
`- Contract digest: ${current.contractDigest || "(unknown)"}`,
|
|
509
|
+
`- Compatibility digest: ${current.compatibilityDigest || "(unknown)"}`,
|
|
510
|
+
`- Major line: ${current.majorLine || "(unknown)"}`,
|
|
511
|
+
"",
|
|
512
|
+
"## Compatibility",
|
|
513
|
+
"",
|
|
514
|
+
`- Status: ${evaluation.status || "(unknown)"}`,
|
|
515
|
+
`- Compatible: ${evaluation.compatible ? "yes" : "no"}`,
|
|
516
|
+
evaluation.reasons?.length ? evaluation.reasons.map((reason) => `- ${reason}`).join("\n") : "- No breaking drift detected.",
|
|
517
|
+
"",
|
|
518
|
+
"## Suggested next action",
|
|
519
|
+
"",
|
|
520
|
+
evaluation.compatible
|
|
521
|
+
? "Review the Buildchain release notes, then update the consumer contract lock to the current SHA and contract digest."
|
|
522
|
+
: "Failing before heavy build is intentional. Review the Buildchain contract change, update the consumer workflow/configuration, or pin the previous Buildchain SHA.",
|
|
523
|
+
].join("\n");
|
|
524
|
+
}
|
package/packages/core/index.js
CHANGED
|
@@ -104,21 +104,44 @@ export {
|
|
|
104
104
|
verifyArtifactPassport,
|
|
105
105
|
} from "./artifact-passport.js";
|
|
106
106
|
|
|
107
|
+
export {
|
|
108
|
+
BUILDCHAIN_CONTRACT_LOCK,
|
|
109
|
+
BUILDCHAIN_RUNTIME_CONTRACT_WORLD,
|
|
110
|
+
contractSummary,
|
|
111
|
+
createBuildchainContractLock,
|
|
112
|
+
createBuildchainContractWorld,
|
|
113
|
+
evaluateBuildchainContractLock,
|
|
114
|
+
finalizeBuildchainContractWorld,
|
|
115
|
+
readBuildchainContractLock,
|
|
116
|
+
readBuildchainContractWorld,
|
|
117
|
+
renderBuildchainContractDriftIssueBody,
|
|
118
|
+
sha256Json as sha256BuildchainContractJson,
|
|
119
|
+
} from "./buildchain-contract.js";
|
|
120
|
+
|
|
107
121
|
export {
|
|
108
122
|
BUILDCHAIN_JSON_FORMATTING_POLICY,
|
|
109
123
|
KFD1_RELEASE_GATE_CONTRACT,
|
|
110
124
|
KFD1_WITNESS_SET_CONTRACT,
|
|
125
|
+
KFD3_ARTIFACT_WITNESS_CONTRACT,
|
|
126
|
+
KFD3_PREBUILD_WITNESS_CONTRACT,
|
|
127
|
+
KFD3_RELEASE_GATE_CONTRACT,
|
|
111
128
|
createKfd1ReleaseGateEvidence,
|
|
129
|
+
createKfd3CollaborationInterfaceReleaseGateEvidence,
|
|
112
130
|
normalizeKfd1ContractWorldWitness,
|
|
131
|
+
normalizeKfd3CollaborationInterfaceArtifactWitness,
|
|
132
|
+
normalizeKfd3CollaborationInterfacePrebuildWitness,
|
|
113
133
|
resolveKfd1Metadata,
|
|
134
|
+
resolveKfd3Metadata,
|
|
114
135
|
sha256Json as sha256KfdJson,
|
|
115
136
|
validateKfd1ReleaseGateEvidence,
|
|
137
|
+
validateKfd3CollaborationInterfaceReleaseGateEvidence,
|
|
116
138
|
} from "./kfd-gate.js";
|
|
117
139
|
|
|
118
140
|
export {
|
|
119
141
|
AGENT_INDEX_CONTRACT,
|
|
120
142
|
ARTIFACT_EVIDENCE_CONTRACT,
|
|
121
143
|
IMPACT_LEDGER_CONTRACT,
|
|
144
|
+
KFD2_RELEASE_TRUST_PASSPORT_CONTRACT,
|
|
122
145
|
PRODUCT_MECHANISM_CONTRACT,
|
|
123
146
|
RELEASE_CHECK_REPORT_CONTRACT,
|
|
124
147
|
RELEASE_PASSPORT_CONTRACT,
|