Files
arah2/bot/test/privacy.test.mjs
araharah-executor 0cfa0bde38 test(06-03): re-pin GE4/GE7 to the live service + behavioral envelope suite + PG1 storage list (RED)
- GE1/GE2/GE3 UNCHANGED in meaning (TTL 7200, 4+6 field freeze, inline snapshot)
- GE4 re-pinned: types-only token ban -> the module must EXPORT mint/get/deleteGuidanceSession
- GE7 re-pinned: total consumer ban -> named-imports-only of the service surface
- GE5/GE6 stand (exactly one kv namespace; no Env guidance field)
- GS1-GS5 behavioral: TTL'd mint, roundtrip, 7-class defensive null read,
  UUID key-guard without KV touch (V5), active delete (D-14 server half)
- PG1 storageModules now covers kv/guidance-sessions.ts (same-line TTL law)
2026-10-24 13:02:28 +08:00

63 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
/*
* privacy.test.mjs — PRIV-02 architecture gates (zero-KV, source-introspection).
* ------------------------------------------------------------------------------
* The storage promise is pinned by reading the source itself (D-17:
* "verifiable by reading the code's storage calls"):
*
* PG1 every `.put(` line in the TTL storage module carries
* `expirationTtl` on the same line — no TTL-free write path can ship
* PG2 the storage module exposes no persistence-shaped exports
* (no saveProfile / storeLocation / saveLocation / storeHistory)
*
* src/webhook.ts is included when it exists (plan 03 builds it) so the
* gate tightens automatically as call sites grow — future write paths
* fail this suite unless they, too, are TTL-carried.
*/
import { strict as assert } from "node:assert";
import { readFileSync, existsSync } 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");
const storageModules = ["kv/sessions.ts", "kv/location-pin.ts", "kv/guidance-sessions.ts"]
.map((rel) => path.join(SRC, ...rel.split("/")));
const webhookModule = path.join(SRC, "webhook.ts");
if (existsSync(webhookModule)) storageModules.push(webhookModule);
/* ————— PG1 — same-line expirationTtl gate on every write path ————— */
test("PG1 every .put( in storage sources carries expirationTtl on the same line", () => {
for (const file of storageModules) {
const lines = readFileSync(file, "utf8").split("\n");
lines.forEach((line, i) => {
if (line.includes(".put(")) {
assert.ok(
line.includes("expirationTtl"),
`${file}:${i + 1} — KV write without expirationTtl (PRIV-02 gate): ${line.trim()}`,
);
const ttl = line.match(/expirationTtl:\s*([A-Za-z0-9_]+)/)?.[1];
assert.ok(
ttl && /^[A-Z_]{2,}$/.test(ttl),
`${file}:${i + 1} — expirationTtl must reference a named TTL constant, not an inline literal`,
);
}
});
}
});
/* ————— PG2 — no persistent-shaped surface ————— */
test("PG2 storage module exports nothing persistence-shaped (no profiles, no saved locations)", () => {
for (const file of storageModules) {
const source = readFileSync(file, "utf8");
assert.doesNotMatch(
source,
/\b(?:saveProfile|storeProfile|saveLocation|storeLocation|saveHistory|storeHistory|putHistory|setLocation)\b/,
`${file} — persistence-shaped export found (D-08 claim surface violation)`,
);
}
});