clusterflux-public/scripts/public-release-dryrun-contract-smoke.js
Disasmer release dry run 20c72e6066 Public dry run dryrun-1714a9eedd5b
Source commit: 1714a9eedd5b1e30c6c9aceb7a999f2f695ba83c

Public tree identity: sha256:e5daffad23fa7c7f1822adccd3c3b4ee0bc7f1a45e0d321eb9a6fb8282b269db
2026-07-03 17:31:04 +02:00

283 lines
18 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:9443";
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");
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");
for (const [name, source] of [
["phase 2 criteria", phase2],
["README", readme],
]) {
if (source) {
expect(source, name, new RegExp(serviceHost.replaceAll(".", "\\.")));
expect(source, `${name} DNS publication state`, /DNS record|public DNS|dns[-_]publication/i);
expect(source, `${name} resolver fallback`, /no resolver override|required resolver override|hosts entry|controlled resolution|fallback/i);
expect(source, `${name} Forgejo host`, new RegExp(forgejoHost.replaceAll(".", "\\.")));
expect(source, `${name} Forgejo Release`, /Forgejo Release/);
expect(source, `${name} compiled assets`, /compiled\s+(release\s+)?assets|compiled release assets/);
expect(source, `${name} filtered tree`, /private\/\*\*[\s\S]*experiments\/\*\*/);
expect(source, `${name} root Markdown export filter`, /root `\*\.md`|root Markdown/);
expect(source, `${name} host-neutral workflow filter`, /\.forgejo[\s\S]*--include-forgejo-workflows/);
expect(source, `${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 operator constant", new RegExp(`DEFAULT_OPERATOR_ENDPOINT: &str = "${serviceEndpoint.replaceAll(".", "\\.")}"`));
expect(cliSource, "login uses default operator", /default_value_t = default_operator_endpoint\(\)/);
expect(cliSource, "hosted run records operator endpoint", /operator_endpoint[\s\S]*Some\(default_operator_endpoint\(\)\)/);
expect(cliSource, "hosted URL maps to JSON-line transport", /fn json_line_transport_addr[\s\S]*\("https:\/\/", 443\)/);
expect(cliSource, "JSON-line session uses mapped transport", /TcpStream::connect\(&transport_addr\)/);
expect(cliSource, "default operator maps to port 9443", /json_line_transport_addr\(DEFAULT_OPERATOR_ENDPOINT\)[\s\S]*"disasmer\.michelpaulissen\.com:9443"/);
assert.doesNotMatch(cliSource, /coord\.disasmer\.invalid/);
expect(cliLoginSmoke, "CLI login smoke covers default operator", new RegExp(`defaultOperatorEndpoint = "${serviceEndpoint.replaceAll(".", "\\.")}"`));
expect(vscodeExtension, "VS Code default operator constant", new RegExp(`DEFAULT_OPERATOR_ENDPOINT = "${serviceEndpoint.replaceAll(".", "\\.")}"`));
assert.strictEqual(
vscodePackage.contributes.debuggers[0].configurationAttributes.launch.properties.operatorEndpoint.default,
serviceEndpoint
);
expect(vscodeSmoke, "VS Code smoke covers operator endpoint", /operatorEndpoint\.default/);
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 filters generated view state", /filteredDirectoryNames = \["\.disasmer"\]/],
["release prep filters root Markdown", /isRootMarkdown[\s\S]*path\.extname\(entry\.name\)\.toLowerCase\(\) === "\.md"/],
["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 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 says private hosted coordinator", /default operator is the private hosted coordinator/],
["selected-user guide rejects standalone public coordinator", /standalone open-source\/public coordinator/],
["selected-user invite documents friends dry run", /friends helping test[\s\S]*not broadly advertised/],
["selected-user invite says hosted server is private", /hosted server is not the standalone public\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:9443/],
["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 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 verifies public branch commit", /remoteMain[\s\S]*manifest\.public_tree_publish\.commit/],
["preflight verifies local asset checksums", /parseSha256Sums[\s\S]*checksum mismatch/],
["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 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 root Markdown leaks", /root Markdown file leaked into public repo/],
["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 operator", /defaultLoginPlan\.coordinator[\s\S]*serviceEndpoint/],
["e2e runner completes browser login", /--complete-browser-code/],
["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 task through coordinator", /type: "launch_task"/],
["e2e runner verifies assignment polling", /worker_assignment_poll_verified/],
["e2e runner validates standalone public coordinator", /validateStandalonePublicCoordinator/],
["e2e runner records public coordinator validation", /public_coordinator_validated/],
["e2e runner validates released live DAP", /validateReleasedLiveDap/],
["e2e runner starts live DAP worker after coordinator-side breakpoint", /worker_started_after_coordinator_side_breakpoint/],
["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 public operator compatibility report", /public-operator-compat\.json/],
["final evidence reads public coordinator compatibility report", /public-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 operator", /serviceEndpoint = "https:\/\/disasmer\.michelpaulissen\.com:9443"/],
["final evidence requires private hosted coordinator", /operator_implementation[\s\S]*"private-hosted-coordinator"/],
["final evidence requires service private coordinator marker", /service\.operator_implementation[\s\S]*"private-hosted-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 public operator source", /compat\.source_commit[\s\S]*manifest\.source_commit/],
["final evidence requires current public operator release", /compat\.release_name[\s\S]*manifest\.release_name/],
["final evidence requires current public coordinator source", /publicCoordinator\.source_commit[\s\S]*manifest\.source_commit/],
["final evidence requires current public coordinator release", /publicCoordinator\.release_name[\s\S]*manifest\.release_name/],
["final evidence requires service launch task", /service\.evidence\.public_launch_task[\s\S]*"task_launched"/],
["final evidence requires launch task", /launch_task_response[\s\S]*"task_launched"/],
["final evidence requires assignment polling", /worker_assignment_poll_verified/],
["final evidence requires public coordinator validation", /public_coordinator_validated/],
["final evidence requires standalone public coordinator", /standalone-public-coordinator/],
["final evidence requires released live DAP", /released_live_dap_verified/],
["final evidence requires pushed public tree", /public_tree_publish[\s\S]*pushed/],
["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);
}
}
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");