test(05-05): Worker no-route-proxy gates RP1+RP2 (D-05)

- RP1: zero /api/route + registerRoute/routeProxy tokens in comment-stripped
  bot/src — the Worker gains no route endpoint, even accidentally (T-05-16)
- RP2: zero @araharah/routing specifiers in bot/src — routing lives in the
  webview bundle only; the Worker imports only @araharah/search
- scans grow with the bot/src tree automatically (the PG-gate pattern)
- TDD note: gates target an already-clean tree, so the RED run is a teeth
  proof — a planted violating file fails both gates; removed, both pass
  (74/0 bot suite green)
This commit is contained in:
araharah-executor
2026-10-24 06:03:01 +08:00
parent af744e49c7
commit 6302901f82
+79
View File
@@ -0,0 +1,79 @@
#!/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)`,
);
}
});