Files
arah2/web/test/build-artifact.test.mjs
T
araharah-executor 747eeb7b34 test(04-04): the BA7/BA8 drift gates + the search-tree GEO3 pin + the destination-preload capture leg
- 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
2026-10-24 01:38:24 +08:00

177 lines
7.8 KiB
JavaScript

#!/usr/bin/env node
/*
* build-artifact.test.mjs — the D-14 build-artifact gate (T-03-20, T-03-21).
* ------------------------------------------------------------------------------
* Reads web/dist (the PRODUCTION build only — the [assets] binding serves
* exactly this directory) and proves the shipped bundle is fully
* self-contained and free of the Mock-Telegram installer:
*
* BA1 no emitted .js/.css file references a third-party CDN
* (cdn.tailwindcss.com, fonts.googleapis.com, fonts.gstatic.com,
* unpkg.com, cdn.jsdelivr.net) — T-03-20: a CDN slip would leak
* user navigation requests to third parties
* BA2 at least one self-hosted .woff2 font was copied through by Vite's
* url() resolution of @araharah/theme/dist/rams.css
* BA3 a hashed MapLibre worker chunk exists under web/dist/assets
* BA4 the built JS carries the inlined Rams style ("liberty-rams" or
* "ne2_shaded" — build-time import, never a runtime fetch)
* BA5 the PRODUCTION bundle contains "installMockWebApp"/"__ARAHARAH_MOCK__"
* in NO emitted file — .js AND .html (web/index.html is the ONE entry
* shared by both builds, so an unguarded runner-page profile block
* would ship the mock and the fetch stubs as inline HTML that a
* JS-only scan would miss entirely). Scans web/dist ONLY: the harness
* build emits to web/dist-harness/ and is deliberately NOT clean —
* BA5 pointed there would invert the gate it exists to enforce.
* BA6 no emitted HTML carries the runner page's /api/session or
* /api/location-fallback fetch stubs (same dead-code-elimination proof)
* BA7 the shipped JS carries EVERY gazetteer id from the committed
* dataset (search/src/data/kl-gazetteer.json) — the WEB half of the
* bot/web id-set parity gate (T-04-39): a stale web bundle ships
* without an id the bot just deep-linked, and the webview could
* never resolve it
* BA8 the shipped JS carries NO deep-link crypto (createHmac,
* crypto.subtle, sha256, HMAC() — the deep link is a plain public id
* (D-11, T-04-40). Scans the JS ASSETS only, never the HTML: the
* OFFICIAL Telegram SDK script is loaded EXTERNALLY from
* telegram.org (not bundled) and may contain crypto internals of
* its own.
*
* If web/dist does not exist, every case fails with a build-first message.
*/
import { strict as assert } from "node:assert";
import { readdirSync, readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { test } from "./harness.mjs";
import gazetteer from "../../search/src/data/kl-gazetteer.json" with { type: "json" };
const WEB = path.join(fileURLToPath(new URL(import.meta.url)), "..", "..");
const DIST = join(WEB, "dist");
const DIST_ASSETS = join(DIST, "assets");
const CDN_HOSTS = [
"cdn.tailwindcss.com",
"fonts.googleapis.com",
"fonts.gstatic.com",
"unpkg.com",
"cdn.jsdelivr.net",
];
function claimBuildFirst() {
return new Error(
"web/dist does not exist — run `npm run build -w theme && npm run build -w web` before this suite",
);
}
/* walk web/dist recursively — DirEnt-based, no dependency */
function walk(dir) {
const out = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) out.push(...walk(full));
else out.push(full);
}
return out;
}
function readIfExists(file) {
return existsSync(file) ? readFileSync(file, "utf8") : null;
}
const allFiles = existsSync(DIST) ? walk(DIST) : [];
const jsFiles = allFiles.filter((f) => f.endsWith(".js"));
const cssFiles = allFiles.filter((f) => f.endsWith(".css"));
const htmlFiles = allFiles.filter((f) => f.endsWith(".html"));
test("BA1 no emitted .js/.css file references a third-party CDN (D-14)", () => {
assert.ok(existsSync(DIST), claimBuildFirst().message);
for (const file of [...jsFiles, ...cssFiles]) {
const content = readFileSync(file, "utf8");
for (const host of CDN_HOSTS) {
assert.ok(
!content.includes(host),
`${file} references CDN host ${host} (D-14 forbids CDN fallbacks)`,
);
}
}
});
test("BA2 web/dist carries self-hosted woff2 fonts copied at build time", () => {
assert.ok(existsSync(DIST), claimBuildFirst().message);
const woff2 = allFiles.filter((f) => f.endsWith(".woff2"));
assert.ok(woff2.length >= 1, `no .woff2 under web/dist — apply BA2's contingency (public/fonts copy or postbuild script), never a CDN link`);
});
test("BA3 web/dist/assets contains a hashed MapLibre worker chunk", () => {
assert.ok(existsSync(DIST_ASSETS), claimBuildFirst().message);
const worker = readdirSync(DIST_ASSETS).filter((f) => f.endsWith(".js") && /worker/i.test(f));
assert.ok(worker.length >= 1, `no MapLibre worker chunk in web/dist/assets (found: ${readdirSync(DIST_ASSETS).join(", ")})`);
});
test("BA4 built JS carries the inlined Rams style JSON (liberty-rams/ne2_shaded)", () => {
assert.ok(existsSync(DIST_ASSETS), claimBuildFirst().message);
const bundle = jsFiles
.filter((f) => f.startsWith(DIST_ASSETS))
.map((f) => readFileSync(f, "utf8"))
.join("");
const hasStyle = bundle.includes("liberty-rams") || bundle.includes("ne2_shaded");
assert.ok(hasStyle, "inlined Rams style marker absent from the JS bundle — style JSON was not inlined at build time");
});
test("BA5 production bundle carries NO Mock-Telegram installer in ANY emitted file (.js + .html)", () => {
assert.ok(existsSync(DIST), claimBuildFirst().message);
const jsAssets = jsFiles.map((f) => readFileSync(f, "utf8")).join("");
const allHtml = htmlFiles.map((f) => readFileSync(f, "utf8")).join("");
for (const token of ["installMockWebApp", "__ARAHARAH_MOCK__"]) {
assert.ok(
!jsAssets.includes(token),
`installMockWebApp gate: "${token}" found in a web/dist JS asset — the mock survived tree-shaking`,
);
assert.ok(
!allHtml.includes(token),
`installMockWebApp gate: "${token}" found in an emitted web/dist HTML (${htmlFiles.join(", ")}) — the runner page's profile block shipped`,
);
}
});
test("BA6 production HTML carries NO runner-page fetch stub endpoints", () => {
assert.ok(existsSync(DIST), claimBuildFirst().message);
const allHtml = htmlFiles.map((f) => readFileSync(f, "utf8")).join("");
for (const endpoint of ["/api/session", "/api/location-fallback"]) {
assert.ok(
!allHtml.includes(endpoint),
`runner-page fetch stub "${endpoint}" present in emitted web/dist HTML — an unguarded profile block shipped`,
);
}
});
test("BA7 the shipped bundle carries EVERY gazetteer id (the bot/web id-set parity gate, T-04-39)", () => {
assert.ok(existsSync(DIST_ASSETS), claimBuildFirst().message);
const bundle = jsFiles
.filter((f) => f.startsWith(DIST_ASSETS))
.map((f) => readFileSync(f, "utf8"))
.join("");
assert.ok(gazetteer.entries.length >= 30, `scanned a real dataset (found ${gazetteer.entries.length} entries)`);
for (const entry of gazetteer.entries) {
assert.ok(
bundle.includes(entry.id),
`gazetteer id "${entry.id}" is MISSING from the shipped web bundle — a stale bundle the webview cannot resolve against (T-04-39); ids are never renamed, only added`,
);
}
});
test("BA8 the shipped bundle carries NO deep-link crypto (D-11, T-04-40)", () => {
assert.ok(existsSync(DIST_ASSETS), claimBuildFirst().message);
const bundle = jsFiles
.filter((f) => f.startsWith(DIST_ASSETS))
.map((f) => readFileSync(f, "utf8"))
.join("");
for (const token of ["createHmac", "crypto.subtle", "sha256", "HMAC("]) {
assert.ok(
!bundle.includes(token),
`"${token}" found in a web/dist JS asset — the deep link is a PLAIN public id, no crypto anywhere in the path (D-11)`,
);
}
});