- POST: WR-01 cap (MAX_GUIDANCE_BODY_CHARS 8192, pre/post-read double check), hand-rolled snapshot validation (slug destinationId, bounded name, finite metrics, integer startedAtSec, polyline6 geometry >= 2 points via decodePolyline6) -> mintGuidanceSession with the CLAIM's chat_id — a body chat_id is structurally ignored (T-06-02 injection probe GW4) - GET/DELETE: resolve then OWN — envelope.chat_id === claim.chat_id else 404, foreign and absent indistinguishable (T-06-02b); DELETE actively removes the record (D-14's server half); non-POST/GET/DELETE -> 405 - category-only logging: [guidance] rejected: unauthorized|malformed — literal strings, no interpolation of body/claim values (D-14) - RP2 re-pin ships WITH the change (gate re-pin law): the total @araharah/routing ban becomes an explicit allowlist — exactly the polyline6 codec may enter the Worker (geometry validation); the routing stack stays web-side; RP1 unchanged - bot/package.json + lock: @araharah/routing workspace dep
86 lines
3.8 KiB
JavaScript
86 lines
3.8 KiB
JavaScript
#!/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)`,
|
|
);
|
|
}
|
|
}
|
|
});
|