test(06-04): failing http-source suite H1-H8 + /api/route block RT1-RT11 + RP1/RP2 re-pin (RED)
- routing/test/http-source.test.mjs: backend #2 client seam — slug-guard-first, fail-honest on throw/non-200/malformed/null-body, defensive RouteResult shape - webhook.test.mjs RT block: 401/413/400 classes, out-of-bbox origin (V5), 200-null on unknown dest / absent graph / corrupt binary / snap miss / A* miss, happy path (engine-a, ONE option, steps) with the 1-GET isolate-memo probe - route-privacy re-pin (06-04's own gate law): RP1 = claim-bound local routing (getClaimByToken, no fetch/proxy, arm wired to worker-router, no routing hosts), RP2 = workspace allowlist @araharah/{search,routing}/* subpaths only
This commit is contained in:
@@ -1,25 +1,31 @@
|
||||
#!/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).
|
||||
* route-privacy.test.mjs — the Worker routing boundary, gated (05-05 T-05-16;
|
||||
* re-pinned by 06-03, then re-pinned AGAIN by 06-04 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.
|
||||
* PHASE-5 SHAPE (historical): D-05 banned any /api/route outright — the
|
||||
* routing stack lived web-side and the Worker grew no route proxy.
|
||||
*
|
||||
* 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
|
||||
* 06-03's re-pin: the frozen guidance envelope's geometry IS polyline6, so
|
||||
* the polyline codec entered the Worker and RP2 became an explicit allowlist.
|
||||
* RP1 stood unchanged.
|
||||
*
|
||||
* 06-04's re-pin (THIS FILE — D-01 made the Worker the router, so the
|
||||
* negative-space ban is superseded by a positive-space law):
|
||||
*
|
||||
* RP1 claim-bound routing — the Worker MAY route, but only under the
|
||||
* session identity: bot/src/routing/worker-router.ts must derive
|
||||
* identity from the Bearer CLAIM (getClaimByToken — T-06-01), must
|
||||
* NEVER forward to an external routing host (no fetch, no proxy —
|
||||
* the routing computation is local A* over the R2 graph), and every
|
||||
* file referencing the /api/route arm must be wired to that
|
||||
* claim-deriving module. An unauthenticated or proxy-shaped routing
|
||||
* arm fails here at test time.
|
||||
* RP2 workspace allowlist — every @araharah/* specifier in bot/src must
|
||||
* be a SUBPATH-WITH-EXTENSION import of the two allowed workspaces:
|
||||
* @araharah/search/* and @araharah/routing/*. Any OTHER workspace
|
||||
* package, or a bare package specifier (no subpath), fails. (GE5
|
||||
* still owns the KV-namespace count; this gate owns imports.)
|
||||
*
|
||||
* The scans cover EVERY TypeScript file under bot/src and grow with the
|
||||
* tree automatically.
|
||||
@@ -31,6 +37,7 @@ import path from "node:path";
|
||||
import { test } from "./harness.mjs";
|
||||
|
||||
const SRC = path.join(fileURLToPath(new URL(import.meta.url)), "..", "..", "src");
|
||||
const ROUTER = path.join(SRC, "routing", "worker-router.ts");
|
||||
|
||||
function listFiles(dir, exts, acc = []) {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
@@ -51,35 +58,79 @@ function codeText(file) {
|
||||
return stripComments(readFileSync(file, "utf8"));
|
||||
}
|
||||
|
||||
/* ————— RP1 — the Worker gains no route endpoint (D-05, T-05-16) ————— */
|
||||
/* ————— RP1 — the routing arm is claim-bound and never a proxy (T-06-01) ————— */
|
||||
|
||||
test("RP1 no route proxy — zero /api/route + registerRoute/routeProxy tokens in bot/src (comment-stripped, D-05)", () => {
|
||||
test("RP1a the router derives identity from the Bearer CLAIM and never forwards (worker-router.ts, T-06-01)", () => {
|
||||
assert.ok(
|
||||
existsIn(ROUTER),
|
||||
"bot/src/routing/worker-router.ts must exist — 06-04's engine-A arm is the one lawful routing surface",
|
||||
);
|
||||
const code = codeText(ROUTER);
|
||||
assert.ok(
|
||||
code.includes("getClaimByToken"),
|
||||
"worker-router.ts must resolve the Bearer token through getClaimByToken — an unauthenticated routing arm fails RP1 (T-06-01)",
|
||||
);
|
||||
assert.ok(
|
||||
!/\bfetch\s*\(/.test(code),
|
||||
"worker-router.ts must NEVER fetch — the routing computation is local A* over the R2 graph; a forwarding/proxy-shaped router fails RP1",
|
||||
);
|
||||
});
|
||||
|
||||
test("RP1b every /api/route reference is wired to the claim-deriving router module", () => {
|
||||
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`,
|
||||
);
|
||||
}
|
||||
if (!code.includes("/api/route")) continue;
|
||||
const wired = code.includes("worker-router") || code.includes("getClaimByToken");
|
||||
assert.ok(
|
||||
wired,
|
||||
`${path.relative(process.cwd(), file)} references "/api/route" but neither delegates to ` +
|
||||
`worker-router.ts nor derives the claim itself — a detached/unauthenticated route arm fails RP1`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/* ————— 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"];
|
||||
test("RP1c no external routing hostnames anywhere in bot/src (the routing oracle is OUR Worker)", () => {
|
||||
const banned = /(overpass|project-osrm|osrm|valhalla|graphhopper|openrouteservice|routing\.openstreetmap)/i;
|
||||
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]);
|
||||
const hit = code.match(banned);
|
||||
assert.ok(
|
||||
hit === null,
|
||||
`${path.relative(process.cwd(), file)} references routing-host "${hit?.[0]}" — the Worker routes locally (D-01); external routing hosts are maintainer-time build tools, never runtime dependencies`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/* ————— RP2 — the workspace import allowlist (re-pinned 06-04: search + routing) ————— */
|
||||
|
||||
test("RP2 workspace allowlist — @araharah/* imports in bot/src are search/routing SUBPATHS only (D-01 + 06-04)", () => {
|
||||
const ALLOWED_PREFIXES = ["@araharah/search/", "@araharah/routing/"];
|
||||
const files = listFiles(SRC, [".ts"]);
|
||||
for (const file of files) {
|
||||
const code = codeText(file);
|
||||
const specifiers = [...code.matchAll(/["'](@araharah\/[^"']*)["']/g)].map((m) => m[1]);
|
||||
for (const spec of specifiers) {
|
||||
const allowed =
|
||||
ALLOWED_PREFIXES.some((prefix) => spec.startsWith(prefix)) &&
|
||||
!ALLOWED_PREFIXES.some((prefix) => spec === prefix.slice(0, -1)); // a bare prefix with no subpath fails too
|
||||
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)`,
|
||||
allowed,
|
||||
`${path.relative(process.cwd(), file)} imports "${spec}" — bot may import ONLY @araharah/search/* and ` +
|
||||
`@araharah/routing/* as subpath-with-extension specifiers (the subpath law); any other workspace ` +
|
||||
`package (or a bare specifier) fails RP2`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function existsIn(file) {
|
||||
try {
|
||||
statSync(file);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user