#!/usr/bin/env node import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const docPath = "docs/reference/m3-loop-rollout-runbook.md"; async function readText(relativePath) { return readFile(path.join(repoRoot, relativePath), "utf8"); } function extractSection(doc, heading) { const marker = `## ${heading}\n`; const start = doc.indexOf(marker); assert.ok(start >= 0, `missing section ${heading}`); const bodyStart = start + marker.length; const nextHeading = doc.indexOf("\n## ", bodyStart); return doc.slice(bodyStart, nextHeading >= 0 ? nextHeading + 1 : doc.length); } function assertIncludes(text, snippet, label) { assert.ok(text.includes(snippet), `${label} must include ${snippet}`); } function assertNotIncludes(text, snippet, label) { assert.equal(text.includes(snippet), false, `${label} must not include ${snippet}`); } async function main() { const doc = await readText(docPath); const boundary = extractSection(doc, "M3 Loop Boundary"); assertIncludes(doc, "support / diagnostics / contract", "m3 runbook"); assertIncludes(doc, "2 x hwlab-box-simu", "m3 runbook"); assertIncludes(doc, "2 x hwlab-gateway-simu", "m3 runbook"); assertIncludes(doc, "1 x hwlab-patch-panel", "m3 runbook"); assertIncludes(doc, "DO1 -> patch-panel -> DI1", "m3 runbook"); assertIncludes(doc, "deploy/deploy.yaml", "m3 runbook"); assertIncludes(doc, "deploy/artifact-catalog.dev.json", "m3 runbook"); assertIncludes(doc, "pikasTech/HWLAB#63", "m3 runbook"); assertIncludes(doc, "Do not promote SOURCE / LOCAL / DRY-RUN / fixture output to `DEV-LIVE`.", "m3 runbook"); assertIncludes(doc, "node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run", "m3 runbook"); assertIncludes(doc, "node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod", "m3 runbook"); assertIncludes(doc, "liveOperation.status: \"not_run\"", "m3 runbook"); assertNotIncludes(boundary, "http://74.48.78.17:6666/", "m3 current boundary"); assertNotIncludes(boundary, "http://74.48.78.17:6667/", "m3 current boundary"); assertIncludes(boundary, "http://74.48.78.17:16666/", "m3 current boundary"); assertIncludes(boundary, "http://74.48.78.17:16667/", "m3 current boundary"); assertIncludes(boundary, ":6666", "m3 historical boundary"); assertIncludes(boundary, ":6667", "m3 historical boundary"); console.log(`validated ${docPath}`); } try { await main(); } catch (error) { process.stderr.write(`[validate-m3-rollout-runbook] ${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; }