Source commit: 20b59dc46b72103c2f8a516c692b5cc3d54fab19 Public tree identity: sha256:aaa5ac7b58b2a0d23c6b11e5f76324bf3839ca1f62653a83deb736932adcfbd9
413 lines
19 KiB
JavaScript
413 lines
19 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const repo = path.resolve(__dirname, "..");
|
|
const readmePath = path.join(repo, "README.md");
|
|
const filteredPublicTree = fs.existsSync(path.join(repo, "DISASMER_PUBLIC_TREE.json"));
|
|
if (!fs.existsSync(readmePath)) throw new Error("README.md is missing");
|
|
const readme = fs.readFileSync(readmePath, "utf8");
|
|
const canonicalPublicDocs = [
|
|
"architecture.md",
|
|
"security.md",
|
|
"task-abi.md",
|
|
"artifacts.md",
|
|
"debugging.md",
|
|
"self-hosting.md",
|
|
];
|
|
for (const file of canonicalPublicDocs) {
|
|
const documentationPath = path.join(repo, "docs", file);
|
|
assert(
|
|
fs.existsSync(documentationPath),
|
|
`canonical public documentation is missing docs/${file}`
|
|
);
|
|
assert(
|
|
fs.readFileSync(documentationPath, "utf8").trim().length > 400,
|
|
`canonical public documentation docs/${file} is unexpectedly empty`
|
|
);
|
|
}
|
|
if (filteredPublicTree) {
|
|
assert.match(readme, /^# Disasmer/m, "filtered public README must identify the product");
|
|
assert.match(readme, /## Quickstart/, "filtered public README must retain the quickstart");
|
|
assert.match(
|
|
readme,
|
|
/docs\/architecture\.md/,
|
|
"filtered public README must link canonical public documentation"
|
|
);
|
|
assert.match(
|
|
readme,
|
|
/cargo install --path crates\/disasmer-cli --bin disasmer/,
|
|
"filtered public README must retain CLI installation"
|
|
);
|
|
assert.doesNotMatch(
|
|
readme,
|
|
/DISASMER_PUBLISH_PUBLIC_TREE|DISASMER_FORGEJO_TOKEN|public-release-manifest\.json|public-release-dryrun-e2e\.json/,
|
|
"filtered public README must not expose release-internal dry-run mechanics"
|
|
);
|
|
console.log("Docs smoke passed for filtered public product README");
|
|
process.exit(0);
|
|
}
|
|
const dryrunDoc = fs.readFileSync(
|
|
path.join(repo, "public_release_dryrun.md"),
|
|
"utf8"
|
|
);
|
|
const userFacingDocs = [
|
|
"README.md",
|
|
"public_release_dryrun.md",
|
|
"MVP.md",
|
|
"acceptance_criteria.md",
|
|
"acceptance_criteria_phase2.md",
|
|
"cli_acceptance_criteria.md",
|
|
"website_mvp_inventory.md",
|
|
"phase_3_acceptance_criteria.md",
|
|
].map((file) => [file, fs.readFileSync(path.join(repo, file), "utf8")]);
|
|
const publicAcceptance = fs.readFileSync(
|
|
path.join(repo, "scripts/acceptance-public.sh"),
|
|
"utf8"
|
|
);
|
|
const publicSplit = fs.readFileSync(
|
|
path.join(repo, "scripts/verify-public-split.sh"),
|
|
"utf8"
|
|
);
|
|
const privateAcceptance = fs.readFileSync(
|
|
path.join(repo, "scripts/acceptance-private.sh"),
|
|
"utf8"
|
|
);
|
|
|
|
const requiredReadmePatterns = [
|
|
["quickstart heading", /## Quickstart/],
|
|
["workspace build", /cargo build --workspace/],
|
|
["CLI install", /cargo install --path crates\/disasmer-cli --bin disasmer/],
|
|
["node install", /cargo install --path crates\/disasmer-node --bin disasmer-node/],
|
|
[
|
|
"coordinator install",
|
|
/cargo install --path crates\/disasmer-coordinator --bin disasmer-coordinator/,
|
|
],
|
|
["DAP install", /cargo install --path crates\/disasmer-dap --bin disasmer-debug-dap/],
|
|
["VS Code local extension", /code --extensionDevelopmentPath/],
|
|
["local coordinator", /disasmer-coordinator --listen/],
|
|
["node attach", /disasmer node attach --coordinator/],
|
|
["automatic local run", /disasmer run --local --project examples\/launch-build-demo build/],
|
|
["demo run", /disasmer run --local --coordinator/],
|
|
["entrypoint selection", /disasmer run \[entry\]/],
|
|
["implicit hosted mode", /uses the hosted coordinator/],
|
|
["local override", /force local coordinator mode/],
|
|
["project override", /--project[\s\S]*overrides the project\s+directory/],
|
|
["VS Code debug", /Disasmer: Launch\s+Virtual Process/],
|
|
["artifact download smoke", /node scripts\/artifact-download-smoke\.js/],
|
|
["artifact export smoke", /node scripts\/artifact-export-smoke\.js/],
|
|
["acceptance report smoke", /node scripts\/acceptance-report-smoke\.js/],
|
|
["CLI-first acceptance gate", /scripts\/acceptance-cli-first\.sh/],
|
|
["CLI-first acceptance before e2e", /CLI-first non-e2e gate before any final public-release e2e attempt/],
|
|
["public private boundary smoke", /node scripts\/public-private-boundary-smoke\.js/],
|
|
["release blocker smoke", /node scripts\/release-blocker-smoke\.js/],
|
|
["explicit export", /attached receiver node or user-provided\s+storage integration/],
|
|
["cleanup", /Cleanup for the local quickstart/],
|
|
["flush docs", /`flush\(\)` publishes metadata/],
|
|
["sync docs", /`sync\(\)` is explicit/],
|
|
["storage integration is user code", /User-provided storage\/export integrations are ordinary project code or external\s+commands/],
|
|
["no managed artifact store feature", /does not provide\s+or manage an explicit artifact-store feature/],
|
|
["best-effort retention", /best-effort retained on nodes/],
|
|
[
|
|
"secure downloads",
|
|
/Download links are scoped to the tenant, project, process, artifact, actor, and policy context, expire after a bounded TTL, can be revoked/,
|
|
],
|
|
["node trust", /Users attach their own nodes for real work/],
|
|
["self-hosted trusted teams", /self-hosted local clouds, trusted teams, or VPN deployments/],
|
|
["self-hosted coordinator smoke", /node scripts\/self-hosted-coordinator-smoke\.js/],
|
|
["wasmtime node smoke", /node scripts\/wasmtime-node-smoke\.js/],
|
|
["wasmtime command host import", /versioned `disasmer\.command_run_v1` host capability/],
|
|
["Windows sandbox limitation", /Production-grade managed Windows sandboxing is behind an explicit backend stub/],
|
|
["hosted community limit", /community tier does not provide arbitrary hosted native commands or hosted containers/],
|
|
["browser login", /disasmer login --browser/],
|
|
["public-key agents", /disasmer agent enroll --public-key/],
|
|
["noninteractive agent CLI", /DISASMER_AGENT_PRIVATE_KEY=<agent-private-key> disasmer run --non-interactive build/],
|
|
["agent key lifecycle", /register, list, rotate, and revoke an agent key/],
|
|
["capability auto-detect", /auto-detects OS, architecture/],
|
|
["capability override", /--cap <name>/],
|
|
["non-Git source provider", /Non-Git source providers can implement the public source-provider interface/],
|
|
["first-run diagnostics", /## First-Run Diagnostics/],
|
|
["missing nodes diagnostic", /Missing nodes/],
|
|
["missing environment diagnostic", /Missing environments/],
|
|
["quota diagnostic", /Quota limits/],
|
|
["unavailable artifact diagnostic", /Unavailable artifacts/],
|
|
["auth diagnostic", /Auth failures/],
|
|
["debug freeze diagnostic", /Failed debug freezes/],
|
|
["source-provider diagnostic", /Source-provider capability gaps/],
|
|
["browser and VS Code report metadata", /browser\/VS Code harness metadata/],
|
|
["Podman incomplete report", /Linux Podman backend behavior is marked `incomplete`/],
|
|
["manual Windows validation workflow", /manual `Windows validation`\s+workflow/],
|
|
["intermittent Forgejo Windows runner", /intermittent Windows runner/],
|
|
["Windows validation env gate", /DISASMER_WINDOWS_VALIDATION = "forgejo-windows-runner"/],
|
|
["Windows validation attach", /runs `disasmer node attach`/],
|
|
];
|
|
|
|
const requiredDryrunPatterns = [
|
|
["dry-run runbook status", /source-side release runbook, not public product quickstart/],
|
|
["public dry-run hosted coordinator endpoint", /https:\/\/disasmer\.michelpaulissen\.com/],
|
|
["public dry-run DNS record", /record is live[\s\S]*no resolver override is required|DNS record is deployed[\s\S]*no resolver override is required/],
|
|
["public dry-run real deployment", /real externally reachable service/],
|
|
["public dry-run selected users", /shared with selected users/],
|
|
["public dry-run Forgejo repo", /git\.michelpaulissen\.com/],
|
|
["public dry-run Forgejo Release assets", /Forgejo Release publishes compiled\s+assets/],
|
|
["public dry-run filters internal root markdown", /internal root Markdown/],
|
|
["public dry-run retains product README", /product-facing `README\.md` remains in the public repository/],
|
|
["public dry-run filters Forgejo workflows by default", /\.forgejo\/\*\*` by\s+default/],
|
|
["public dry-run Forgejo workflow opt in", /--include-forgejo-workflows/],
|
|
["public dry-run selected-user quickstart", /DISASMER_PUBLIC_DRYRUN_GETTING_STARTED-\*\.md/],
|
|
["public dry-run selected-user invite", /DISASMER_PUBLIC_DRYRUN_INVITE-\*\.md/],
|
|
["public dry-run resolver instructions env", /DISASMER_PUBLIC_DRYRUN_RESOLVER_INSTRUCTIONS=<instructions>/],
|
|
["public dry-run fallback hosts entry env", /DISASMER_PUBLIC_DRYRUN_HOSTS_ENTRY="<ip-address> disasmer\.michelpaulissen\.com"/],
|
|
["public dry-run deployment IP env", /DISASMER_PUBLIC_RELEASE_DRYRUN_IP=<ip-address>/],
|
|
["public dry-run prep script", /node scripts\/prepare-public-release-dryrun\.js/],
|
|
["public dry-run publish opt-in", /DISASMER_PUBLISH_PUBLIC_TREE=1/],
|
|
["public dry-run public repo remote", /DISASMER_PUBLIC_REPO_REMOTE=ssh:\/\/git\.michelpaulissen\.com/],
|
|
["public dry-run Forgejo workflow", /Public release dry run assets/],
|
|
["public dry-run manifest", /public-release-manifest\.json/],
|
|
["public dry-run release publisher", /node scripts\/publish-public-release-dryrun\.js/],
|
|
["public dry-run non-e2e preflight", /node scripts\/public-release-dryrun-preflight\.js/],
|
|
["public dry-run Forgejo token", /DISASMER_FORGEJO_TOKEN=<token>/],
|
|
["public dry-run publisher infers repo", /publisher infers the Forgejo owner and repository name/],
|
|
["public dry-run Forgejo repo owner override", /DISASMER_PUBLIC_REPO_OWNER=<owner>/],
|
|
["public dry-run Forgejo repo name override", /DISASMER_PUBLIC_REPO_NAME=<public-repo>/],
|
|
["public dry-run service smoke", /public-release-dryrun-service-smoke\.js/],
|
|
["public dry-run service address", /DISASMER_PUBLIC_RELEASE_DRYRUN_SERVICE_ADDR=disasmer\.michelpaulissen\.com:443/],
|
|
["public dry-run hosted coordinator", /hosted coordinator[\s\S]*disasmer\.michelpaulissen\.com[\s\S]*private\/hosted-policy/],
|
|
["public dry-run distinguishes Core", /standalone Core coordinator remains available for local\/self-hosted use/],
|
|
["public dry-run validates both coordinators", /dry-run acceptance validates both coordinator deployments/],
|
|
["public dry-run validates Core coordinator separately", /standalone Core coordinator is validated separately/],
|
|
["public browser login control entry", /POST https:\/\/disasmer\.michelpaulissen\.com\/api\/v1\/control/],
|
|
["public browser login hosted callback", /hosted[\s\S]*\/auth\/callback|\/auth\/callback[\s\S]*hosted/],
|
|
["public browser login server authority", /hosted service creates the OIDC state, nonce, and PKCE verifier/],
|
|
["public browser test driver", /DISASMER_PUBLIC_RELEASE_DRYRUN_BROWSER_OPEN_COMMAND/],
|
|
["browser login plan diagnostic", /disasmer login --browser --plan/],
|
|
["public dry-run deployment prep", /prepare-public-release-dryrun-deployment\.js/],
|
|
["public dry-run systemd unit", /systemd unit[\s\S]*127\.0\.0\.1:9080|loopback[\s\S]*127\.0\.0\.1:9080/],
|
|
["public dry-run e2e runner", /public-release-dryrun-e2e\.js/],
|
|
["public dry-run e2e gate env", /DISASMER_PUBLIC_RELEASE_DRYRUN_E2E=1/],
|
|
["public dry-run e2e service address", /DISASMER_PUBLIC_RELEASE_DRYRUN_SERVICE_ADDR=disasmer\.michelpaulissen\.com:443/],
|
|
["public dry-run final verifier", /public-release-dryrun-final-evidence\.js/],
|
|
["public dry-run final gate env", /DISASMER_PUBLIC_RELEASE_DRYRUN_FINAL=1/],
|
|
["public dry-run e2e evidence", /public-release-dryrun-e2e\.json/],
|
|
["public dry-run e2e checks", /downloaded release assets[\s\S]*VS Code debugger behavior[\s\S]*artifact metadata/],
|
|
["hosted Client compatibility smoke", /hosted-client-compat-smoke\.js/],
|
|
["hosted Client compatibility purpose", /Client CLI browser login[\s\S]*scoped session[\s\S]*node enrollment[\s\S]*cross-tenant denial/],
|
|
];
|
|
|
|
for (const [name, pattern] of requiredReadmePatterns) {
|
|
assert.match(readme, pattern, `README missing ${name}`);
|
|
}
|
|
|
|
for (const [name, pattern] of requiredDryrunPatterns) {
|
|
assert.match(dryrunDoc, pattern, `public release dry-run runbook missing ${name}`);
|
|
}
|
|
|
|
assert.doesNotMatch(
|
|
readme,
|
|
/DISASMER_PUBLISH_PUBLIC_TREE|DISASMER_FORGEJO_TOKEN|public-release-manifest\.json|public-release-dryrun-e2e\.json/,
|
|
"README must stay product-facing; release-internal dry-run commands belong in public_release_dryrun.md"
|
|
);
|
|
|
|
for (const [file, contents] of userFacingDocs) {
|
|
assert.doesNotMatch(
|
|
contents,
|
|
/community-tier/i,
|
|
`${file} should use "community tier" in user-facing prose`
|
|
);
|
|
}
|
|
|
|
for (const script of [publicAcceptance, publicSplit]) {
|
|
assert(
|
|
script.includes("node scripts/docs-smoke.js"),
|
|
"public acceptance gates must run docs-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/cli-install-smoke.js"),
|
|
"public acceptance gates must run cli-install-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/cli-output-mode-smoke.js"),
|
|
"public acceptance gates must run cli-output-mode-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/cli-login-smoke.js"),
|
|
"public acceptance gates must run cli-login-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/cli-error-exit-smoke.js"),
|
|
"public acceptance gates must run cli-error-exit-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/acceptance-report-smoke.js"),
|
|
"public acceptance gates must run acceptance-report-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/acceptance-doc-contract-smoke.js"),
|
|
"public acceptance gates must run acceptance-doc-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/acceptance-environment-contract-smoke.js"),
|
|
"public acceptance gates must run acceptance-environment-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/acceptance-evidence-contract-smoke.js"),
|
|
"public acceptance gates must run acceptance-evidence-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/public-private-boundary-smoke.js"),
|
|
"public acceptance gates must run public-private-boundary-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/release-blocker-smoke.js"),
|
|
"public acceptance gates must run release-blocker-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/resource-metering-contract-smoke.js"),
|
|
"public acceptance gates must run resource-metering-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/hostile-input-contract-smoke.js"),
|
|
"public acceptance gates must run hostile-input-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/tenant-isolation-contract-smoke.js"),
|
|
"public acceptance gates must run tenant-isolation-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/public-story-contract-smoke.js"),
|
|
"public acceptance gates must run public-story-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/public-release-dryrun-contract-smoke.js"),
|
|
"public acceptance gates must run public-release-dryrun-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/prepare-public-release-dryrun.js"),
|
|
"public acceptance gates must run prepare-public-release-dryrun.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/self-hosted-coordinator-smoke.js"),
|
|
"public acceptance gates must run self-hosted-coordinator-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/public-local-demo-matrix-smoke.js"),
|
|
"public acceptance gates must run public-local-demo-matrix-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("scripts/release-source-scan.sh"),
|
|
"public acceptance gates must run release-source-scan.sh"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/flagship-demo-smoke.js"),
|
|
"public acceptance gates must run flagship-demo-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/wasmtime-assignment-smoke.js"),
|
|
"public acceptance gates must run the real coordinator-to-Wasm assignment smoke"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/sdk-spawn-runtime-smoke.js"),
|
|
"public acceptance gates must run sdk-spawn-runtime-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/node-lifecycle-contract-smoke.js"),
|
|
"public acceptance gates must run node-lifecycle-contract-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/artifact-export-smoke.js"),
|
|
"public acceptance gates must run artifact-export-smoke.js"
|
|
);
|
|
assert(
|
|
script.includes("node scripts/windows-validation-contract-smoke.js"),
|
|
"public acceptance gates must run windows-validation-contract-smoke.js"
|
|
);
|
|
}
|
|
|
|
assert(
|
|
publicAcceptance.includes("node scripts/podman-backend-smoke.js"),
|
|
"public acceptance must run the Linux Podman backend smoke when Podman is available"
|
|
);
|
|
|
|
assert(
|
|
publicAcceptance.includes("node scripts/wasmtime-node-smoke.js"),
|
|
"public acceptance must run the Wasmtime node smoke"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node scripts/resource-metering-contract-smoke.js"),
|
|
"private acceptance must run resource-metering-contract-smoke.js"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node scripts/hostile-input-contract-smoke.js"),
|
|
"private acceptance must run hostile-input-contract-smoke.js"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node scripts/tenant-isolation-contract-smoke.js"),
|
|
"private acceptance must run tenant-isolation-contract-smoke.js"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node scripts/acceptance-doc-contract-smoke.js"),
|
|
"private acceptance must run acceptance-doc-contract-smoke.js"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node scripts/acceptance-environment-contract-smoke.js"),
|
|
"private acceptance must run acceptance-environment-contract-smoke.js"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node scripts/acceptance-evidence-contract-smoke.js"),
|
|
"private acceptance must run acceptance-evidence-contract-smoke.js"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node private/hosted-policy/scripts/hosted-deployment-smoke.js"),
|
|
"private acceptance must run hosted-deployment-smoke.js"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node private/hosted-policy/scripts/hosted-client-compat-smoke.js"),
|
|
"private acceptance must run hosted-client-compat-smoke.js"
|
|
);
|
|
assert(
|
|
privateAcceptance.includes("node scripts/self-hosted-coordinator-smoke.js"),
|
|
"private acceptance must run self-hosted-coordinator-smoke.js before final dry-run evidence"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node private/hosted-policy/scripts/prepare-public-release-dryrun-deployment.js"),
|
|
"private acceptance must prepare public release dry-run deployment bundle"
|
|
);
|
|
|
|
assert(
|
|
privateAcceptance.includes("node private/hosted-policy/scripts/public-release-dryrun-service-smoke.js"),
|
|
"private acceptance must be able to run public-release-dryrun-service-smoke.js"
|
|
);
|
|
|
|
for (const script of [publicAcceptance, privateAcceptance]) {
|
|
assert(
|
|
script.includes("node scripts/public-release-dryrun-final-evidence.js"),
|
|
"acceptance must be able to run public-release-dryrun-final-evidence.js"
|
|
);
|
|
assert(
|
|
script.includes("DISASMER_PUBLIC_RELEASE_DRYRUN_FINAL"),
|
|
"acceptance must gate public-release-dryrun-final-evidence.js"
|
|
);
|
|
}
|
|
|
|
assert(
|
|
publicAcceptance.includes("node scripts/public-release-dryrun-e2e.js"),
|
|
"public acceptance must be able to run public-release-dryrun-e2e.js"
|
|
);
|
|
|
|
assert(
|
|
publicAcceptance.includes("DISASMER_PUBLIC_RELEASE_DRYRUN_E2E"),
|
|
"public acceptance must gate public-release-dryrun-e2e.js"
|
|
);
|
|
|
|
console.log("Docs smoke passed");
|