#!/usr/bin/env node /* * route-privacy.test.mjs — the D-05 Worker boundary, gated (05-05, T-05-16; * re-pinned by 06-03 per the gate re-pin law). * ------------------------------------------------------------------------------ * D-05: "The Worker gains no /api/route this phase. Fixtures ride in the web * bundle." The routing STACK lives in the webview — the Worker must never * grow a route proxy or import the routing machinery (router, graph, A*, * fixtures). Phase 6 plan 06-03 legitimately broke ONE pane of that glass: * the frozen guidance envelope's geometry field IS polyline6, and the * Worker must validate it server-side (a corrupt geometry can never reach * the cron). So RP2 re-pins from a total ban to an explicit allowlist — * exactly ONE routing module may enter the Worker, the pure polyline6 * codec; everything else routing-shaped stays out. RP1 stands unchanged. * * RP1 no route proxy — zero `/api/route` tokens AND zero * `registerRoute` / `routeProxy` tokens anywhere in bot/src * (comment-stripped): the Worker gains no route endpoint, not even * an accidentally-named one * RP2 routing allowlist — every `@araharah/routing` specifier in bot/src * must be EXACTLY `@araharah/routing/src/polyline.ts` (codec * validation only); any other subpath fails here at test time * * The scans cover EVERY TypeScript file under bot/src and grow with the * tree automatically. */ import { strict as assert } from "node:assert"; import { readdirSync, readFileSync, statSync } from "node:fs"; import { fileURLToPath } from "node:url"; import path from "node:path"; import { test } from "./harness.mjs"; const SRC = path.join(fileURLToPath(new URL(import.meta.url)), "..", "..", "src"); function listFiles(dir, exts, acc = []) { for (const entry of readdirSync(dir)) { const full = path.join(dir, entry); if (statSync(full).isDirectory()) listFiles(full, exts, acc); else if (exts.some((e) => entry.endsWith(e))) acc.push(full); } return acc; } function stripComments(src) { return src .replace(/\/\*[\s\S]*?\*\//g, " ") // block comments .replace(/(^|[^:\\])\/\/[^\n]*/g, "$1"); // line comments (not URLs inside strings — benign over-stripping, gate is prefer-fail reading) } function codeText(file) { return stripComments(readFileSync(file, "utf8")); } /* ————— RP1 — the Worker gains no route endpoint (D-05, T-05-16) ————— */ test("RP1 no route proxy — zero /api/route + registerRoute/routeProxy tokens in bot/src (comment-stripped, D-05)", () => { const files = listFiles(SRC, [".ts"]); assert.ok(files.length >= 8, `scanned a real tree (found ${files.length} files)`); for (const file of files) { const code = codeText(file); for (const banned of ["/api/route", "registerRoute", "routeProxy"]) { assert.ok( !code.includes(banned), `${path.relative(process.cwd(), file)} references "${banned}" — D-05 forbids a Worker route proxy (T-05-16); fixtures ride the web bundle`, ); } } }); /* ————— RP2 — the routing allowlist (re-pinned 06-03: codec only) ————— */ test("RP2 routing allowlist — every @araharah/routing specifier in bot/src is EXACTLY the polyline6 codec (D-05 + 06-03)", () => { const ALLOWED = ["@araharah/routing/src/polyline.ts"]; const files = listFiles(SRC, [".ts"]); for (const file of files) { const code = codeText(file); const specifiers = [...code.matchAll(/["'](@araharah\/routing[^"']*)["']/g)].map((m) => m[1]); for (const spec of specifiers) { assert.ok( ALLOWED.includes(spec), `${path.relative(process.cwd(), file)} imports "${spec}" — only the polyline6 codec may enter the Worker (06-03 geometry validation); the routing stack (router/graph/astar/fixtures) stays web-side (D-05)`, ); } } });