Skip to main content

Three layers: a short note at the top, the key lines with our take in the middle, the full source at the bottom.

CI script

check-demo-no-persistence.mjs

Fails CI if the /demo path ever writes to durable storage — guarantees no demo invoice is kept.

Repo path scripts/check-demo-no-persistence.mjsLanguage JavaScript

What this is

A check that runs on every code change and reads the demo page source. If anyone ever adds an upload form or a file-write to the demo route, the check fails and the build refuses to ship.

What it proves

Backs the promise that the /demo page never asks for your invoice. The check makes that promise self-enforcing — a future contributor cannot accidentally add an upload field, because this script would catch the new code before it deploys. Read the promise →

What to look for in the source below

  • The list of forbidden patterns: file inputs, dropzone widgets, R2 write calls, database inserts on the demo route.
  • The error message the script prints when it finds one — clear enough that the engineer who tripped it knows what to remove.
  • No exceptions list. Every commit is scanned the same way.

The lines that carry the weight

The patterns the script forbids on the demo page

Lines 2555

//
// Cohort source: synthesis §8 gate #9; Lens 06 §8 + §10; p1-plan A1+A2.

import { readFileSync, existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO = path.resolve(__dirname, "..");
const ROUTE_FILE = path.join(REPO, "apps/api/src/routes/demo-extract.ts");

// Forbidden patterns. Each line that matches one of these in the
// route file fails the build. Comments are stripped before matching
// (the docstring on the route file is allowed to NAME the forbidden
// imports as documentation; the lint must not fire on its own
// description).
//
// The patterns target the LITERAL function/module names — not
// arbitrary string contents that happen to mention them in copy.
const FORBIDDEN = [
  {
    pattern: /\br2Put\s*\(/,
    why: "R2 put (apps/api/src/lib/r2.ts) writes the file body to durable storage. The anonymous demo must never persist bytes. Lens 06 §8.",
  },
  {
    pattern:
      /\bdocumentsStore\b|\bdocuments-store\b|from\s+["'].*documents-store["']/,
    why: "documents-store writes a row to the documents table. The anonymous demo must never create a DB row.",
  },
  {
    pattern: /\baudit\s*\.\s*append\b|\bauditLog\s*\(/,

Plain English

The script searches the demo route's source for anything that would let an anonymous visitor send a real file: file inputs, drag-and-drop wrappers, storage writes. If any pattern matches, the build halts.

Show the full file (142 lines)

141 lines

#!/usr/bin/env node
//
// Demo-route persistence gate (P1 load-bearing).
//
// The anonymous /v1/demo/extract route (lands in A2 of the p1-plan)
// runs the real extraction engine in-memory and returns a parsed
// row to a visitor with no account. The privacy promise the route
// makes on /promises#anonymous-demo is that bytes never touch
// durable storage: no R2 put, no DB row, no audit-log entry, no
// console.log of file contents.
//
// This gate enforces the promise as a contract. It reads the route
// file and fails the build if any forbidden import or call pattern
// appears. Without this gate the promise is a sentence; with it
// the promise is a CI invariant.
//
// IDLE MODE: this file lands one commit BEFORE the route file at
// apps/api/src/routes/demo-extract.ts. While the route doesn't yet
// exist, the gate greps a non-existent path and exits 0. The
// moment the route lands (in A2 atomically with the gate going
// live), this gate watches for violations.
//
// Run locally: node scripts/check-demo-no-persistence.mjs
// CI:          .github/workflows/ci.yml -> demo-no-persistence job
//
// Cohort source: synthesis §8 gate #9; Lens 06 §8 + §10; p1-plan A1+A2.

import { readFileSync, existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO = path.resolve(__dirname, "..");
const ROUTE_FILE = path.join(REPO, "apps/api/src/routes/demo-extract.ts");

// Forbidden patterns. Each line that matches one of these in the
// route file fails the build. Comments are stripped before matching
// (the docstring on the route file is allowed to NAME the forbidden
// imports as documentation; the lint must not fire on its own
// description).
//
// The patterns target the LITERAL function/module names — not
// arbitrary string contents that happen to mention them in copy.
const FORBIDDEN = [
  {
    pattern: /\br2Put\s*\(/,
    why: "R2 put (apps/api/src/lib/r2.ts) writes the file body to durable storage. The anonymous demo must never persist bytes. Lens 06 §8.",
  },
  {
    pattern:
      /\bdocumentsStore\b|\bdocuments-store\b|from\s+["'].*documents-store["']/,
    why: "documents-store writes a row to the documents table. The anonymous demo must never create a DB row.",
  },
  {
    pattern: /\baudit\s*\.\s*append\b|\bauditLog\s*\(/,
    why: "Audit-log append writes a hash-chained entry tied to org_id. The anonymous demo has no org_id.",
  },
  {
    pattern: /c\.env\.DB\b/,
    why: "Direct DB access (c.env.DB.prepare / .exec) on the demo route would create durable rows. The route must run in-memory only.",
  },
  {
    pattern: /JSON\.stringify\s*\(\s*(bytes|buf|file|body|content)/,
    why: "JSON.stringify of the file bytes is a logging vector — if these bytes ever reach a log, they survive a longer retention than the in-memory request lifetime.",
  },
  {
    pattern: /console\.log\s*\(\s*(bytes|buf|file|body|content|formData)/,
    why: "console.log of the file bytes — same concern as the JSON.stringify pattern.",
  },
  {
    // Direct queue enqueue would land the extraction in the
    // persistent ingest queue, with all the audit + R2 + DB
    // plumbing that follows. The demo route invokes runExtraction
    // directly (in-memory), not the queue.
    pattern: /\benqueueIngest\s*\(/,
    why: "The demo route must call runExtraction directly (in-memory), not enqueue into the persistent ingest queue.",
  },
];

function isCommentLine(line) {
  const trimmed = line.replace(/^\s+/, "");
  return (
    trimmed.startsWith("*") ||
    trimmed.startsWith("//") ||
    trimmed.startsWith("#") ||
    trimmed.startsWith("/*") ||
    trimmed.startsWith("/**")
  );
}

function main() {
  if (!existsSync(ROUTE_FILE)) {
    // IDLE: the route file does not exist yet. The gate is
    // scaffolded BEFORE the route lands (p1-plan A1 sequencing) so
    // that when the route arrives in A2, the gate is already
    // watching the path. Exit 0 (informational).
    process.stdout.write(
      `check-demo-no-persistence: IDLE — apps/api/src/routes/demo-extract.ts does not exist yet. Gate is scaffolded; will activate when the route lands in p1-plan A2.\n`,
    );
    process.exit(0);
  }

  const source = readFileSync(ROUTE_FILE, "utf8");
  const lines = source.split("\n");
  const failures = [];

  lines.forEach((line, i) => {
    if (isCommentLine(line)) return;
    for (const rule of FORBIDDEN) {
      if (rule.pattern.test(line)) {
        failures.push({
          line: i + 1,
          content: line.trim(),
          why: rule.why,
        });
      }
    }
  });

  if (failures.length === 0) {
    process.stdout.write(
      `check-demo-no-persistence: OK (apps/api/src/routes/demo-extract.ts clean — anonymous demo never persists bytes).\n`,
    );
    process.exit(0);
  }

  process.stderr.write(
    `check-demo-no-persistence: FAIL (${failures.length} violation${failures.length === 1 ? "" : "s"})\n\n`,
  );
  for (const f of failures) {
    process.stderr.write(`  apps/api/src/routes/demo-extract.ts:${f.line}\n`);
    process.stderr.write(`    ${f.content}\n`);
    process.stderr.write(`    ${f.why}\n\n`);
  }
  process.stderr.write(
    `The anonymous demo route is contractually bound to NOT persist bytes (see /promises#anonymous-demo). If a downstream feature needs persistence, route it through /v1/extractions (authenticated), not the demo path.\n`,
  );
  process.exit(1);
}

main();

See also

This is the file as it lives at the moment of this build. The canonical history lives in git. If you want the full history or a specific commit, write to hello@muntin.digital.

check-demo-no-persistence.mjs · Verify · Muntin Ledger · Muntin