Public release release-e47f9c27bbeb

Source commit: e47f9c27bbebe6759f6b6fe7b12fd00bb20d116d

Public tree identity: sha256:4c1af1dfd67d3f2b5088531ea8727b11124b013d2251a4d5088f51a6424c0196
This commit is contained in:
Clusterflux release 2026-07-24 15:52:30 +02:00
parent 3a4d4fa7ae
commit 9223c54939
30 changed files with 4195 additions and 434 deletions

View file

@ -47,12 +47,12 @@ function agentWorkflowSignatureMessage({
function signedAgentWorkflowProof(identity, request, options = {}) {
const nonce =
options.nonce ||
options.nonce ??
`${request.type}-${process.pid}-${Date.now()}-${crypto
.randomBytes(8)
.toString("hex")}`;
const issuedAtEpochSeconds =
options.issuedAtEpochSeconds || Math.floor(Date.now() / 1000);
options.issuedAtEpochSeconds ?? Math.floor(Date.now() / 1000);
const processId =
request.type === "launch_task" ? request.task_spec?.process : request.process;
const task =

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,55 @@ const { DapClient } = require("./dap-client");
sourceLines.findIndex((line) => line.includes("pub async fn build_main()")) + 1;
assert(buildMainLine > 0, "flagship source must contain build_main");
const hostileDapClient = new DapClient();
try {
const initialize = hostileDapClient.send("initialize", {
adapterID: "clusterflux",
linesStartAt1: true,
columnsStartAt1: true,
});
await hostileDapClient.response(initialize, "initialize");
for (const processId of [
"",
" ",
"bad\u0000process",
"bad process!",
"x".repeat(256),
]) {
const malformedLaunch = hostileDapClient.send("launch", {
entry: "build",
project,
runtimeBackend: "local-services",
processId,
});
const rejection = await hostileDapClient.failure(
malformedLaunch,
"launch"
);
assert.match(
rejection.message,
/invalid DAP processId: ProcessId is invalid/,
`unexpected malformed DAP identifier response: ${JSON.stringify(rejection)}`
);
}
const validLaunch = hostileDapClient.send("launch", {
entry: "build",
project,
runtimeBackend: "local-services",
processId: "vp-valid-after-malformed",
});
await hostileDapClient.response(validLaunch, "launch");
await hostileDapClient.waitFor(
(message) => message.type === "event" && message.event === "initialized"
);
await hostileDapClient.close();
} catch (error) {
if (hostileDapClient.child.exitCode === null) {
hostileDapClient.child.kill("SIGKILL");
}
throw error;
}
const asynchronousClient = new DapClient();
try {
const initialize = asynchronousClient.send("initialize", {

View file

@ -3,9 +3,53 @@
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const {
agentIdentity,
signedAgentWorkflowRequest,
} = require("./agent-signing");
const {
nodeIdentity,
signedNodeHeartbeat,
} = require("./node-signing");
const repo = path.resolve(__dirname, "..");
const signingInstrumentAgent = agentIdentity(
"hostile-input-contract-agent",
"agent-hostile-input-contract"
);
const explicitlyEmptyAgentNonce = signedAgentWorkflowRequest(
signingInstrumentAgent,
{
type: "start_process",
tenant: "tenant",
project: "project",
actor_agent: "agent-hostile-input-contract",
process: "process",
launch_attempt: "attempt",
restart: false,
},
{ nonce: "" }
);
assert.strictEqual(
explicitlyEmptyAgentNonce.agent_signature.nonce,
"",
"Agent signing instrument replaced an explicitly empty hostile nonce"
);
const signingInstrumentNode = nodeIdentity(
"hostile-input-contract-node",
"node-hostile-input-contract"
);
assert.strictEqual(
signedNodeHeartbeat(
"node-hostile-input-contract",
signingInstrumentNode,
{ nonce: "" }
).nonce,
"",
"Node signing instrument replaced an explicitly empty hostile nonce"
);
function read(relativePath) {
return fs.readFileSync(path.join(repo, relativePath), "utf8");
}

View file

@ -127,7 +127,7 @@ function nodeSignatureMessage(
function signedNodeProof(node, identity, requestKind, request, options = {}) {
const nonce =
options.nonce ||
options.nonce ??
`${requestKind}-${process.pid}-${Date.now()}-${crypto
.randomBytes(8)
.toString("hex")}`;

View file

@ -51,7 +51,7 @@ function copyVerifiedAsset(asset, destinationRoot) {
return destination;
}
function extractBinaries(archive, destinationRoot) {
function extractBinaries(archive, destinationRoot, expectedDigests) {
const staging = path.join(destinationRoot, ".binary-extract");
fs.mkdirSync(staging, { recursive: true });
const extracted = spawnSync("tar", ["-xzf", archive, "-C", staging], {
@ -69,6 +69,11 @@ function extractBinaries(archive, destinationRoot) {
const destination = path.join(destinationRoot, "binaries", name);
fs.copyFileSync(source, destination);
fs.chmodSync(destination, 0o755);
assert.strictEqual(
`sha256:${sha256(destination)}`,
expectedDigests[name],
`tested binary digest changed: ${name}`
);
copied.push(destination);
}
fs.rmSync(staging, { recursive: true, force: true });
@ -87,11 +92,21 @@ function main() {
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.length >= 29);
assert(
result.strict_requirement_ledger.every((requirement) => requirement.passed === true),
result.strict_requirement_ledger.every(
(requirement) =>
requirement.passed === true &&
Number.isFinite(requirement.duration_ms) &&
requirement.duration_ms > 0 &&
requirement.evidence
),
"final validation contains a failed launch requirement"
);
assert.strictEqual(
result.named_scenarios.length,
result.strict_requirement_ledger.length
);
const destinationRoot = path.resolve(
process.env.CLUSTERFLUX_GITHUB_RELEASE_DIR ||
@ -104,11 +119,26 @@ function main() {
const copiedAssets = manifest.assets.map((asset) =>
copyVerifiedAsset(asset, destinationRoot)
);
const publicationGuidance = [
JSON.stringify(manifest.notes || []),
...copiedAssets
.filter((file) => file.endsWith(".md"))
.map((file) => fs.readFileSync(file, "utf8")),
].join("\n");
assert.doesNotMatch(
publicationGuidance,
/(?:download|upload|release downloads?)[^\n]*Forgejo/i,
"manual GitHub package contains Forgejo release-publication instructions"
);
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 binaries = extractBinaries(
binaryArchive,
destinationRoot,
manifest.binary_digests
);
const vsix = copiedAssets.find((file) => file.endsWith(".vsix"));
assert(vsix, "final manifest omitted the tested VSIX");
assert.strictEqual(
@ -124,7 +154,6 @@ function main() {
);
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"),
@ -138,6 +167,31 @@ function main() {
"- Full Windows sandbox validation is deferred.\n" +
"- Providers, billing, teams, managed compute, and operator-panel UI are not part of this release.\n"
);
const testedUploadAssets = [
...copiedAssets,
...binaries,
]
.sort()
.map((file) => ({
file: path.relative(destinationRoot, file),
sha256: `sha256:${sha256(file)}`,
}));
write(
path.join(destinationRoot, "final-result.json"),
`${JSON.stringify(
{
...result,
manual_release: {
source_revision: manifest.source_commit,
source_tree_digest: manifest.source_tree_digest,
tested_upload_assets: testedUploadAssets,
publication: "manual GitHub upload from this directory",
},
},
null,
2
)}\n`
);
const checksummed = [
...copiedAssets,

View file

@ -632,10 +632,22 @@ function stageEvidenceAsset(
}
if (
!Array.isArray(liveResult.named_scenarios) ||
liveResult.named_scenarios.length !== 25 ||
liveResult.named_scenarios.some((scenario) => scenario.status !== "passed")
!Array.isArray(liveResult.strict_requirement_ledger) ||
liveResult.named_scenarios.length !==
liveResult.strict_requirement_ledger.length ||
liveResult.named_scenarios.length < 29 ||
liveResult.named_scenarios.some(
(scenario) =>
scenario.status !== "passed" ||
!Number.isFinite(scenario.duration_ms) ||
scenario.duration_ms <= 0
) ||
new Set(liveResult.named_scenarios.map((scenario) => scenario.id))
.size !== liveResult.named_scenarios.length
) {
throw new Error("all twenty-five named final live checks must pass");
throw new Error(
"every named final live check must be unique, measured, and passed"
);
}
if (
!liveResult.release_binding?.deployment ||
@ -649,7 +661,17 @@ function stageEvidenceAsset(
if (
!Array.isArray(liveResult.strict_requirement_ledger) ||
!liveResult.strict_requirement_ledger.length ||
liveResult.strict_requirement_ledger.some((requirement) => requirement.passed !== true)
liveResult.strict_requirement_ledger.some(
(requirement) =>
requirement.passed !== true ||
!Number.isFinite(requirement.duration_ms) ||
requirement.duration_ms <= 0 ||
!requirement.evidence
) ||
liveResult.strict_requirement_ledger.some(
(requirement, index) =>
requirement.id !== liveResult.named_scenarios[index].id
)
) {
throw new Error("the strict final requirement ledger must be complete and passed");
}
@ -685,8 +707,7 @@ function stageEvidenceAsset(
evidence_packaged_at: packagedAt,
},
release_commands: [
"nix develop -c ./scripts/acceptance-private.sh",
"nix develop -c ./scripts/acceptance-public.sh",
"nix develop -c node scripts/release-quality-gates.js",
"node scripts/cli-happy-path-live-smoke.js (strict production-shaped environment)",
],
};
@ -827,7 +848,7 @@ ${resolution}
## Install
1. Download the binary archive for your platform from the Forgejo release.
1. Download the binary archive for your platform from the manually published GitHub release.
2. Check the archive against \`SHA256SUMS\`.
3. Extract it and put \`bin/\` on your \`PATH\`.
4. Install \`clusterflux-vscode-*.vsix\` when you want the VS Code debugger:
@ -880,8 +901,8 @@ function writeReleaseNotesAsset(releaseName, publicTreeIdentity, resolution) {
process.env.CLUSTERFLUX_PUBLIC_REPO_URL ||
"https://git.michelpaulissen.com/michel/clusterflux-public";
const releaseUrl =
process.env.CLUSTERFLUX_FORGEJO_RELEASE_URL ||
"the Forgejo release attached to the public repository";
process.env.CLUSTERFLUX_GITHUB_RELEASE_URL ||
"the manually published GitHub release";
fs.writeFileSync(
file,
`# Clusterflux Release Notes
@ -1200,7 +1221,8 @@ function main() {
public_repo_remote:
process.env.CLUSTERFLUX_PUBLIC_REPO_REMOTE || candidate?.public_repo_remote || null,
public_tree_publish: publicTreePublish,
forgejo_release_url: process.env.CLUSTERFLUX_FORGEJO_RELEASE_URL || null,
github_release_url: process.env.CLUSTERFLUX_GITHUB_RELEASE_URL || null,
forgejo_release_url: null,
default_hosted_coordinator_endpoint: defaultHostedCoordinatorEndpoint,
dns_publication_state:
process.env.CLUSTERFLUX_DNS_PUBLICATION_STATE || "published",
@ -1239,10 +1261,18 @@ function main() {
name: path.basename(asset),
sha256: sha256File(asset),
})),
notes: [
"Upload the assets to the Forgejo Release for the filtered public repository.",
"The real service deployment and full e2e release remain separate acceptance evidence.",
],
notes:
releaseStage === "final"
? [
"Publish manually to GitHub from the prepared manual release directory.",
"The manifest embeds the exact production-shaped validation evidence and candidate binding.",
"Forgejo release publication is not launch authority.",
]
: [
"This is a validation candidate, not a published release.",
"GitHub publication remains manual after final production-shaped validation.",
"Forgejo release publication is not launch authority.",
],
};
const manifestPath = path.join(outputRoot, "public-release-manifest.json");

View file

@ -316,14 +316,27 @@ if (manifest.candidate_proxy_configuration_identity) {
}
assert(
Array.isArray(finalResult.named_scenarios) &&
finalResult.named_scenarios.length === 25 &&
finalResult.named_scenarios.every((scenario) => scenario.status === "passed"),
"all twenty-five named final checks must be present and passed"
finalResult.named_scenarios.length >= 29 &&
finalResult.named_scenarios.length ===
finalResult.strict_requirement_ledger.length &&
finalResult.named_scenarios.every(
(scenario) =>
scenario.status === "passed" &&
Number.isFinite(scenario.duration_ms) &&
scenario.duration_ms > 0
),
"all named final checks must be present, measured, and passed"
);
assert(
Array.isArray(finalResult.strict_requirement_ledger) &&
finalResult.strict_requirement_ledger.length > 0 &&
finalResult.strict_requirement_ledger.every((requirement) => requirement.passed === true),
finalResult.strict_requirement_ledger.every(
(requirement) =>
requirement.passed === true &&
Number.isFinite(requirement.duration_ms) &&
requirement.duration_ms > 0 &&
requirement.evidence
),
"the strict final requirement ledger must be present and passed"
);
const finalTranscript = readArchiveMember(

322
scripts/release-quality-gates.js Executable file
View file

@ -0,0 +1,322 @@
#!/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_waits_for_final_child_then_preserves_history_and_releases_the_slot",
],
},
{
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);
}

View file

@ -5,16 +5,20 @@ const os = require("os");
const path = require("path");
const assert = require("assert");
const extension = require("../vscode-extension/extension");
const packageJson = require("../vscode-extension/package.json");
const extensionRoot = path.resolve(
process.env.CLUSTERFLUX_VSCODE_EXTENSION_ROOT ||
path.join(__dirname, "../vscode-extension")
);
const extension = require(path.join(extensionRoot, "extension.js"));
const packageJson = require(path.join(extensionRoot, "package.json"));
const extensionSource = fs.readFileSync(
path.join(__dirname, "../vscode-extension/extension.js"),
path.join(extensionRoot, "extension.js"),
"utf8"
);
const repo = path.resolve(__dirname, "..");
assert.strictEqual(packageJson.main, "./extension.js");
assert(fs.existsSync(path.join(__dirname, "../vscode-extension", packageJson.main)));
assert(fs.existsSync(path.join(extensionRoot, packageJson.main)));
assert.deepStrictEqual(packageJson.dependencies || {}, {});
const root = fs.mkdtempSync(path.join(os.tmpdir(), "clusterflux-vscode-"));
fs.mkdirSync(path.join(root, "envs/linux"), { recursive: true });
@ -157,7 +161,7 @@ assert(
"package.json must contribute a Clusterflux activity-bar container"
);
assert(
fs.existsSync(path.join(__dirname, "../vscode-extension/resources/clusterflux.svg")),
fs.existsSync(path.join(extensionRoot, "resources/clusterflux.svg")),
"Clusterflux activity-bar icon must exist"
);
const packageViewIds = packageJson.contributes.views.clusterflux.map((view) => view.id).sort();