#!/usr/bin/env node /* * route-privacy.test.mjs — the D-05 Worker boundary, gated (05-05, T-05-16). * ------------------------------------------------------------------------------ * D-05: "The Worker gains no /api/route this phase. Fixtures ride in the web * bundle." The routing stack lives ENTIRELY in the webview — @araharah/routing * is imported by web/src only; the Worker imports only @araharah/search. * The pivot's privacy posture ("your coordinates were never sent anywhere") * is true partly BECAUSE no route endpoint exists to send them to — so the * abstinence is pinned by reading the source itself (the privacy.test.mjs / * PG-gate pattern: comment-stripped, prefer-fail token scans): * * 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 no routing import — zero `@araharah/routing` specifiers in bot/src: * routing workspace code cannot leak into the Worker bundle even as * a dead import (the bundle would grow and the boundary would blur) * * The scans cover EVERY TypeScript file under bot/src and grow with the * tree automatically — a future route proxy fails here at test time, not * in review. */ 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 — routing workspace code never enters the Worker ————— */ test("RP2 no routing import — zero @araharah/routing specifiers in bot/src (D-05)", () => { const files = listFiles(SRC, [".ts"]); for (const file of files) { const code = codeText(file); assert.ok( !code.includes("@araharah/routing"), `${path.relative(process.cwd(), file)} imports @araharah/routing — routing lives in the webview bundle only; the Worker imports only @araharah/search (D-05)`, ); } });