From a3e2c3253f4f19075c7c4f6ccbdf2a9de0fff762 Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Mon, 26 Oct 2026 06:29:32 +0800 Subject: [PATCH] test(08.1-01): add failing CH construction core tests (RED) - C1 total order / C2 shortcut weights / C3 upward shortcuts - C4 hand-computed 3-node fixture (literal rank + shortcut set) - C5 overlay-only witness law (contracted node cannot witness) - C6 explosion tripwire (hand-traced 42-shortcut cascade on 9 nodes) - C7 determinism + bit0 oneway law --- routing/test/contraction.test.mjs | 326 ++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 routing/test/contraction.test.mjs diff --git a/routing/test/contraction.test.mjs b/routing/test/contraction.test.mjs new file mode 100644 index 0000000..f8bf068 --- /dev/null +++ b/routing/test/contraction.test.mjs @@ -0,0 +1,326 @@ +#!/usr/bin/env node +/* + * contraction.test.mjs — the CH construction core gates (C1–C7). + * ------------------------------------------------------------------ + * RED first (plan 08.1-01 task 1): every case fails while + * routing/src/contraction.ts does not exist (lazy-import pattern — + * each case reports its own ERR_MODULE_NOT_FOUND, the landmark.test.mjs + * precedent this suite replaces). + * + * The module under test is the ONE contraction authority (the + * single-authority law): build-time node ordering + contraction + + * overlay-only witness search + shortcut emission, deterministic and + * pure. The case index: + * + * C1 total order — ranks are distinct and cover exactly [0, N) + * (a tie would drop legitimate upward edges — + * research Pitfall 9) + * C2 shortcut weights — every emitted shortcut's seconds equal the + * sum of the weights of the two edges it + * replaced (f32 round-trip tolerance) + * C3 upward shortcuts — rank(from) > rank(via) and rank(to) > + * rank(via) for every shortcut (the query's + * strict uphill law depends on it) + * C4 hand fixture — a 3-node bidirectional path yields the + * LITERAL expected contraction order and + * shortcut set (never a tautology — research + * Pitfall 10) + * C5 overlay witness — the witness search sees ONLY the overlay + * graph: a previously contracted node cannot + * witness a shortcut, so the shortcut that + * keeps the hierarchy distance-preserving IS + * emitted (the #1 correctness trap — research + * Pitfall 1) + * C6 explosion tripwire — a budget-miss cascade above + * CH_SHORTCUT_EXPLOSION_RATIO * nodeCount + * fails loud, naming the ratio and the + * measured count; a healthy graph passes + * C7 determinism — two calls on identical input produce + * byte-identical rank and shortcut arrays + * + * Run: npm run routing:test (exit 0 = all green) + */ +import { strict as assert } from "node:assert"; +import { test } from "./harness.mjs"; + +// Lazy module load — missing module => per-case ERR_MODULE_NOT_FOUND (RED). +let contractionPromise = null; +function loadContraction() { + if (!contractionPromise) contractionPromise = import("../src/contraction.ts"); + return contractionPromise; +} + +/* ————— hand-built fixtures (hand-computed expectations) ————— */ + +/** 5-node bidirectional chain + one long chord. Invariant-sweep fixture + * for C1/C2/C3 (no literal order pinned — C4 owns the literal case). */ +function chainInput() { + return { + nodeCount: 5, + edges: [ + { from: 0, to: 1, seconds: 2, flags: 0 }, + { from: 1, to: 0, seconds: 2, flags: 0 }, + { from: 1, to: 2, seconds: 3, flags: 0 }, + { from: 2, to: 1, seconds: 3, flags: 0 }, + { from: 2, to: 3, seconds: 1, flags: 0 }, + { from: 3, to: 2, seconds: 1, flags: 0 }, + { from: 3, to: 4, seconds: 5, flags: 0 }, + { from: 4, to: 3, seconds: 5, flags: 0 }, + { from: 0, to: 4, seconds: 20, flags: 0 }, // the long chord + ], + }; +} + +/** + * C4's hand fixture: 3 nodes, bidirectional path 0 <-> 1 <-> 2, unit + * weights. HAND-COMPUTED under the plan's exact algorithm: + * init priorities: node 1: 2 shortcuts − 4 edges = −2; nodes 0/2: + * 0 − 1 = −1 → node 1 contracts first (rank 0), emitting shortcuts + * 0→2 (via 1, w2) and 2→0 (via 1, w2) — its in/out neighbourhoods are + * {0, 2} and the overlay has no alternative path between them. Then + * nodes 0 and 2 tie at recompute (0 alive edges + λ·deleted) → lower + * id first: rank = [1, 0, 2]. Emission order (ascending from, then + * ascending to) gives the CSR below. + */ +function handInput() { + return { + nodeCount: 3, + edges: [ + { from: 0, to: 1, seconds: 1, flags: 0 }, + { from: 1, to: 0, seconds: 1, flags: 0 }, + { from: 1, to: 2, seconds: 1, flags: 0 }, + { from: 2, to: 1, seconds: 1, flags: 0 }, + ], + }; +} + +/** + * C5's hand fixture: the overlay-witness law. Nodes: 0 = the mid node u, + * 1 = the witness source a, 2 = the bypass c, 3 = the target b. Edges: + * a→u→b (1+1) and a→c→b (1+1). ALL four priorities tie at −2 → id order: + * node 0 (u!) contracts FIRST — its pair (a=1, b=3) is witnessed by the + * then-alive bypass (1→2→3, cost 2 ≤ 2) → NO shortcut. The bypass (node + * 2) contracts before a re-runs the pair… but the pinned moment is u's + * own pop AFTER the bypass died on a LATER tie… — concretely: after u + * (rank 0) the id order contracts the bypass (node 2, rank 1; its pair + * (1,3) is witnessed by… nothing alive → hmm, no: the bypass's in-edge + * came from a=1 which is alive; its pair (1,3) is witnessed by u? u is + * contracted. So the bypass emits 1→3 (via 2, w2) — and THAT is the + * pinned fact: on the ORIGINAL graph the pair had a live alternative + * (through u), on the OVERLAY it does not, so the shortcut MUST be + * emitted. A witness search that leaked into the original graph would + * find u's path and silently drop the shortcut — the exact #1 trap. + */ +function overlayInput() { + return { + nodeCount: 4, + edges: [ + { from: 1, to: 0, seconds: 1, flags: 0 }, // a -> u + { from: 0, to: 3, seconds: 1, flags: 0 }, // u -> b + { from: 1, to: 2, seconds: 1, flags: 0 }, // a -> c + { from: 2, to: 3, seconds: 1, flags: 0 }, // c -> b + ], + }; +} + +/** + * C6's explosion fixture (9 nodes, 40 edges): node 0 is the BYPASS hub, + * node 1 the BURST hub, nodes 2..8 the seven Y nodes; both hubs ride + * every Y with weight 1 in both directions, and the Y nodes carry a + * weight-100 ring (never on a cheapest path — pure degree ballast). + * HAND-TRACED under the plan's exact algorithm: the two hubs tie at + * priority −14 (42 pairs, all witnessed by the OTHER hub — 0 shortcuts — + * minus 14 incident edges) and the Y nodes sit at −6, so the tie-break + * contracts the bypass (id 0) FIRST. Its own 42 pairs are witnessed by + * the still-alive burst hub → no shortcuts. The burst hub's pop then + * recomputes against the overlay: its witness (the bypass) is DEAD, the + * weight-100 ring exceeds every candidate bound → all 7×6 = 42 pairs + * emit shortcuts (2 each, via the burst hub) — 42 > 4×9 = 36 → the + * tripwire MUST fail loud. (The Y nodes then witness each other through + * the fresh shortcuts — no further growth — but the count already blew + * the guard.) + */ +function explosionInput() { + const edges = []; + for (let y = 2; y <= 8; y++) { + edges.push({ from: 0, to: y, seconds: 1, flags: 0 }); + edges.push({ from: y, to: 0, seconds: 1, flags: 0 }); + edges.push({ from: 1, to: y, seconds: 1, flags: 0 }); + edges.push({ from: y, to: 1, seconds: 1, flags: 0 }); + } + for (let y = 2; y < 8; y++) { + edges.push({ from: y, to: y + 1, seconds: 100, flags: 0 }); + edges.push({ from: y + 1, to: y, seconds: 100, flags: 0 }); + } + return { nodeCount: 9, edges }; +} + +/** Min seconds over the ORIGINAL traversable (bit0-clear) edges u->v. */ +function minTraversableSeconds(input, from, to) { + let best = Infinity; + for (const e of input.edges) { + if (e.from === from && e.to === to && (e.flags & 1) === 0 && e.seconds < best) { + best = e.seconds; + } + } + return best; +} + +/* ————— C1–C7 ————— */ + +test("C1 total order — ranks are distinct and cover exactly [0, nodeCount)", async () => { + const { contractGraph, CH_LAMBDA_DELETED_NEIGHBOURS } = await loadContraction(); + assert.equal(CH_LAMBDA_DELETED_NEIGHBOURS, 1, "the λ constant is the named export"); + const input = chainInput(); + const result = contractGraph(input); + assert.ok(result.rank instanceof Uint32Array, "rank is a Uint32Array"); + assert.equal(result.rank.length, input.nodeCount, "one rank per node"); + const seen = new Set(result.rank); + assert.equal(seen.size, input.nodeCount, "ranks are DISTINCT (a tie drops upward edges — Pitfall 9)"); + for (const r of result.rank) { + assert.ok(Number.isInteger(r) && r >= 0 && r < input.nodeCount, `rank ${r} in [0, ${input.nodeCount})`); + } + // stats travel with the result (the build prints them — D-08/D-09) + assert.equal(typeof result.stats.shortcutCount, "number"); + assert.equal(typeof result.stats.ratioPerNode, "number"); + assert.equal(typeof result.stats.witnessSearches, "number"); + assert.equal(typeof result.stats.witnessBudgetHits, "number"); + assert.equal(result.stats.shortcutCount, result.shortcutTarget.length, "stats agree with the CSR"); + assert.ok( + Math.abs(result.stats.ratioPerNode - result.stats.shortcutCount / input.nodeCount) < 1e-12, + "ratioPerNode is shortcuts / nodeCount", + ); +}); + +test("C2 shortcut weights — every shortcut's seconds equal the sum of the two edges it replaced", async () => { + const { contractGraph } = await loadContraction(); + const input = chainInput(); + const result = contractGraph(input); + assert.ok(result.shortcutTarget.length > 0, "the chain fixture emits at least one shortcut"); + for (let u = 0; u < input.nodeCount; u++) { + for (let s = result.shortcutStart[u]; s < result.shortcutStart[u + 1]; s++) { + const to = result.shortcutTarget[s]; + const via = result.shortcutVia[s]; + const w1 = minTraversableSeconds(input, u, via); + const w2 = minTraversableSeconds(input, via, to); + assert.ok( + Number.isFinite(w1) && Number.isFinite(w2), + `shortcut ${u}->${to} (via ${via}): both constituent edges exist in the input`, + ); + assert.ok( + Math.abs(result.shortcutSeconds[s] - (w1 + w2)) < 1e-6, + `shortcut ${u}->${to}: ${result.shortcutSeconds[s]} === ${w1} + ${w2} (f32 tolerance)`, + ); + } + } +}); + +test("C3 upward shortcuts — rank(from) > rank(via) and rank(to) > rank(via) for every shortcut", async () => { + const { contractGraph } = await loadContraction(); + const input = chainInput(); + const result = contractGraph(input); + assert.ok(result.shortcutStart instanceof Uint32Array, "shortcutStart is a Uint32Array"); + assert.equal(result.shortcutStart.length, input.nodeCount + 1, "CSR row pointers: N+1 entries"); + for (let u = 0; u < input.nodeCount; u++) { + for (let s = result.shortcutStart[u]; s < result.shortcutStart[u + 1]; s++) { + const to = result.shortcutTarget[s]; + const via = result.shortcutVia[s]; + assert.ok( + result.rank[u] > result.rank[via], + `shortcut ${u}->${to}: rank(from)=${result.rank[u]} > rank(via)=${result.rank[via]}`, + ); + assert.ok( + result.rank[to] > result.rank[via], + `shortcut ${u}->${to}: rank(to)=${result.rank[to]} > rank(via)=${result.rank[via]}`, + ); + } + } +}); + +test("C4 hand fixture — the 3-node path yields its literal contraction order and shortcut set", async () => { + const { contractGraph } = await loadContraction(); + const result = contractGraph(handInput()); + // Literal rank (hand-computed in handInput's doc comment). + assert.deepEqual([...result.rank], [1, 0, 2], "rank: node 1 first, then 0, then 2"); + // Literal shortcut set: 0->2 (via 1, w2) and 2->0 (via 1, w2). + assert.deepEqual([...result.shortcutStart], [0, 1, 1, 2], "CSR: node 0 has 1, node 1 has 0, node 2 has 1"); + assert.deepEqual([...result.shortcutTarget], [2, 0], "targets: 0->2 and 2->0"); + assert.deepEqual([...result.shortcutSeconds], [2, 2], "unit weights sum to 2"); + assert.deepEqual([...result.shortcutVia], [1, 1], "both shortcuts ride the contracted mid node"); + assert.equal(result.stats.shortcutCount, 2); +}); + +test("C5 overlay witness law — a contracted node cannot witness a shortcut (the #1 trap)", async () => { + const { contractGraph } = await loadContraction(); + const result = contractGraph(overlayInput()); + // Hand-traced (overlayInput's doc comment): the bypass node 2 contracts + // after u (node 0) is gone; its pair (a=1 -> b=3) then has NO overlay + // witness (u is contracted), so the shortcut 1->3 (via 2) MUST be + // emitted — even though the ORIGINAL graph still holds the u-path as a + // "witness" for a buggy search. The emitted shortcut is exactly what + // keeps the overlay distance-preserving. + const found = []; + for (let u = 0; u < 4; u++) { + for (let s = result.shortcutStart[u]; s < result.shortcutStart[u + 1]; s++) { + found.push({ from: u, to: result.shortcutTarget[s], seconds: result.shortcutSeconds[s], via: result.shortcutVia[s] }); + } + } + assert.equal(found.length, 1, `exactly the one required shortcut was emitted (got ${JSON.stringify(found)})`); + assert.equal(found[0].from, 1, "shortcut source is a"); + assert.equal(found[0].to, 3, "shortcut target is b"); + assert.equal(found[0].via, 2, "via is the contracted bypass"); + assert.ok(Math.abs(found[0].seconds - 2) < 1e-6, "weight is the 1+1 sum"); +}); + +test("C6 explosion tripwire — fails loud above CH_SHORTCUT_EXPLOSION_RATIO * nodeCount; a healthy graph passes", async () => { + const { contractGraph, CH_SHORTCUT_EXPLOSION_RATIO, CH_WPS_MAX_SETTLED, CH_WPS_SETTLED_PER_DEGREE } = + await loadContraction(); + assert.equal(CH_SHORTCUT_EXPLOSION_RATIO, 4, "the tripwire ratio is the named constant"); + assert.equal(CH_WPS_MAX_SETTLED, 60, "the witness settle budget floor is the named constant"); + assert.equal(CH_WPS_SETTLED_PER_DEGREE, 4, "the per-degree settle scaling is the named constant"); + // Healthy graphs pass silently. + assert.doesNotThrow(() => contractGraph(handInput()), "a 2-shortcut build is healthy"); + assert.doesNotThrow(() => contractGraph(chainInput()), "the chain build is healthy"); + // The hand-traced cascade: 42 shortcuts on 9 nodes (4×9 = 36) — the + // message names the ratio and the measured count. + assert.throws( + () => contractGraph(explosionInput()), + (err) => { + assert.ok(err instanceof Error, "the tripwire throws an Error"); + assert.match(err.message, /shortcut explosion/, "the message names the failure"); + assert.match(err.message, /42/, "the message names the measured shortcut count"); + assert.match(err.message, /4/, "the message names the ratio"); + return true; + }, + "the budget-miss cascade fails loud above the ratio", + ); +}); + +test("C7 determinism — identical input yields byte-identical rank and shortcut arrays", async () => { + const { contractGraph } = await loadContraction(); + const a = contractGraph(chainInput()); + const b = contractGraph(chainInput()); + assert.deepEqual([...a.rank], [...b.rank], "ranks identical"); + assert.deepEqual([...a.shortcutStart], [...b.shortcutStart], "CSR pointers identical"); + assert.deepEqual([...a.shortcutTarget], [...b.shortcutTarget], "targets identical"); + assert.deepEqual([...a.shortcutSeconds], [...b.shortcutSeconds], "seconds identical"); + assert.deepEqual([...a.shortcutVia], [...b.shortcutVia], "via identical"); + assert.equal(a.stats.witnessSearches, b.stats.witnessSearches, "even the stats are deterministic"); + // The oneway bit0 law: a blocked mirror edge is never relaxed — dropping + // the traversable direction changes the result; the FLAGGED one does not. + const withBlocked = { + nodeCount: 3, + edges: [ + { from: 0, to: 1, seconds: 1, flags: 0 }, + { from: 1, to: 0, seconds: 1, flags: 1 }, // bit0: stored, NEVER relaxed + { from: 1, to: 2, seconds: 1, flags: 0 }, + { from: 2, to: 1, seconds: 1, flags: 0 }, + ], + }; + const blocked = contractGraph(withBlocked); + // 1->0 is blocked, so node 0 has no in-edge: contracting 1 emits only 0->2. + assert.deepEqual([...blocked.shortcutStart], [0, 1, 1, 1], "only node 0's shortcut exists"); + assert.deepEqual([...blocked.shortcutTarget], [2], "0->2 via 1"); + assert.deepEqual([...blocked.shortcutVia], [1]); + assert.equal(blocked.stats.shortcutCount, 1, "the bit0 edge was never relaxed"); +});