Publish Clusterflux 45bbc21

This commit is contained in:
Michel Paulissen 2026-07-20 11:08:26 +02:00
parent eb1077f380
commit 3f88254c70
28 changed files with 896 additions and 167 deletions

View file

@ -537,7 +537,9 @@ function stageEvidenceAsset(
sourceDigest,
publicTreeIdentity,
binaryDigests,
candidateBinding
candidateBinding,
candidateConfigurationIdentity,
candidateProxyConfigurationIdentity
) {
const stage = path.join(stagingDir, "evidence");
fs.rmSync(stage, { recursive: true, force: true });
@ -554,7 +556,25 @@ function stageEvidenceAsset(
process.env.CLUSTERFLUX_FINAL_RESULT_PATH ||
path.join(evidenceRoot, "cli-happy-path-live.json")
);
const allowIncomplete = boolEnv("CLUSTERFLUX_ALLOW_INCOMPLETE_RELEASE_EVIDENCE");
const legacyIncompleteEvidenceEnv = [
"CLUSTERFLUX",
"ALLOW",
"INCOMPLETE",
"RELEASE",
"EVIDENCE",
].join("_");
if (process.env[legacyIncompleteEvidenceEnv]) {
throw new Error(
"the legacy incomplete-evidence environment switch is unsupported; use CLUSTERFLUX_RELEASE_STAGE=candidate or final"
);
}
const releaseStage =
process.env.CLUSTERFLUX_RELEASE_STAGE ||
(candidateManifestPath ? "final" : "candidate");
if (!new Set(["candidate", "final"]).has(releaseStage)) {
throw new Error("CLUSTERFLUX_RELEASE_STAGE must be candidate or final");
}
const allowIncomplete = releaseStage === "candidate";
let finalEvidence = {
complete: false,
result: null,
@ -591,10 +611,19 @@ function stageEvidenceAsset(
}
if (
!Array.isArray(liveResult.named_scenarios) ||
liveResult.named_scenarios.length !== 16 ||
liveResult.named_scenarios.length !== 25 ||
liveResult.named_scenarios.some((scenario) => scenario.status !== "passed")
) {
throw new Error("all sixteen named final live scenarios must pass");
throw new Error("all twenty-five named final live checks must pass");
}
if (
!liveResult.release_binding?.deployment ||
!liveResult.release_binding?.configuration ||
!liveResult.release_binding?.proxy_configuration
) {
throw new Error(
"final live evidence must bind deployment, runtime configuration, and proxy configuration identities"
);
}
if (
!Array.isArray(liveResult.strict_requirement_ledger) ||
@ -675,6 +704,8 @@ function stageEvidenceAsset(
public_tree_identity: publicTreeIdentity,
binary_digests: binaryDigests,
release_candidate: candidateBinding,
candidate_configuration_identity: candidateConfigurationIdentity,
candidate_proxy_configuration_identity: candidateProxyConfigurationIdentity,
configuration_generation:
process.env.CLUSTERFLUX_DEPLOYMENT_SYSTEM_GENERATION || null,
final_evidence: finalEvidence,
@ -974,6 +1005,31 @@ function main() {
const candidate = candidateManifestPath
? JSON.parse(fs.readFileSync(candidateManifestPath, "utf8"))
: null;
if (candidate) {
for (const asset of candidate.assets ?? []) {
asset.file = path.isAbsolute(asset.file)
? asset.file
: path.resolve(path.dirname(candidateManifestPath), asset.file);
}
}
const releaseStage =
process.env.CLUSTERFLUX_RELEASE_STAGE ||
(candidateManifestPath ? "final" : "candidate");
if (releaseStage === "final" && !candidate) {
throw new Error("final release stage requires CLUSTERFLUX_RELEASE_CANDIDATE_MANIFEST");
}
if (releaseStage === "candidate" && candidate) {
throw new Error("candidate release stage cannot consume a prior candidate manifest");
}
if (
releaseStage === "candidate" &&
(!process.env.CLUSTERFLUX_CANDIDATE_CONFIGURATION_IDENTITY ||
!process.env.CLUSTERFLUX_CANDIDATE_PROXY_CONFIGURATION_IDENTITY)
) {
throw new Error(
"candidate release stage requires configuration and proxy configuration identities"
);
}
if (candidate) {
assertCandidateManifest(candidate);
if (path.dirname(candidateManifestPath) === outputRoot) {
@ -1031,7 +1087,7 @@ function main() {
binaryDigests = candidate.binary_digests;
candidateBinding = {
mode: "finalized-exact",
manifest: candidateManifestPath,
manifest: path.relative(outputRoot, candidateManifestPath).split(path.sep).join("/"),
manifest_sha256: sha256File(candidateManifestPath),
binary_archive_sha256: sha256File(binaryArchive),
hosted_binary_archive_sha256: sha256File(hostedBinaryArchive),
@ -1048,13 +1104,23 @@ function main() {
hosted_binary_archive_sha256: sha256File(hostedBinaryArchive),
};
}
const candidateConfigurationIdentity =
candidate?.candidate_configuration_identity ||
process.env.CLUSTERFLUX_CANDIDATE_CONFIGURATION_IDENTITY ||
null;
const candidateProxyConfigurationIdentity =
candidate?.candidate_proxy_configuration_identity ||
process.env.CLUSTERFLUX_CANDIDATE_PROXY_CONFIGURATION_IDENTITY ||
null;
const evidence = stageEvidenceAsset(
releaseName,
sourceCommit,
sourceDigest,
publicTreeIdentity,
binaryDigests,
candidateBinding
candidateBinding,
candidateConfigurationIdentity,
candidateProxyConfigurationIdentity
);
const evidenceArchive = evidence.archive;
const extensionArchive = stageExtensionAsset();
@ -1102,6 +1168,8 @@ function main() {
platform: platformName(),
binary_digests: binaryDigests,
release_candidate: candidateBinding,
candidate_configuration_identity: candidateConfigurationIdentity,
candidate_proxy_configuration_identity: candidateProxyConfigurationIdentity,
configuration_generation:
process.env.CLUSTERFLUX_DEPLOYMENT_SYSTEM_GENERATION || null,
final_evidence: evidence.finalEvidence,
@ -1126,7 +1194,7 @@ function main() {
]),
],
assets: [...assets, sha256Sums].map((asset) => ({
file: asset,
file: path.relative(outputRoot, asset).split(path.sep).join("/"),
name: path.basename(asset),
sha256: sha256File(asset),
})),