- BA7: the shipped JS must carry EVERY committed gazetteer id (the web half of bot/web id-set parity, T-04-39) — RED against the pre-gazetteer bundle, green after the resolver lands in the build - BA8: the shipped JS carries NO deep-link crypto (JS assets only — the official Telegram SDK is external, D-11/T-04-40) - no-storage: explicit web/src/search memory-only + no-crypto pin - capture.mjs: the ARAHARAH-DEST signal race + the 'dest' wait mode (state=resolved asserted, then the rendered card's text is waited on — the card mounts at live, T-04-42) + the shell-dest-preload leg; 8/8 expected captures
105 lines
4.7 KiB
JavaScript
105 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
||
/*
|
||
* no-storage.test.mjs — the GEO-03 grep gate (zero persistence, source-introspection).
|
||
* -----------------------------------------------------------------------------------
|
||
* Inverted form of bot/test/privacy.test.mjs (PG1 pins writes to carry TTLs; this
|
||
* pin that web/src contains NO persistence surface at all — D-03 (memory-only
|
||
* token) + D-07 (coordinates live in memory only)).
|
||
*
|
||
* The gate MUST strip comments before scanning: web/src/mock/telegram-mock.ts's
|
||
* header documents the ban in prose, and prose is not a call site. Strip block
|
||
* comments (/* … */) and line comments (// …), THEN assert none of the banned
|
||
* tokens appear — code occurrences only.
|
||
*/
|
||
import { strict as assert } from "node:assert";
|
||
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||
import path from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
import { test } from "./harness.mjs";
|
||
|
||
const SRC = path.join(path.dirname(fileURLToPath(new URL(import.meta.url))), "..", "src");
|
||
|
||
const BANNED = [
|
||
"localStorage", "sessionStorage", "indexedDB", "caches", "CacheStorage",
|
||
"document.cookie", "WebApp.DeviceStorage", "WebApp.SecureStorage", "WebApp.CloudStorage",
|
||
];
|
||
|
||
/* path-scrape + comment strip (the plan's contract for this gate) */
|
||
|
||
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"));
|
||
}
|
||
|
||
/* ————— GEO3 — the mechanical gate over every web/src source ————— */
|
||
|
||
test("GEO3 no banned storage token appears in web/src code (comments stripped, all *.ts/*.html)", () => {
|
||
const files = listFiles(SRC, [".ts", ".html"]);
|
||
assert.ok(files.length >= 12, `scanned a real tree (found ${files.length} files)`);
|
||
for (const file of files) {
|
||
const code = codeText(file);
|
||
for (const banned of BANNED) {
|
||
assert.ok(!code.includes(banned), `${path.relative(process.cwd(), file)} references ${banned} — GEO-03 violated`);
|
||
}
|
||
}
|
||
});
|
||
|
||
test("GEO3 the runtime-probe-preceding invariant: no service worker / registration call exists", () => {
|
||
// Vite emits no SW by default; this pins the "and none was added" half (RESEARCH GEO-03 §).
|
||
for (const file of listFiles(SRC, [".ts", ".html"])) {
|
||
const code = codeText(file);
|
||
for (const token of ["serviceWorker.register", "navigator.serviceWorker", "workbox"]) {
|
||
assert.ok(!code.includes(token), `${path.relative(process.cwd(), file)} references ${token} — GEO-03 surface added`);
|
||
}
|
||
}
|
||
});
|
||
|
||
test("GEO3 web/src/session/store.ts assigns nothing to any storage object (the D-03 half)", () => {
|
||
const code = codeText(path.join(SRC, "session", "store.ts"));
|
||
assert.ok(!/Storage\s*\.\s*setItem|Storage\s*\.\s*getItem/.test(code), "store.ts touches a Storage object");
|
||
assert.ok(!/indexedDB\s*\.\s*open/.test(code), "store.ts opens IndexedDB");
|
||
assert.ok(!/document\s*\.\s*cookie\s*=/.test(code), "store.ts writes a cookie");
|
||
assert.ok(!/caches\s*\.\s*open/.test(code), "store.ts opens a CacheStorage");
|
||
// ...and keeps the named TTL constant the client revalidates against (D-03).
|
||
assert.ok(code.includes("SESSION_TTL_SECONDS = 900"), "the 900s TTL mirror by name is present");
|
||
});
|
||
|
||
test("GEO3 web/src/search/ holds no persistence surface and no deep-link crypto", () => {
|
||
// The ONBD-02 tree (04-04): the resolved destination is user context and
|
||
// stays MEMORY-ONLY (GEO-03/D-07), and the deep-link path is a plain
|
||
// public id with NO crypto (D-11). The whole-tree GEO3 gate above already
|
||
// walks this directory; this explicit case pins the new tree + the
|
||
// no-crypto law so a future file there fails HERE, by name.
|
||
const searchDir = path.join(SRC, "search");
|
||
assert.ok(existsSync(searchDir), "web/src/search exists (the ONBD-02 resolver tree)");
|
||
for (const file of listFiles(searchDir, [".ts"])) {
|
||
const code = codeText(file);
|
||
for (const banned of BANNED) {
|
||
assert.ok(
|
||
!code.includes(banned),
|
||
`${path.relative(process.cwd(), file)} references ${banned} — GEO-03 violated (web/src/search must stay memory-only)`,
|
||
);
|
||
}
|
||
for (const token of ["createHmac", "crypto.subtle", "sha256"]) {
|
||
assert.ok(
|
||
!code.includes(token),
|
||
`${path.relative(process.cwd(), file)} references ${token} — D-11 forbids crypto in the deep-link path`,
|
||
);
|
||
}
|
||
}
|
||
});
|