@kungfu-tech/buildchain 2.8.2-alpha.0 → 2.8.2-alpha.2
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/dist/site/agent-index.json +4 -1
- package/dist/site/artifact-schemas.json +3 -0
- package/dist/site/buildchain-contract.json +65 -13
- package/dist/site/buildchain-site.json +46 -16
- package/dist/site/kfd-claims.json +786 -0
- package/dist/site/manual-registry.json +132 -0
- package/dist/site/node-api-registry.json +93 -0
- package/dist/site/release-model.json +11 -0
- package/dist/site/release-provenance.json +4 -0
- package/dist/site/site-manifest.json +35 -16
- package/docs/MAP.md +21 -0
- package/docs/cli.md +25 -0
- package/docs/release-candidate.md +7 -7
- package/docs/release-governance.md +15 -7
- package/docs/release-passport.md +41 -3
- package/docs/site-bundle-contract.md +19 -1
- package/package.json +5 -1
- package/packages/core/buildchain-contract.js +37 -0
- package/packages/core/buildchain-kfd-claims.js +488 -0
- package/packages/core/index.js +14 -0
- package/packages/core/release-passport.js +78 -8
- package/scripts/check-inventory.mjs +79 -7
- package/scripts/ensure-github-release.mjs +9 -1
- package/scripts/generate-buildchain-kfd-witnesses.mjs +116 -0
- package/scripts/generate-site-bundle.mjs +105 -21
|
@@ -21,6 +21,7 @@ export const AGENT_INDEX_CONTRACT = "kungfu-buildchain-agent-index";
|
|
|
21
21
|
export const PRODUCT_MECHANISM_CONTRACT = "kungfu-buildchain-product-mechanism";
|
|
22
22
|
export const RELEASE_CHECK_REPORT_CONTRACT = "kungfu-buildchain-release-check-report";
|
|
23
23
|
export const KFD2_RELEASE_TRUST_PASSPORT_CONTRACT = "kungfu-buildchain-kfd-2-release-trust-passport-audit";
|
|
24
|
+
export const KFD2_TRUST_PROOF_CONTRACT = "kungfu-buildchain-kfd-2-trust-proof";
|
|
24
25
|
|
|
25
26
|
const CONTRACTS = new Set([
|
|
26
27
|
RELEASE_PASSPORT_CONTRACT,
|
|
@@ -571,6 +572,9 @@ function normalizeKfd2Claim(raw = {}, index = 0) {
|
|
|
571
572
|
: {};
|
|
572
573
|
const responsibility = claim.responsibility && typeof claim.responsibility === "object" && !Array.isArray(claim.responsibility) ? claim.responsibility : {};
|
|
573
574
|
const residualRisk = arrayOrEmpty(claim.residualRisk || claim.residual_risk);
|
|
575
|
+
const trustProof = claim.trustProof && typeof claim.trustProof === "object" && !Array.isArray(claim.trustProof)
|
|
576
|
+
? claim.trustProof
|
|
577
|
+
: undefined;
|
|
574
578
|
const missingBindings = [];
|
|
575
579
|
if (sourceBindings.length === 0) missingBindings.push("declared-sources");
|
|
576
580
|
if (machineEvidence.length === 0) missingBindings.push("machine-readable-evidence");
|
|
@@ -597,6 +601,7 @@ function normalizeKfd2Claim(raw = {}, index = 0) {
|
|
|
597
601
|
auditBoundary,
|
|
598
602
|
responsibility,
|
|
599
603
|
residualRisk,
|
|
604
|
+
...(trustProof ? { trustProof } : {}),
|
|
600
605
|
proseOnly,
|
|
601
606
|
missingBindings,
|
|
602
607
|
status,
|
|
@@ -642,6 +647,17 @@ function kfd2ClaimFromKfd1World(world = {}, index = 0) {
|
|
|
642
647
|
}
|
|
643
648
|
|
|
644
649
|
function kfd2ClaimFromKfd3Interface(entry = {}, index = 0) {
|
|
650
|
+
const witnessHashes = {
|
|
651
|
+
prebuildWitnessSha256: entry.preBuildWitnessSha256,
|
|
652
|
+
artifactWitnessSha256: entry.artifactWitnessSha256,
|
|
653
|
+
prebuildCanonicalSha256: entry.witnessEvidence?.prebuild?.canonicalSha256 || "",
|
|
654
|
+
artifactCanonicalSha256: entry.witnessEvidence?.artifact?.canonicalSha256 || "",
|
|
655
|
+
};
|
|
656
|
+
const reverseAuditBoundary = entry.reverseAudit?.auditBoundary || entry.auditBoundary;
|
|
657
|
+
const residualRisk = arrayOrEmpty(entry.residualRisk);
|
|
658
|
+
const proofResult = entry.trustProof?.result === "pass"
|
|
659
|
+
? (residualRisk.length > 0 ? "downgraded" : "passed")
|
|
660
|
+
: "failed";
|
|
645
661
|
return normalizeKfd2Claim({
|
|
646
662
|
id: `kfd-3:${entry.id || index + 1}`,
|
|
647
663
|
public: true,
|
|
@@ -658,10 +674,7 @@ function kfd2ClaimFromKfd3Interface(entry = {}, index = 0) {
|
|
|
658
674
|
{ id: "reverse-audit", result: entry.reverseAudit?.status || "" },
|
|
659
675
|
],
|
|
660
676
|
hashes: {
|
|
661
|
-
|
|
662
|
-
artifactWitnessSha256: entry.artifactWitnessSha256,
|
|
663
|
-
prebuildCanonicalSha256: entry.witnessEvidence?.prebuild?.canonicalSha256 || "",
|
|
664
|
-
artifactCanonicalSha256: entry.witnessEvidence?.artifact?.canonicalSha256 || "",
|
|
677
|
+
...witnessHashes,
|
|
665
678
|
},
|
|
666
679
|
artifacts: entry.artifactWitness?.artifact?.name || entry.artifactWitness?.artifact?.path
|
|
667
680
|
? [entry.artifactWitness.artifact]
|
|
@@ -673,10 +686,25 @@ function kfd2ClaimFromKfd3Interface(entry = {}, index = 0) {
|
|
|
673
686
|
},
|
|
674
687
|
auditBoundary: entry.auditBoundary,
|
|
675
688
|
responsibility: entry.responsibility,
|
|
676
|
-
residualRisk
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
: "
|
|
689
|
+
residualRisk,
|
|
690
|
+
trustProof: {
|
|
691
|
+
contract: KFD2_TRUST_PROOF_CONTRACT,
|
|
692
|
+
source: "kfd-3-collaboration-interface-release-gate",
|
|
693
|
+
result: proofResult,
|
|
694
|
+
statement: entry.trustProof?.statement || "",
|
|
695
|
+
kfd3TrustProof: {
|
|
696
|
+
contract: entry.trustProof?.contract || "",
|
|
697
|
+
result: entry.trustProof?.result || "",
|
|
698
|
+
releaseStatus: entry.trustProof?.releaseStatus || entry.releaseStatus || "",
|
|
699
|
+
},
|
|
700
|
+
witnessHashes,
|
|
701
|
+
declaredCapabilityVerification: entry.declaredCapabilityVerification,
|
|
702
|
+
reverseAudit: entry.reverseAudit,
|
|
703
|
+
reverseAuditBoundary,
|
|
704
|
+
residualRisk,
|
|
705
|
+
responsibility: entry.responsibility,
|
|
706
|
+
},
|
|
707
|
+
status: proofResult,
|
|
680
708
|
}, index);
|
|
681
709
|
}
|
|
682
710
|
|
|
@@ -1220,6 +1248,48 @@ function validateKfd2ReleaseTrustPassportAudit(section, issues) {
|
|
|
1220
1248
|
proseOnly: Boolean(claim.proseOnly),
|
|
1221
1249
|
}));
|
|
1222
1250
|
}
|
|
1251
|
+
if (String(claim.id || "").startsWith("kfd-3:")) {
|
|
1252
|
+
const trustProof = claim.trustProof && typeof claim.trustProof === "object" && !Array.isArray(claim.trustProof)
|
|
1253
|
+
? claim.trustProof
|
|
1254
|
+
: undefined;
|
|
1255
|
+
if (!trustProof || trustProof.contract !== KFD2_TRUST_PROOF_CONTRACT) {
|
|
1256
|
+
issues.push(issue("error", `kfd-2.claims[${index}].trustProof`, "KFD-3 generated public claims must include a KFD-2 trust proof", {
|
|
1257
|
+
id: claim.id || "",
|
|
1258
|
+
expectedContract: KFD2_TRUST_PROOF_CONTRACT,
|
|
1259
|
+
}));
|
|
1260
|
+
} else {
|
|
1261
|
+
if (!trustProof.witnessHashes?.prebuildWitnessSha256 || !trustProof.witnessHashes?.artifactWitnessSha256) {
|
|
1262
|
+
issues.push(issue("error", `kfd-2.claims[${index}].trustProof.witnessHashes`, "KFD-2 trust proof must preserve KFD-3 witness hashes", {
|
|
1263
|
+
id: claim.id || "",
|
|
1264
|
+
}));
|
|
1265
|
+
}
|
|
1266
|
+
if (!trustProof.declaredCapabilityVerification?.result) {
|
|
1267
|
+
issues.push(issue("error", `kfd-2.claims[${index}].trustProof.declaredCapabilityVerification`, "KFD-2 trust proof must preserve declared capability verification", {
|
|
1268
|
+
id: claim.id || "",
|
|
1269
|
+
}));
|
|
1270
|
+
}
|
|
1271
|
+
if (!trustProof.reverseAudit?.status) {
|
|
1272
|
+
issues.push(issue("error", `kfd-2.claims[${index}].trustProof.reverseAudit`, "KFD-2 trust proof must preserve reverse audit result", {
|
|
1273
|
+
id: claim.id || "",
|
|
1274
|
+
}));
|
|
1275
|
+
}
|
|
1276
|
+
if (!trustProof.reverseAuditBoundary || typeof trustProof.reverseAuditBoundary !== "object" || Array.isArray(trustProof.reverseAuditBoundary)) {
|
|
1277
|
+
issues.push(issue("error", `kfd-2.claims[${index}].trustProof.reverseAuditBoundary`, "KFD-2 trust proof must preserve the reverse audit boundary", {
|
|
1278
|
+
id: claim.id || "",
|
|
1279
|
+
}));
|
|
1280
|
+
}
|
|
1281
|
+
if (!Array.isArray(trustProof.residualRisk)) {
|
|
1282
|
+
issues.push(issue("error", `kfd-2.claims[${index}].trustProof.residualRisk`, "KFD-2 trust proof must preserve residual risk as an array", {
|
|
1283
|
+
id: claim.id || "",
|
|
1284
|
+
}));
|
|
1285
|
+
}
|
|
1286
|
+
if (!trustProof.responsibility?.registryFactsOwner || !trustProof.responsibility?.artifactVerificationOwner || !trustProof.responsibility?.releasePassportProofOwner) {
|
|
1287
|
+
issues.push(issue("error", `kfd-2.claims[${index}].trustProof.responsibility`, "KFD-2 trust proof must preserve KFD-3 responsibility state", {
|
|
1288
|
+
id: claim.id || "",
|
|
1289
|
+
}));
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1223
1293
|
}
|
|
1224
1294
|
if (section.status === "failed") {
|
|
1225
1295
|
issues.push(issue("error", "kfd-2.status", "kfd-2 release trust passport audit must not contain failed public claims"));
|
|
@@ -30,6 +30,7 @@ const requiredPaths = [
|
|
|
30
30
|
"scripts/create-release-bundle.mjs",
|
|
31
31
|
"scripts/ensure-github-release.mjs",
|
|
32
32
|
"scripts/generate-site-bundle.mjs",
|
|
33
|
+
"scripts/generate-buildchain-kfd-witnesses.mjs",
|
|
33
34
|
"scripts/generate-release-candidate-passport.mjs",
|
|
34
35
|
"scripts/artifact-relay-s3.mjs",
|
|
35
36
|
"scripts/npm-publish-dry-run.mjs",
|
|
@@ -105,6 +106,18 @@ if (rootPackage.exports?.["./release-passport"] !== "./packages/core/release-pas
|
|
|
105
106
|
if (rootPackage.exports?.["./release-propagation"] !== "./packages/core/release-propagation.js") {
|
|
106
107
|
throw new Error("root package must export @kungfu-tech/buildchain/release-propagation");
|
|
107
108
|
}
|
|
109
|
+
if (rootPackage.exports?.["./buildchain-kfd-claims"] !== "./packages/core/buildchain-kfd-claims.js") {
|
|
110
|
+
throw new Error("root package must export @kungfu-tech/buildchain/buildchain-kfd-claims");
|
|
111
|
+
}
|
|
112
|
+
if (rootPackage.exports?.["./site/manual-registry.json"] !== "./dist/site/manual-registry.json") {
|
|
113
|
+
throw new Error("root package must export @kungfu-tech/buildchain/site/manual-registry.json");
|
|
114
|
+
}
|
|
115
|
+
if (rootPackage.exports?.["./site/node-api-registry.json"] !== "./dist/site/node-api-registry.json") {
|
|
116
|
+
throw new Error("root package must export @kungfu-tech/buildchain/site/node-api-registry.json");
|
|
117
|
+
}
|
|
118
|
+
if (rootPackage.exports?.["./site/kfd-claims.json"] !== "./dist/site/kfd-claims.json") {
|
|
119
|
+
throw new Error("root package must export @kungfu-tech/buildchain/site/kfd-claims.json");
|
|
120
|
+
}
|
|
108
121
|
if (rootPackage.publishConfig?.access !== "public") {
|
|
109
122
|
throw new Error("root package publishConfig.access must be public");
|
|
110
123
|
}
|
|
@@ -128,6 +141,7 @@ const cliSource = fs.readFileSync(path.join(root, "bin/buildchain.mjs"), "utf8")
|
|
|
128
141
|
const coreIndexSource = fs.readFileSync(path.join(root, "packages/core/index.js"), "utf8");
|
|
129
142
|
const versioningDoc = fs.readFileSync(path.join(root, "docs/versioning.md"), "utf8");
|
|
130
143
|
const cliDoc = fs.readFileSync(path.join(root, "docs/cli.md"), "utf8");
|
|
144
|
+
const docsMap = fs.readFileSync(path.join(root, "docs/MAP.md"), "utf8");
|
|
131
145
|
const installDoc = fs.readFileSync(path.join(root, "docs/install.md"), "utf8");
|
|
132
146
|
if (!cliSource.startsWith("#!/usr/bin/env node")) {
|
|
133
147
|
throw new Error("bin/buildchain.mjs must be executable with a node shebang");
|
|
@@ -150,6 +164,20 @@ if (!coreIndexSource.includes("createBuildchainContractWorld")) {
|
|
|
150
164
|
if (!coreIndexSource.includes("KFD2_RELEASE_TRUST_PASSPORT_CONTRACT")) {
|
|
151
165
|
throw new Error("packages/core/index.js must export KFD-2 release trust passport contract");
|
|
152
166
|
}
|
|
167
|
+
if (!coreIndexSource.includes("KFD2_TRUST_PROOF_CONTRACT")) {
|
|
168
|
+
throw new Error("packages/core/index.js must export KFD-2 trust proof contract");
|
|
169
|
+
}
|
|
170
|
+
for (const requiredSnippet of [
|
|
171
|
+
"createBuildchainKfdClaimRegistry",
|
|
172
|
+
"createBuildchainKfd1Witness",
|
|
173
|
+
"createBuildchainKfd2Claims",
|
|
174
|
+
"createBuildchainKfd3PrebuildWitness",
|
|
175
|
+
"BUILDCHAIN_AGENT_MANUALS",
|
|
176
|
+
]) {
|
|
177
|
+
if (!coreIndexSource.includes(requiredSnippet)) {
|
|
178
|
+
throw new Error(`packages/core/index.js must export Buildchain self KFD claim API: ${requiredSnippet}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
153
181
|
for (const requiredSnippet of [
|
|
154
182
|
"createKfd1ReleaseGateEvidence",
|
|
155
183
|
"resolveKfd1Metadata",
|
|
@@ -204,6 +232,20 @@ for (const requiredSnippet of [
|
|
|
204
232
|
throw new Error(`release passport doc missing KFD-1 gate snippet: ${requiredSnippet}`);
|
|
205
233
|
}
|
|
206
234
|
}
|
|
235
|
+
for (const requiredSnippet of [
|
|
236
|
+
"Capability Coverage",
|
|
237
|
+
"KFD-1 / KFD-2 / KFD-3",
|
|
238
|
+
"floating `@v2`",
|
|
239
|
+
"npm publish transactions",
|
|
240
|
+
"GitHub Release",
|
|
241
|
+
"release propagation",
|
|
242
|
+
"manual-registry.json",
|
|
243
|
+
"node-api-registry.json",
|
|
244
|
+
]) {
|
|
245
|
+
if (!docsMap.includes(requiredSnippet)) {
|
|
246
|
+
throw new Error(`documentation map missing capability coverage snippet: ${requiredSnippet}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
207
249
|
const reusableBuildSurfaceDoc = fs.readFileSync(path.join(root, "docs/reusable-build-surface.md"), "utf8");
|
|
208
250
|
for (const requiredSnippet of [
|
|
209
251
|
"Floating Ref Contract Lock",
|
|
@@ -291,6 +333,7 @@ for (const requiredSnippet of [
|
|
|
291
333
|
"github.event.workflow_run.event == 'push'",
|
|
292
334
|
"target-sha: ${{ github.event.workflow_run.head_sha || inputs.sha || github.sha }}",
|
|
293
335
|
"github-release: true",
|
|
336
|
+
"release-passport-buildchain-self-kfd: true",
|
|
294
337
|
"publish-required-artifacts-json: \"[]\"",
|
|
295
338
|
"release-passport-impact-json: >-",
|
|
296
339
|
"Buildchain v2.8 promotes KFD self-verification",
|
|
@@ -308,17 +351,21 @@ for (const requiredSnippet of [
|
|
|
308
351
|
const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/release-candidate-promote.yml"), "utf8");
|
|
309
352
|
for (const requiredSnippet of [
|
|
310
353
|
"release-passport-kfd-1-witness-jsons:",
|
|
311
|
-
"release-passport-kfd-1-witness-jsons: ${{ inputs.release-passport-kfd-1-witness-jsons }}",
|
|
354
|
+
"release-passport-kfd-1-witness-jsons: ${{ inputs.release-passport-kfd-1-witness-jsons || steps.buildchain-self-kfd.outputs.kfd-1-witness-jsons }}",
|
|
312
355
|
"release-passport-kfd-2-claim-jsons:",
|
|
313
|
-
"release-passport-kfd-2-claim-jsons: ${{ inputs.release-passport-kfd-2-claim-jsons }}",
|
|
356
|
+
"release-passport-kfd-2-claim-jsons: ${{ inputs.release-passport-kfd-2-claim-jsons || steps.buildchain-self-kfd.outputs.kfd-2-claim-jsons }}",
|
|
314
357
|
"release-passport-kfd-3-prebuild-witness-jsons:",
|
|
315
|
-
"release-passport-kfd-3-prebuild-witness-jsons: ${{ inputs.release-passport-kfd-3-prebuild-witness-jsons }}",
|
|
358
|
+
"release-passport-kfd-3-prebuild-witness-jsons: ${{ inputs.release-passport-kfd-3-prebuild-witness-jsons || steps.buildchain-self-kfd.outputs.kfd-3-prebuild-witness-jsons }}",
|
|
316
359
|
"release-passport-kfd-3-artifact-witness-jsons:",
|
|
360
|
+
"release-passport-kfd-3-artifact-witness-jsons: ${{ inputs.release-passport-kfd-3-artifact-witness-jsons || steps.buildchain-self-kfd.outputs.kfd-3-artifact-witness-jsons }}",
|
|
317
361
|
"release-passport-kfd-3-artifact-verify-command:",
|
|
362
|
+
"release-passport-buildchain-self-kfd:",
|
|
363
|
+
"Generate Buildchain self KFD release claims",
|
|
364
|
+
"generate-buildchain-kfd-witnesses.mjs",
|
|
318
365
|
"github-release:",
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
"
|
|
366
|
+
"github-release: ${{ inputs.github-release }}",
|
|
367
|
+
"github-release-title: ${{ inputs.github-release-title }}",
|
|
368
|
+
"github-release-notes: ${{ inputs.github-release-notes }}",
|
|
322
369
|
"require-publish-source-lock: \"true\"",
|
|
323
370
|
"publish-source-ref: ${{ steps.publish-gate.outputs.ref }}",
|
|
324
371
|
"publish-source-sha: ${{ steps.publish-gate.outputs.sha }}",
|
|
@@ -374,6 +421,31 @@ for (const retiredWorkflow of [
|
|
|
374
421
|
}
|
|
375
422
|
}
|
|
376
423
|
}
|
|
424
|
+
const promoteBuildchainRefAction = fs.readFileSync(path.join(root, "actions/promote-buildchain-ref/action.yml"), "utf8");
|
|
425
|
+
const promoteBuildchainRefIndex = fs.readFileSync(path.join(root, "actions/promote-buildchain-ref/index.js"), "utf8");
|
|
426
|
+
for (const requiredSnippet of [
|
|
427
|
+
"github-release:",
|
|
428
|
+
"github-release-title:",
|
|
429
|
+
"github-release-notes:",
|
|
430
|
+
"github-release-url:",
|
|
431
|
+
"github-release-action:",
|
|
432
|
+
]) {
|
|
433
|
+
if (!promoteBuildchainRefAction.includes(requiredSnippet)) {
|
|
434
|
+
throw new Error(`promote-buildchain-ref action missing GitHub Release surface: ${requiredSnippet}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (const requiredSnippet of [
|
|
438
|
+
"ensureGitHubRelease",
|
|
439
|
+
"publishGitHubReleaseEvidence",
|
|
440
|
+
"collectGitHubReleaseEvidenceAssets",
|
|
441
|
+
"uploadReleaseAsset",
|
|
442
|
+
"result.publishTransaction?.state === \"complete\"",
|
|
443
|
+
"result.publishTransaction?.finalizationNeeded !== true",
|
|
444
|
+
]) {
|
|
445
|
+
if (!promoteBuildchainRefIndex.includes(requiredSnippet)) {
|
|
446
|
+
throw new Error(`promote-buildchain-ref index missing semver GitHub Release implementation: ${requiredSnippet}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
377
449
|
for (const forbiddenSnippet of [
|
|
378
450
|
"run: node scripts/release-candidate-resolver.mjs",
|
|
379
451
|
"uses: ./actions/promote-buildchain-ref",
|
|
@@ -490,7 +562,7 @@ for (const artifact of [
|
|
|
490
562
|
}
|
|
491
563
|
}
|
|
492
564
|
|
|
493
|
-
for (const siteFile of ["buildchain-site.json", "site-manifest.json", "cli-registry.json", "release-model.json", "buildchain-contract.json"]) {
|
|
565
|
+
for (const siteFile of ["buildchain-site.json", "site-manifest.json", "cli-registry.json", "manual-registry.json", "node-api-registry.json", "release-model.json", "buildchain-contract.json"]) {
|
|
494
566
|
if (!fs.existsSync(path.join(root, "dist", "site", siteFile))) {
|
|
495
567
|
throw new Error(`site bundle missing ${siteFile}`);
|
|
496
568
|
}
|
|
@@ -168,7 +168,15 @@ async function main() {
|
|
|
168
168
|
console.log(`github-release-make-latest=${result.metadata.makeLatest}`);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
function isCliEntrypoint() {
|
|
172
|
+
const entry = process.argv[1] || "";
|
|
173
|
+
return (
|
|
174
|
+
entry.replace(/\\/g, "/").endsWith("/ensure-github-release.mjs") &&
|
|
175
|
+
import.meta.url === pathToFileURL(entry).href
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (isCliEntrypoint()) {
|
|
172
180
|
main().catch((error) => {
|
|
173
181
|
console.error(error.message);
|
|
174
182
|
process.exit(1);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import {
|
|
7
|
+
createBuildchainKfd1Witness,
|
|
8
|
+
createBuildchainKfd2Claims,
|
|
9
|
+
createBuildchainKfd3ArtifactWitness,
|
|
10
|
+
createBuildchainKfd3PrebuildWitness,
|
|
11
|
+
} from "../packages/core/buildchain-kfd-claims.js";
|
|
12
|
+
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
13
|
+
|
|
14
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
15
|
+
const args = {
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
outputDir: ".buildchain/kfd",
|
|
18
|
+
sourceSha: process.env.BUILDCHAIN_SOURCE_SHA || "",
|
|
19
|
+
};
|
|
20
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
21
|
+
const arg = argv[index];
|
|
22
|
+
if (arg === "--cwd") {
|
|
23
|
+
args.cwd = argv[++index] || args.cwd;
|
|
24
|
+
} else if (arg === "--output-dir") {
|
|
25
|
+
args.outputDir = argv[++index] || args.outputDir;
|
|
26
|
+
} else if (arg === "--source-sha") {
|
|
27
|
+
args.sourceSha = argv[++index] || args.sourceSha;
|
|
28
|
+
} else if (arg === "--help") {
|
|
29
|
+
args.help = true;
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return args;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function gitSha(cwd) {
|
|
38
|
+
try {
|
|
39
|
+
return execFileSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" }).trim();
|
|
40
|
+
} catch {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeJson(filePath, value) {
|
|
46
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
47
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toRepoRelative(cwd, filePath) {
|
|
51
|
+
return path.relative(cwd, filePath).replace(/\\/g, "/");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function generateBuildchainKfdWitnesses({
|
|
55
|
+
cwd = process.cwd(),
|
|
56
|
+
outputDir = ".buildchain/kfd",
|
|
57
|
+
sourceSha = "",
|
|
58
|
+
emitOutputs = true,
|
|
59
|
+
} = {}) {
|
|
60
|
+
const root = path.resolve(cwd);
|
|
61
|
+
const outDir = path.resolve(root, outputDir);
|
|
62
|
+
const resolvedSourceSha = sourceSha || gitSha(root);
|
|
63
|
+
const paths = {
|
|
64
|
+
kfd1Witness: path.join(outDir, "buildchain-kfd-1-witness.json"),
|
|
65
|
+
kfd3PrebuildWitness: path.join(outDir, "buildchain-kfd-3-prebuild-witness.json"),
|
|
66
|
+
kfd3ArtifactWitness: path.join(outDir, "buildchain-kfd-3-artifact-witness.json"),
|
|
67
|
+
kfd2ClaimsDir: path.join(outDir, "kfd-2-claims"),
|
|
68
|
+
};
|
|
69
|
+
writeJson(paths.kfd1Witness, createBuildchainKfd1Witness({ root, sourceSha: resolvedSourceSha }));
|
|
70
|
+
writeJson(paths.kfd3PrebuildWitness, createBuildchainKfd3PrebuildWitness({ root, sourceSha: resolvedSourceSha }));
|
|
71
|
+
writeJson(paths.kfd3ArtifactWitness, createBuildchainKfd3ArtifactWitness({ root, sourceSha: resolvedSourceSha }));
|
|
72
|
+
const witnessFiles = {
|
|
73
|
+
"kfd-1-witness": toRepoRelative(root, paths.kfd1Witness),
|
|
74
|
+
"kfd-3-prebuild-witness": toRepoRelative(root, paths.kfd3PrebuildWitness),
|
|
75
|
+
"kfd-3-artifact-witness": toRepoRelative(root, paths.kfd3ArtifactWitness),
|
|
76
|
+
};
|
|
77
|
+
const kfd2ClaimPaths = createBuildchainKfd2Claims({ root, witnessFiles }).map((claim) => {
|
|
78
|
+
const slug = String(claim.id || "claim")
|
|
79
|
+
.replace(/^claim:/, "")
|
|
80
|
+
.replace(/[^0-9A-Za-z._-]+/g, "-")
|
|
81
|
+
.replace(/^-+|-+$/g, "") || "claim";
|
|
82
|
+
const claimPath = path.join(paths.kfd2ClaimsDir, `${slug}.json`);
|
|
83
|
+
writeJson(claimPath, claim);
|
|
84
|
+
return claimPath;
|
|
85
|
+
});
|
|
86
|
+
const outputs = {
|
|
87
|
+
"kfd-1-witness-jsons": toRepoRelative(root, paths.kfd1Witness),
|
|
88
|
+
"kfd-2-claim-jsons": kfd2ClaimPaths.map((claimPath) => toRepoRelative(root, claimPath)).join(","),
|
|
89
|
+
"kfd-3-prebuild-witness-jsons": toRepoRelative(root, paths.kfd3PrebuildWitness),
|
|
90
|
+
"kfd-3-artifact-witness-jsons": toRepoRelative(root, paths.kfd3ArtifactWitness),
|
|
91
|
+
"source-sha": resolvedSourceSha,
|
|
92
|
+
"output-dir": toRepoRelative(root, outDir),
|
|
93
|
+
};
|
|
94
|
+
if (emitOutputs) {
|
|
95
|
+
writeGitHubOutputs(outputs);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: 1,
|
|
99
|
+
contract: "kungfu-buildchain-self-kfd-witness-generation",
|
|
100
|
+
outputs,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
105
|
+
try {
|
|
106
|
+
const args = parseArgs();
|
|
107
|
+
if (args.help) {
|
|
108
|
+
process.stdout.write("Usage: generate-buildchain-kfd-witnesses [--cwd <repo>] [--output-dir <dir>] [--source-sha <sha>]\n");
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
process.stdout.write(`${JSON.stringify(generateBuildchainKfdWitnesses(args), null, 2)}\n`);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`buildchain self KFD witnesses: ${error.message}`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { createBuildchainContractWorld } from "../packages/core/buildchain-contract.js";
|
|
7
|
+
import {
|
|
8
|
+
BUILDCHAIN_AGENT_MANUALS,
|
|
9
|
+
createBuildchainKfdClaimRegistry,
|
|
10
|
+
} from "../packages/core/buildchain-kfd-claims.js";
|
|
6
11
|
|
|
7
12
|
const SITE_BUNDLE_CONTRACT = "kungfu-buildchain-site-bundle";
|
|
8
13
|
const root = path.resolve(import.meta.dirname, "..");
|
|
@@ -21,6 +26,13 @@ function stableJson(value) {
|
|
|
21
26
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
function sha256File(rel) {
|
|
30
|
+
const filePath = path.join(root, rel);
|
|
31
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isFile()
|
|
32
|
+
? `sha256:${crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex")}`
|
|
33
|
+
: "";
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
function existingJson(filePath) {
|
|
25
37
|
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
|
|
26
38
|
}
|
|
@@ -32,30 +44,14 @@ function docEntry(id, title, pathName, plane) {
|
|
|
32
44
|
path: pathName,
|
|
33
45
|
plane,
|
|
34
46
|
exists: fs.existsSync(path.join(root, pathName)),
|
|
47
|
+
digest: sha256File(pathName),
|
|
35
48
|
};
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
function buildSiteBundle() {
|
|
39
52
|
const packageJson = readJson("package.json");
|
|
40
53
|
const inventory = readJson("tests/buildchain-inventory.json");
|
|
41
|
-
const docs =
|
|
42
|
-
docEntry("install", "Install and verify Buildchain", "docs/install.md", "use"),
|
|
43
|
-
docEntry("release-passport", "Release Passport protocol", "docs/release-passport.md", "verify"),
|
|
44
|
-
docEntry("release-propagation", "Release propagation", "docs/release-propagation.md", "use"),
|
|
45
|
-
docEntry("binary-distribution", "Binary distribution contract", "docs/binary-distribution.md", "verify"),
|
|
46
|
-
docEntry("toolkit-observability", "Toolkit observability", "docs/toolkit-observability.md", "use"),
|
|
47
|
-
docEntry("site-bundle-contract", "Site bundle contract", "docs/site-bundle-contract.md", "use"),
|
|
48
|
-
docEntry("product-mechanism", "Product mechanism", "docs/product-mechanism.md", "why"),
|
|
49
|
-
docEntry("cli", "CLI and npm package", "docs/cli.md", "use"),
|
|
50
|
-
docEntry("lifecycle-protocol", "Lifecycle protocol", "docs/lifecycle-protocol.md", "use"),
|
|
51
|
-
docEntry("reusable-build-surface", "Reusable build surface", "docs/reusable-build-surface.md", "use"),
|
|
52
|
-
docEntry("publish-transaction", "Publish transaction", "docs/publish-transaction.md", "verify"),
|
|
53
|
-
docEntry("release-governance", "Release governance", "docs/release-governance.md", "why"),
|
|
54
|
-
docEntry("release-flow", "Release flow", "docs/release-flow.md", "verify"),
|
|
55
|
-
docEntry("versioning", "Versioning", "docs/versioning.md", "why"),
|
|
56
|
-
docEntry("web-surface-deployments", "Web surface deployments", "docs/web-surface-deployments.md", "use"),
|
|
57
|
-
docEntry("infra-contract", "Infra Contract", "docs/infra-contract.md", "use"),
|
|
58
|
-
];
|
|
54
|
+
const docs = BUILDCHAIN_AGENT_MANUALS.map((manual) => docEntry(manual.id, manual.title, manual.path, manual.plane));
|
|
59
55
|
|
|
60
56
|
const cliRegistry = {
|
|
61
57
|
schemaVersion: 1,
|
|
@@ -80,6 +76,52 @@ function buildSiteBundle() {
|
|
|
80
76
|
],
|
|
81
77
|
};
|
|
82
78
|
|
|
79
|
+
const manualRegistry = {
|
|
80
|
+
schemaVersion: 1,
|
|
81
|
+
contract: "kungfu-buildchain-agent-manual-registry",
|
|
82
|
+
package: packageJson.name,
|
|
83
|
+
source: "npm package docs/",
|
|
84
|
+
manuals: docs.map((entry) => ({
|
|
85
|
+
id: entry.id,
|
|
86
|
+
title: entry.title,
|
|
87
|
+
path: entry.path,
|
|
88
|
+
plane: entry.plane,
|
|
89
|
+
digest: entry.digest,
|
|
90
|
+
})),
|
|
91
|
+
requiredAgentManuals: [
|
|
92
|
+
"docs/MAP.md",
|
|
93
|
+
"docs/install.md",
|
|
94
|
+
"docs/cli.md",
|
|
95
|
+
"docs/reusable-build-surface.md",
|
|
96
|
+
"docs/release-candidate.md",
|
|
97
|
+
"docs/release-governance.md",
|
|
98
|
+
"docs/release-passport.md",
|
|
99
|
+
"docs/publish-transaction.md",
|
|
100
|
+
"docs/site-bundle-contract.md",
|
|
101
|
+
],
|
|
102
|
+
guidance: "Agent consumers should use this registry to find packaged Buildchain manuals before inferring behavior from workflow snippets or release notes.",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const nodeApiRegistry = {
|
|
106
|
+
schemaVersion: 1,
|
|
107
|
+
contract: "kungfu-buildchain-node-api-registry",
|
|
108
|
+
package: packageJson.name,
|
|
109
|
+
moduleSystem: packageJson.type || "module",
|
|
110
|
+
exports: Object.entries(packageJson.exports || {})
|
|
111
|
+
.filter(([specifier]) => !specifier.startsWith("./site/") && specifier !== "./package.json")
|
|
112
|
+
.map(([specifier, target]) => ({
|
|
113
|
+
specifier: specifier === "." ? packageJson.name : `${packageJson.name}/${specifier.replace(/^\.\//, "")}`,
|
|
114
|
+
export: specifier,
|
|
115
|
+
target,
|
|
116
|
+
digest: typeof target === "string" ? sha256File(target.replace(/^\.\//, "")) : "",
|
|
117
|
+
})),
|
|
118
|
+
docs: [
|
|
119
|
+
{ id: "cli-and-node-package", path: "docs/cli.md", digest: sha256File("docs/cli.md") },
|
|
120
|
+
{ id: "site-bundle-contract", path: "docs/site-bundle-contract.md", digest: sha256File("docs/site-bundle-contract.md") },
|
|
121
|
+
],
|
|
122
|
+
guidance: "These are the public Node import surfaces shipped by the npm package. Agents should prefer these exports over internal file paths.",
|
|
123
|
+
};
|
|
124
|
+
|
|
83
125
|
const workflowRegistry = {
|
|
84
126
|
schemaVersion: 1,
|
|
85
127
|
contract: "kungfu-buildchain-workflow-registry",
|
|
@@ -124,6 +166,17 @@ function buildSiteBundle() {
|
|
|
124
166
|
alphaDistTag: "alpha",
|
|
125
167
|
stableDistTag: "latest",
|
|
126
168
|
},
|
|
169
|
+
githubRelease: {
|
|
170
|
+
exactTagRelease: true,
|
|
171
|
+
prereleaseTags: "semver prerelease tags set prerelease=true and make_latest=false",
|
|
172
|
+
stableTags: "stable semver tags set make_latest=true",
|
|
173
|
+
evidenceAssets: [
|
|
174
|
+
"publish evidence JSON",
|
|
175
|
+
"buildchain.release.json",
|
|
176
|
+
"release passport assets",
|
|
177
|
+
],
|
|
178
|
+
owner: "promote-buildchain-ref",
|
|
179
|
+
},
|
|
127
180
|
};
|
|
128
181
|
|
|
129
182
|
const artifactSchemas = {
|
|
@@ -144,10 +197,13 @@ function buildSiteBundle() {
|
|
|
144
197
|
"buildchain-site.json",
|
|
145
198
|
"site-manifest.json",
|
|
146
199
|
"cli-registry.json",
|
|
200
|
+
"manual-registry.json",
|
|
201
|
+
"node-api-registry.json",
|
|
147
202
|
"workflow-registry.json",
|
|
148
203
|
"release-model.json",
|
|
149
204
|
"artifact-schemas.json",
|
|
150
205
|
"buildchain-contract.json",
|
|
206
|
+
"kfd-claims.json",
|
|
151
207
|
"product-mechanism.json",
|
|
152
208
|
"release-provenance.json",
|
|
153
209
|
"agent-index.json",
|
|
@@ -211,11 +267,14 @@ function buildSiteBundle() {
|
|
|
211
267
|
docs,
|
|
212
268
|
facts: [
|
|
213
269
|
"cli-registry.json",
|
|
270
|
+
"manual-registry.json",
|
|
271
|
+
"node-api-registry.json",
|
|
214
272
|
"workflow-registry.json",
|
|
215
273
|
"release-model.json",
|
|
216
274
|
"artifact-schemas.json",
|
|
217
275
|
"product-mechanism.json",
|
|
218
276
|
"release-provenance.json",
|
|
277
|
+
"kfd-claims.json",
|
|
219
278
|
"agent-index.json",
|
|
220
279
|
],
|
|
221
280
|
};
|
|
@@ -228,9 +287,12 @@ function buildSiteBundle() {
|
|
|
228
287
|
"product-mechanism.json",
|
|
229
288
|
"release-model.json",
|
|
230
289
|
"cli-registry.json",
|
|
290
|
+
"manual-registry.json",
|
|
291
|
+
"node-api-registry.json",
|
|
231
292
|
"workflow-registry.json",
|
|
232
293
|
"artifact-schemas.json",
|
|
233
294
|
"buildchain-contract.json",
|
|
295
|
+
"kfd-claims.json",
|
|
234
296
|
],
|
|
235
297
|
instruction: "Use this bundle as the package-owned fact source for Buildchain pages. Do not infer current release mechanics from prose alone.",
|
|
236
298
|
};
|
|
@@ -252,10 +314,13 @@ function buildSiteBundle() {
|
|
|
252
314
|
"buildchain-site.json": siteBundle,
|
|
253
315
|
"site-manifest.json": siteManifest,
|
|
254
316
|
"cli-registry.json": cliRegistry,
|
|
317
|
+
"manual-registry.json": manualRegistry,
|
|
318
|
+
"node-api-registry.json": nodeApiRegistry,
|
|
255
319
|
"workflow-registry.json": workflowRegistry,
|
|
256
320
|
"release-model.json": releaseModel,
|
|
257
321
|
"artifact-schemas.json": artifactSchemas,
|
|
258
322
|
"buildchain-contract.json": createBuildchainContractWorld({ root }),
|
|
323
|
+
"kfd-claims.json": createBuildchainKfdClaimRegistry({ root }),
|
|
259
324
|
"product-mechanism.json": productMechanism,
|
|
260
325
|
"release-provenance.json": releaseProvenance,
|
|
261
326
|
"agent-index.json": agentIndex,
|
|
@@ -263,7 +328,7 @@ function buildSiteBundle() {
|
|
|
263
328
|
}
|
|
264
329
|
|
|
265
330
|
export function writeSiteBundle({ check = false } = {}) {
|
|
266
|
-
|
|
331
|
+
let files = buildSiteBundle();
|
|
267
332
|
const mismatches = [];
|
|
268
333
|
for (const [name, value] of Object.entries(files)) {
|
|
269
334
|
const filePath = path.join(outputDir, name);
|
|
@@ -272,13 +337,32 @@ export function writeSiteBundle({ check = false } = {}) {
|
|
|
272
337
|
if (existingJson(filePath) !== next) {
|
|
273
338
|
mismatches.push(path.relative(root, filePath));
|
|
274
339
|
}
|
|
275
|
-
} else {
|
|
276
|
-
writeJson(filePath, value);
|
|
277
340
|
}
|
|
278
341
|
}
|
|
279
342
|
if (mismatches.length > 0) {
|
|
280
343
|
throw new Error(`site bundle is stale: ${mismatches.join(", ")}`);
|
|
281
344
|
}
|
|
345
|
+
if (!check) {
|
|
346
|
+
for (let iteration = 0; iteration < 5; iteration += 1) {
|
|
347
|
+
for (const [name, value] of Object.entries(files)) {
|
|
348
|
+
writeJson(path.join(outputDir, name), value);
|
|
349
|
+
}
|
|
350
|
+
const nextFiles = buildSiteBundle();
|
|
351
|
+
const stable = Object.entries(nextFiles).every(([name, value]) => (
|
|
352
|
+
existingJson(path.join(outputDir, name)) === stableJson(value)
|
|
353
|
+
));
|
|
354
|
+
files = nextFiles;
|
|
355
|
+
if (stable) {
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
if (iteration === 4) {
|
|
359
|
+
throw new Error("site bundle did not converge after 5 generations");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
for (const [name, value] of Object.entries(files)) {
|
|
363
|
+
writeJson(path.join(outputDir, name), value);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
282
366
|
return {
|
|
283
367
|
schemaVersion: 1,
|
|
284
368
|
contract: "kungfu-buildchain-site-bundle-generation",
|