@kungfu-tech/buildchain 2.14.3 → 2.14.4-alpha.0
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 +8 -0
- package/bin/buildchain.mjs +4 -0
- package/dist/site/buildchain-contract.json +39 -25
- package/dist/site/buildchain-site.json +26 -26
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/controller-registry.json +28 -4
- package/dist/site/kfd-claims.json +24 -6
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +20 -7
- package/dist/site/page-registry.json +16 -16
- package/dist/site/public-surface-audit.json +11 -5
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +1 -0
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +9 -3
- package/docs/MAP.md +1 -0
- package/docs/cli.md +7 -0
- package/docs/lifecycle-protocol.md +14 -0
- package/docs/release-governance.md +10 -1
- package/docs/release-passport.md +21 -0
- package/docs/versioning.md +1 -0
- package/package.json +2 -1
- package/packages/core/README.md +2 -0
- package/packages/core/anchored-version-material.js +281 -0
- package/packages/core/buildchain-config.js +68 -0
- package/packages/core/buildchain-contract.js +4 -0
- package/packages/core/controller-evidence.js +30 -4
- package/packages/core/index.js +6 -0
- package/packages/core/release-passport-contract.js +2 -0
- package/packages/core/release-passport.js +186 -1
- package/scripts/anchored-version-material.mjs +42 -0
- package/scripts/check-inventory.mjs +6 -0
- package/scripts/generate-site-bundle.mjs +1 -0
- package/scripts/stable-candidate-patrol.mjs +55 -6
|
@@ -26,6 +26,7 @@ export const PRODUCT_MECHANISM_CONTRACT = "kungfu-buildchain-product-mechanism";
|
|
|
26
26
|
export const RELEASE_CHECK_REPORT_CONTRACT = "kungfu-buildchain-release-check-report";
|
|
27
27
|
export const KFD2_RELEASE_TRUST_PASSPORT_CONTRACT = "kungfu-buildchain-kfd-2-release-trust-passport-audit";
|
|
28
28
|
export const KFD2_TRUST_PROOF_CONTRACT = "kungfu-buildchain-kfd-2-trust-proof";
|
|
29
|
+
export const INVARIANT_PASSPORT_GATE_CONTRACT = "buildchain.invariant-passport-gate/v1";
|
|
29
30
|
|
|
30
31
|
const CONTRACTS = new Set([
|
|
31
32
|
RELEASE_PASSPORT_CONTRACT,
|
|
@@ -128,6 +129,80 @@ export function sha256Text(value) {
|
|
|
128
129
|
return crypto.createHash("sha256").update(value).digest("hex");
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
function invariantSemanticPreimage(passport) {
|
|
133
|
+
const value = structuredClone(passport);
|
|
134
|
+
delete value.passportRoot;
|
|
135
|
+
delete value.observedAt;
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeInvariantPassport(meta, index = 0) {
|
|
140
|
+
const value = meta?.value;
|
|
141
|
+
const label = `invariantPassportJsons[${index}]`;
|
|
142
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
143
|
+
throw new Error(`${label} must be a JSON object`);
|
|
144
|
+
}
|
|
145
|
+
for (const field of ["schema", "product", "canonicalization", "passportRoot", "contractRoot", "registryRoot", "verdict"]) {
|
|
146
|
+
nonEmptyString(value[field], `${label}.${field}`);
|
|
147
|
+
}
|
|
148
|
+
if (value.canonicalization !== "stable-json-sha256-v1") {
|
|
149
|
+
throw new Error(`${label}.canonicalization must be stable-json-sha256-v1`);
|
|
150
|
+
}
|
|
151
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(value.passportRoot)) {
|
|
152
|
+
throw new Error(`${label}.passportRoot must be sha256:<64-lowercase-hex>`);
|
|
153
|
+
}
|
|
154
|
+
const expectedRoot = `sha256:${sha256Text(stableJson(invariantSemanticPreimage(value)))}`;
|
|
155
|
+
if (value.passportRoot !== expectedRoot) {
|
|
156
|
+
throw new Error(`${label}.passportRoot mismatch: expected ${expectedRoot}, got ${value.passportRoot}`);
|
|
157
|
+
}
|
|
158
|
+
if (value.verdict !== "verified") {
|
|
159
|
+
throw new Error(`${label}.verdict must be verified, got ${value.verdict}`);
|
|
160
|
+
}
|
|
161
|
+
if (value.coverage?.complete !== true) {
|
|
162
|
+
throw new Error(`${label}.coverage.complete must be true`);
|
|
163
|
+
}
|
|
164
|
+
if (value.source?.dirty !== false) {
|
|
165
|
+
throw new Error(`${label}.source.dirty must be false`);
|
|
166
|
+
}
|
|
167
|
+
if (!/^[0-9a-f]{40}$/.test(optionalString(value.source?.revision))) {
|
|
168
|
+
throw new Error(`${label}.source.revision must be an exact 40-hex revision`);
|
|
169
|
+
}
|
|
170
|
+
const platforms = Array.isArray(value.coverage?.platforms)
|
|
171
|
+
? [...new Set(value.coverage.platforms.map(String))].sort()
|
|
172
|
+
: [];
|
|
173
|
+
if (platforms.length === 0) throw new Error(`${label}.coverage.platforms must be non-empty`);
|
|
174
|
+
if (!Array.isArray(value.residualRisk)) throw new Error(`${label}.residualRisk must be an array`);
|
|
175
|
+
return {
|
|
176
|
+
path: optionalString(meta.path),
|
|
177
|
+
sha256: optionalString(meta.sha256),
|
|
178
|
+
schema: value.schema,
|
|
179
|
+
product: value.product,
|
|
180
|
+
passportRoot: value.passportRoot,
|
|
181
|
+
contractRoot: value.contractRoot,
|
|
182
|
+
registryRoot: value.registryRoot,
|
|
183
|
+
verdict: value.verdict,
|
|
184
|
+
source: structuredClone(value.source),
|
|
185
|
+
coverage: structuredClone(value.coverage),
|
|
186
|
+
platforms,
|
|
187
|
+
residualRisk: structuredClone(value.residualRisk),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function createInvariantPassportGate(passportMetas = []) {
|
|
192
|
+
const passports = passportMetas.filter((meta) => meta?.value).map(normalizeInvariantPassport);
|
|
193
|
+
if (passports.length === 0) return undefined;
|
|
194
|
+
return {
|
|
195
|
+
contract: INVARIANT_PASSPORT_GATE_CONTRACT,
|
|
196
|
+
result: "passed",
|
|
197
|
+
passports,
|
|
198
|
+
responsibility: {
|
|
199
|
+
invariantSemanticsOwner: "consumer",
|
|
200
|
+
passportVerificationOwner: "consumer",
|
|
201
|
+
releaseAdmissionOwner: "Buildchain",
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
131
206
|
export function sha256File(filePath) {
|
|
132
207
|
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
133
208
|
}
|
|
@@ -411,7 +486,12 @@ function parseJsonCommandOutput({ command = "", cwd = process.cwd(), label = "co
|
|
|
411
486
|
if (!stdout) {
|
|
412
487
|
throw new Error(`${label} produced no JSON on stdout`);
|
|
413
488
|
}
|
|
414
|
-
|
|
489
|
+
let parsed;
|
|
490
|
+
try {
|
|
491
|
+
parsed = JSON.parse(stdout);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
throw new Error(`${label} output must be valid JSON: ${error.message}`, { cause: error });
|
|
494
|
+
}
|
|
415
495
|
return {
|
|
416
496
|
value: parsed,
|
|
417
497
|
path: "",
|
|
@@ -1016,6 +1096,7 @@ export function createReleasePassport({
|
|
|
1016
1096
|
assets = [],
|
|
1017
1097
|
packageSet = undefined,
|
|
1018
1098
|
anchorManifest = undefined,
|
|
1099
|
+
versionMaterial = undefined,
|
|
1019
1100
|
publishEvidence = undefined,
|
|
1020
1101
|
trustedPublishing = undefined,
|
|
1021
1102
|
transaction = undefined,
|
|
@@ -1030,6 +1111,7 @@ export function createReleasePassport({
|
|
|
1030
1111
|
kfd1 = undefined,
|
|
1031
1112
|
kfd2Claims = [],
|
|
1032
1113
|
kfd3 = undefined,
|
|
1114
|
+
invariantPassports = undefined,
|
|
1033
1115
|
controllerReceipts = [],
|
|
1034
1116
|
controllerReceiptReferences = [],
|
|
1035
1117
|
} = {}) {
|
|
@@ -1186,6 +1268,7 @@ export function createReleasePassport({
|
|
|
1186
1268
|
...(normalizedPackageSet ? { packageSet: normalizedPackageSet } : {}),
|
|
1187
1269
|
...(normalizedPublishSummary ? { publish: normalizedPublishSummary } : {}),
|
|
1188
1270
|
...(anchorManifest ? { anchorManifest } : {}),
|
|
1271
|
+
...(versionMaterial ? { versionMaterial } : {}),
|
|
1189
1272
|
...(normalizedTrustedPublishing ? { trustedPublishing: normalizedTrustedPublishing } : {}),
|
|
1190
1273
|
...(normalizedTransaction ? { transaction: normalizedTransaction } : {}),
|
|
1191
1274
|
...(normalizedPromotionRouting ? { promotionRouting: normalizedPromotionRouting } : {}),
|
|
@@ -1196,6 +1279,7 @@ export function createReleasePassport({
|
|
|
1196
1279
|
...(normalizedKfd1 ? { [normalizedKfd1.key || kfd1Metadata.key]: normalizedKfd1.passportSection } : {}),
|
|
1197
1280
|
...(normalizedKfd2 ? { "kfd-2": normalizedKfd2 } : {}),
|
|
1198
1281
|
...(normalizedKfd3 ? { [normalizedKfd3.key || "kfd-3"]: normalizedKfd3.passportSection } : {}),
|
|
1282
|
+
...(invariantPassports ? { invariantPassports } : {}),
|
|
1199
1283
|
...(normalizedControllerReceipts.length > 0 ? { controllerReceipts: normalizedControllerReceipts } : {}),
|
|
1200
1284
|
versionImpact: normalizedImpact.versionImpact,
|
|
1201
1285
|
surfaceImpacts: normalizedImpact.surfaceImpacts,
|
|
@@ -1238,6 +1322,7 @@ export function createReleasePassport({
|
|
|
1238
1322
|
kfd1: normalizedKfd1 ? `${normalizedKfd1.key || kfd1Metadata.key}` : "",
|
|
1239
1323
|
kfd2: normalizedKfd2 ? "kfd-2" : "",
|
|
1240
1324
|
kfd3: normalizedKfd3 ? `${normalizedKfd3.key || "kfd-3"}` : "",
|
|
1325
|
+
invariantPassports: invariantPassports ? "invariantPassports" : "",
|
|
1241
1326
|
impact: impactPath,
|
|
1242
1327
|
checkReport: checkReportPath,
|
|
1243
1328
|
agentIndex: agentIndexPath,
|
|
@@ -1267,6 +1352,7 @@ export function collectGitHubReleasePassport({
|
|
|
1267
1352
|
trustedPublishingJson = "",
|
|
1268
1353
|
transactionJson = "",
|
|
1269
1354
|
anchorManifestJson = "",
|
|
1355
|
+
versionMaterialJson = "",
|
|
1270
1356
|
impactJson = "",
|
|
1271
1357
|
buildSummaryJson = "",
|
|
1272
1358
|
buildFactsJsons = [],
|
|
@@ -1277,6 +1363,8 @@ export function collectGitHubReleasePassport({
|
|
|
1277
1363
|
kfd3PrebuildWitnessJsons = [],
|
|
1278
1364
|
kfd3ArtifactWitnessJsons = [],
|
|
1279
1365
|
kfd3ArtifactVerifyCommand = "",
|
|
1366
|
+
invariantPassportJsons = [],
|
|
1367
|
+
invariantPassportCommand = "",
|
|
1280
1368
|
controllerReceiptReferences = [],
|
|
1281
1369
|
basePassportJson = "",
|
|
1282
1370
|
requireBaseKfd = false,
|
|
@@ -1292,6 +1380,11 @@ export function collectGitHubReleasePassport({
|
|
|
1292
1380
|
const trustedPublishing = parseJsonInput(trustedPublishingJson, undefined, { cwd, label: "trustedPublishingJson" });
|
|
1293
1381
|
const transactionMeta = parseJsonInputWithMeta(transactionJson, undefined, { cwd, label: "transactionJson" });
|
|
1294
1382
|
const anchorManifest = normalizeAnchorManifest(parseJsonInputWithMeta(anchorManifestJson, undefined, { cwd, label: "anchorManifestJson" }));
|
|
1383
|
+
const versionMaterial = parseJsonInput(
|
|
1384
|
+
versionMaterialJson,
|
|
1385
|
+
undefined,
|
|
1386
|
+
{ cwd, label: "versionMaterialJson" },
|
|
1387
|
+
);
|
|
1295
1388
|
const impactMeta = parseJsonInputWithMeta(impactJson, undefined, { cwd, label: "impactJson" });
|
|
1296
1389
|
const buildSummaryMeta = parseJsonInputWithMeta(buildSummaryJson, undefined, { cwd, label: "buildSummaryJson" });
|
|
1297
1390
|
const buildFactMetas = (buildFactsJsons || [])
|
|
@@ -1324,6 +1417,17 @@ export function collectGitHubReleasePassport({
|
|
|
1324
1417
|
cwd,
|
|
1325
1418
|
label: "KFD-3 artifact verify command",
|
|
1326
1419
|
});
|
|
1420
|
+
const invariantPassportMetas = (invariantPassportJsons || [])
|
|
1421
|
+
.filter(Boolean)
|
|
1422
|
+
.map((passportJson) => parseJsonInputWithMeta(passportJson, undefined, { cwd, label: "invariantPassportJsons entry" }))
|
|
1423
|
+
.filter((meta) => meta.value);
|
|
1424
|
+
const invariantPassportCommandMeta = parseJsonCommandOutput({
|
|
1425
|
+
command: invariantPassportCommand,
|
|
1426
|
+
cwd,
|
|
1427
|
+
label: "invariant passport command",
|
|
1428
|
+
});
|
|
1429
|
+
if (invariantPassportCommandMeta.value) invariantPassportMetas.push(invariantPassportCommandMeta);
|
|
1430
|
+
const invariantPassports = createInvariantPassportGate(invariantPassportMetas);
|
|
1327
1431
|
const kfd3ArtifactWitnesses = [
|
|
1328
1432
|
...kfd3ArtifactWitnessMetas.map((meta) => meta.value),
|
|
1329
1433
|
...(kfd3ArtifactCommandMeta.value ? [kfd3ArtifactCommandMeta.value] : []),
|
|
@@ -1369,6 +1473,7 @@ export function collectGitHubReleasePassport({
|
|
|
1369
1473
|
assets,
|
|
1370
1474
|
packageSet,
|
|
1371
1475
|
anchorManifest,
|
|
1476
|
+
versionMaterial,
|
|
1372
1477
|
publishEvidence: publishEvidenceMeta.value,
|
|
1373
1478
|
trustedPublishing,
|
|
1374
1479
|
transaction: transactionMeta.value,
|
|
@@ -1400,6 +1505,7 @@ export function collectGitHubReleasePassport({
|
|
|
1400
1505
|
kfd1,
|
|
1401
1506
|
kfd2Claims: kfd2ClaimMetas.map((meta) => meta.value),
|
|
1402
1507
|
kfd3,
|
|
1508
|
+
invariantPassports,
|
|
1403
1509
|
controllerReceiptReferences,
|
|
1404
1510
|
publishEvidencePath: publishEvidenceMeta.path ? path.relative(resolvedOutputDir, publishEvidenceMeta.path).split(path.sep).join("/") : "",
|
|
1405
1511
|
transactionStatePath: transactionMeta.path ? path.relative(resolvedOutputDir, transactionMeta.path).split(path.sep).join("/") : "",
|
|
@@ -1881,6 +1987,56 @@ export function createReleaseCheckReport({
|
|
|
1881
1987
|
issues.push(issue("error", "anchorManifest.fields", "anchorManifest.fields must be an object"));
|
|
1882
1988
|
}
|
|
1883
1989
|
}
|
|
1990
|
+
if (passport?.versionMaterial) {
|
|
1991
|
+
if (passport.versionMaterial.contract !== "kungfu-buildchain-anchored-version-material/v1") {
|
|
1992
|
+
issues.push(issue(
|
|
1993
|
+
"error",
|
|
1994
|
+
"versionMaterial.contract",
|
|
1995
|
+
"versionMaterial contract must be kungfu-buildchain-anchored-version-material/v1",
|
|
1996
|
+
));
|
|
1997
|
+
}
|
|
1998
|
+
if (!passport.versionMaterial.alpha?.tree || !passport.versionMaterial.release?.tree) {
|
|
1999
|
+
issues.push(issue(
|
|
2000
|
+
"error",
|
|
2001
|
+
"versionMaterial.tree",
|
|
2002
|
+
"versionMaterial must record alpha and release tree identities",
|
|
2003
|
+
));
|
|
2004
|
+
}
|
|
2005
|
+
const allowedPaths = Array.isArray(passport.versionMaterial.allowedPaths)
|
|
2006
|
+
? passport.versionMaterial.allowedPaths
|
|
2007
|
+
: [];
|
|
2008
|
+
const derivedFiles = Array.isArray(passport.versionMaterial.derivedFiles)
|
|
2009
|
+
? passport.versionMaterial.derivedFiles
|
|
2010
|
+
: [];
|
|
2011
|
+
for (const [index, file] of derivedFiles.entries()) {
|
|
2012
|
+
if (!file?.path || !file?.sha256 || !allowedPaths.includes(file.path)) {
|
|
2013
|
+
issues.push(issue(
|
|
2014
|
+
"error",
|
|
2015
|
+
`versionMaterial.derivedFiles[${index}]`,
|
|
2016
|
+
"derived version material must have a path, digest, and matching allowed path",
|
|
2017
|
+
));
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
for (const side of ["alpha", "release"]) {
|
|
2021
|
+
const material = Array.isArray(passport.versionMaterial[side]?.material)
|
|
2022
|
+
? passport.versionMaterial[side].material
|
|
2023
|
+
: [];
|
|
2024
|
+
for (const [index, file] of material.entries()) {
|
|
2025
|
+
if (
|
|
2026
|
+
!file?.path ||
|
|
2027
|
+
!allowedPaths.includes(file.path) ||
|
|
2028
|
+
file.present !== true ||
|
|
2029
|
+
!/^sha256:[0-9a-f]{64}$/.test(file.sha256 || "")
|
|
2030
|
+
) {
|
|
2031
|
+
issues.push(issue(
|
|
2032
|
+
"error",
|
|
2033
|
+
`versionMaterial.${side}.material[${index}]`,
|
|
2034
|
+
"version material must have an allowed path, present bytes, and sha256 digest",
|
|
2035
|
+
));
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
1884
2040
|
if (!passport?.runnerPolicy?.productionDefault) {
|
|
1885
2041
|
issues.push(issue("warning", "runnerPolicy.productionDefault", "runner policy should record the production default"));
|
|
1886
2042
|
}
|
|
@@ -1917,6 +2073,35 @@ export function createReleaseCheckReport({
|
|
|
1917
2073
|
issues.push(issue("error", "kfd-3.metadata", error.message));
|
|
1918
2074
|
}
|
|
1919
2075
|
}
|
|
2076
|
+
if (passport?.invariantPassports) {
|
|
2077
|
+
const section = passport.invariantPassports;
|
|
2078
|
+
if (section.contract !== INVARIANT_PASSPORT_GATE_CONTRACT) {
|
|
2079
|
+
issues.push(issue("error", "invariantPassports.contract", `invariantPassports.contract must be ${INVARIANT_PASSPORT_GATE_CONTRACT}`));
|
|
2080
|
+
}
|
|
2081
|
+
if (section.result !== "passed") {
|
|
2082
|
+
issues.push(issue("error", "invariantPassports.result", "invariantPassports.result must be passed"));
|
|
2083
|
+
}
|
|
2084
|
+
if (!Array.isArray(section.passports) || section.passports.length === 0) {
|
|
2085
|
+
issues.push(issue("error", "invariantPassports.empty", "invariantPassports must contain at least one verified passport"));
|
|
2086
|
+
}
|
|
2087
|
+
const acceptedSourceShas = new Set([
|
|
2088
|
+
passport?.release?.sourceSha,
|
|
2089
|
+
passport?.release?.builtSourceSha,
|
|
2090
|
+
passport?.release?.promotionChannelSha,
|
|
2091
|
+
].filter(Boolean));
|
|
2092
|
+
for (const [index, entry] of (section.passports || []).entries()) {
|
|
2093
|
+
const prefix = `invariantPassports.passports[${index}]`;
|
|
2094
|
+
if (entry.verdict !== "verified") issues.push(issue("error", `${prefix}.verdict`, `${prefix}.verdict must be verified`));
|
|
2095
|
+
if (entry.coverage?.complete !== true) issues.push(issue("error", `${prefix}.coverage`, `${prefix}.coverage.complete must be true`));
|
|
2096
|
+
if (entry.source?.dirty !== false) issues.push(issue("error", `${prefix}.source.dirty`, `${prefix}.source.dirty must be false`));
|
|
2097
|
+
if (acceptedSourceShas.size > 0 && !acceptedSourceShas.has(entry.source?.revision)) {
|
|
2098
|
+
issues.push(issue("error", `${prefix}.source.revision`, `${prefix}.source.revision must match a release source identity`));
|
|
2099
|
+
}
|
|
2100
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(optionalString(entry.passportRoot))) issues.push(issue("error", `${prefix}.passportRoot`, `${prefix}.passportRoot is invalid`));
|
|
2101
|
+
if (!Array.isArray(entry.platforms) || entry.platforms.length === 0) issues.push(issue("error", `${prefix}.platforms`, `${prefix}.platforms must be non-empty`));
|
|
2102
|
+
if (!Array.isArray(entry.residualRisk)) issues.push(issue("error", `${prefix}.residualRisk`, `${prefix}.residualRisk must be an array`));
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
1920
2105
|
if (impactVersion) {
|
|
1921
2106
|
if (!impactLine) {
|
|
1922
2107
|
issues.push(issue("error", "impact.release.line", "version-bound impact requires release.line"));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createAnchoredVersionMaterialEvidence } from "../packages/core/anchored-version-material.js";
|
|
5
|
+
|
|
6
|
+
function env(name, fallback = "") {
|
|
7
|
+
return process.env[name] || fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function writeOutput(name, value) {
|
|
11
|
+
if (!process.env.GITHUB_OUTPUT) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${String(value)}\n`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const cwd = path.resolve(env("BUILDCHAIN_ANCHORED_SOURCE_CWD", process.cwd()));
|
|
19
|
+
const outputPath = path.resolve(
|
|
20
|
+
env(
|
|
21
|
+
"BUILDCHAIN_ANCHORED_MATERIAL_OUTPUT",
|
|
22
|
+
".buildchain/artifacts/anchored-version-material.json",
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
const evidence = createAnchoredVersionMaterialEvidence({
|
|
26
|
+
cwd,
|
|
27
|
+
targetChannel: env("BUILDCHAIN_ANCHORED_TARGET_CHANNEL"),
|
|
28
|
+
targetRef: env("BUILDCHAIN_ANCHORED_TARGET_REF"),
|
|
29
|
+
alphaRef: env("BUILDCHAIN_ANCHORED_ALPHA_REF"),
|
|
30
|
+
releaseRef: env("BUILDCHAIN_ANCHORED_RELEASE_REF", "HEAD"),
|
|
31
|
+
runLifecycle: env("BUILDCHAIN_ANCHORED_RUN_LIFECYCLE", "true") !== "false",
|
|
32
|
+
});
|
|
33
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
34
|
+
fs.writeFileSync(outputPath, `${JSON.stringify(evidence, null, 2)}\n`);
|
|
35
|
+
writeOutput("anchored-version-material-applicable", evidence.applicable === true);
|
|
36
|
+
writeOutput("anchored-version-material-digest", evidence.digest || "");
|
|
37
|
+
writeOutput("anchored-version-material-path", outputPath);
|
|
38
|
+
process.stdout.write(`${JSON.stringify(evidence)}\n`);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`anchored version material: ${error.message}`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
@@ -26,6 +26,7 @@ const requiredPaths = [
|
|
|
26
26
|
"bin/buildchain.mjs",
|
|
27
27
|
"packages/core/homebrew.js",
|
|
28
28
|
"packages/core/artifact-verification-envelope.js",
|
|
29
|
+
"packages/core/anchored-version-material.js",
|
|
29
30
|
"packages/core/build-facts.js",
|
|
30
31
|
"packages/core/publication-package.js",
|
|
31
32
|
"packages/core/release-line-bootstrap.js",
|
|
@@ -58,6 +59,7 @@ const requiredPaths = [
|
|
|
58
59
|
"scripts/generate-release-candidate-passport.mjs",
|
|
59
60
|
"scripts/shifu-gate-profile.mjs",
|
|
60
61
|
"scripts/artifact-relay-s3.mjs",
|
|
62
|
+
"scripts/anchored-version-material.mjs",
|
|
61
63
|
"scripts/npm-publish-dry-run.mjs",
|
|
62
64
|
"scripts/npm-publish-transaction.mjs",
|
|
63
65
|
"scripts/publication-package.mjs",
|
|
@@ -876,6 +878,10 @@ for (const requiredSnippet of [
|
|
|
876
878
|
"release-passport-kfd-3-artifact-witness-jsons:",
|
|
877
879
|
"release-passport-kfd-3-artifact-witness-jsons: ${{ inputs.release-passport-kfd-3-artifact-witness-jsons }}",
|
|
878
880
|
"release-passport-kfd-3-artifact-verify-command:",
|
|
881
|
+
"release-passport-invariant-passport-jsons:",
|
|
882
|
+
"release-passport-invariant-passport-jsons: ${{ inputs.release-passport-invariant-passport-jsons }}",
|
|
883
|
+
"release-passport-invariant-passport-command:",
|
|
884
|
+
"release-passport-invariant-passport-command: ${{ inputs.release-passport-invariant-passport-command }}",
|
|
879
885
|
"release-passport-buildchain-self-kfd:",
|
|
880
886
|
"release-passport-buildchain-self-kfd: ${{ inputs.release-passport-buildchain-self-kfd }}",
|
|
881
887
|
"github-release:",
|
|
@@ -499,6 +499,7 @@ function nodeApiMeta(exportName) {
|
|
|
499
499
|
"./buildchain-publication-authority": { group: "release-passport-trust", summary: "Buildchain-owned closed-world publication authority descriptor registry." },
|
|
500
500
|
"./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
|
|
501
501
|
"./artifact-verification-envelope": { group: "release-passport-trust", summary: "Sealed exact-root, lifecycle, identity, and existing KFD assessment inputs for KFX admission." },
|
|
502
|
+
"./anchored-version-material": { group: "reusable-build", summary: "Anchored/manual derived version material preflight, exact-tree binding, and digest evidence APIs." },
|
|
502
503
|
"./release-passport": { group: "release-passport-trust", summary: "Release passport collection, verification, explanation, and evidence APIs." },
|
|
503
504
|
"./release-passport-contract": { group: "release-passport-trust", summary: "Standalone release passport JSON Schema, ownership/check manifest, and structural validation APIs." },
|
|
504
505
|
"./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
|
|
@@ -223,7 +223,7 @@ export async function runStableCandidatePatrol(optionsInput = {}, clientInput) {
|
|
|
223
223
|
`Buildchain Stable Candidate Patrol human release-now for ${selection.candidate.version}`,
|
|
224
224
|
);
|
|
225
225
|
}
|
|
226
|
-
await client.ensureBranch(refs.sourceRef, selection.candidate.sha);
|
|
226
|
+
await client.ensureBranch(refs.sourceRef, selection.candidate.sha, refs.targetRef);
|
|
227
227
|
const pullRequest = await client.ensurePromotionPullRequest({
|
|
228
228
|
head: refs.sourceRef,
|
|
229
229
|
base: refs.targetRef,
|
|
@@ -371,15 +371,64 @@ export function createGitHubStableCandidateClient({ repository: repositoryInput,
|
|
|
371
371
|
},
|
|
372
372
|
});
|
|
373
373
|
},
|
|
374
|
-
async ensureBranch(ref, candidateSha) {
|
|
374
|
+
async ensureBranch(ref, candidateSha, targetRef) {
|
|
375
375
|
const current = await api(`/repos/${owner}/${repo}/git/ref/heads/${encodeRef(ref)}`, { allow404: true });
|
|
376
|
-
if (!
|
|
376
|
+
if (!targetRef) {
|
|
377
|
+
if (!current) {
|
|
378
|
+
return api(`/repos/${owner}/${repo}/git/refs`, { method: "POST", body: { ref: `refs/heads/${ref}`, sha: candidateSha } });
|
|
379
|
+
}
|
|
380
|
+
if (current.object.sha !== candidateSha) {
|
|
381
|
+
throw new Error(`source-lock branch ${ref} already points to ${current.object.sha}, not ${candidateSha}`);
|
|
382
|
+
}
|
|
383
|
+
return current;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const candidateCommit = await api(`/repos/${owner}/${repo}/git/commits/${candidateSha}`);
|
|
387
|
+
let sourceSha = current?.object?.sha || candidateSha;
|
|
388
|
+
if (sourceSha !== candidateSha) {
|
|
389
|
+
const sourceCommit = await api(`/repos/${owner}/${repo}/git/commits/${sourceSha}`);
|
|
390
|
+
if (sourceCommit.tree?.sha !== candidateCommit.tree?.sha) {
|
|
391
|
+
throw new Error(`source-lock branch ${ref} no longer preserves candidate ${candidateSha} tree`);
|
|
392
|
+
}
|
|
393
|
+
const candidateLineage = await api(`/repos/${owner}/${repo}/compare/${candidateSha}...${sourceSha}`);
|
|
394
|
+
if (!["ahead", "identical"].includes(candidateLineage.status)) {
|
|
395
|
+
throw new Error(`source-lock branch ${ref} no longer descends from candidate ${candidateSha}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const target = await api(`/repos/${owner}/${repo}/git/ref/heads/${encodeRef(targetRef)}`);
|
|
400
|
+
const targetSha = target.object?.sha || "";
|
|
401
|
+
const targetLineage = await api(`/repos/${owner}/${repo}/compare/${targetSha}...${sourceSha}`);
|
|
402
|
+
if (["ahead", "identical"].includes(targetLineage.status)) {
|
|
403
|
+
if (current) return current;
|
|
377
404
|
return api(`/repos/${owner}/${repo}/git/refs`, { method: "POST", body: { ref: `refs/heads/${ref}`, sha: candidateSha } });
|
|
378
405
|
}
|
|
379
|
-
|
|
380
|
-
|
|
406
|
+
|
|
407
|
+
const identity = { name: "Buildchain Patrol", email: "buildchain-patrol@kungfu.link" };
|
|
408
|
+
const reconciliation = await api(`/repos/${owner}/${repo}/git/commits`, {
|
|
409
|
+
method: "POST",
|
|
410
|
+
body: {
|
|
411
|
+
message: [
|
|
412
|
+
"chore(buildchain): reconcile stable source-lock ancestry",
|
|
413
|
+
"",
|
|
414
|
+
`Preserve the exact candidate tree from ${candidateSha} while admitting ${targetRef} at ${targetSha}.`,
|
|
415
|
+
"",
|
|
416
|
+
"Signed-off-by: Buildchain Patrol <buildchain-patrol@kungfu.link>",
|
|
417
|
+
].join("\n"),
|
|
418
|
+
tree: candidateCommit.tree.sha,
|
|
419
|
+
parents: [sourceSha, targetSha],
|
|
420
|
+
author: identity,
|
|
421
|
+
committer: identity,
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
sourceSha = reconciliation.sha;
|
|
425
|
+
if (!current) {
|
|
426
|
+
return api(`/repos/${owner}/${repo}/git/refs`, { method: "POST", body: { ref: `refs/heads/${ref}`, sha: sourceSha } });
|
|
381
427
|
}
|
|
382
|
-
return
|
|
428
|
+
return api(`/repos/${owner}/${repo}/git/refs/heads/${encodeRef(ref)}`, {
|
|
429
|
+
method: "PATCH",
|
|
430
|
+
body: { sha: sourceSha, force: false },
|
|
431
|
+
});
|
|
383
432
|
},
|
|
384
433
|
async ensurePromotionPullRequest({ head, base, title, body }) {
|
|
385
434
|
const open = await api(`/repos/${owner}/${repo}/pulls?state=open&head=${encodeURIComponent(`${owner}:${head}`)}&base=${encodeURIComponent(base)}&per_page=20`);
|