clusterflux-public/scripts/public-release-dryrun-contract-smoke.js
Disasmer release dry run 2bef715211 Public dry run dryrun-20b59dc46b72
Source commit: 20b59dc46b72103c2f8a516c692b5cc3d54fab19

Public tree identity: sha256:aaa5ac7b58b2a0d23c6b11e5f76324bf3839ca1f62653a83deb736932adcfbd9
2026-07-14 10:08:15 +02:00

321 lines
22 KiB
JavaScript

#!/usr/bin/env node
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const repo = path.resolve(__dirname, "..");
const serviceEndpoint = "https://disasmer.michelpaulissen.com";
const serviceHost = "disasmer.michelpaulissen.com";
const forgejoHost = "git.michelpaulissen.com";
function read(relativePath) {
return fs.readFileSync(path.join(repo, relativePath), "utf8");
}
function maybeRead(relativePath) {
const file = path.join(repo, relativePath);
return fs.existsSync(file) ? fs.readFileSync(file, "utf8") : null;
}
function expect(source, name, pattern) {
assert(source !== null && source !== undefined, `missing source for ${name}`);
assert.match(source, pattern, `missing public release dry-run evidence: ${name}`);
}
const phase2 = maybeRead("acceptance_criteria_phase2.md");
const readme = maybeRead("README.md");
const cliSource = [
read("crates/disasmer-cli/src/main.rs"),
read("crates/disasmer-cli/src/config.rs"),
read("crates/disasmer-cli/src/run.rs"),
read("crates/disasmer-cli/src/client.rs"),
read("crates/disasmer-cli/src/tests.rs"),
].join("\n");
const controlSource = read("crates/disasmer-control/src/lib.rs");
const cliLoginSmoke = read("scripts/cli-login-smoke.js");
const vscodePackage = JSON.parse(read("vscode-extension/package.json"));
const vscodeExtension = read("vscode-extension/extension.js");
const vscodeSmoke = read("scripts/vscode-extension-smoke.js");
const prepScript = read("scripts/prepare-public-release-dryrun.js");
const publishScript = read("scripts/publish-public-release-dryrun.js");
const preflightScript = read("scripts/public-release-dryrun-preflight.js");
const e2eScript = read("scripts/public-release-dryrun-e2e.js");
const finalEvidenceScript = read("scripts/public-release-dryrun-final-evidence.js");
const workflow = maybeRead(".forgejo/workflows/public-release-dryrun.yml");
const publicAcceptance = read("scripts/acceptance-public.sh");
const privateAcceptance = read("scripts/acceptance-private.sh");
const publicSplit = read("scripts/verify-public-split.sh");
if (readme) {
expect(readme, "README", new RegExp(serviceHost.replaceAll(".", "\\.")));
}
if (phase2) {
const name = "phase 2 criteria";
expect(phase2, name, new RegExp(serviceHost.replaceAll(".", "\\.")));
expect(phase2, `${name} DNS publication state`, /DNS record|public DNS|dns[-_]publication/i);
expect(phase2, `${name} resolver fallback`, /no resolver override|required resolver override|hosts entry|controlled resolution|fallback/i);
expect(phase2, `${name} Forgejo host`, new RegExp(forgejoHost.replaceAll(".", "\\.")));
expect(phase2, `${name} Forgejo Release`, /Forgejo Release/);
expect(phase2, `${name} compiled assets`, /compiled\s+(release\s+)?assets|compiled release assets/);
expect(phase2, `${name} filtered tree`, /private\/\*\*[\s\S]*experiments\/\*\*/);
expect(phase2, `${name} root Markdown export filter`, /root `\*\.md`|root Markdown/);
expect(phase2, `${name} host-neutral workflow filter`, /\.forgejo[\s\S]*--include-forgejo-workflows/);
expect(phase2, `${name} GitHub release out of scope`, /GitHub[\s\S]*outside this dry run|public GitHub-release[\s\S]*out of scope/);
}
expect(cliSource, "CLI default hosted coordinator constant", new RegExp(`DEFAULT_HOSTED_COORDINATOR_ENDPOINT: &str =[\\s\\S]*"${serviceEndpoint.replaceAll(".", "\\.")}"`));
expect(cliSource, "login uses default hosted coordinator", /default_value_t = default_hosted_coordinator_endpoint\(\)/);
expect(cliSource, "hosted run records coordinator endpoint", /hosted_coordinator_endpoint[\s\S]*Some\(default_hosted_coordinator_endpoint\(\)\)/);
expect(controlSource, "hosted URL uses the shared HTTP control transport", /ControlTransport::Https[\s\S]*agent[\s\S]*\.post\(url\)/);
expect(controlSource, "hosted control transport uses the canonical API path", /CONTROL_API_PATH: &str = "\/api\/v1\/control"/);
expect(controlSource, "plain HTTP is restricted to loopback", /endpoint\.starts_with\("http:\/\/"\) && !endpoint_is_loopback\(endpoint\)/);
expect(cliSource, "default hosted coordinator resolves to the HTTPS control API", /hosted_coordinator_remains_a_real_https_control_endpoint[\s\S]*https:\/\/disasmer\.michelpaulissen\.com\/api\/v1\/control/);
assert.doesNotMatch(cliSource, /coord\.disasmer\.invalid/);
expect(cliLoginSmoke, "CLI login smoke covers default hosted coordinator", new RegExp(`defaultHostedCoordinatorEndpoint = "${serviceEndpoint.replaceAll(".", "\\.")}"`));
assert.strictEqual(
vscodePackage.contributes.debuggers[0].configurationAttributes.launch.properties.coordinatorEndpoint.default,
undefined
);
expect(vscodeSmoke, "VS Code smoke covers session-inferred coordinator endpoint", /coordinatorEndpoint\.default, undefined/);
for (const binary of [
"disasmer",
"disasmer-coordinator",
"disasmer-node",
"disasmer-debug-dap",
]) {
expect(prepScript, `release prep includes ${binary}`, new RegExp(`"${binary}"`));
}
for (const [name, pattern] of [
["release prep filters private and experiments", /filteredTopLevel = \["private", "experiments", "\.git", "target"\]/],
["release prep enumerates Git-ignored source paths", /function gitIgnoredSourcePaths\(\)[\s\S]*"ls-files"[\s\S]*"--ignored"[\s\S]*"--exclude-standard"[\s\S]*"--directory"/],
["release prep has archive-safe ignored-path fallbacks without Git metadata", /archiveIgnoredPathFallbacks = \[[\s\S]*"target"[\s\S]*"\.disasmer"[\s\S]*"vscode-extension\/node_modules"[\s\S]*"scripts\/containers-home"[\s\S]*if \(output === null\) \{[\s\S]*return archiveIgnoredPathFallbacks/],
["release prep filters Git-ignored source paths", /isGitIgnored\(childRelative, ignoredSourcePaths\)/],
["release prep verifies Git-ignored paths are absent", /Git-ignored source path leaked into the dry-run public tree/],
["release prep filters generated view state", /filteredDirectoryNames = \["\.disasmer"\]/],
["release prep filters internal root Markdown", /isFilteredRootMarkdown[\s\S]*path\.extname\(entry\.name\)\.toLowerCase\(\) === "\.md"[\s\S]*README\.md[\s\S]*SECURITY\.md/],
["release prep retains product README", /product README\.md is missing from the dry-run public tree/],
["release prep filters .forgejo by default", /topLevel === "\.forgejo" && !includeForgejoWorkflows/],
["release prep has Forgejo workflow opt-in argument", /--include-forgejo-workflows/],
["release prep builds public release bins", /cargo"[\s\S]*"build"[\s\S]*"--workspace"[\s\S]*"--bins"[\s\S]*"--release"/],
["release prep writes binary archive", /disasmer-public-binaries-\$\{releaseName\}-\$\{platformName\(\)\}\.tar\.gz/],
["release prep writes source archive", /disasmer-public-source-\$\{releaseName\}\.tar\.gz/],
["release prep writes extension VSIX", /\$\{packageJson\.name\}-\$\{packageJson\.version\}\.vsix[\s\S]*@vscode\/vsce[\s\S]*package/],
["release prep packages extension non-interactively", /@vscode\/vsce[\s\S]*package[\s\S]*--skip-license/],
["release prep writes selected-user guide", /DISASMER_PUBLIC_DRYRUN_GETTING_STARTED-\$\{releaseName\}\.md/],
["release prep writes selected-user invite", /DISASMER_PUBLIC_DRYRUN_INVITE-\$\{releaseName\}\.md/],
["release prep accepts resolver instructions", /DISASMER_PUBLIC_DRYRUN_RESOLVER_INSTRUCTIONS/],
["release prep accepts hosts entry", /DISASMER_PUBLIC_DRYRUN_HOSTS_ENTRY/],
["release prep accepts deployment IP", /DISASMER_PUBLIC_RELEASE_DRYRUN_IP/],
["selected-user guide documents DNS or fallback", /public DNS[\s\S]*(fallback|hosts entry)|DNS record[\s\S]*controlled resolution/],
["selected-user guide names hosted coordinator", /default hosted coordinator is the deployment/],
["selected-user guide rejects standalone Core coordinator", /standalone open-source Core coordinator/],
["selected-user invite documents friends dry run", /friends helping test[\s\S]*not broadly advertised/],
["selected-user invite distinguishes Core", /hosted server is not the standalone Core\s+coordinator/],
["selected-user guide documents default login", /disasmer login --browser/],
["selected-user guide documents VSIX install", /code --install-extension disasmer-vscode-\*\.vsix/],
["selected-user guide documents node attach", /disasmer node attach --coordinator https:\/\/disasmer\.michelpaulissen\.com/],
["release prep packages generated evidence", /stageEvidenceAsset[\s\S]*EVIDENCE_BINDING\.json[\s\S]*disasmer-public-evidence-/],
["release prep binds binary digests", /binary_digests: binaryDigests/],
["release prep writes checksums", /SHA256SUMS/],
["release prep writes manifest", /public-release-manifest\.json/],
["release prep writes public tree provenance", /DISASMER_PUBLIC_TREE\.json/],
["release prep records public tree identity", /public_tree_identity: publicTreeIdentity/],
["release prep records DNS state", /dns_publication_state:/],
["release prep records resolver override", /resolver_override:/],
["release prep records Forgejo URLs", /public_repo_url:[\s\S]*public_repo_remote:[\s\S]*forgejo_release_url:/],
["release prep requires publish opt-in", /DISASMER_PUBLISH_PUBLIC_TREE/],
["release prep requires Forgejo remote", /remote\.includes\(forgejoHost\)/],
["release prep can push filtered tree", /git"[\s\S]*"push"[\s\S]*"public"[\s\S]*`HEAD:\$\{publicRepoBranch\}`/],
["release prep records publish result", /public_tree_publish: publicTreePublish/],
["release prep publishes tree before building target", /const publicTreePublish = publishPublicTree[\s\S]*buildPublicBinaries\(\)/],
]) {
expect(prepScript, name, pattern);
}
for (const [name, pattern] of [
["release publisher requires Forgejo token", /DISASMER_FORGEJO_TOKEN/],
["release publisher infers Forgejo repo identity", /function resolveRepoIdentity\(manifest\)[\s\S]*parseForgejoRepoIdentity/],
["release publisher uses manifest public repo URL", /manifest\.public_repo_url[\s\S]*manifest\.public_repo_remote/],
["release publisher uses token auth", /Authorization: `token \$\{token\}`/],
["release publisher lists releases", /\/repos\/\$\{encodeURIComponent\(owner\)\}\/\$\{encodeURIComponent\(repoName\)\}\/releases\?limit=100/],
["release publisher creates release", /POST[\s\S]*\/repos\/\$\{encodeURIComponent\(owner\)\}\/\$\{encodeURIComponent\(repoName\)\}\/releases/],
["release publisher reloads release details", /function loadRelease\(releaseId\)[\s\S]*\/releases\/\$\{releaseId\}/],
["release publisher uploads assets", /releases\/\$\{release\.id\}\/assets\?name=\$\{encodeURIComponent\(asset\.name\)\}/],
["release publisher uses attachment multipart field", /multipartFile\("attachment", asset\.file\)/],
["release publisher skips already attached assets", /existingAssetByName\(release, asset\.name\)/],
["release publisher rejects stale release manifests", /manifest\.source_commit[\s\S]*expectedSourceCommit\(\)/],
["release publisher checks manifest kind", /manifest\.kind !== "disasmer-public-release-dryrun"/],
["release publisher requires public tree push", /public tree must be pushed before publishing the Forgejo Release/],
["release publisher accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/],
["release publisher targets external public tree commit", /function publicTreeCommit\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_COMMIT/],
["release publisher writes evidence report", /public-release-dryrun-forgejo-release\.json/],
["release publisher records reused assets", /reused_assets:/],
]) {
expect(publishScript, name, pattern);
}
for (const [name, pattern] of [
["preflight rejects stale release manifests", /manifest\.source_commit[\s\S]*currentSourceCommit/],
["preflight disables interactive remote prompts", /function nonInteractiveEnv[\s\S]*GIT_TERMINAL_PROMPT[\s\S]*GIT_ASKPASS[\s\S]*SSH_ASKPASS[\s\S]*GIT_SSH_COMMAND[\s\S]*BatchMode=yes[\s\S]*NumberOfPasswordPrompts=0[\s\S]*git", \["ls-remote"[\s\S]*timeout/],
["preflight accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/],
["preflight verifies public branch commit", /remoteMain[\s\S]*publicTreeCommit/],
["preflight verifies local asset checksums", /parseSha256Sums[\s\S]*checksum mismatch/],
["preflight records public tree push source", /public_tree_push_source/],
["preflight records pending external gates", /external_gates:[\s\S]*forgejo_release_publication[\s\S]*public_release_e2e/],
["preflight writes evidence report", /public-release-dryrun-preflight\.json/],
]) {
expect(preflightScript, name, pattern);
}
for (const [name, pattern] of [
["e2e runner requires explicit opt-in", /DISASMER_PUBLIC_RELEASE_DRYRUN_E2E=1/],
["e2e runner rejects stale release manifests", /manifest\.source_commit[\s\S]*expectedSourceCommit\(\)/],
["e2e runner disables interactive git prompts", /function nonInteractiveGitEnv[\s\S]*GIT_TERMINAL_PROMPT[\s\S]*GIT_ASKPASS[\s\S]*SSH_ASKPASS[\s\S]*GIT_SSH_COMMAND[\s\S]*BatchMode=yes[\s\S]*NumberOfPasswordPrompts=0[\s\S]*function commandEnv/],
["e2e runner accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/],
["e2e runner downloads Forgejo Release assets", /downloadReleaseAssets/],
["e2e runner verifies SHA256SUMS", /verifyChecksums/],
["e2e runner clones public repo", /"git", \["clone", "--depth", "1"/],
["e2e runner rejects private tree leaks", /private[\s\S]*experiments[\s\S]*target/],
["e2e runner rejects internal root Markdown leaks", /internal root Markdown file leaked into public repo/],
["e2e runner requires product README", /public checkout must include product README\.md/],
["e2e runner rejects .forgejo leaks by default", /\.forgejo\/ leaked into public repo/],
["e2e runner rejects generated view state", /generated \.disasmer view state leaked into public repo/],
["e2e runner checks public tree identity", /hashTree\(checkout\)[\s\S]*manifest\.public_tree_identity/],
["e2e runner extracts public binaries", /tar"[\s\S]*"-xzf"/],
["e2e runner loads public coordinator binary", /executable\(installDir, "disasmer-coordinator"\)/],
["e2e runner checks default hosted coordinator", /defaultLoginPlan\.coordinator[\s\S]*serviceEndpoint/],
["e2e runner requires an external browser driver", /DISASMER_PUBLIC_RELEASE_DRYRUN_BROWSER_OPEN_COMMAND/],
["e2e runner uses the CLI browser opener boundary", /DISASMER_BROWSER_OPEN_COMMAND: browserOpenCommand/],
["e2e runner derives scope from the hosted session", /const tenant = loginSession\.tenant[\s\S]*const project = loginSession\.project[\s\S]*const user = loginSession\.user/],
["e2e runner uses public CLI node attach", /"node"[\s\S]*"attach"[\s\S]*serviceEndpoint/],
["e2e runner starts public worker runtime", /workerArgs[\s\S]*"--worker"[\s\S]*cp\.spawn\(disasmerNode/],
["e2e runner launches the released product through CLI", /"run"[\s\S]*"build"[\s\S]*runReport\.status[\s\S]*main_launched/],
["standalone Core proof launches a real Wasm TaskSpec", /type: "launch_task"[\s\S]*task_spec:[\s\S]*kind: "coordinator_node_wasm"[\s\S]*bundle_digest: manifest\.bundle_digest/],
["e2e runner verifies assignment polling", /worker_assignment_poll_verified/],
["e2e runner validates standalone Core coordinator", /validateStandaloneCoreCoordinator/],
["e2e runner uses canonical standalone Core wire envelopes", /function sendCore\(addr, message\)[\s\S]*coordinatorWireRequest\(message, "public-e2e-core"\)/],
["e2e runner records public coordinator validation", /core_coordinator_validated/],
["e2e runner validates released live DAP", /validateReleasedLiveDap/],
["e2e runner verifies attach-mode Debug Epoch transition", /validateReleasedLiveDap[\s\S]*resumed through coordinator[\s\S]*requested freeze/],
["e2e runner verifies attached coordinator task completion", /validateReleasedLiveDap[\s\S]*variable\.name === "state" && variable\.value === "Completed"[\s\S]*coordinator task event from node/],
["e2e runner attaches live DAP to Client-authorized process", /attached_to_existing_client_process/],
["e2e runner verifies task events", /type: "list_task_events"/],
["e2e runner verifies download link", /type: "create_artifact_download_link"/],
["e2e runner verifies VS Code debugger", /scripts\/vscode-f5-smoke\.js/],
["e2e runner writes e2e report", /public-release-dryrun-e2e\.json/],
]) {
expect(e2eScript, name, pattern);
}
for (const [name, pattern] of [
["final evidence reads manifest", /public-release-manifest\.json/],
["final evidence rejects stale release manifests", /manifest\.source_commit[\s\S]*expectedSourceCommit\(\)/],
["final evidence verifies filtered export", /function assertFilteredOut\(manifest\)/],
["final evidence reads Forgejo Release report", /public-release-dryrun-forgejo-release\.json/],
["final evidence reads deployment manifest", /deployment-manifest\.json/],
["final evidence reads service smoke report", /public-release-dryrun-service\.json/],
["final evidence reads hosted Client compatibility report", /hosted-client-compat\.json/],
["final evidence reads Core coordinator compatibility report", /core-coordinator-compat\.json/],
["final evidence requires public e2e report", /public-release-dryrun-e2e\.json/],
["final evidence verifies Forgejo host", /forgejoHost = "git\.michelpaulissen\.com"/],
["final evidence verifies default hosted coordinator", /serviceEndpoint = "https:\/\/disasmer\.michelpaulissen\.com"/],
["final evidence accepts external public tree push", /function publicTreeAlreadyPushed\(manifest\)[\s\S]*DISASMER_PUBLIC_TREE_ALREADY_PUSHED/],
["final evidence records resolved public repo", /resolvedPublicRepoRemote[\s\S]*public_repository_url: resolvedPublicRepoRemote/],
["final evidence requires private hosted coordinator", /coordinator_implementation[\s\S]*"hosted-policy-coordinator"/],
["final evidence requires service private coordinator marker", /service\.coordinator_implementation[\s\S]*"hosted-policy-coordinator"/],
["final evidence requires current service smoke source", /service\.source_commit[\s\S]*manifest\.source_commit/],
["final evidence requires current service smoke release", /service\.release_name[\s\S]*manifest\.release_name/],
["final evidence requires current Client compatibility source", /compat\.source_commit[\s\S]*manifest\.source_commit/],
["final evidence requires current Client compatibility release", /compat\.release_name[\s\S]*manifest\.release_name/],
["final evidence requires current public coordinator source", /coreCoordinator\.source_commit[\s\S]*manifest\.source_commit/],
["final evidence requires current public coordinator release", /coreCoordinator\.release_name[\s\S]*manifest\.release_name/],
["final evidence requires server-generated service enrollment", /"server_generated_enrollment_grant"/],
["final evidence requires service probe cleanup", /"process_abort"/],
["final evidence requires process main launch", /e2e\.launch_task_response[\s\S]*"main_launched"/],
["final evidence requires assignment polling", /worker_assignment_poll_verified/],
["final evidence requires public coordinator validation", /core_coordinator_validated/],
["final evidence requires standalone Core coordinator", /standalone-core-coordinator/],
["final evidence requires released live DAP", /released_live_dap_verified/],
["final evidence requires pushed public tree", /publicTreeAlreadyPushed\(manifest\)/],
["final evidence requires release assets", /downloaded_release_assets/],
["final evidence requires VS Code debugger", /vscode_debugger_verified/],
["final evidence writes final report", /public-release-dryrun-final\.json/],
]) {
expect(finalEvidenceScript, name, pattern);
}
if (workflow) {
for (const [name, pattern] of [
["manual Forgejo workflow", /workflow_dispatch:/],
["Linux Forgejo asset job", /linux-assets:[\s\S]*runs-on: docker/],
["Windows Forgejo asset job", /windows-assets:[\s\S]*runs-on: windows/],
["Windows runner caveat", /intermittently online/],
["workflow runs release prep", /node scripts\/prepare-public-release-dryrun\.js/],
["workflow uploads assets", /actions\/upload-artifact@v4[\s\S]*target\/public-release-dryrun\/assets\/\*/],
]) {
expect(workflow, name, pattern);
}
}
expect(
prepScript,
"public tree prep disables interactive git prompts",
/function nonInteractiveGitEnv[\s\S]*GIT_TERMINAL_PROMPT[\s\S]*GIT_ASKPASS[\s\S]*SSH_ASKPASS[\s\S]*GIT_SSH_COMMAND[\s\S]*BatchMode=yes[\s\S]*NumberOfPasswordPrompts=0[\s\S]*function commandEnv/
);
for (const [scriptName, script] of [
["public acceptance", publicAcceptance],
["public split", publicSplit],
]) {
assert(
script.includes("node scripts/public-release-dryrun-contract-smoke.js"),
`${scriptName} must run public-release-dryrun-contract-smoke.js`
);
assert(
script.includes("node scripts/prepare-public-release-dryrun.js"),
`${scriptName} must run prepare-public-release-dryrun.js`
);
assert(
script.includes("node scripts/publish-public-release-dryrun.js"),
`${scriptName} must be able to publish the Forgejo Release`
);
assert(
script.includes("DISASMER_FORGEJO_TOKEN"),
`${scriptName} must gate Forgejo Release publishing on a token`
);
assert(
script.includes('if [[ -n "${DISASMER_FORGEJO_TOKEN:-}" ]]'),
`${scriptName} must not require owner/repo env when the manifest can infer the Forgejo repository`
);
}
for (const [scriptName, script] of [
["public acceptance", publicAcceptance],
["private acceptance", privateAcceptance],
]) {
if (scriptName === "public acceptance") {
assert(
script.includes("node scripts/public-release-dryrun-e2e.js"),
"public acceptance must be able to run public-release-dryrun-e2e.js"
);
assert(
script.includes("DISASMER_PUBLIC_RELEASE_DRYRUN_E2E"),
"public acceptance must gate public-release-dryrun-e2e.js"
);
}
assert(
script.includes("node scripts/public-release-dryrun-final-evidence.js"),
`${scriptName} must be able to run public-release-dryrun-final-evidence.js`
);
assert(
script.includes("DISASMER_PUBLIC_RELEASE_DRYRUN_FINAL"),
`${scriptName} must gate final public release dry-run evidence`
);
}
console.log("Public release dry-run contract smoke passed");