// Headless depth-2 nesting guard for the `aura graph` viewer (graph-viewer.js). // // Property protected: the viewer renders MULTIPLY-nested composites — a composite // inside a composite inside the root — with valid Graphviz node identifiers at // every depth. genDot builds ids as `n` + the node-key path joined by `__` // (graph-viewer.js: `cid = "n" + cpath.join("__")`), so a depth-2 interior node // gets a three-segment id like `n0__0__0`. A digit-led non-numeral id (`0__0__0`) // would be misparsed by Graphviz and collapse the interior; this guard pins that // the `n`-prefix rule holds at depth 2, beyond the depth-1 viewer_dot_ids.mjs guard. // // It loads the real viewer module and the re-captured sample fixture (which is now // the enriched root → signals → {trend, momentum} graph), then EXPANDS RECURSIVELY: // discover the top-level expandable (signals), expand, re-discover the newly // revealed expandables (trend, momentum), expand those — until none remain — and // asserts (a) every DOT id is valid and (b) at least one three-segment (depth-2) id // was actually produced, so the guard cannot pass vacuously on a flat model. import { createRequire } from "node:module"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const require = createRequire(import.meta.url); const here = dirname(fileURLToPath(import.meta.url)); const model = JSON.parse(readFileSync(join(here, "fixtures", "sample-model.json"), "utf8")); // Stub the global BEFORE requiring the viewer (mirrors the browser bootstrap). global.window = { AURA_MODEL: model }; const { normalizeModel, genDot } = require(join(here, "..", "assets", "graph-viewer.js")); const ROOT = normalizeModel(model).root; // Expand every expandable composite, re-discovering deeper ones each pass, until // the expandable set stops growing (reaches every nesting level). let expanded = new Set(); let dot = ""; for (let pass = 0; pass < 16; pass++) { const g = genDot(ROOT, "root", expanded, false); dot = g.dot; const fresh = g.expandable.filter((id) => !expanded.has(id)); if (!fresh.length) break; fresh.forEach((id) => expanded.add(id)); } // Collect every node identifier referenced in the final DOT (node-def lines + edge // endpoints), mirroring viewer_dot_ids.mjs. const ids = new Set(); for (const m of dot.matchAll(/^\s*([^\s\[]+)\s*\[label=/gm)) ids.add(m[1]); for (const m of dot.matchAll(/(\S+?):[a-z]\w*:[a-z]\s*->\s*(\S+?):[a-z]\w*:[a-z]/g)) { ids.add(m[1]); ids.add(m[2]); } const QUOTED = /^".*"$/s; const NUMERAL = /^-?(\.[0-9]+|[0-9]+(\.[0-9]*)?)$/; const BARE = /^[A-Za-z_][A-Za-z0-9_]*$/; const isValid = (id) => QUOTED.test(id) || NUMERAL.test(id) || BARE.test(id); const bad = [...ids].filter((id) => !isValid(id)); if (bad.length) { console.error( "INVALID DOT node identifier(s) at nesting depth — Graphviz will misparse a\n" + "digit-prefixed, non-numeral id and collapse the nested composite:\n " + bad.map((id) => JSON.stringify(id)).join("\n ") ); process.exit(1); } // Anti-vacuous: at least one three-segment (depth-2) id must exist, proving the // guard actually expanded two composite levels (root → signals → trend/momentum). const depth2 = [...ids].filter((id) => /^n\d+__\d+__\d+/.test(id)); if (!depth2.length) { console.error( "no depth-2 (three-segment `nA__B__C`) id found — the guard did not reach two\n" + "nesting levels; the fixture is not multiply-nested or the expand loop stalled." ); process.exit(1); } console.log(`OK — ${ids.size} DOT ids valid; ${depth2.length} reached nesting depth 2.`); process.exit(0);