Publish Clusterflux release candidate 6e83783

This commit is contained in:
Clusterflux release 2026-07-20 19:32:05 +02:00
parent 4d75257c6e
commit f3640643bd
16 changed files with 4667 additions and 106 deletions

View file

@ -0,0 +1,163 @@
#!/usr/bin/env node
"use strict";
const assert = require("node:assert");
const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");
const { spawnSync } = require("node:child_process");
const repo = path.resolve(__dirname, "..");
const releaseRoot = path.resolve(
process.env.CLUSTERFLUX_PUBLIC_RELEASE_DIR ||
path.join(repo, "target/public-release-final")
);
const manifestPath = path.join(releaseRoot, "public-release-manifest.json");
const resultPath = path.resolve(
process.env.CLUSTERFLUX_FINAL_RESULT ||
path.join(repo, "target/acceptance/cli-happy-path-live.json")
);
const transcriptPath = path.resolve(
process.env.CLUSTERFLUX_VALIDATION_TRANSCRIPT ||
path.join(repo, "target/acceptance/clusterflux-manual-launch-transcript.log")
);
function sha256(file) {
const hash = crypto.createHash("sha256");
hash.update(fs.readFileSync(file));
return hash.digest("hex");
}
function readJson(file) {
return JSON.parse(fs.readFileSync(file, "utf8"));
}
function write(file, contents) {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, contents);
}
function copyVerifiedAsset(asset, destinationRoot) {
const source = path.resolve(releaseRoot, asset.file);
assert(fs.existsSync(source), `missing finalized asset ${source}`);
assert.strictEqual(
sha256(source),
asset.sha256,
`finalized asset digest changed: ${asset.name}`
);
const destination = path.join(destinationRoot, "assets", asset.name);
fs.copyFileSync(source, destination);
assert.strictEqual(sha256(destination), asset.sha256);
return destination;
}
function extractBinaries(archive, destinationRoot) {
const staging = path.join(destinationRoot, ".binary-extract");
fs.mkdirSync(staging, { recursive: true });
const extracted = spawnSync("tar", ["-xzf", archive, "-C", staging], {
encoding: "utf8",
});
if (extracted.status !== 0) {
throw new Error(`failed to extract ${archive}: ${extracted.stderr}`);
}
const binRoot = path.join(staging, "bin");
assert(fs.existsSync(binRoot), `binary archive omitted bin/: ${archive}`);
const copied = [];
for (const name of fs.readdirSync(binRoot).sort()) {
const source = path.join(binRoot, name);
if (!fs.statSync(source).isFile()) continue;
const destination = path.join(destinationRoot, "binaries", name);
fs.copyFileSync(source, destination);
fs.chmodSync(destination, 0o755);
copied.push(destination);
}
fs.rmSync(staging, { recursive: true, force: true });
assert(copied.length > 0, `binary archive contained no binaries: ${archive}`);
return copied;
}
function main() {
assert(fs.existsSync(manifestPath), `missing final manifest ${manifestPath}`);
assert(fs.existsSync(resultPath), `missing final result ${resultPath}`);
assert(fs.existsSync(transcriptPath), `missing validation transcript ${transcriptPath}`);
const manifest = readJson(manifestPath);
const result = readJson(resultPath);
assert.strictEqual(result.acceptance_result, "passed");
assert.strictEqual(result.source_commit, manifest.source_commit);
assert.strictEqual(result.source_tree_digest, manifest.source_tree_digest);
assert.deepStrictEqual(result.binary_digests, manifest.binary_digests);
assert.strictEqual(result.scenario_skips.length, 0);
assert.strictEqual(result.strict_requirement_ledger.length, 25);
assert(
result.strict_requirement_ledger.every((requirement) => requirement.passed === true),
"final validation contains a failed launch requirement"
);
const destinationRoot = path.resolve(
process.env.CLUSTERFLUX_GITHUB_RELEASE_DIR ||
path.join(repo, "target/github-release", manifest.release_name)
);
fs.rmSync(destinationRoot, { recursive: true, force: true });
fs.mkdirSync(path.join(destinationRoot, "assets"), { recursive: true });
fs.mkdirSync(path.join(destinationRoot, "binaries"), { recursive: true });
const copiedAssets = manifest.assets.map((asset) =>
copyVerifiedAsset(asset, destinationRoot)
);
const binaryArchive = copiedAssets.find((file) =>
path.basename(file).startsWith("clusterflux-public-binaries-")
);
assert(binaryArchive, "final manifest omitted the public binary archive");
const binaries = extractBinaries(binaryArchive, destinationRoot);
const vsix = copiedAssets.find((file) => file.endsWith(".vsix"));
assert(vsix, "final manifest omitted the tested VSIX");
assert.strictEqual(
sha256(vsix),
manifest.release_candidate.extension_sha256,
"tested VSIX digest does not match final candidate binding"
);
write(path.join(destinationRoot, "SOURCE_REVISION"), `${manifest.source_commit}\n`);
write(
path.join(destinationRoot, "SOURCE_TREE_DIGEST"),
`${manifest.source_tree_digest}\n`
);
write(path.join(destinationRoot, "VSIX_SHA256"), `${sha256(vsix)} ${path.basename(vsix)}\n`);
fs.copyFileSync(manifestPath, path.join(destinationRoot, "public-release-manifest.json"));
fs.copyFileSync(resultPath, path.join(destinationRoot, "final-result.json"));
fs.copyFileSync(transcriptPath, path.join(destinationRoot, "VALIDATION_TRANSCRIPT.log"));
write(
path.join(destinationRoot, "RELEASE_NOTES.md"),
`# Clusterflux ${manifest.release_name}\n\n` +
"First manual GitHub launch release of Clusterflux. The release includes the filtered public source, real Linux CLI/node/coordinator binaries, and the exact VSIX used by the final production-shaped validation batch.\n"
);
write(
path.join(destinationRoot, "KNOWN_LIMITATIONS.md"),
"# Known limitations\n\n" +
"- The launch batch validates one enrolled Linux node with rootless Podman.\n" +
"- Full Windows sandbox validation is deferred.\n" +
"- Providers, billing, teams, managed compute, and operator-panel UI are not part of this release.\n"
);
const checksummed = [
...copiedAssets,
...binaries,
path.join(destinationRoot, "SOURCE_REVISION"),
path.join(destinationRoot, "SOURCE_TREE_DIGEST"),
path.join(destinationRoot, "VSIX_SHA256"),
path.join(destinationRoot, "public-release-manifest.json"),
path.join(destinationRoot, "final-result.json"),
path.join(destinationRoot, "VALIDATION_TRANSCRIPT.log"),
path.join(destinationRoot, "RELEASE_NOTES.md"),
path.join(destinationRoot, "KNOWN_LIMITATIONS.md"),
].sort();
write(
path.join(destinationRoot, "SHA256SUMS"),
`${checksummed
.map((file) => `${sha256(file)} ${path.relative(destinationRoot, file)}`)
.join("\n")}\n`
);
console.log(destinationRoot);
}
main();