feat(06-03): /api/guidance POST/GET/DELETE — Bearer-claim gated, TTL-only service beneath (GREEN)

- 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
This commit is contained in:
araharah-executor
2026-10-24 13:06:25 +08:00
parent 212aa57c14
commit 7875ed4d94
5 changed files with 201 additions and 29 deletions
+24 -18
View File
@@ -1,26 +1,28 @@
#!/usr/bin/env node
/*
* route-privacy.test.mjs — the D-05 Worker boundary, gated (05-05, T-05-16).
* 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 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):
* 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 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)
* 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 — a future route proxy fails here at test time, not
* in review.
* tree automatically.
*/
import { strict as assert } from "node:assert";
import { readdirSync, readFileSync, statSync } from "node:fs";
@@ -65,15 +67,19 @@ test("RP1 no route proxy — zero /api/route + registerRoute/routeProxy tokens i
}
});
/* ————— RP2 — routing workspace code never enters the Worker ————— */
/* ————— RP2 — the routing allowlist (re-pinned 06-03: codec only) ————— */
test("RP2 no routing import — zero @araharah/routing specifiers in bot/src (D-05)", () => {
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);
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)`,
);
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)`,
);
}
}
});