@kungfu-tech/buildchain 3.0.8 → 3.0.9-alpha.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 +8 -7
- package/bin/buildchain.mjs +34 -33
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +16 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +57 -0
- package/contracts/release-cut-v1.schema.json +1 -0
- package/contracts/release-train-transition-v1.schema.json +1 -0
- package/dist/site/agent-index.json +0 -1
- package/dist/site/artifact-schemas.json +0 -2
- package/dist/site/badge-endpoint-registry.json +9 -9
- package/dist/site/badges/v1/kfd-8/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-8/declared.json +1 -1
- package/dist/site/badges/v1/kfd-8/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-8/draft.json +1 -1
- package/dist/site/badges/v1/kfd-8/failed.json +1 -1
- package/dist/site/badges/v1/kfd-8/missing.json +1 -1
- package/dist/site/badges/v1/kfd-8/passed.json +1 -1
- package/dist/site/badges/v1/kfd-8/planned.json +1 -1
- package/dist/site/buildchain-contract.json +38 -34
- package/dist/site/buildchain-site.json +59 -39
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +8 -7
- package/dist/site/controller-registry.json +13 -5
- package/dist/site/kfd-claims.json +35 -8
- package/dist/site/kfd-upstream-aggregate.json +10 -10
- package/dist/site/manual-registry.json +9 -9
- package/dist/site/node-api-registry.json +1143 -320
- package/dist/site/page-registry.json +46 -26
- package/dist/site/public-surface-audit.json +19 -9
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +33 -5
- package/dist/site/release-provenance.json +2 -1
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +13 -13
- package/dist/site/workflow-registry.json +19 -9
- package/docs/aws-us-elastic-runner-burst-plane.md +15 -3
- package/docs/cli-reference.md +6 -6
- package/docs/cli.md +2 -3
- package/docs/dev-alpha-candidate-patrol.md +45 -20
- package/docs/dev-delivery-warrant.md +12 -2
- package/docs/kfd-support.md +7 -7
- package/docs/node-api-reference.md +295 -246
- package/docs/release-passport.md +25 -12
- package/docs/release-train.md +54 -4
- package/docs/reusable-build-surface.md +26 -9
- package/docs/runtime-train-validation.md +6 -0
- package/docs/versioning.md +3 -2
- package/package.json +4 -3
- package/packages/core/artifact-verification-envelope.js +114 -0
- package/packages/core/buildchain-channel-identity.js +119 -0
- package/packages/core/buildchain-compatibility-proof.js +32 -0
- package/packages/core/buildchain-contract.js +7 -0
- package/packages/core/controller-evidence.js +2 -1
- package/packages/core/dev-alpha-active-release-train.js +460 -0
- package/packages/core/dev-alpha-candidate-selection.js +181 -0
- package/packages/core/dev-delivery-warrant.js +15 -4
- package/packages/core/index.js +4 -5
- package/packages/core/kfd-adopter-manifest.js +390 -0
- package/packages/core/kfd-product-gates.js +0 -300
- package/packages/core/release-blocker-priority.js +192 -0
- package/packages/core/release-passport-contract.js +219 -6
- package/packages/core/release-passport.js +139 -242
- package/packages/core/release-train.js +220 -0
- package/scripts/aws-macos-jit-controller-core.mjs +76 -0
- package/scripts/aws-macos-jit-controller.mjs +29 -0
- package/scripts/aws-macos-jit-source-rebind.mjs +503 -0
- package/scripts/buildchain-channel-router.mjs +18 -2
- package/scripts/buildchain-cli-help.mjs +4 -2
- package/scripts/buildchain-contract-lock.mjs +11 -1
- package/scripts/check-inventory.mjs +4 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +196 -201
- package/scripts/generate-buildchain-kfd-witnesses.mjs +88 -94
- package/scripts/generate-channel-build-workflow.mjs +78 -31
- package/scripts/generate-site-bundle.mjs +1 -8
- package/scripts/promotion-channel-router.mjs +5 -3
- package/scripts/release-train-self-dogfood.mjs +579 -0
- package/scripts/runtime-ref-core.mjs +4 -17
- package/scripts/site-capability-metadata.mjs +3 -1
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +0 -106
|
@@ -1,121 +1,115 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import path from "node:path";
|
|
5
7
|
import { pathToFileURL } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "../packages/core/buildchain-kfd-claims.js";
|
|
12
|
-
import {
|
|
13
|
-
BUILDCHAIN_KFD1_CONTRACT_WORLD_WITNESS_PATH,
|
|
14
|
-
BUILDCHAIN_KFD2_CLAIMS_DIR,
|
|
15
|
-
BUILDCHAIN_KFD3_ARTIFACT_WITNESS_PATH,
|
|
16
|
-
BUILDCHAIN_KFD3_PREBUILD_WITNESS_PATH,
|
|
17
|
-
} from "../packages/core/buildchain-layout.js";
|
|
8
|
+
import { addAdopterWitness, initAdopterManifest } from "@kungfu-tech/kfd/adopter-conformance/toolchain";
|
|
9
|
+
import kfdStandards from "@kungfu-tech/kfd/standards.json" with { type: "json" };
|
|
10
|
+
import { installedKfdPackageArtifactRoot } from "../packages/core/artifact-verification-envelope.js";
|
|
11
|
+
import { createKfdAdopterManifestGate, createKfdLegacySupportMatrixProjection, validateKfdAdopterManifestGate, validateKfdLegacySupportMatrixProjection } from "../packages/core/kfd-adopter-manifest.js";
|
|
12
|
+
import { KFD_PRODUCT_GATE_INPUT_CONTRACT, evaluateKfdProductGate, kfdProductGateDigest, validateKfdProductGateResult } from "../packages/core/kfd-product-gates.js";
|
|
13
|
+
import { createBuildchainKfd1Witness, createBuildchainKfd2Claims, createBuildchainKfd3ArtifactWitness, createBuildchainKfd3PrebuildWitness } from "../packages/core/buildchain-kfd-claims.js";
|
|
14
|
+
import { BUILDCHAIN_KFD1_CONTRACT_WORLD_WITNESS_PATH, BUILDCHAIN_KFD2_CLAIMS_DIR, BUILDCHAIN_KFD3_ARTIFACT_WITNESS_PATH, BUILDCHAIN_KFD3_PREBUILD_WITNESS_PATH } from "../packages/core/buildchain-layout.js";
|
|
18
15
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
19
|
-
|
|
16
|
+
const REPOSITORY = "kungfu-systems/buildchain", require = createRequire(import.meta.url), kfdRoot = path.dirname(require.resolve("@kungfu-tech/kfd/package.json"));
|
|
17
|
+
const evidenceSources = { "KFD-1": "dist/site/release-passport-check-manifest.json", "KFD-2": "dist/site/kfd-claims.json", "KFD-3": "dist/site/public-surface-audit.json", "KFD-4": "packages/core/kfd-product-gates.js", "KFD-5": "packages/core/kfd-product-gates.js", "KFD-7": "packages/core/kfd-product-gates.js", "KFD-10": "packages/core/dev-delivery-warrant.js" };
|
|
20
18
|
function parseArgs(argv = process.argv.slice(2)) {
|
|
21
|
-
const args = {
|
|
22
|
-
cwd: process.cwd(),
|
|
23
|
-
outputDir: ".buildchain/kfd",
|
|
24
|
-
sourceSha: process.env.BUILDCHAIN_SOURCE_SHA || "",
|
|
25
|
-
};
|
|
19
|
+
const args = { cwd: process.cwd(), outputDir: ".buildchain/kfd", sourceSha: process.env.BUILDCHAIN_SOURCE_SHA || "" };
|
|
26
20
|
for (let index = 0; index < argv.length; index += 1) {
|
|
27
21
|
const arg = argv[index];
|
|
28
|
-
if (arg === "--cwd")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
args.sourceSha = argv[++index] || args.sourceSha;
|
|
34
|
-
} else if (arg === "--help") {
|
|
35
|
-
args.help = true;
|
|
36
|
-
} else {
|
|
37
|
-
throw new Error(`unknown argument: ${arg}`);
|
|
38
|
-
}
|
|
22
|
+
if (arg === "--cwd") args.cwd = argv[++index] || args.cwd;
|
|
23
|
+
else if (arg === "--output-dir") args.outputDir = argv[++index] || args.outputDir;
|
|
24
|
+
else if (arg === "--source-sha") args.sourceSha = argv[++index] || args.sourceSha;
|
|
25
|
+
else if (arg === "--help") args.help = true;
|
|
26
|
+
else throw new Error(`unknown argument: ${arg}`);
|
|
39
27
|
}
|
|
40
28
|
return args;
|
|
41
29
|
}
|
|
42
|
-
|
|
43
30
|
function gitSha(cwd) {
|
|
44
|
-
try {
|
|
45
|
-
return execFileSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" }).trim();
|
|
46
|
-
} catch {
|
|
47
|
-
return "";
|
|
48
|
-
}
|
|
31
|
+
try { return execFileSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" }).trim(); } catch { return ""; }
|
|
49
32
|
}
|
|
50
|
-
|
|
51
|
-
function
|
|
33
|
+
function digest(bytes) { return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`; }
|
|
34
|
+
function fileDigest(filePath) { return digest(fs.readFileSync(filePath)); }
|
|
35
|
+
function relative(root, filePath) { return path.relative(root, filePath).replace(/\\/g, "/"); }
|
|
36
|
+
function writeJson(root, relativePath, value) {
|
|
37
|
+
const filePath = path.join(root, relativePath);
|
|
52
38
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
53
39
|
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
40
|
+
return { path: relativePath, sha256: fileDigest(filePath), filePath };
|
|
54
41
|
}
|
|
55
|
-
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function generateBuildchainKfdWitnesses({
|
|
61
|
-
cwd = process.cwd(),
|
|
62
|
-
outputDir = ".buildchain/kfd",
|
|
63
|
-
sourceSha = "",
|
|
64
|
-
emitOutputs = true,
|
|
65
|
-
} = {}) {
|
|
66
|
-
const root = path.resolve(cwd);
|
|
67
|
-
const outDir = path.resolve(root, outputDir);
|
|
68
|
-
const resolvedSourceSha = sourceSha || gitSha(root);
|
|
42
|
+
function readJson(filePath, sourceSha = "") { return JSON.parse(fs.readFileSync(filePath, "utf8").replaceAll("{{SOURCE_SHA}}", sourceSha)); }
|
|
43
|
+
export function generateBuildchainKfdWitnesses({ cwd = process.cwd(), outputDir = ".buildchain/kfd", sourceSha = "", emitOutputs = true } = {}) {
|
|
44
|
+
const root = path.resolve(cwd), outDir = path.resolve(root, outputDir), resolvedSourceSha = sourceSha || gitSha(root);
|
|
69
45
|
const outputPath = (canonicalPath) => path.join(outDir, path.relative(".buildchain/kfd", canonicalPath));
|
|
70
|
-
const paths = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
76
|
-
writeJson(paths.kfd1Witness, createBuildchainKfd1Witness({ root, sourceSha: resolvedSourceSha }));
|
|
77
|
-
writeJson(paths.kfd3PrebuildWitness, createBuildchainKfd3PrebuildWitness({ root, sourceSha: resolvedSourceSha }));
|
|
78
|
-
writeJson(paths.kfd3ArtifactWitness, createBuildchainKfd3ArtifactWitness({ root, sourceSha: resolvedSourceSha }));
|
|
79
|
-
const witnessFiles = {
|
|
80
|
-
"kfd-1-witness": toRepoRelative(root, paths.kfd1Witness),
|
|
81
|
-
"kfd-3-prebuild-witness": toRepoRelative(root, paths.kfd3PrebuildWitness),
|
|
82
|
-
"kfd-3-artifact-witness": toRepoRelative(root, paths.kfd3ArtifactWitness),
|
|
83
|
-
};
|
|
46
|
+
const paths = { kfd1Witness: outputPath(BUILDCHAIN_KFD1_CONTRACT_WORLD_WITNESS_PATH), kfd3PrebuildWitness: outputPath(BUILDCHAIN_KFD3_PREBUILD_WITNESS_PATH), kfd3ArtifactWitness: outputPath(BUILDCHAIN_KFD3_ARTIFACT_WITNESS_PATH), kfd2ClaimsDir: outputPath(BUILDCHAIN_KFD2_CLAIMS_DIR) };
|
|
47
|
+
writeJson(path.dirname(paths.kfd1Witness), path.basename(paths.kfd1Witness), createBuildchainKfd1Witness({ root, sourceSha: resolvedSourceSha }));
|
|
48
|
+
writeJson(path.dirname(paths.kfd3PrebuildWitness), path.basename(paths.kfd3PrebuildWitness), createBuildchainKfd3PrebuildWitness({ root, sourceSha: resolvedSourceSha }));
|
|
49
|
+
writeJson(path.dirname(paths.kfd3ArtifactWitness), path.basename(paths.kfd3ArtifactWitness), createBuildchainKfd3ArtifactWitness({ root, sourceSha: resolvedSourceSha }));
|
|
50
|
+
const witnessFiles = { "kfd-1-witness": relative(root, paths.kfd1Witness), "kfd-3-prebuild-witness": relative(root, paths.kfd3PrebuildWitness), "kfd-3-artifact-witness": relative(root, paths.kfd3ArtifactWitness) };
|
|
84
51
|
const kfd2ClaimPaths = createBuildchainKfd2Claims({ root, witnessFiles }).map((claim) => {
|
|
85
|
-
const slug = String(claim.id || "claim")
|
|
86
|
-
|
|
87
|
-
.replace(/[^0-9A-Za-z._-]+/g, "-")
|
|
88
|
-
.replace(/^-+|-+$/g, "") || "claim";
|
|
89
|
-
const claimPath = path.join(paths.kfd2ClaimsDir, `${slug}.json`);
|
|
90
|
-
writeJson(claimPath, claim);
|
|
91
|
-
return claimPath;
|
|
52
|
+
const slug = String(claim.id || "claim").replace(/^claim:/, "").replace(/[^0-9A-Za-z._-]+/g, "-").replace(/^-+|-+$/g, "") || "claim";
|
|
53
|
+
return writeJson(paths.kfd2ClaimsDir, `${slug}.json`, claim).filePath;
|
|
92
54
|
});
|
|
93
|
-
const outputs = {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
55
|
+
const outputs = { "kfd-1-witness-jsons": relative(root, paths.kfd1Witness), "kfd-2-claim-jsons": kfd2ClaimPaths.map((filePath) => relative(root, filePath)).join(","), "kfd-3-prebuild-witness-jsons": relative(root, paths.kfd3PrebuildWitness), "kfd-3-artifact-witness-jsons": relative(root, paths.kfd3ArtifactWitness), "source-sha": resolvedSourceSha, "output-dir": relative(root, outDir) };
|
|
56
|
+
if (emitOutputs) writeGitHubOutputs(outputs);
|
|
57
|
+
return { schemaVersion: 1, contract: "kungfu-buildchain-self-kfd-witness-generation", outputs };
|
|
58
|
+
}
|
|
59
|
+
function productRecords(root, standard, sourceSha) {
|
|
60
|
+
if (standard === "kfd-4") return [
|
|
61
|
+
["observer-perspective", readJson(path.join(root, "contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json"), sourceSha)],
|
|
62
|
+
["perspective-replay", readJson(path.join(root, "contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json"), sourceSha)],
|
|
63
|
+
];
|
|
64
|
+
if (standard === "kfd-5") return [["primitive-discovery", readJson(path.join(kfdRoot, "cases/live/software-work-perspective-settlement/cuts/0001-assignment.json"))]];
|
|
65
|
+
const profile = readJson(path.join(kfdRoot, "verifier/fixtures/kfd-7/valid-domain-profile.json"));
|
|
66
|
+
profile.evidenceObligations = profile.evidenceObligations.map((entry) => ({ ...entry, status: "passed", artifactRefs: ["qualification-proof"], residualRisk: "Bound to the retained Buildchain product gate cut; independent certification remains external." }));
|
|
67
|
+
profile.activation = { decision: "activate", evidenceCut: `git://${REPOSITORY}@${sourceSha}`, independentReview: "review://protected-release-review", productWitnesses: ["qualification-proof"], residualRisk: "The product gate is non-qualifying and non-self-certifying." };
|
|
68
|
+
profile.domainProfile = { ...profile.domainProfile, product: "Buildchain", implementation: `git+https://github.com/${REPOSITORY}@${sourceSha}`, qualificationStatus: "qualified" };
|
|
69
|
+
return [["domain-profile", profile]];
|
|
70
|
+
}
|
|
71
|
+
async function generateProductGate(root, outDir, standard, sourceSha, checkedAt) {
|
|
72
|
+
const records = productRecords(root, standard, sourceSha).map(([role, value], index) => ({ role, ...writeJson(outDir, `records/${standard}-${index}.json`, value) }));
|
|
73
|
+
const kinds = standard === "kfd-4" ? [["projection-fsck", "projection-fsck"], ["negative", "negative-fixture"]] : standard === "kfd-5" ? [["negative", "negative-fixture"]] : [["qualification-proof", "qualification-proof"], ["independent-review", "independent-review"], ["negative", "negative-fixture"]];
|
|
74
|
+
const evidence = kinds.map(([id, kind]) => ({ id, kind, ...writeJson(outDir, `evidence/${standard}-${id}.json`, { schemaVersion: 1, contract: "kungfu-buildchain-kfd-adopter-evidence-reference", id, kind, source: { repository: REPOSITORY, sha: sourceSha }, observedAt: checkedAt, status: "retained", nonClaim: "This product-owned reference does not independently qualify or certify KFD adoption." }) }));
|
|
75
|
+
const input = { schemaVersion: 1, contract: KFD_PRODUCT_GATE_INPUT_CONTRACT, standard, standardRevision: kfdStandards.standards[standard].revision, source: { repository: REPOSITORY, sha: sourceSha }, evidenceCut: { generatedAt: checkedAt, expiresAt: new Date(Date.parse(checkedAt) + 86400000).toISOString() }, records: records.map(({ role, path, sha256 }) => ({ role, path, sha256 })), evidence: evidence.map(({ id, kind, path, sha256 }) => ({ id, kind, path, sha256 })), responsibility: { owner: REPOSITORY, evidenceOwner: "Buildchain maintainers", proofOwner: "Buildchain release gate" }, nonClaims: ["A passed product gate does not qualify, certify, activate, or independently approve KFD adoption."] };
|
|
76
|
+
const gate = await evaluateKfdProductGate({ cwd: outDir, input, expectedSourceSha: sourceSha, checkedAt });
|
|
77
|
+
if (gate.status !== "passed" || !validateKfdProductGateResult(gate, { expectedSourceSha: sourceSha, checkedAt }).valid) throw new Error(`${standard} product gate failed: ${JSON.stringify(gate.issues)}`);
|
|
78
|
+
writeJson(outDir, `${standard}/product-gate.json`, gate);
|
|
79
|
+
return gate;
|
|
80
|
+
}
|
|
81
|
+
export async function generateBuildchainKfdAdopterRelease({ cwd = process.cwd(), outputDir = ".buildchain/kfd/adopter-release", sourceSha = "", checkedAt = new Date().toISOString(), emitOutputs = true } = {}) {
|
|
82
|
+
const root = path.resolve(cwd), outDir = path.resolve(root, outputDir);
|
|
83
|
+
if (!/^[0-9a-f]{40}$/.test(sourceSha) || !Number.isFinite(Date.parse(checkedAt))) throw new Error("source SHA and checked-at must be exact");
|
|
84
|
+
for (const sourcePath of Object.values(evidenceSources)) if (!fs.statSync(path.join(root, sourcePath)).isFile()) throw new Error(`required Buildchain evidence source is missing: ${sourcePath}`);
|
|
85
|
+
const packageArtifactRoot = installedKfdPackageArtifactRoot(), gates = [];
|
|
86
|
+
for (const standard of ["kfd-4", "kfd-5", "kfd-7"]) gates.push(await generateProductGate(root, outDir, standard, sourceSha, checkedAt));
|
|
87
|
+
const sourceRoot = kfdProductGateDigest({ repository: REPOSITORY, sourceSha, files: [...new Set(Object.values(evidenceSources))].sort().map((sourcePath) => ({ path: sourcePath, sha256: fileDigest(path.join(root, sourcePath)) })) });
|
|
88
|
+
let manifest = initAdopterManifest({ manifestId: "buildchain-v3-full-cut", adopterId: REPOSITORY, artifactKind: "git-commit", artifactCoordinate: `${REPOSITORY}@${sourceSha}`, artifactRoot: sourceRoot, scope: "Buildchain v3 release and protected delivery authority", packageArtifactRoot, verifiedAt: checkedAt, maxAgeSeconds: 86400 });
|
|
89
|
+
const gateById = new Map(gates.map((gate) => [gate.standard.toUpperCase(), gate]));
|
|
90
|
+
for (const id of ["KFD-1", "KFD-2", "KFD-3", "KFD-4", "KFD-5", "KFD-7"]) {
|
|
91
|
+
const row = manifest.decisions.find((entry) => entry.id === id);
|
|
92
|
+
const sourcePath = evidenceSources[id];
|
|
93
|
+
const sourceEvidence = (kind, rootValue = fileDigest(path.join(root, sourcePath))) => ({ kind, coordinate: `git+https://github.com/${REPOSITORY}@${sourceSha}#${sourcePath}`, root: rootValue, observedAt: checkedAt, kfdPackageRoot: packageArtifactRoot });
|
|
94
|
+
row.state = "candidate"; row.usage = "used"; row.implementationEvidence = [sourceEvidence("implementation")]; row.verificationEvidence = [sourceEvidence("verification", gateById.get(id)?.gateRoot)]; row.gaps = ["Independent decision-specific assessment and certification remain external."];
|
|
103
95
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
};
|
|
96
|
+
const kfd6 = manifest.decisions.find((entry) => entry.id === "KFD-6");
|
|
97
|
+
kfd6.state = "unsupported"; kfd6.usage = "unused"; kfd6.gaps = ["Buildchain does not claim KFD-6 support in this cut."];
|
|
98
|
+
manifest = addAdopterWitness(manifest, { decisionId: "KFD-10", profileId: "kfd-warrant-evidence", witnessCoordinate: `git+https://github.com/${REPOSITORY}@${sourceSha}#${evidenceSources["KFD-10"]}`, witnessRoot: fileDigest(path.join(root, evidenceSources["KFD-10"])), packageArtifactRoot, verifiedAt: checkedAt, maxAgeSeconds: 86400 });
|
|
99
|
+
const manifestPath = writeJson(outDir, "kfd-adopter-manifest.json", manifest).filePath, manifestGate = createKfdAdopterManifestGate({ manifest, packageArtifactRoot, gateResults: gates, authorityPath: "kfd-adopter-manifest.json", expectedSourceSha: sourceSha, checkedAt });
|
|
100
|
+
if (!validateKfdAdopterManifestGate(manifestGate, { expectedSourceSha: sourceSha, checkedAt }).valid) throw new Error(`generated adopter manifest gate failed: ${JSON.stringify(manifestGate.issues)}`);
|
|
101
|
+
const gatePath = writeJson(outDir, "kfd-adopter-manifest-gate.json", manifestGate).filePath, support = createKfdLegacySupportMatrixProjection({ manifest, manifestGate });
|
|
102
|
+
if (!validateKfdLegacySupportMatrixProjection(support, { manifest, manifestGate }).valid) throw new Error("generated KFD support projection failed");
|
|
103
|
+
const supportPath = writeJson(outDir, "kfd-support.json", support).filePath;
|
|
104
|
+
const outputs = { "kfd-adopter-manifest-json": relative(root, manifestPath), "kfd-adopter-manifest-gate-json": relative(root, gatePath), "kfd-support-matrix-json": relative(root, supportPath), "kfd-product-gate-jsons": gates.map((gate) => relative(root, path.join(outDir, gate.standard, "product-gate.json"))).join(","), "kfd-adopter-manifest-root": manifestGate.authority.manifestRoot, "kfd-adopter-gate-root": manifestGate.gateRoot, "kfd-package-artifact-root": packageArtifactRoot, "source-sha": sourceSha };
|
|
105
|
+
if (emitOutputs) writeGitHubOutputs(outputs);
|
|
106
|
+
return { schemaVersion: 1, contract: "kungfu-buildchain-kfd-adopter-release-generation", status: "passed", qualifying: false, selfCertified: false, outputs };
|
|
109
107
|
}
|
|
110
|
-
|
|
111
108
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
112
109
|
try {
|
|
113
110
|
const args = parseArgs();
|
|
114
|
-
if (args.help)
|
|
115
|
-
|
|
116
|
-
process.exit(0);
|
|
117
|
-
}
|
|
118
|
-
process.stdout.write(`${JSON.stringify(generateBuildchainKfdWitnesses(args), null, 2)}\n`);
|
|
111
|
+
if (args.help) process.stdout.write("Usage: generate-buildchain-kfd-witnesses [--cwd <repo>] [--output-dir <dir>] [--source-sha <sha>]\n");
|
|
112
|
+
else process.stdout.write(`${JSON.stringify(generateBuildchainKfdWitnesses(args), null, 2)}\n`);
|
|
119
113
|
} catch (error) {
|
|
120
114
|
console.error(`buildchain self KFD witnesses: ${error.message}`);
|
|
121
115
|
process.exitCode = 1;
|
|
@@ -46,26 +46,26 @@ function routerInputs(inputBlock) {
|
|
|
46
46
|
|
|
47
47
|
function routerOutputs(outputBlock) {
|
|
48
48
|
const forwarded = outputBlock
|
|
49
|
-
.replace(/^ value: \$\{\{ jobs\.[^.]+\.outputs\.([^ }]+) \}\}$/gm, " value: ${{ jobs.
|
|
49
|
+
.replace(/^ value: \$\{\{ jobs\.[^.]+\.outputs\.([^ }]+) \}\}$/gm, " value: ${{ jobs.alpha.outputs.$1 || jobs.stable.outputs.$1 }}")
|
|
50
50
|
.replaceAll("build-lifecycle", "build-channel-router")
|
|
51
|
-
.replace("value: ${{ jobs.
|
|
52
|
-
.replace("value: ${{ jobs.
|
|
53
|
-
.replace("value: ${{ jobs.
|
|
54
|
-
.replace("value: ${{ jobs.
|
|
55
|
-
.replace("value: ${{ jobs.
|
|
56
|
-
.replace("value: ${{ jobs.
|
|
57
|
-
.replace("value: ${{ jobs.
|
|
51
|
+
.replace("value: ${{ jobs.alpha.outputs.controller-plan-artifact || jobs.stable.outputs.controller-plan-artifact }}", "value: ${{ jobs.controller-plan.outputs.controller-plan-artifact }}")
|
|
52
|
+
.replace("value: ${{ jobs.alpha.outputs.controller-plan-json || jobs.stable.outputs.controller-plan-json }}", "value: ${{ jobs.controller-plan.outputs.controller-plan-json }}")
|
|
53
|
+
.replace("value: ${{ jobs.alpha.outputs.controller-plan-digest || jobs.stable.outputs.controller-plan-digest }}", "value: ${{ jobs.controller-plan.outputs.controller-plan-digest }}")
|
|
54
|
+
.replace("value: ${{ jobs.alpha.outputs.controller-receipt-artifact || jobs.stable.outputs.controller-receipt-artifact }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-artifact }}")
|
|
55
|
+
.replace("value: ${{ jobs.alpha.outputs.controller-receipt-json || jobs.stable.outputs.controller-receipt-json }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-json }}")
|
|
56
|
+
.replace("value: ${{ jobs.alpha.outputs.controller-receipt-digest || jobs.stable.outputs.controller-receipt-digest }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-digest }}")
|
|
57
|
+
.replace("value: ${{ jobs.alpha.outputs.controller-receipt-status || jobs.stable.outputs.controller-receipt-status }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-status }}")
|
|
58
58
|
.replace(
|
|
59
|
-
/( credential-island-macos-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.
|
|
60
|
-
"$1 ${{ jobs.
|
|
59
|
+
/( credential-island-macos-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.alpha\.outputs\.artifact-name \|\| jobs\.stable\.outputs\.artifact-name \}\}/,
|
|
60
|
+
"$1 ${{ jobs.alpha.outputs.credential-island-macos-artifact || jobs.stable.outputs.credential-island-macos-artifact }}",
|
|
61
61
|
)
|
|
62
62
|
.replace(
|
|
63
|
-
/( credential-island-macos-manifest-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.
|
|
64
|
-
"$1 ${{ jobs.
|
|
63
|
+
/( credential-island-macos-manifest-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.alpha\.outputs\.manifest-artifact-name \|\| jobs\.stable\.outputs\.manifest-artifact-name \}\}/,
|
|
64
|
+
"$1 ${{ jobs.alpha.outputs.credential-island-macos-manifest-artifact || jobs.stable.outputs.credential-island-macos-manifest-artifact }}",
|
|
65
65
|
);
|
|
66
66
|
return [
|
|
67
67
|
" buildchain-channel:",
|
|
68
|
-
' description: "Resolved Buildchain channel: alpha
|
|
68
|
+
' description: "Resolved Buildchain channel: alpha or stable"',
|
|
69
69
|
" value: ${{ jobs.resolve-channel.outputs.channel }}",
|
|
70
70
|
" buildchain-channel-selection-source:",
|
|
71
71
|
' description: "Evidence source used to select the Buildchain channel"',
|
|
@@ -154,7 +154,8 @@ function routerControllerReceiptJob() {
|
|
|
154
154
|
needs:
|
|
155
155
|
- resolve-channel
|
|
156
156
|
- controller-plan
|
|
157
|
-
-
|
|
157
|
+
- alpha
|
|
158
|
+
- stable
|
|
158
159
|
if: \${{ always() && needs.controller-plan.result == 'success' }}
|
|
159
160
|
runs-on: \${{ fromJSON(inputs.control-runner-json) }}
|
|
160
161
|
outputs:
|
|
@@ -183,9 +184,9 @@ function routerControllerReceiptJob() {
|
|
|
183
184
|
env:
|
|
184
185
|
BUILDCHAIN_CONTROLLER_PLAN_PATH: .buildchain/controller/plan.json
|
|
185
186
|
BUILDCHAIN_CONTROLLER_STAGES_JSON: >-
|
|
186
|
-
[{"id":"resolve-channel","status":"\${{ needs.resolve-channel.result }}"},{"id":"
|
|
187
|
-
BUILDCHAIN_CONTROLLER_EVIDENCE_JSON: \${{ needs.
|
|
188
|
-
BUILDCHAIN_CONTROLLER_REASON_CODE: \${{ needs.
|
|
187
|
+
[{"id":"resolve-channel","status":"\${{ needs.resolve-channel.result }}"},{"id":"alpha","status":"\${{ needs.alpha.result }}"},{"id":"stable","status":"\${{ needs.stable.result }}"},{"id":"aggregate","status":"\${{ needs.resolve-channel.outputs.channel == 'alpha' && needs.alpha.result || needs.stable.result }}"}]
|
|
188
|
+
BUILDCHAIN_CONTROLLER_EVIDENCE_JSON: \${{ (needs.alpha.outputs.controller-receipt-digest != '' || needs.stable.outputs.controller-receipt-digest != '') && format('[{{"kind":"nested-controller-receipt","digest":"{0}"}}]', needs.alpha.outputs.controller-receipt-digest || needs.stable.outputs.controller-receipt-digest) || '[]' }}
|
|
189
|
+
BUILDCHAIN_CONTROLLER_REASON_CODE: \${{ ((needs.resolve-channel.outputs.channel == 'alpha' && needs.alpha.result == 'success') || (needs.resolve-channel.outputs.channel == 'stable' && needs.stable.result == 'success')) && '' || 'nested-build-incomplete' }}
|
|
189
190
|
BUILDCHAIN_CONTROLLER_REASON_SUMMARY: Nested build controller did not complete successfully
|
|
190
191
|
BUILDCHAIN_CONTROLLER_RECEIPT_ARTIFACT: buildchain-channel-controller-receipt-\${{ github.sha }}
|
|
191
192
|
BUILDCHAIN_CONTROLLER_RECEIPT_PATH: .buildchain/controller/receipt.json
|
|
@@ -215,7 +216,9 @@ function routerAggregateJob() {
|
|
|
215
216
|
return ` summarize:
|
|
216
217
|
name: Summarize build contract
|
|
217
218
|
needs:
|
|
218
|
-
-
|
|
219
|
+
- resolve-channel
|
|
220
|
+
- alpha
|
|
221
|
+
- stable
|
|
219
222
|
- controller-receipt
|
|
220
223
|
if: \${{ always() }}
|
|
221
224
|
runs-on: \${{ fromJSON(inputs.control-runner-json) }}
|
|
@@ -223,12 +226,16 @@ function routerAggregateJob() {
|
|
|
223
226
|
- name: Enforce public channel router aggregate
|
|
224
227
|
shell: bash
|
|
225
228
|
env:
|
|
226
|
-
|
|
229
|
+
CHANNEL: \${{ needs.resolve-channel.outputs.channel }}
|
|
230
|
+
ALPHA_RESULT: \${{ needs.alpha.result }}
|
|
231
|
+
STABLE_RESULT: \${{ needs.stable.result }}
|
|
227
232
|
CONTROLLER_RECEIPT_RESULT: \${{ needs.controller-receipt.result }}
|
|
228
233
|
run: |
|
|
229
234
|
set -euo pipefail
|
|
230
|
-
|
|
231
|
-
|
|
235
|
+
selected_result="\${STABLE_RESULT}"
|
|
236
|
+
if [[ "\${CHANNEL}" = "alpha" ]]; then selected_result="\${ALPHA_RESULT}"; fi
|
|
237
|
+
if [[ "\${selected_result}" != "success" || "\${CONTROLLER_RECEIPT_RESULT}" != "success" ]]; then
|
|
238
|
+
echo "::error::Buildchain channel router did not qualify: channel=\${CHANNEL} selected=\${selected_result} controller-receipt=\${CONTROLLER_RECEIPT_RESULT}"
|
|
232
239
|
exit 1
|
|
233
240
|
fi
|
|
234
241
|
echo "Buildchain channel router aggregate passed."`;
|
|
@@ -245,6 +252,47 @@ function generateChannelBuildWorkflowBase(source) {
|
|
|
245
252
|
return `# Generated by scripts/generate-channel-build-workflow.mjs. Do not edit directly.\nname: Buildchain Channel Build\n\non:\n workflow_call:\n inputs:\n${routerInputs(inputs)}\n secrets:\n${secrets.trimEnd()}\n outputs:\n${routerOutputs(outputs)}\n\npermissions:\n contents: read\n issues: write\n id-token: write\n\njobs:\n resolve-channel:\n name: Resolve Buildchain channel\n runs-on: ubuntu-24.04\n outputs:\n channel: \${{ steps.channel.outputs.channel }}\n buildchain-ref: \${{ steps.channel.outputs.buildchain-ref }}\n contract-lock-path: \${{ steps.lock.outputs.path }}\n selection-source: \${{ steps.channel.outputs.selection-source }}\n reason: \${{ steps.channel.outputs.reason }}\n steps:\n - name: Resolve router source\n id: router\n shell: bash\n env:\n BUILDCHAIN_ROUTER_WORKFLOW_REF: \${{ job.workflow_ref }}\n run: |\n set -euo pipefail\n workflow_ref=\"\${BUILDCHAIN_ROUTER_WORKFLOW_REF}\"\n repository=\"\${workflow_ref%%/.github/workflows/*}\"\n ref=\"\${workflow_ref##*@}\"\n ref=\"\${ref#refs/heads/}\"\n ref=\"\${ref#refs/tags/}\"\n if [[ -z \"\${repository}\" || \"\${repository}\" = \"\${workflow_ref}\" || -z \"\${ref}\" ]]; then\n echo \"Unable to resolve Buildchain router source from job.workflow_ref=\${workflow_ref}\" >&2\n exit 1\n fi\n {\n echo \"repository=\${repository}\"\n echo \"ref=\${ref}\"\n } >> \"\${GITHUB_OUTPUT}\"\n\n - name: Checkout Buildchain router\n uses: actions/checkout@v7.0.0\n with:\n repository: \${{ steps.router.outputs.repository }}\n ref: \${{ steps.router.outputs.ref }}\n path: .buildchain/router\n persist-credentials: false\n\n - name: Resolve channel\n id: channel\n shell: bash\n env:\n BUILDCHAIN_CHANNEL: \${{ inputs.buildchain-channel }}\n BUILDCHAIN_REQUESTED_REF: \${{ inputs.buildchain-ref }}\n BUILDCHAIN_PUBLISH_CHANNEL: \${{ inputs.publish-channel }}\n BUILDCHAIN_EVENT_NAME: \${{ github.event_name }}\n BUILDCHAIN_GIT_REF: \${{ github.ref }}\n BUILDCHAIN_RELEASE_PRERELEASE: \${{ github.event.release.prerelease }}\n BUILDCHAIN_ROUTER_REF: \${{ steps.router.outputs.ref }}\n run: |\n node .buildchain/router/scripts/buildchain-channel-router.mjs \\\n --cwd .buildchain/router \\\n --channel \"\${BUILDCHAIN_CHANNEL}\" \\\n --buildchain-ref \"\${BUILDCHAIN_REQUESTED_REF}\" \\\n --publish-channel \"\${BUILDCHAIN_PUBLISH_CHANNEL}\" \\\n --event-name \"\${BUILDCHAIN_EVENT_NAME}\" \\\n --ref \"\${BUILDCHAIN_GIT_REF}\" \\\n --release-prerelease \"\${BUILDCHAIN_RELEASE_PRERELEASE}\" \\\n --router-ref \"\${BUILDCHAIN_ROUTER_REF}\"\n\n - name: Select consumer contract lock\n id: lock\n shell: bash\n env:\n EXPLICIT_PATH: \${{ inputs.buildchain-contract-lock-path }}\n ALPHA_PATH: \${{ inputs.buildchain-alpha-contract-lock-path }}\n STABLE_PATH: \${{ inputs.buildchain-stable-contract-lock-path }}\n CHANNEL: \${{ steps.channel.outputs.channel }}\n run: |\n set -euo pipefail\n path=\"\${EXPLICIT_PATH}\"\n if [[ -z \"\${path}\" ]]; then\n if [[ \"\${CHANNEL}\" = \"alpha\" ]]; then\n path=\"\${ALPHA_PATH}\"\n else\n path=\"\${STABLE_PATH}\"\n fi\n fi\n echo \"path=\${path}\" >> \"\${GITHUB_OUTPUT}\"\n\n build:\n name: Build with resolved channel\n needs: resolve-channel\n uses: ./.github/workflows/.build.yml\n permissions:\n contents: read\n issues: write\n id-token: write\n with:\n${forwardedInputs(names)}\n secrets: inherit\n`;
|
|
246
253
|
}
|
|
247
254
|
|
|
255
|
+
function splitBuildJobsByChannel(workflow, names, major) {
|
|
256
|
+
const marker = "\n build:\n";
|
|
257
|
+
const index = workflow.indexOf(marker);
|
|
258
|
+
if (index < 0) throw new Error("generated workflow is missing the build job");
|
|
259
|
+
const inputs = forwardedInputs(names);
|
|
260
|
+
const jobs = `
|
|
261
|
+
alpha:
|
|
262
|
+
name: Build alpha channel
|
|
263
|
+
needs:
|
|
264
|
+
- resolve-channel
|
|
265
|
+
- controller-plan
|
|
266
|
+
if: \${{ needs.resolve-channel.outputs.channel == 'alpha' }}
|
|
267
|
+
uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v${major}-alpha
|
|
268
|
+
permissions:
|
|
269
|
+
actions: read
|
|
270
|
+
contents: read
|
|
271
|
+
issues: write
|
|
272
|
+
id-token: write
|
|
273
|
+
with:
|
|
274
|
+
${inputs}
|
|
275
|
+
secrets: inherit
|
|
276
|
+
|
|
277
|
+
stable:
|
|
278
|
+
name: Build stable channel
|
|
279
|
+
needs:
|
|
280
|
+
- resolve-channel
|
|
281
|
+
- controller-plan
|
|
282
|
+
if: \${{ needs.resolve-channel.outputs.channel == 'stable' }}
|
|
283
|
+
uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v${major}
|
|
284
|
+
permissions:
|
|
285
|
+
actions: read
|
|
286
|
+
contents: read
|
|
287
|
+
issues: write
|
|
288
|
+
id-token: write
|
|
289
|
+
with:
|
|
290
|
+
${inputs}
|
|
291
|
+
secrets: inherit
|
|
292
|
+
`;
|
|
293
|
+
return `${workflow.slice(0, index)}${jobs}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
248
296
|
function bindRouterCheckoutToWorkflowSha(workflow) {
|
|
249
297
|
return workflow
|
|
250
298
|
.replace(
|
|
@@ -294,7 +342,14 @@ function bindRouterCheckoutToWorkflowSha(workflow) {
|
|
|
294
342
|
}
|
|
295
343
|
|
|
296
344
|
export function generateChannelBuildWorkflow(source) {
|
|
297
|
-
const
|
|
345
|
+
const inputBlock = blockBetween(source, " inputs:\n", " secrets:\n");
|
|
346
|
+
const names = inputNames(inputBlock);
|
|
347
|
+
const packageVersion = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version || "";
|
|
348
|
+
const major = Number(String(packageVersion).split(".")[0]);
|
|
349
|
+
if (!Number.isInteger(major) || major < 1) throw new Error("package.json version must declare a positive major");
|
|
350
|
+
const generated = bindRouterCheckoutToWorkflowSha(
|
|
351
|
+
splitBuildJobsByChannel(generateChannelBuildWorkflowBase(source), names, major),
|
|
352
|
+
)
|
|
298
353
|
.replaceAll("runs-on: ubuntu-24.04", "runs-on: ${{ fromJSON(inputs.control-runner-json) }}")
|
|
299
354
|
.replace(
|
|
300
355
|
"\npermissions:\n contents: read\n",
|
|
@@ -310,15 +365,7 @@ export function generateChannelBuildWorkflow(source) {
|
|
|
310
365
|
"",
|
|
311
366
|
].join("\n"),
|
|
312
367
|
)
|
|
313
|
-
.replace("\n
|
|
314
|
-
.replace(
|
|
315
|
-
" needs: resolve-channel\n uses: ./.github/workflows/.build.yml",
|
|
316
|
-
" needs:\n - resolve-channel\n - controller-plan\n uses: ./.github/workflows/.build.yml",
|
|
317
|
-
)
|
|
318
|
-
.replace(
|
|
319
|
-
" uses: ./.github/workflows/.build.yml\n permissions:\n contents: read\n",
|
|
320
|
-
" uses: ./.github/workflows/.build.yml\n permissions:\n actions: read\n contents: read\n",
|
|
321
|
-
);
|
|
368
|
+
.replace("\n alpha:\n", `\n${routerControllerPlanJob()}\n\n alpha:\n`);
|
|
322
369
|
return `${generated.trimEnd()}\n\n${routerControllerReceiptJob()}\n\n${routerAggregateJob()}\n`;
|
|
323
370
|
}
|
|
324
371
|
|
|
@@ -15,10 +15,7 @@ import {
|
|
|
15
15
|
collectKfdUpstreamFacts,
|
|
16
16
|
} from "../packages/core/kfd.js";
|
|
17
17
|
import { KFD_AGENT_HUB_ADOPTION_SCHEMA } from "../packages/core/kfd-agent-hub.js";
|
|
18
|
-
import {
|
|
19
|
-
KFD_PRODUCT_GATE_INPUT_SCHEMA,
|
|
20
|
-
KFD_SUPPORT_PROJECTION_SCHEMA,
|
|
21
|
-
} from "../packages/core/kfd-product-gates.js";
|
|
18
|
+
import { KFD_PRODUCT_GATE_INPUT_SCHEMA } from "../packages/core/kfd-product-gates.js";
|
|
22
19
|
import {
|
|
23
20
|
createReadmeBadgeEndpointRegistry,
|
|
24
21
|
} from "../packages/core/readme-badges.js";
|
|
@@ -933,7 +930,6 @@ function buildSiteBundle() {
|
|
|
933
930
|
"schemas/release-tail-provider-bindings-v1.schema.json",
|
|
934
931
|
"schemas/kfd-agent-hub-adoption.schema.json",
|
|
935
932
|
"schemas/kfd-product-gate-input-v1.schema.json",
|
|
936
|
-
"schemas/kfd-support-projection-v1.schema.json",
|
|
937
933
|
"kfd-support.json",
|
|
938
934
|
"artifact-evidence.json",
|
|
939
935
|
"product-mechanism.json",
|
|
@@ -971,7 +967,6 @@ function buildSiteBundle() {
|
|
|
971
967
|
"schemas/release-tail-provider-bindings-v1.schema.json",
|
|
972
968
|
"schemas/kfd-agent-hub-adoption.schema.json",
|
|
973
969
|
"schemas/kfd-product-gate-input-v1.schema.json",
|
|
974
|
-
"schemas/kfd-support-projection-v1.schema.json",
|
|
975
970
|
"buildchain-contract.json",
|
|
976
971
|
"kfd-claims.json",
|
|
977
972
|
"product-mechanism.json",
|
|
@@ -1105,7 +1100,6 @@ function buildSiteBundle() {
|
|
|
1105
1100
|
"schemas/release-tail-provider-bindings-v1.schema.json",
|
|
1106
1101
|
"schemas/kfd-agent-hub-adoption.schema.json",
|
|
1107
1102
|
"schemas/kfd-product-gate-input-v1.schema.json",
|
|
1108
|
-
"schemas/kfd-support-projection-v1.schema.json",
|
|
1109
1103
|
"buildchain-contract.json",
|
|
1110
1104
|
"kfd-upstream-aggregate.json",
|
|
1111
1105
|
"kfd-claims.json",
|
|
@@ -1289,7 +1283,6 @@ function buildSiteBundle() {
|
|
|
1289
1283
|
),
|
|
1290
1284
|
"schemas/kfd-agent-hub-adoption.schema.json": KFD_AGENT_HUB_ADOPTION_SCHEMA,
|
|
1291
1285
|
"schemas/kfd-product-gate-input-v1.schema.json": KFD_PRODUCT_GATE_INPUT_SCHEMA,
|
|
1292
|
-
"schemas/kfd-support-projection-v1.schema.json": KFD_SUPPORT_PROJECTION_SCHEMA,
|
|
1293
1286
|
"badge-endpoint-registry.json": badgeEndpointRegistry,
|
|
1294
1287
|
"publication-registry.json": publicationRegistry,
|
|
1295
1288
|
"buildchain-contract.json": createBuildchainContractWorld({ root, controllerRegistry }),
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { resolveBuildchainChannel } from "./buildchain-channel-router.mjs";
|
|
7
|
+
import { parseBuildchainRefIdentity } from "../packages/core/buildchain-channel-identity.js";
|
|
7
8
|
|
|
8
9
|
const TARGETS = [
|
|
9
10
|
{ pattern: /^alpha\/v(\d+)\/v\d+\.\d+$/, publicationChannel: "alpha", shellChannel: "alpha" },
|
|
@@ -54,9 +55,10 @@ export function resolvePromotionChannel({
|
|
|
54
55
|
routerRef,
|
|
55
56
|
packageVersion,
|
|
56
57
|
});
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const
|
|
58
|
+
const runtimeIdentity = parseBuildchainRefIdentity(selected.buildchainRef);
|
|
59
|
+
const opaqueOverride = new Set(["train", "authority", "exact-sha"]).has(runtimeIdentity.kind);
|
|
60
|
+
const trustedRouterPin = opaqueOverride && sameExactSha(selected.buildchainRef, routerSha);
|
|
61
|
+
const overrideUsed = opaqueOverride && !trustedRouterPin;
|
|
60
62
|
if (!overrideUsed && !trustedRouterPin && selected.channel !== intent.shellChannel) {
|
|
61
63
|
throw new Error(
|
|
62
64
|
`promotion target ${intent.targetRef} requires ${intent.shellChannel} shell/runtime, got ${selected.channel}`,
|