#!/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"].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)`, ); } });