clusterflux-public/scripts/release-quality-gates.js
Clusterflux release 2a0f7ded04 Public release release-ea887c8f56cd
Source commit: ea887c8f56cd53985a1179b13e5f1b85c485f584

Public tree identity: sha256:b96a97aacdbc8fd4fc2ea20d0e1450280d46db3f24aabe1475e5fbe20e05f255
2026-07-25 02:55:35 +02:00

322 lines
8.7 KiB
JavaScript
Executable file

#!/usr/bin/env node
const assert = require("assert");
const cp = require("child_process");
const crypto = require("crypto");
const fs = require("fs");
const os = require("os");
const path = require("path");
const repo = path.resolve(__dirname, "..");
const manifestPath = path.resolve(
process.env.CLUSTERFLUX_PUBLIC_RELEASE_MANIFEST ||
path.join(
process.env.CLUSTERFLUX_PUBLIC_RELEASE_DIR ||
path.join(repo, "target/release-candidate"),
"public-release-manifest.json"
)
);
const evidencePath = path.resolve(
process.env.CLUSTERFLUX_QUALITY_GATE_EVIDENCE_PATH ||
path.join(repo, "target/acceptance/quality-gates.json")
);
const transcriptPath = path.resolve(
process.env.CLUSTERFLUX_QUALITY_GATE_TRANSCRIPT_PATH ||
path.join(repo, "target/acceptance/quality-gates-transcript.txt")
);
function readJson(file) {
return JSON.parse(fs.readFileSync(file, "utf8"));
}
function sha256(file) {
return crypto
.createHash("sha256")
.update(fs.readFileSync(file))
.digest("hex");
}
function commandText(command, args) {
return [command, ...args]
.map((value) =>
/^[A-Za-z0-9_./:=+-]+$/.test(value)
? value
: JSON.stringify(value)
)
.join(" ");
}
function main() {
assert(fs.existsSync(manifestPath), `missing release manifest ${manifestPath}`);
const manifest = readJson(manifestPath);
fs.mkdirSync(path.dirname(evidencePath), { recursive: true });
fs.mkdirSync(path.dirname(transcriptPath), { recursive: true });
const transcript = fs.openSync(transcriptPath, "w");
const evidence = {
kind: "clusterflux-release-quality-gates",
source_commit: manifest.source_commit,
source_tree_digest: manifest.source_tree_digest,
public_tree_identity: manifest.public_tree_identity,
checks: {},
};
const runGroup = (id, commands, extra = {}) => {
const startedAt = Date.now();
const commandEvidence = [];
let status = "passed";
for (const command of commands) {
const args = command.args || [];
const cwd = command.cwd || repo;
const text = commandText(command.command, args);
fs.writeSync(transcript, `\n[${id}] ${text}\n`);
const commandStartedAt = Date.now();
const result = cp.spawnSync(command.command, args, {
cwd,
env: { ...process.env, ...(command.env || {}) },
stdio: ["ignore", transcript, transcript],
});
const durationMs = Math.max(1, Date.now() - commandStartedAt);
commandEvidence.push({
command: text,
cwd,
exit_status: result.status,
duration_ms: durationMs,
});
if (result.error || result.status !== 0) {
status = "failed";
evidence.checks[id] = {
status,
duration_ms: Math.max(1, Date.now() - startedAt),
commands: commandEvidence,
error: result.error?.message || `command exited ${result.status}`,
...extra,
};
fs.writeFileSync(evidencePath, `${JSON.stringify(evidence, null, 2)}\n`);
throw new Error(`${id} failed: ${text}`);
}
}
evidence.checks[id] = {
status,
duration_ms: Math.max(1, Date.now() - startedAt),
commands: commandEvidence,
...extra,
};
};
try {
runGroup("repository_structure", [
{ command: path.join(repo, "scripts/check-old-name.sh") },
{ command: "node", args: [path.join(repo, "scripts/check-docs.js")] },
{ command: path.join(repo, "scripts/check-code-size.sh") },
]);
runGroup("formatting", [
{ command: "cargo", args: ["fmt", "--all", "--check"] },
{
command: "cargo",
args: [
"fmt",
"--all",
"--manifest-path",
"private/hosted-policy/Cargo.toml",
"--check",
],
},
]);
runGroup("clippy_warnings_denied", [
{
command: "cargo",
args: ["clippy", "--workspace", "--all-targets", "--", "-D", "warnings"],
},
{
command: "cargo",
args: [
"clippy",
"--manifest-path",
"private/hosted-policy/Cargo.toml",
"--all-targets",
"--",
"-D",
"warnings",
],
},
]);
runGroup("public_workspace_tests", [
{
command: "cargo",
args: ["test", "--workspace", "--all-targets"],
},
{
command: "cargo",
args: ["build", "--workspace", "--all-targets"],
},
]);
runGroup("private_hosted_policy_locked_tests", [
{
command: "cargo",
args: [
"test",
"--locked",
"--manifest-path",
"private/hosted-policy/Cargo.toml",
"--all-targets",
],
},
]);
runGroup("process_lifecycle_regressions", [
{
command: "cargo",
args: [
"test",
"-p",
"clusterflux-coordinator",
"completed_main_",
],
},
{
command: "cargo",
args: [
"test",
"-p",
"clusterflux-coordinator",
"failed_main_aborts_unfinished_children_and_clears_process_debug_state",
],
},
{
command: "cargo",
args: [
"test",
"-p",
"clusterflux-coordinator",
"service_cancels_whole_process_and_blocks_new_task_launches",
],
},
]);
runGroup("wasm_example_builds", [
{
command: "cargo",
args: [
"build",
"-p",
"hello-build",
"--target",
"wasm32-unknown-unknown",
],
},
{
command: "cargo",
args: [
"build",
"-p",
"recovery-build",
"--target",
"wasm32-unknown-unknown",
],
},
{
command: "cargo",
args: [
"build",
"-p",
"runtime-conformance",
"--target",
"wasm32-unknown-unknown",
],
},
]);
runGroup("filtered_public_tree_build_and_tests", [
{ command: path.join(repo, "scripts/verify-public-split.sh") },
]);
const extensionAsset = manifest.assets.find((asset) =>
asset.name.endsWith(".vsix")
);
assert(extensionAsset, "candidate manifest omitted its VSIX");
const extensionFile = path.resolve(
path.dirname(manifestPath),
extensionAsset.file
);
assert(fs.existsSync(extensionFile), `missing candidate VSIX ${extensionFile}`);
const extensionDigest = sha256(extensionFile);
assert.strictEqual(extensionDigest, extensionAsset.sha256);
assert.strictEqual(
extensionDigest,
manifest.release_candidate.extension_sha256
);
const extractedVsix = fs.mkdtempSync(
path.join(os.tmpdir(), "clusterflux-tested-vsix-")
);
try {
runGroup(
"vscode_extension_candidate",
[
{
command: "npm",
args: ["ci", "--ignore-scripts"],
cwd: path.join(repo, "vscode-extension"),
},
{
command: "unzip",
args: ["-q", extensionFile, "-d", extractedVsix],
},
{
command: "node",
args: [path.join(repo, "scripts/vscode-extension-smoke.js")],
env: {
CLUSTERFLUX_VSCODE_EXTENSION_ROOT: path.join(
extractedVsix,
"extension"
),
},
},
],
{
candidate_vsix: {
file: extensionFile,
sha256: extensionDigest,
},
}
);
} finally {
fs.rmSync(extractedVsix, { recursive: true, force: true });
}
const publicCheckIds = [
"repository_structure",
"formatting",
"clippy_warnings_denied",
"public_workspace_tests",
"process_lifecycle_regressions",
"wasm_example_builds",
"filtered_public_tree_build_and_tests",
"vscode_extension_candidate",
];
evidence.public = {
status: "passed",
duration_ms: publicCheckIds.reduce(
(total, id) => total + evidence.checks[id].duration_ms,
0
),
independent_filtered_tree: true,
};
evidence.private = {
status: "passed",
duration_ms:
evidence.checks.formatting.duration_ms +
evidence.checks.clippy_warnings_denied.duration_ms +
evidence.checks.private_hosted_policy_locked_tests.duration_ms,
};
evidence.status = "passed";
evidence.transcript = transcriptPath;
fs.writeFileSync(evidencePath, `${JSON.stringify(evidence, null, 2)}\n`);
} finally {
fs.closeSync(transcript);
}
console.log(`Release quality gates passed: ${evidencePath}`);
}
try {
main();
} catch (error) {
console.error(error.stack || error.message);
process.exit(1);
}