> ## Documentation Index
> Fetch the complete documentation index at: https://schemabrain.mintlify.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> How SchemaBrain is put together — pipeline, contracts, cache, retrieval, cost model, eval, and the known scalability ceilings.

<Frame caption="Three boxes, one trust boundary. The agent on the left, your Postgres on the right, SchemaBrain in the middle — every tool call lands in the audit chain before the parameterized SQL leaves the firewall.">
  <img src="https://mintcdn.com/schemabrain/jJxFsPsdbVtmus2Z/assets/architecture-premium-hero.png?fit=max&auto=format&n=jJxFsPsdbVtmus2Z&q=85&s=8730bb4e417725f34c5b2b8104ed275c" alt="SchemaBrain architecture — the trust boundary between AI agents and production data. Agent connects to SchemaBrain over MCP/stdio; SchemaBrain ships PII-aware, audit-logged, parameterized SQL to Postgres." width="1440" height="1040" data-path="assets/architecture-premium-hero.png" />
</Frame>

<div style={{ display: "flex", justifyContent: "center", margin: "-0.5rem 0 1.5rem" }}>
  <button
    onClick={() => {
  const m = document.getElementById("arch-modal");
  if (!m) return;
  if (m.parentElement !== document.body) document.body.appendChild(m);
  m.style.display = "flex";
  document.body.style.overflow = "hidden";
}}
    style={{
  display: "inline-flex",
  alignItems: "center",
  gap: "0.4rem",
  padding: "0.45rem 0.85rem",
  background: "transparent",
  color: "#3ECF8E",
  border: "1px solid rgba(62, 207, 142, 0.4)",
  borderRadius: "6px",
  fontFamily: "var(--font-mono, ui-monospace, monospace)",
  fontSize: "0.75rem",
  fontWeight: 500,
  cursor: "pointer",
  letterSpacing: "0.02em"
}}
  >
    ▶ Interactive scene (animated 4-mode flow)
  </button>
</div>

The diagram has three load-bearing pieces. **On the left**, the MCP client host (Claude Desktop, Cursor, Windsurf, Claude Code, or any other stdio MCP client) speaks JSON-RPC over stdin/stdout to the SchemaBrain process. The agent sees twelve typed tools — `find_relevant_tables`, `describe_entity`, `get_metric`, and so on — none of which accept arbitrary SQL.

**In the middle**, SchemaBrain resolves each tool call against the local SQLite store (cached schema + entities + metrics + joins), compiles strictly-parameterized read-only SQL, and writes a hash-chained audit row before the query leaves the boundary. The four mechanisms that make this a firewall — [read-only by architecture](mechanism/read-only), [PII taxonomy with compile-time propagation](mechanism/pii-taxonomy), [tamper-evident audit chain](mechanism/audit-chain), and [structured recovery envelopes](mechanism/structured-recovery) — all live inside this box. None of them can be flipped by a prompt.

**On the right**, the agent's effective surface on your Postgres is the parameterized SQL SchemaBrain emits. The session is opened with `default_transaction_read_only=on` and a `NullPool` connection pool, so no session-level flag survives long enough to be exploited and no two tool calls share a connection. The same architecture works against any SQLAlchemy-compatible source; the v0.5 release targets Postgres because that's where the operator demand is.

<div
  id="arch-modal"
  role="dialog"
  aria-modal="true"
  aria-label="Interactive architecture diagram"
  onClick={(e) => {
if (e.target.id === "arch-modal") {
  e.target.style.display = "none";
  document.body.style.overflow = "";
}
}}
  style={{
display: "none",
position: "fixed",
inset: 0,
zIndex: 9999,
background: "rgba(0, 0, 0, 0.96)",
flexDirection: "column",
padding: "16px"
}}
>
  <div
    style={{
  width: "100%",
  maxWidth: "1800px",
  height: "100%",
  margin: "0 auto",
  display: "flex",
  flexDirection: "column",
  border: "1px solid #1E2230",
  borderRadius: "14px",
  background: "#07080B",
  boxShadow: "0 24px 64px rgba(0, 0, 0, 0.6)",
  overflow: "hidden"
}}
  >
    <div
      style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "10px 16px",
    background: "#0F1117",
    borderBottom: "1px solid #1E2230",
    color: "#F5F5F1",
    fontFamily: "var(--font-mono, ui-monospace, monospace)",
    fontSize: "12px",
    letterSpacing: "0.04em"
  }}
    >
      <span style={{ color: "#3ECF8E", fontWeight: 600 }}>
        SchemaBrain · architecture · interactive
      </span>

      <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
        <button onClick={() => {
                        if (!window.__archZoom) window.__archZoom = 1;
                        window.__archZoom = Math.max(0.5, Math.round((window.__archZoom - 0.25) * 100) / 100);
                        const iframe = document.getElementById("arch-iframe");
                        if (iframe) iframe.style.transform = "scale(" + window.__archZoom + ")";
                        const lbl = document.getElementById("arch-zoom-label");
                        if (lbl) lbl.textContent = Math.round(window.__archZoom * 100) + "%";
                      }} aria-label="Zoom out" style={{ width: "28px", height: "28px", background: "rgba(15,17,23,0.85)", color: "#F5F5F1", border: "1px solid #2A2F40", borderRadius: "6px", cursor: "pointer", fontSize: "16px", lineHeight: 1 }}>−</button>
        <span id="arch-zoom-label" style={{ display: "inline-block", minWidth: "44px", textAlign: "center", color: "rgba(245,245,241,0.72)" }}>100%</span>
        <button onClick={() => {
                        if (!window.__archZoom) window.__archZoom = 1;
                        window.__archZoom = Math.min(3, Math.round((window.__archZoom + 0.25) * 100) / 100);
                        const iframe = document.getElementById("arch-iframe");
                        if (iframe) iframe.style.transform = "scale(" + window.__archZoom + ")";
                        const lbl = document.getElementById("arch-zoom-label");
                        if (lbl) lbl.textContent = Math.round(window.__archZoom * 100) + "%";
                      }} aria-label="Zoom in" style={{ width: "28px", height: "28px", background: "rgba(15,17,23,0.85)", color: "#F5F5F1", border: "1px solid #2A2F40", borderRadius: "6px", cursor: "pointer", fontSize: "16px", lineHeight: 1 }}>+</button>
        <button onClick={() => {
                        window.__archZoom = 1;
                        const iframe = document.getElementById("arch-iframe");
                        if (iframe) iframe.style.transform = "scale(1)";
                        const lbl = document.getElementById("arch-zoom-label");
                        if (lbl) lbl.textContent = "100%";
                      }} aria-label="Reset zoom" style={{ padding: "0 10px", height: "28px", background: "rgba(15,17,23,0.85)", color: "rgba(245,245,241,0.72)", border: "1px solid #2A2F40", borderRadius: "6px", cursor: "pointer", fontSize: "11px", fontFamily: "inherit", letterSpacing: "0.04em" }}>Fit</button>
        <button onClick={() => {
                        const m = document.getElementById("arch-modal");
                        if (m) { m.style.display = "none"; document.body.style.overflow = ""; }
                        window.__archZoom = 1;
                        const iframe = document.getElementById("arch-iframe");
                        if (iframe) iframe.style.transform = "scale(1)";
                        const lbl = document.getElementById("arch-zoom-label");
                        if (lbl) lbl.textContent = "100%";
                      }} aria-label="Close" style={{ width: "28px", height: "28px", background: "transparent", color: "#F5F5F1", border: "1px solid #2A2F40", borderRadius: "6px", cursor: "pointer", fontSize: "14px", marginLeft: "8px" }}>✕</button>
      </div>
    </div>

    <div style={{ flex: 1, overflow: "hidden", background: "#07080B", position: "relative" }}>
      <iframe
        id="arch-iframe"
        srcDoc={`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>schemabrain · architecture · premium</title>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIj4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iYiI+PHBhdGggZD0iTTIyIDEyIEMgMjIgOCwgMzAgNiwgMzIgMTAgQyAzNiA2LCA0NCA4LCA0NCAxNCBDIDUwIDEyLCA1NiAxOCwgNTIgMjQgQyA1OCAyOCwgNTYgMzYsIDUwIDM4IEMgNTQgNDQsIDQ4IDUyLCA0MCA1MCBDIDM4IDU2LCAyOCA1NiwgMjYgNTIgQyAxOCA1NCwgMTIgNDgsIDE0IDQyIEMgOCA0MCwgNiAzMiwgMTIgMzAgQyA4IDI0LCAxMiAxNiwgMTggMTggQyAxOCAxNCwgMjAgMTIsIDIyIDEyIFoiPjwvcGF0aD48L2NsaXBQYXRoPgogIDwvZGVmcz4KICA8cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIGZpbGw9IiNGQUZBRjciPjwvcmVjdD4KICA8ZyBjbGlwLXBhdGg9InVybCgjYikiPgogICAgPHJlY3QgeD0iMzIiIHk9IjAiIHdpZHRoPSIzMiIgaGVpZ2h0PSI2NCIgZmlsbD0iIzNFQ0Y4RSI+PC9yZWN0PgogIDwvZz4KICA8cGF0aCBkPSJNMjIgMTIgQyAyMiA4LCAzMCA2LCAzMiAxMCBDIDM2IDYsIDQ0IDgsIDQ0IDE0IEMgNTAgMTIsIDU2IDE4LCA1MiAyNCBDIDU4IDI4LCA1NiAzNiwgNTAgMzggQyA1NCA0NCwgNDggNTIsIDQwIDUwIEMgMzggNTYsIDI4IDU2LCAyNiA1MiBDIDE4IDU0LCAxMiA0OCwgMTQgNDIgQyA4IDQwLCA2IDMyLCAxMiAzMCBDIDggMjQsIDEyIDE2LCAxOCAxOCBDIDE4IDE0LCAyMCAxMiwgMjIgMTIgWiIgc3Ryb2tlPSIjMEEwQTBBIiBzdHJva2Utd2lkdGg9IjIuNiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPgogIDxsaW5lIHgxPSIzMiIgeTE9IjEwIiB4Mj0iMzIiIHkyPSI1MiIgc3Ryb2tlPSIjMEEwQTBBIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9saW5lPgogIDxwYXRoIGQ9Ik0xNCAyMiBDIDIwIDE5LCAyNiAyMiwgMzAgMjEiIHN0cm9rZT0iIzBBMEEwQSIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPgogIDxwYXRoIGQ9Ik0xMiAzMiBDIDE4IDI4LCAyNiAzMywgMzAgMzEiIHN0cm9rZT0iIzBBMEEwQSIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPgogIDxwYXRoIGQ9Ik0xNCA0MiBDIDIwIDM5LCAyNiA0MywgMzAgNDEiIHN0cm9rZT0iIzBBMEEwQSIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSI+PC9wYXRoPgogIDxnIGNsaXAtcGF0aD0idXJsKCNiKSI+CiAgICA8Y2lyY2xlIGN4PSIzNiIgY3k9IjIyIiByPSIxLjgiIGZpbGw9IiNGQUZBRjciPjwvY2lyY2xlPgogICAgPGxpbmUgeDE9IjM5IiB5MT0iMjIiIHgyPSI1MCIgeTI9IjIyIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2xpbmU+CiAgICA8Y2lyY2xlIGN4PSIzNiIgY3k9IjMyIiByPSIxLjgiIGZpbGw9IiNGQUZBRjciPjwvY2lyY2xlPgogICAgPGxpbmUgeDE9IjM5IiB5MT0iMzIiIHgyPSI1MiIgeTI9IjMyIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2xpbmU+CiAgICA8Y2lyY2xlIGN4PSIzNiIgY3k9IjQyIiByPSIxLjgiIGZpbGw9IiNGQUZBRjciPjwvY2lyY2xlPgogICAgPGxpbmUgeDE9IjM5IiB5MT0iNDIiIHgyPSI1MCIgeTI9IjQyIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2xpbmU+CiAgPC9nPgo8L3N2Zz4="/>
<style>
:root {
/* Brand */
--mint: #3DCD8B;
--mint-bright: #4DE69A;
--mint-deep: #1FA86C;
--mint-glow: rgba(61, 205, 139, 0.55);
--mint-tint: rgba(61, 205, 139, 0.08);

/* Cyber palette */
--obsidian: #07080B;
--slate-950: #0A0B10;
--slate-900: #0F1117;
--slate-850: #13151D;
--slate-800: #181B26;
--slate-700: #1E2230;
--slate-600: #2A2F40;

--cyan: #06B6D4;
--cyan-bright: #22D3EE;
--cyan-glow: rgba(34, 211, 238, 0.5);

--red: #EF4444;
--red-bright: #F87171;
--red-glow: rgba(239, 68, 68, 0.55);

--amber: #F5C26B;

/* Type */
--paper: #F5F5F1;
--paper-dim: rgba(245, 245, 241, 0.72);
--paper-dimmer: rgba(245, 245, 241, 0.52);
--paper-faint: rgba(245, 245, 241, 0.32);

--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
background: var(--obsidian);
color: var(--paper);
font-family: var(--sans);
min-height: 100vh;
overflow-x: hidden;
}
body {
background:
  radial-gradient(ellipse 80% 60% at 50% 0%, rgba(61, 205, 139, 0.06), transparent 60%),
  radial-gradient(ellipse 60% 50% at 20% 80%, rgba(6, 182, 212, 0.04), transparent 60%),
  var(--obsidian);
}

/* Stage wrapper — scales 1440-wide scene to fit viewport */
#stage-root {
width: 100vw;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 24px 0 48px;
}
.scene {
width: 1440px;
height: 1040px;
position: relative;
transform-origin: top center;
flex-shrink: 0;
}

/* Background grid */
.grid-bg {
position: absolute; inset: 0;
background-image:
  linear-gradient(rgba(30, 41, 59, 0.35) 1px, transparent 1px),
  linear-gradient(90deg, rgba(30, 41, 59, 0.35) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: radial-gradient(ellipse 85% 75% at 50% 50%, #000 40%, transparent 100%);
pointer-events: none;
}
.grid-bg::after {
content: "";
position: absolute; inset: 0;
background:
  radial-gradient(ellipse 50% 40% at 50% 50%, rgba(61, 205, 139, 0.04), transparent 70%);
}

/* Header */
header.scene-header {
position: relative;
z-index: 10;
padding: 28px 56px 0;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 32px;
}
.brand-mark {
display: flex; align-items: center; gap: 14px;
}
.brand-mark img { width: 36px; height: 36px; display: block; }
.brand-text { display: flex; flex-direction: column; gap: 2px; }
.brand-text .name {
font-family: var(--mono);
font-size: 14px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--paper);
}
.brand-text .tag {
font-family: var(--mono);
font-size: 10.5px;
color: var(--mint);
letter-spacing: 0.08em;
text-transform: uppercase;
}

.header-title {
flex: 1; text-align: center;
}
.header-title .eyebrow {
font-family: var(--mono);
font-size: 10.5px;
color: var(--paper-faint);
letter-spacing: 0.18em;
text-transform: uppercase;
}
.header-title h1 {
font-family: var(--mono);
font-size: 22px;
font-weight: 600;
letter-spacing: -0.01em;
margin: 4px 0 0;
color: var(--paper);
}
.header-title h1 .accent { color: var(--mint); }

.header-meta {
display: flex; gap: 24px; align-items: center;
font-family: var(--mono); font-size: 10.5px; color: var(--paper-faint);
letter-spacing: 0.06em;
}
.header-meta .dot {
display: inline-block; width: 7px; height: 7px; border-radius: 50%;
background: var(--mint);
box-shadow: 0 0 12px var(--mint-glow);
margin-right: 8px;
animation: livePulse 2.2s ease-in-out infinite;
}
@keyframes livePulse {
0%, 100% { opacity: 0.55; }
50% { opacity: 1; }
}

/* Tabs */
.tab-bar {
position: relative;
z-index: 10;
margin: 24px auto 0;
padding: 6px;
display: inline-flex;
gap: 4px;
background: rgba(15, 17, 23, 0.85);
border: 1px solid var(--slate-700);
border-radius: 12px;
backdrop-filter: blur(12px);
box-shadow:
  0 1px 0 rgba(255,255,255,0.04) inset,
  0 8px 32px rgba(0,0,0,0.6);
}
.tab-bar-wrap {
text-align: center;
margin-top: 18px;
}
.tab {
appearance: none;
background: transparent;
border: 1px solid transparent;
border-radius: 8px;
padding: 10px 16px 10px 14px;
color: var(--paper-dim);
font-family: var(--mono);
font-size: 11.5px;
font-weight: 500;
letter-spacing: 0.02em;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 10px;
white-space: nowrap;
transition: color 160ms, background 160ms, border-color 160ms;
}
.tab:hover { color: var(--paper); background: rgba(255,255,255,0.03); }
.tab .num {
font-family: var(--mono);
font-size: 10px;
color: var(--paper-faint);
background: rgba(255,255,255,0.04);
border: 1px solid var(--slate-700);
border-radius: 4px;
padding: 2px 6px;
font-weight: 600;
letter-spacing: 0.04em;
}
.tab.active {
color: var(--paper);
background: linear-gradient(180deg, rgba(61, 205, 139, 0.16), rgba(61, 205, 139, 0.06));
border-color: rgba(61, 205, 139, 0.5);
box-shadow:
  0 0 0 1px rgba(61, 205, 139, 0.18) inset,
  0 0 24px rgba(61, 205, 139, 0.18);
}
.tab.active .num {
color: var(--mint);
background: rgba(61, 205, 139, 0.1);
border-color: rgba(61, 205, 139, 0.4);
}
.tab.pii.active {
background: linear-gradient(180deg, rgba(239, 68, 68, 0.16), rgba(239, 68, 68, 0.06));
border-color: rgba(239, 68, 68, 0.5);
box-shadow:
  0 0 0 1px rgba(239, 68, 68, 0.18) inset,
  0 0 24px rgba(239, 68, 68, 0.18);
}
.tab.pii.active .num { color: var(--red-bright); background: rgba(239,68,68,0.1); border-color: rgba(239,68,68,0.4); }
.tab.cyan.active {
background: linear-gradient(180deg, rgba(6, 182, 212, 0.16), rgba(6, 182, 212, 0.06));
border-color: rgba(6, 182, 212, 0.5);
box-shadow:
  0 0 0 1px rgba(6, 182, 212, 0.18) inset,
  0 0 24px rgba(6, 182, 212, 0.18);
}
.tab.cyan.active .num { color: var(--cyan-bright); background: rgba(6,182,212,0.1); border-color: rgba(6,182,212,0.4); }

/* The stage canvas */
.stage-canvas {
position: relative;
margin: 24px 56px 0;
height: 780px;
}

/* Cards (glass) */
.glass {
position: absolute;
background: linear-gradient(180deg, rgba(19, 21, 29, 0.88), rgba(15, 17, 23, 0.78));
border: 1px solid var(--slate-700);
border-radius: 12px;
backdrop-filter: blur(10px);
box-shadow:
  0 1px 0 rgba(255,255,255,0.04) inset,
  0 24px 48px -24px rgba(0,0,0,0.7),
  0 0 0 1px rgba(255,255,255,0.02) inset;
}
.glass-header {
padding: 12px 14px;
border-bottom: 1px solid rgba(255,255,255,0.05);
display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.glass-header .ttl {
font-family: var(--mono);
font-size: 10.5px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--paper-dim);
}
.glass-header .ttl .sym {
color: var(--mint);
margin-right: 8px;
font-size: 10px;
}
.glass-header .tag-pill {
font-family: var(--mono);
font-size: 9.5px;
padding: 2px 7px;
border-radius: 4px;
border: 1px solid var(--slate-700);
background: rgba(255,255,255,0.02);
color: var(--paper-faint);
letter-spacing: 0.06em;
}
.glass-header .tag-pill.mint { color: var(--mint); border-color: rgba(61,205,139,0.4); background: rgba(61,205,139,0.06); }
.glass-header .tag-pill.cyan { color: var(--cyan-bright); border-color: rgba(6,182,212,0.4); background: rgba(6,182,212,0.06); }
.glass-header .tag-pill.red { color: var(--red-bright); border-color: rgba(239,68,68,0.4); background: rgba(239,68,68,0.06); }
.glass-body { padding: 14px; }

/* Highlight state for active container in current flow */
.glass.lit-mint {
border-color: rgba(61, 205, 139, 0.55);
box-shadow:
  0 0 0 1px rgba(61, 205, 139, 0.22) inset,
  0 0 32px rgba(61, 205, 139, 0.18),
  0 24px 48px -24px rgba(0,0,0,0.7);
}
.glass.lit-red {
border-color: rgba(239, 68, 68, 0.6);
box-shadow:
  0 0 0 1px rgba(239, 68, 68, 0.22) inset,
  0 0 36px rgba(239, 68, 68, 0.25),
  0 24px 48px -24px rgba(0,0,0,0.7);
animation: redBreath 1.6s ease-in-out infinite;
}
@keyframes redBreath {
0%, 100% { box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.22) inset, 0 0 24px rgba(239, 68, 68, 0.18), 0 24px 48px -24px rgba(0,0,0,0.7); }
50%      { box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.32) inset, 0 0 48px rgba(239, 68, 68, 0.35), 0 24px 48px -24px rgba(0,0,0,0.7); }
}
.glass.lit-cyan {
border-color: rgba(6, 182, 212, 0.55);
box-shadow:
  0 0 0 1px rgba(6, 182, 212, 0.22) inset,
  0 0 32px rgba(6, 182, 212, 0.18),
  0 24px 48px -24px rgba(0,0,0,0.7);
}
.glass.dimmed {
opacity: 0.42;
filter: saturate(0.6);
transition: opacity 280ms, filter 280ms;
}
.glass:not(.dimmed) {
transition: opacity 280ms, filter 280ms, border-color 280ms, box-shadow 280ms;
}

/* Trust boundary container */
.trust-boundary {
position: absolute;
border-radius: 18px;
border: 1px solid rgba(61, 205, 139, 0.35);
background: linear-gradient(180deg, rgba(61, 205, 139, 0.025), rgba(15, 17, 23, 0.0));
box-shadow:
  0 0 0 1px rgba(61, 205, 139, 0.12) inset,
  0 0 60px rgba(61, 205, 139, 0.08);
pointer-events: none;
}
.trust-boundary::before {
content: "";
position: absolute;
inset: -1px;
border-radius: 18px;
padding: 1px;
background: linear-gradient(135deg, rgba(61,205,139,0.5), rgba(61,205,139,0.05) 40%, rgba(61,205,139,0.5));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
-webkit-mask-composite: xor;
pointer-events: none;
opacity: 0.6;
}
.trust-label {
position: absolute;
top: -14px;
left: 32px;
background: linear-gradient(180deg, #0E1018, #07080B);
padding: 4px 14px;
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--mint);
display: flex; align-items: center; gap: 10px;
border: 1px solid rgba(61, 205, 139, 0.55);
border-radius: 6px;
z-index: 5;
box-shadow:
  0 0 12px rgba(61, 205, 139, 0.25),
  0 0 0 4px rgba(7, 8, 11, 0.9);
text-shadow: 0 0 8px rgba(61, 205, 139, 0.5);
}
.trust-label .pip {
width: 6px; height: 6px; border-radius: 50%;
background: var(--mint); box-shadow: 0 0 8px var(--mint-glow);
}
.trust-label .right {
color: var(--paper-faint); margin-left: 6px;
letter-spacing: 0.08em;
}
.trust-corner {
position: absolute;
width: 14px; height: 14px;
border-color: var(--mint);
border-style: solid;
border-width: 0;
}
.trust-corner.tl { top: -1px; left: -1px; border-top-width: 2px; border-left-width: 2px; border-top-left-radius: 18px; }
.trust-corner.tr { top: -1px; right: -1px; border-top-width: 2px; border-right-width: 2px; border-top-right-radius: 18px; }
.trust-corner.bl { bottom: -1px; left: -1px; border-bottom-width: 2px; border-left-width: 2px; border-bottom-left-radius: 18px; }
.trust-corner.br { bottom: -1px; right: -1px; border-bottom-width: 2px; border-right-width: 2px; border-bottom-right-radius: 18px; }

/* Module sub-cards inside core */
.module {
background: linear-gradient(180deg, rgba(24, 27, 38, 0.92), rgba(19, 21, 29, 0.88));
border: 1px solid var(--slate-700);
border-radius: 10px;
overflow: hidden;
transition: border-color 240ms, box-shadow 240ms, transform 240ms;
}
.module-header {
padding: 10px 12px;
display: flex; align-items: center; justify-content: space-between;
border-bottom: 1px solid rgba(255,255,255,0.04);
}
.module-header .label {
font-family: var(--mono); font-size: 10px;
color: var(--paper-dim);
letter-spacing: 0.08em; text-transform: uppercase;
font-weight: 600;
display: flex; align-items: center; gap: 8px;
}
.module-header .label .idot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--mint); box-shadow: 0 0 6px var(--mint-glow);
}
.module-header .label .idot.cyan { background: var(--cyan-bright); box-shadow: 0 0 6px var(--cyan-glow); }
.module-header .label .idot.red  { background: var(--red-bright);  box-shadow: 0 0 6px var(--red-glow); }
.module-header .badge {
font-family: var(--mono); font-size: 9px;
color: var(--paper-faint); letter-spacing: 0.06em;
padding: 2px 6px; border-radius: 4px;
background: rgba(255,255,255,0.025); border: 1px solid var(--slate-700);
}
.module-body { padding: 10px 12px; }

.module.lit-mint { border-color: rgba(61, 205, 139, 0.55); box-shadow: 0 0 24px rgba(61, 205, 139, 0.15); }
.module.lit-cyan { border-color: rgba(6, 182, 212, 0.55); box-shadow: 0 0 24px rgba(6, 182, 212, 0.15); }
.module.lit-red  {
border-color: rgba(239, 68, 68, 0.6);
box-shadow: 0 0 28px rgba(239, 68, 68, 0.25);
animation: redBreath 1.6s ease-in-out infinite;
}
.module.dimmed { opacity: 0.45; filter: saturate(0.55); }

/* Tool chip */
.tool-chip {
font-family: var(--mono);
font-size: 10px;
color: var(--paper-dim);
padding: 5px 8px;
background: rgba(255,255,255,0.025);
border: 1px solid var(--slate-700);
border-radius: 5px;
letter-spacing: -0.005em;
display: inline-flex; align-items: center; gap: 6px;
transition: all 200ms;
}
.tool-chip::before {
content: ""; width: 4px; height: 4px; border-radius: 50%;
background: var(--paper-faint);
}
.tool-chip.mint::before { background: var(--mint); box-shadow: 0 0 5px var(--mint-glow); }
.tool-chip.cyan::before { background: var(--cyan-bright); box-shadow: 0 0 5px var(--cyan-glow); }
.tool-chip.active {
color: var(--paper);
background: rgba(61, 205, 139, 0.1);
border-color: rgba(61, 205, 139, 0.5);
box-shadow: 0 0 12px rgba(61, 205, 139, 0.25);
}
.tool-chip.active.cyan {
background: rgba(6, 182, 212, 0.12);
border-color: rgba(6, 182, 212, 0.55);
box-shadow: 0 0 12px rgba(6, 182, 212, 0.25);
}
.tool-group-label {
font-family: var(--mono);
font-size: 9px;
color: var(--paper-faint);
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 6px;
display: flex; align-items: center; gap: 8px;
}
.tool-group-label::after {
content: ""; flex: 1; height: 1px;
background: linear-gradient(90deg, rgba(255,255,255,0.08), transparent);
}

/* MCP client items */
.client-row {
display: flex; align-items: center; justify-content: space-between;
padding: 7px 10px;
background: rgba(255,255,255,0.02);
border: 1px solid var(--slate-700);
border-radius: 6px;
font-family: var(--mono); font-size: 11px;
color: var(--paper-dim);
}
.client-row .lhs { display: flex; align-items: center; gap: 8px; }
.client-row .icon {
width: 14px; height: 14px; border-radius: 3px;
background: linear-gradient(135deg, var(--slate-600), var(--slate-700));
display: flex; align-items: center; justify-content: center;
font-size: 8.5px; color: var(--paper-dim); font-weight: 700;
}
.client-row .pulse-dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--mint); box-shadow: 0 0 8px var(--mint-glow);
opacity: 0.45;
}
.client-row.active .pulse-dot { animation: dotPulse 1.4s ease-in-out infinite; opacity: 1; }
@keyframes dotPulse {
0%, 100% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.4); opacity: 1; }
}
.client-row.active {
background: rgba(61, 205, 139, 0.06);
border-color: rgba(61, 205, 139, 0.35);
color: var(--paper);
}

/* Stdio pipe */
.stdio-pipe {
margin-top: 12px;
border: 1px dashed rgba(61, 205, 139, 0.35);
background: rgba(61, 205, 139, 0.04);
border-radius: 6px;
padding: 8px 10px;
font-family: var(--mono);
font-size: 9.5px;
color: var(--paper-dim);
letter-spacing: 0.06em;
text-transform: uppercase;
display: flex; align-items: center; gap: 8px;
}
.stdio-pipe .pipe-dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--mint); box-shadow: 0 0 8px var(--mint-glow);
}
.stdio-pipe .pipe-tag { color: var(--mint); margin-left: auto; }

/* PII grid */
.pii-grid {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
}
.pii-cell {
font-family: var(--mono); font-size: 9px;
text-align: center;
padding: 5px 4px;
background: rgba(239, 68, 68, 0.04);
border: 1px solid rgba(239, 68, 68, 0.25);
border-radius: 4px;
color: var(--red-bright);
letter-spacing: 0.04em;
text-transform: uppercase;
}
.pii-cell.hit {
background: rgba(239, 68, 68, 0.18);
border-color: rgba(239, 68, 68, 0.7);
color: #fff;
box-shadow: 0 0 12px rgba(239, 68, 68, 0.4);
}

.refusal-envelope {
margin-top: 8px;
background: rgba(239, 68, 68, 0.06);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 6px;
padding: 8px 10px;
font-family: var(--mono);
font-size: 10px;
color: var(--paper-dim);
line-height: 1.55;
white-space: pre;
}
.refusal-envelope .k { color: var(--red-bright); }
.refusal-envelope .s { color: var(--amber); }
.refusal-envelope .c { color: var(--paper-faint); }

/* Vector embedder */
.embed-strip {
display: flex; gap: 3px; padding: 4px 0;
}
.embed-strip .bar {
flex: 1;
height: 24px;
background: linear-gradient(180deg, rgba(6, 182, 212, 0.6), rgba(6, 182, 212, 0.2));
border-radius: 2px;
transform-origin: bottom;
}
.sim-bar {
margin-top: 8px;
display: flex; flex-direction: column; gap: 4px;
}
.sim-row {
display: flex; align-items: center; gap: 8px;
font-family: var(--mono); font-size: 9.5px; color: var(--paper-dim);
}
.sim-row .lbl { width: 84px; color: var(--paper-faint); }
.sim-row .meter {
flex: 1; height: 4px; border-radius: 2px;
background: var(--slate-700); overflow: hidden;
position: relative;
}
.sim-row .meter .fill {
height: 100%;
background: linear-gradient(90deg, var(--cyan), var(--cyan-bright));
box-shadow: 0 0 6px var(--cyan-glow);
}
.sim-row .val { color: var(--cyan-bright); width: 32px; text-align: right; }

/* Audit chain */
.audit-chain {
display: flex; flex-direction: column; gap: 3px;
}
.audit-row {
display: flex; align-items: center; gap: 8px;
padding: 4px 8px;
background: rgba(255,255,255,0.02);
border: 1px solid var(--slate-700);
border-radius: 5px;
font-family: var(--mono); font-size: 9.5px;
color: var(--paper-dim);
transition: background 200ms, border-color 200ms;
}
.audit-row .blk {
width: 16px; height: 16px; border-radius: 3px;
background: linear-gradient(135deg, var(--slate-600), var(--slate-700));
display: flex; align-items: center; justify-content: center;
font-size: 8px; color: var(--paper-dim); font-weight: 700;
flex-shrink: 0;
}
.audit-row .hash {
color: var(--paper-faint);
font-size: 9px; letter-spacing: 0.02em;
flex: 1; overflow: hidden;
white-space: nowrap; text-overflow: ellipsis;
}
.audit-row .check {
color: var(--mint);
font-size: 11px; font-weight: 700;
}
.audit-row.lit {
background: rgba(61, 205, 139, 0.08);
border-color: rgba(61, 205, 139, 0.45);
color: var(--paper);
}
.audit-row.lit .blk {
background: linear-gradient(135deg, var(--mint-deep), var(--mint));
color: #0A0B10;
}
.audit-row.lit .hash { color: var(--mint); }
.audit-verify {
margin-top: 8px;
display: flex; align-items: center; justify-content: space-between;
padding: 6px 10px;
background: rgba(61, 205, 139, 0.08);
border: 1px solid rgba(61, 205, 139, 0.4);
border-radius: 5px;
font-family: var(--mono); font-size: 9px;
color: var(--mint);
letter-spacing: 0.04em; text-transform: uppercase;
white-space: nowrap;
}
.audit-verify .check {
width: 14px; height: 14px; border-radius: 50%;
background: var(--mint); color: #0A0B10;
display: flex; align-items: center; justify-content: center;
font-size: 9px; font-weight: 700;
}

/* Registry store */
.registry-list {
display: flex; flex-direction: column; gap: 4px;
}
.registry-row {
display: flex; align-items: center; justify-content: space-between;
padding: 5px 8px;
background: rgba(255,255,255,0.02);
border: 1px solid var(--slate-700);
border-radius: 4px;
font-family: var(--mono); font-size: 9.5px;
}
.registry-row .lhs { color: var(--paper-dim); display: flex; align-items: center; gap: 8px; }
.registry-row .lhs .swatch {
width: 8px; height: 8px; border-radius: 2px;
background: var(--mint);
}
.registry-row .lhs .swatch.cyan { background: var(--cyan-bright); }
.registry-row .count { color: var(--paper-faint); font-size: 9px; }

/* Database cylinder */
.db-card {
text-align: center;
}
.db-cylinder {
margin: 14px auto 12px;
width: 110px; height: 130px;
position: relative;
transition: filter 300ms;
}
.db-cylinder .disk {
position: absolute;
left: 0; right: 0;
height: 22px;
background: linear-gradient(180deg, #1B2030, #0D1018);
border: 1px solid var(--slate-700);
border-radius: 50%;
}
.db-cylinder .disk.top {
top: 0;
background: linear-gradient(180deg, #2A2F40, #1B2030);
z-index: 3;
}
.db-cylinder .disk.mid {
top: 50px;
z-index: 2;
opacity: 0.7;
}
.db-cylinder .body {
position: absolute;
top: 10px;
bottom: 10px;
left: 0; right: 0;
background: linear-gradient(180deg, #161A26, #0B0E16);
border-left: 1px solid var(--slate-700);
border-right: 1px solid var(--slate-700);
z-index: 1;
}
.db-cylinder .disk.bot {
bottom: 0;
z-index: 1;
}
.db-cylinder .body::after {
content: "PG";
position: absolute;
inset: 0;
display: flex; align-items: center; justify-content: center;
font-family: var(--mono); font-size: 18px; font-weight: 700;
color: var(--mint);
text-shadow: 0 0 12px var(--mint-glow);
letter-spacing: 0.02em;
}
.db-card.lit .db-cylinder { filter: drop-shadow(0 0 16px rgba(61, 205, 139, 0.55)); }
.db-card.dimmed .db-cylinder { filter: saturate(0.4) brightness(0.7); }

.read-only-badge {
display: inline-flex; align-items: center; gap: 6px;
margin: 4px auto;
padding: 6px 10px;
background: rgba(61, 205, 139, 0.06);
border: 1px solid rgba(61, 205, 139, 0.35);
border-radius: 6px;
font-family: var(--mono);
font-size: 9.5px;
color: var(--mint);
letter-spacing: 0.04em;
}
.read-only-badge .lock {
font-size: 11px;
}

/* SVG layers */
svg.connectors {
position: absolute; inset: 0; pointer-events: none;
width: 100%; height: 100%;
}

/* Bottom legend + status */
.scene-footer {
position: relative;
z-index: 10;
padding: 20px 56px 0;
margin-top: 12px;
display: flex; align-items: center; justify-content: space-between;
gap: 24px;
}
.legend-row {
display: flex; gap: 18px; align-items: center;
font-family: var(--mono); font-size: 10px; color: var(--paper-faint);
letter-spacing: 0.06em; text-transform: uppercase;
}
.legend-row .item { display: flex; align-items: center; gap: 6px; }
.legend-row .swatch {
width: 10px; height: 10px; border-radius: 2px;
background: var(--mint); box-shadow: 0 0 8px var(--mint-glow);
}
.legend-row .swatch.cyan { background: var(--cyan-bright); box-shadow: 0 0 8px var(--cyan-glow); }
.legend-row .swatch.red  { background: var(--red-bright);  box-shadow: 0 0 8px var(--red-glow); }
.legend-row .swatch.ghost { background: var(--slate-600); box-shadow: none; }

.controls-mini {
display: flex; gap: 8px; align-items: center;
font-family: var(--mono); font-size: 10.5px;
color: var(--paper-faint);
}
.controls-mini .ctl {
padding: 6px 10px;
background: rgba(15, 17, 23, 0.85);
border: 1px solid var(--slate-700);
border-radius: 6px;
color: var(--paper-dim);
cursor: pointer;
appearance: none;
font-family: var(--mono); font-size: 10.5px;
transition: border-color 160ms, color 160ms, background 160ms;
}
.controls-mini .ctl:hover { color: var(--paper); border-color: var(--mint); }
.controls-mini .ctl.on { color: var(--mint); border-color: rgba(61, 205, 139, 0.4); }

/* Flow caption */
.flow-caption {
position: absolute;
bottom: 0; left: 0; right: 0;
display: flex; gap: 18px; align-items: flex-start;
padding: 16px 20px;
background: linear-gradient(180deg, rgba(15,17,23,0.6), rgba(7,8,11,0.9));
border: 1px solid var(--slate-700);
border-radius: 10px;
backdrop-filter: blur(10px);
z-index: 6;
}
.flow-caption .num {
font-family: var(--mono); font-size: 36px; font-weight: 600;
color: var(--mint); line-height: 1;
letter-spacing: -0.04em;
text-shadow: 0 0 18px rgba(61, 205, 139, 0.35);
}
.flow-caption.red .num { color: var(--red-bright); text-shadow: 0 0 18px rgba(239,68,68,0.4); }
.flow-caption.cyan .num { color: var(--cyan-bright); text-shadow: 0 0 18px rgba(6,182,212,0.4); }
.flow-caption .txt {
flex: 1;
}
.flow-caption .ttl {
font-family: var(--mono); font-size: 12px; font-weight: 600;
color: var(--paper); letter-spacing: 0;
}
.flow-caption .desc {
font-family: var(--sans); font-size: 11.5px;
color: var(--paper-dim); line-height: 1.55;
margin-top: 4px; max-width: 700px;
}
.flow-caption .desc code {
font-family: var(--mono);
background: rgba(61, 205, 139, 0.08);
color: var(--mint);
padding: 0 5px;
border-radius: 3px;
font-size: 10.5px;
}
.flow-caption .step-block {
flex-shrink: 0;
width: 340px;
border-left: 1px solid var(--slate-700);
padding-left: 20px;
display: flex; flex-direction: column; gap: 6px;
}
.flow-caption .step-num {
font-family: var(--mono);
font-size: 10px;
color: var(--mint);
letter-spacing: 0.14em;
text-transform: uppercase;
}
.flow-caption.red .step-num { color: var(--red-bright); }
.flow-caption.cyan .step-num { color: var(--cyan-bright); }
.flow-caption .step-text {
font-family: var(--mono);
font-size: 11.5px;
line-height: 1.5;
color: var(--paper);
letter-spacing: -0.005em;
}

/* Animations */
@keyframes flowDash {
to { stroke-dashoffset: -200; }
}
@keyframes flowDashReverse {
to { stroke-dashoffset: 200; }
}
.conn-active { animation: flowDash 1.4s linear infinite; }
.conn-active-rev { animation: flowDashReverse 1.4s linear infinite; }

/* Pulses traveling along paths use SVG <animateMotion> */
</style>
</head>
<body>

<div id="stage-root">
<div class="scene" id="scene">
<div class="grid-bg"></div>

<header class="scene-header">
  <div class="brand-mark">
    <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0Ij4KICA8ZGVmcz4KICAgIDxjbGlwUGF0aCBpZD0iYnJhaW4iPjxwYXRoIGQ9Ik0yMiAxMiBDIDIyIDgsIDMwIDYsIDMyIDEwIEMgMzYgNiwgNDQgOCwgNDQgMTQgQyA1MCAxMiwgNTYgMTgsIDUyIDI0IEMgNTggMjgsIDU2IDM2LCA1MCAzOCBDIDU0IDQ0LCA0OCA1MiwgNDAgNTAgQyAzOCA1NiwgMjggNTYsIDI2IDUyIEMgMTggNTQsIDEyIDQ4LCAxNCA0MiBDIDggNDAsIDYgMzIsIDEyIDMwIEMgOCAyNCwgMTIgMTYsIDE4IDE4IEMgMTggMTQsIDIwIDEyLCAyMiAxMiBaIj48L3BhdGg+PC9jbGlwUGF0aD4KICA8L2RlZnM+CiAgPGcgY2xpcC1wYXRoPSJ1cmwoI2JyYWluKSI+CiAgICA8cmVjdCB4PSIzMiIgeT0iMCIgd2lkdGg9IjMyIiBoZWlnaHQ9IjY0IiBmaWxsPSIjM0VDRjhFIj48L3JlY3Q+CiAgPC9nPgogIDxwYXRoIGQ9Ik0yMiAxMiBDIDIyIDgsIDMwIDYsIDMyIDEwIEMgMzYgNiwgNDQgOCwgNDQgMTQgQyA1MCAxMiwgNTYgMTgsIDUyIDI0IEMgNTggMjgsIDU2IDM2LCA1MCAzOCBDIDU0IDQ0LCA0OCA1MiwgNDAgNTAgQyAzOCA1NiwgMjggNTYsIDI2IDUyIEMgMTggNTQsIDEyIDQ4LCAxNCA0MiBDIDggNDAsIDYgMzIsIDEyIDMwIEMgOCAyNCwgMTIgMTYsIDE4IDE4IEMgMTggMTQsIDIwIDEyLCAyMiAxMiBaIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIj48L3BhdGg+CiAgPGxpbmUgeDE9IjMyIiB5MT0iMTAiIHgyPSIzMiIgeTI9IjUyIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMS42IiBzdHJva2UtbGluZWNhcD0icm91bmQiPjwvbGluZT4KICA8cGF0aCBkPSJNMTQgMjIgQyAyMCAxOSwgMjYgMjIsIDMwIDIxIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMS40IiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD4KICA8cGF0aCBkPSJNMTIgMzIgQyAxOCAyOCwgMjYgMzMsIDMwIDMxIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMS40IiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD4KICA8cGF0aCBkPSJNMTQgNDIgQyAyMCAzOSwgMjYgNDMsIDMwIDQxIiBzdHJva2U9IiNGQUZBRjciIHN0cm9rZS13aWR0aD0iMS40IiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD4KICA8ZyBjbGlwLXBhdGg9InVybCgjYnJhaW4pIj4KICAgIDxjaXJjbGUgY3g9IjM2IiBjeT0iMjIiIHI9IjEuNCIgZmlsbD0iIzBCMEYxNCI+PC9jaXJjbGU+CiAgICA8bGluZSB4MT0iMzguNSIgeTE9IjIyIiB4Mj0iNTAiIHkyPSIyMiIgc3Ryb2tlPSIjMEIwRjE0IiBzdHJva2Utd2lkdGg9IjEuNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L2xpbmU+CiAgICA8Y2lyY2xlIGN4PSIzNiIgY3k9IjMyIiByPSIxLjQiIGZpbGw9IiMwQjBGMTQiPjwvY2lyY2xlPgogICAgPGxpbmUgeDE9IjM4LjUiIHkxPSIzMiIgeDI9IjUyIiB5Mj0iMzIiIHN0cm9rZT0iIzBCMEYxNCIgc3Ryb2tlLXdpZHRoPSIxLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9saW5lPgogICAgPGNpcmNsZSBjeD0iMzYiIGN5PSI0MiIgcj0iMS40IiBmaWxsPSIjMEIwRjE0Ij48L2NpcmNsZT4KICAgIDxsaW5lIHgxPSIzOC41IiB5MT0iNDIiIHgyPSI1MCIgeTI9IjQyIiBzdHJva2U9IiMwQjBGMTQiIHN0cm9rZS13aWR0aD0iMS42IiBzdHJva2UtbGluZWNhcD0icm91bmQiPjwvbGluZT4KICA8L2c+Cjwvc3ZnPg==" alt="schemabrain"/>
    <div class="brand-text">
      <span class="name">schemabrain</span>
      <span class="tag">SQL Firewall · v0.5</span>
    </div>
  </div>
  <div class="header-title">
    <span class="eyebrow">Architecture · Live System Diagram</span>
    <h1>The trust boundary between <span class="accent">AI agents</span> and <span class="accent">production data.</span></h1>
  </div>
  <div class="header-meta">
    <span><span class="dot"></span>SYSTEM LIVE</span>
    <span>STDIO · JSON-RPC</span>
    <span>READ-ONLY</span>
  </div>
</header>

<div class="tab-bar-wrap">
  <div class="tab-bar" id="tab-bar"></div>
</div>

<div class="stage-canvas" id="stage-canvas"></div>
<div id="react-mount" style="display:none"></div>

<div class="scene-footer">
  <div class="legend-row">
    <span class="item"><span class="swatch"></span>MCP / Safe</span>
    <span class="item"><span class="swatch cyan"></span>Semantic / Embedding</span>
    <span class="item"><span class="swatch red"></span>Blocked / PII</span>
    <span class="item"><span class="swatch ghost"></span>Idle</span>
  </div>
  <div class="controls-mini" id="controls-mini"></div>
</div>
</div>
</div>

<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>

<script type="text/babel">
/* SchemaBrain · Architecture Premium · Module components
Each module is positioned absolutely inside the stage canvas. */

const { useState, useEffect, useMemo } = React;

// ────────────────────────────────────────────────────────────────────
// AGENT card (Column 1)
// ────────────────────────────────────────────────────────────────────
function AgentCard({ mode, beat }) {
const lit = mode !== "indexing" && (beat === 0 || beat === 5 || beat === 6);
const callLabel = useMemo(() => {
if (mode === "pii") return beat >= 5 ? "← refused {pii_blocked}" : "list_entities()";
if (mode === "audit") return "list_metrics()";
return "get_metric('revenue_30d')";
}, [mode, beat]);

return (
<div
  className={\`glass \${lit ? "lit-mint" : ""} \${mode === "indexing" ? "dimmed" : ""}\`}
  style={{ left: 0, top: 160, width: 260, height: 320 }}
>
  <div className="glass-header">
    <span className="ttl"><span className="sym">◆</span>MCP Client Host</span>
    <span className="tag-pill mint">LLM AGENT</span>
  </div>
  <div className="glass-body" style={{ display: "flex", flexDirection: "column", gap: 6 }}>
    <ClientRow icon="C" name="Claude Desktop" active={lit} />
    <ClientRow icon="cc" name="Claude Code" active={false} />
    <ClientRow icon="C" name="Cursor" active={false} />
    <ClientRow icon="Z" name="Zed" active={false} />
    <ClientRow icon="L" name="LangGraph loop" active={false} />

    <div className="stdio-pipe" style={{ marginTop: 12 }}>
      <span className="pipe-dot"></span>
      stdio · jsonrpc · 2.0
      <span className="pipe-tag">▶ {callLabel}</span>
    </div>
  </div>
</div>
);
}

function ClientRow({ icon, name, active }) {
return (
<div className={\`client-row \${active ? "active" : ""}\`}>
  <span className="lhs">
    <span className="icon">{icon}</span>
    {name}
  </span>
  <span className="pulse-dot"></span>
</div>
);
}

// ────────────────────────────────────────────────────────────────────
// CORE TRUST BOUNDARY (Column 2 wrapper)
// ────────────────────────────────────────────────────────────────────
function CoreBoundary({ mode }) {
return (
<div
  className="trust-boundary"
  style={{ left: 304, top: 0, width: 720, height: 660 }}
>
  <span className="trust-corner tl"></span>
  <span className="trust-corner tr"></span>
  <span className="trust-corner bl"></span>
  <span className="trust-corner br"></span>
  <div className="trust-label">
    <span className="pip"></span>
    SCHEMABRAIN · LOCAL CORE
    <span className="right">SQL FIREWALL · SEMANTIC LAYER · process-local</span>
  </div>
</div>
);
}

// ────────────────────────────────────────────────────────────────────
// MCP STDIO SERVER (12 Read-Only Tools)
// ────────────────────────────────────────────────────────────────────
const PHYSICAL_TOOLS = [
"find_relevant_tables", "describe_table", "describe_column",
"suggest_joins", "get_example_queries"
];
const SEMANTIC_TOOLS = [
"list_entities", "list_metrics", "list_joins",
"find_relevant_entities", "describe_entity", "resolve_join", "get_metric"
];

function MCPServerModule({ mode, beat }) {
const lit = mode === "mcp" && (beat === 1 || beat === 4) ||
          mode === "pii"  && beat === 1 ||
          mode === "audit" && beat === 1;
const dimmed = mode === "indexing";

const activeTool = useMemo(() => {
if (mode === "mcp") return beat === 4 ? "get_metric" : "find_relevant_entities";
if (mode === "pii") return "list_entities";
if (mode === "audit") return "list_metrics";
return null;
}, [mode, beat]);

return (
<div
  className={\`module \${lit ? "lit-mint" : ""} \${dimmed ? "dimmed" : ""}\`}
  style={{ position: "absolute", left: 324, top: 24, width: 680, height: 152 }}
>
  <div className="module-header">
    <span className="label"><span className="idot"></span>MCP STDIO SERVER · 12 READ-ONLY TOOLS</span>
    <span className="badge">stdio · jsonrpc 2.0</span>
  </div>
  <div className="module-body" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
    <div>
      <div className="tool-group-label">▸ Physical Layer · 5</div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
        {PHYSICAL_TOOLS.map(t => (
          <span key={t} className={\`tool-chip mint \${activeTool === t ? "active" : ""}\`}>{t}</span>
        ))}
      </div>
    </div>
    <div>
      <div className="tool-group-label">▸ Semantic Layer · 7</div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
        {SEMANTIC_TOOLS.map(t => (
          <span key={t} className={\`tool-chip cyan \${activeTool === t ? "active cyan" : ""}\`}>{t}</span>
        ))}
      </div>
    </div>
  </div>
</div>
);
}

// ────────────────────────────────────────────────────────────────────
// PII CLASSIFIER & REFUSAL ENGINE
// ────────────────────────────────────────────────────────────────────
const PII_CATS = [
{ code: "CCPA",  hit: false }, { code: "GDPR",  hit: true },
{ code: "HIPAA", hit: false }, { code: "PCI",   hit: false },
{ code: "contact", hit: true }, { code: "health",  hit: false },
{ code: "financial", hit: false }, { code: "name", hit: true }
];

function PIIModule({ mode, beat }) {
const lit = mode === "pii" && beat >= 2 && beat <= 5;
const dimmed = mode === "indexing" || mode === "audit";
return (
<div
  className={\`module \${lit ? "lit-red" : ""} \${dimmed ? "dimmed" : ""}\`}
  style={{ position: "absolute", left: 324, top: 192, width: 332, height: 180 }}
>
  <div className="module-header">
    <span className="label">
      <span className={\`idot \${lit ? "red" : ""}\`}></span>
      PII Classifier · Refusal Engine
    </span>
    <span className="badge">firewall</span>
  </div>
  <div className="module-body" style={{ paddingTop: 8 }}>
    <div className="pii-grid">
      {PII_CATS.map((c, i) => (
        <span key={c.code} className={\`pii-cell \${lit && c.hit ? "hit" : ""}\`}>{c.code}</span>
      ))}
    </div>
    <pre className="refusal-envelope" style={{ marginTop: 10, opacity: mode === "pii" && beat >= 4 ? 1 : 0.45, margin: "10px 0 0" }}>
{'{ '}<span className="k">status</span>: <span className="s">'refused'</span>,{'\\n  '}<span className="k">kind</span>: <span className="s">'pii_blocked'</span>,{'\\n  '}<span className="k">recovery</span>: <span className="c">{'{ … }'}</span>{'\\n}'}
    </pre>
  </div>
</div>
);
}

// ────────────────────────────────────────────────────────────────────
// VECTOR EMBEDDER & SIMILARITY ENGINE
// ────────────────────────────────────────────────────────────────────
function EmbedderModule({ mode, beat }) {
const lit = (mode === "mcp" && beat === 2) ||
          (mode === "indexing" && beat === 2);
const dimmed = mode === "audit";

const [bars] = useState(() => Array.from({ length: 28 }, () => 0.3 + Math.random() * 0.7));

const sims = [
{ lbl: "revenue · sales", val: 0.91 },
{ lbl: "user · customer", val: 0.84 },
{ lbl: "order · purchase", val: 0.79 }
];

return (
<div
  className={\`module \${lit ? "lit-cyan" : ""} \${dimmed ? "dimmed" : ""}\`}
  style={{ position: "absolute", left: 672, top: 192, width: 332, height: 180 }}
>
  <div className="module-header">
    <span className="label">
      <span className="idot cyan"></span>
      Local Vector Embedder
    </span>
    <span className="badge">fastembed · 67MB · ONNX</span>
  </div>
  <div className="module-body" style={{ paddingTop: 6 }}>
    <div style={{ fontFamily: "var(--mono)", fontSize: 9.5, color: "var(--paper-faint)", letterSpacing: "0.06em", marginBottom: 4 }}>
      BAAI/bge-small-en-v1.5 · 384-dim
    </div>
    <div className="embed-strip">
      {bars.map((b, i) => (
        <span key={i} className="bar" style={{
          transform: \`scaleY(\${lit ? (0.4 + Math.sin((Date.now()/180 + i*0.5))*0.3 + b*0.5) : b})\`,
          opacity: lit ? 0.95 : 0.6
        }}></span>
      ))}
    </div>
    <div className="sim-bar">
      {sims.map(s => (
        <div className="sim-row" key={s.lbl}>
          <span className="lbl">{s.lbl}</span>
          <span className="meter"><span className="fill" style={{ width: (s.val * 100) + "%" }}></span></span>
          <span className="val">{s.val.toFixed(2)}</span>
        </div>
      ))}
    </div>
  </div>
</div>
);
}

// ────────────────────────────────────────────────────────────────────
// SHA-256 AUDIT LOG (Cryptographic chain)
// ────────────────────────────────────────────────────────────────────
const AUDIT_ROWS = [
{ id: 14, hash: "a1f2 b9c3 4d7e 8f01" },
{ id: 15, hash: "c8d4 e29a 117b 3fa6" },
{ id: 16, hash: "9b34 7e1d a4c8 5f02" },
{ id: 17, hash: "ff90 2d18 c4ab 6e7c" },
{ id: 18, hash: "3a4c 7b81 e9d2 0f15" }
];

function AuditModule({ mode, beat }) {
const lit = (mode === "mcp"   && beat === 5) ||
          (mode === "pii"   && beat === 5) ||
          (mode === "audit" && beat >= 1);
const dimmed = mode === "indexing";

// Which row is currently "being hashed" in audit mode
const activeRow = mode === "audit" ? Math.min(AUDIT_ROWS.length - 1, beat - 1) : -1;
const verifyDone = mode === "audit" && beat >= AUDIT_ROWS.length + 1;

return (
<div
  className={\`module \${lit ? "lit-mint" : ""} \${dimmed ? "dimmed" : ""}\`}
  style={{ position: "absolute", left: 324, top: 388, width: 332, height: 270 }}
>
  <div className="module-header">
    <span className="label"><span className="idot"></span>SHA-256 Audit Chain</span>
    <span className="badge">append-only · tamper-evident</span>
  </div>
  <div className="module-body" style={{ paddingTop: 6 }}>
    <div style={{ fontFamily: "var(--mono)", fontSize: 9, color: "var(--paper-faint)", letterSpacing: "0.04em", marginBottom: 6 }}>
      H(row<sub>N</sub>) = SHA256( data ‖ H(row<sub>N-1</sub>) )
    </div>
    <div className="audit-chain">
      {AUDIT_ROWS.map((r, i) => (
        <div key={r.id} className={\`audit-row \${activeRow >= i || (mode !== "audit" && lit) ? "lit" : ""}\`}>
          <span className="blk">{r.id}</span>
          <span className="hash">0x{r.hash}</span>
          <span className="check">{(activeRow >= i || (mode !== "audit" && lit)) ? "✓" : "·"}</span>
        </div>
      ))}
    </div>
    <div className="audit-verify" style={{ opacity: verifyDone || (mode !== "audit" && lit) ? 1 : 0.35 }}>
      <span>$ schemabrain audit verify</span>
      <span className="check">✓</span>
    </div>
  </div>
</div>
);
}

// ────────────────────────────────────────────────────────────────────
// LOCAL REGISTRY (SQLite)
// ────────────────────────────────────────────────────────────────────
const REGISTRY_ITEMS = [
{ name: "Table Schemas",       count: "142 tables",   tone: "mint" },
{ name: "Semantic Entities",   count: "38 entities",  tone: "cyan" },
{ name: "Canonical Joins",     count: "94 joins",     tone: "mint" },
{ name: "Custom Metrics",      count: "26 metrics",   tone: "mint" },
{ name: "Vector Embeddings",   count: "4 612 vecs",   tone: "cyan" }
];

function RegistryModule({ mode, beat }) {
const lit = (mode === "mcp" && (beat === 2 || beat === 3)) ||
          (mode === "indexing" && beat >= 3);
const dimmed = mode === "audit";

const activeIdx = mode === "indexing" ? Math.min(REGISTRY_ITEMS.length - 1, beat - 3) : -1;

return (
<div
  className={\`module \${lit ? (mode === "indexing" ? "lit-cyan" : "lit-mint") : ""} \${dimmed ? "dimmed" : ""}\`}
  style={{ position: "absolute", left: 672, top: 388, width: 332, height: 270 }}
>
  <div className="module-header">
    <span className="label">
      <span className={\`idot \${mode === "indexing" ? "cyan" : ""}\`}></span>
      Local Registry Store
    </span>
    <span className="badge">sqlite · ./schemabrain.db</span>
  </div>
  <div className="module-body" style={{ paddingTop: 8 }}>
    <div className="registry-list">
      {REGISTRY_ITEMS.map((r, i) => (
        <div key={r.name} className="registry-row" style={{
          borderColor: activeIdx === i ? "rgba(6,182,212,0.55)" : undefined,
          background:  activeIdx === i ? "rgba(6,182,212,0.08)" : undefined,
        }}>
          <span className="lhs">
            <span className={\`swatch \${r.tone}\`}></span>
            {r.name}
          </span>
          <span className="count">{r.count}</span>
        </div>
      ))}
    </div>
    {mode === "indexing" && (
      <div style={{
        marginTop: 8,
        fontFamily: "var(--mono)", fontSize: 9.5,
        color: "var(--cyan-bright)", letterSpacing: "0.06em",
        display: "flex", justifyContent: "space-between", alignItems: "center",
        padding: "5px 8px",
        background: "rgba(6, 182, 212, 0.06)",
        border: "1px dashed rgba(6, 182, 212, 0.4)",
        borderRadius: 4
      }}>
        <span>$ schemabrain index</span>
        <span style={{ color: "var(--paper-faint)" }}>cron · 04:00 UTC</span>
      </div>
    )}
  </div>
</div>
);
}

// ────────────────────────────────────────────────────────────────────
// DATABASE (Column 3)
// ────────────────────────────────────────────────────────────────────
function DatabaseCard({ mode, beat }) {
const lit = mode === "mcp" && (beat === 4 || beat === 5);
const dimmed = mode === "pii" || mode === "audit" || mode === "indexing";

const sqlLine = useMemo(() => {
if (mode === "pii") return "— blocked at firewall —";
if (mode === "indexing") return "SELECT col FROM information_schema";
return "SELECT sum(amount) FROM orders\\nWHERE created_at > $1";
}, [mode]);

return (
<div
  className={\`glass db-card \${lit ? "lit" : ""} \${dimmed ? "dimmed" : ""}\`}
  style={{ left: 1068, top: 160, width: 260, height: 320 }}
>
  <div className="glass-header">
    <span className="ttl"><span className="sym">◆</span>Production Database</span>
    <span className="tag-pill mint">PG / SQLITE</span>
  </div>
  <div className="glass-body" style={{ textAlign: "center", paddingTop: 4 }}>
    <div className="db-cylinder">
      <div className="disk top"></div>
      <div className="body"></div>
      <div className="disk mid"></div>
      <div className="disk bot"></div>
    </div>

    <div className="read-only-badge">
      <span className="lock">⚿</span>
      default_transaction_read_only = ON
    </div>

    <div style={{
      marginTop: 12,
      padding: "8px 10px",
      background: "rgba(0,0,0,0.35)",
      border: "1px dashed rgba(61, 205, 139, 0.3)",
      borderRadius: 5,
      fontFamily: "var(--mono)",
      fontSize: 9.5,
      color: lit ? "var(--mint)" : "var(--paper-dim)",
      textAlign: "left",
      letterSpacing: "0.01em",
      lineHeight: 1.45,
      whiteSpace: "pre-line",
      minHeight: 38
    }}>
      {sqlLine}
    </div>
  </div>
</div>
);
}

Object.assign(window, {
AgentCard, CoreBoundary,
MCPServerModule, PIIModule, EmbedderModule, AuditModule, RegistryModule,
DatabaseCard
});

</script>
<script type="text/babel">
/* SchemaBrain · Architecture Premium · Connector / flow layer
SVG paths between modules + animated particles. */

// All paths use the stage-canvas coordinate system (1328 × 660).
const SEGMENTS = {
// ── happy path
agentToMcp:        { d: "M 260 420 C 290 420, 290 100, 324 100", color: "mint", label: "list_entities()" },
mcpToDb:           { d: "M 1004 100 C 1040 100, 1040 220, 1068 220", color: "mint", label: "parameterized SQL" },
dbToAudit:         { d: "M 1068 360 C 1020 360, 1014 380, 1014 440 C 1014 500, 1000 500, 940 500 L 656 500", color: "mint", label: "exec metadata" },
auditToAgent:      { d: "M 324 500 C 270 500, 280 470, 280 420 C 280 410, 270 420, 260 420", color: "mint", label: "{ ok: true, data: […] }" },

// ── internal (always-visible faint, lit in specific modes)
mcpToPii:          { d: "M 490 176 L 490 192", color: "mint", label: "" },
mcpToEmbedder:     { d: "M 840 176 L 840 192", color: "cyan", label: "resolve" },
embedderToRegistry:{ d: "M 840 372 L 840 388", color: "cyan", label: "" },
piiToAudit:        { d: "M 490 372 L 490 388", color: "red",  label: "" },

// ── pii refusal exit
piiToAgent:        { d: "M 324 282 C 280 282, 280 282, 280 360 C 280 420, 270 420, 260 420", color: "red",  label: "{ pii_blocked }" },

// ── indexing pipeline (offline)
dbToEmbedder:      { d: "M 1068 320 C 1040 320, 1014 282, 1004 282", color: "cyan", label: "read schemas" },
};

// What segments are active for a given (mode, beat) pair.
const FLOW_SCRIPT = {
// (M)CP stdio happy path
mcp: [
/* 0 */ ["agentToMcp"],
/* 1 */ [],
/* 2 */ ["mcpToEmbedder"],
/* 3 */ ["embedderToRegistry"],
/* 4 */ ["mcpToDb"],
/* 5 */ ["dbToAudit"],
/* 6 */ ["auditToAgent"],
],
// (P)II refusal
pii: [
/* 0 */ ["agentToMcp"],
/* 1 */ [],
/* 2 */ ["mcpToPii"],
/* 3 */ [],
/* 4 */ [],
/* 5 */ ["piiToAudit"],
/* 6 */ ["piiToAgent"],
],
// (I)ndexing pipeline (offline cron)
indexing: [
/* 0 */ ["dbToEmbedder"],
/* 1 */ [],
/* 2 */ ["embedderToRegistry"],
/* 3 */ ["embedderToRegistry"],
/* 4 */ [],
/* 5 */ [],
/* 6 */ [],
],
// (A)udit chain
audit: [
/* 0 */ [],
/* 1 */ ["dbToAudit"],
/* 2 */ [],
/* 3 */ [],
/* 4 */ [],
/* 5 */ [],
/* 6 */ ["auditToAgent"],
],
};

function getActive(mode, beat) {
const script = FLOW_SCRIPT[mode] || FLOW_SCRIPT.mcp;
return script[beat] || [];
}

const COLOR_HEX = {
mint: "#3DCD8B",
cyan: "#22D3EE",
red:  "#EF4444",
};
const COLOR_HEX_DIM = {
mint: "rgba(61, 205, 139, 0.38)",
cyan: "rgba(34, 211, 238, 0.38)",
red:  "rgba(239, 68, 68, 0.38)",
};

function Segment({ id, d, color, label, active }) {
const hex = COLOR_HEX[color];
const dim = COLOR_HEX_DIM[color];
const markerId = \`arrow-\${color}\${active ? "-on" : ""}\`;
return (
<g>
  {/* base faint line, always visible */}
  <path
    d={d}
    stroke={active ? hex : dim}
    strokeWidth={active ? 1.5 : 1.2}
    fill="none"
    strokeDasharray={active ? "0" : "4 5"}
    markerEnd={\`url(#\${markerId})\`}
    style={{ transition: "stroke 240ms, stroke-width 240ms" }}
  />
  {active && (
    <>
      {/* flowing dashes over the path */}
      <path
        d={d}
        stroke={hex}
        strokeWidth={2.5}
        fill="none"
        strokeDasharray="6 10"
        strokeLinecap="round"
        style={{
          filter: \`drop-shadow(0 0 6px \${hex})\`,
          animation: "flowDash 1.4s linear infinite",
        }}
      />
      {/* traveling particle */}
      <circle r={4.5} fill={hex} style={{ filter: \`drop-shadow(0 0 8px \${hex})\` }}>
        <animateMotion dur="1.6s" repeatCount="indefinite" path={d} rotate="auto" />
      </circle>
      <circle r={2.5} fill="#fff" style={{ filter: \`drop-shadow(0 0 4px \${hex})\` }}>
        <animateMotion dur="1.6s" repeatCount="indefinite" path={d} rotate="auto" />
      </circle>
    </>
  )}
</g>
);
}

function ArrowMarkers() {
// pre-defined arrow heads for each color (on + dim)
const make = (id, color, opacity) => (
<marker key={id} id={id} viewBox="0 0 10 10" refX="8" refY="5"
        markerWidth="7" markerHeight="7" orient="auto-start-reverse">
  <path d="M 0 0 L 10 5 L 0 10 Z" fill={color} opacity={opacity}/>
</marker>
);
return (
<defs>
  {Object.entries(COLOR_HEX).map(([k, v]) => make(\`arrow-\${k}-on\`, v, 1))}
  {Object.entries(COLOR_HEX).map(([k, v]) => make(\`arrow-\${k}\`,    v, 0.55))}
</defs>
);
}

function PortDots({ mode, beat }) {
const active = getActive(mode, beat);
// little dots at boundary crossings — emphasize the trust boundary
const dots = [
{ x: 304, y: 100, color: "mint", show: active.includes("agentToMcp") },
{ x: 1024, y: 220, color: "mint", show: active.includes("mcpToDb") },
{ x: 1024, y: 360, color: "mint", show: active.includes("dbToAudit") },
{ x: 304, y: 500, color: "mint", show: active.includes("auditToAgent") },
{ x: 304, y: 282, color: "red",  show: active.includes("piiToAgent") },
{ x: 1024, y: 282, color: "cyan", show: active.includes("dbToEmbedder") },
];
return (
<g>
  {dots.map((dot, i) => (
    <g key={i}>
      <circle cx={dot.x} cy={dot.y} r={dot.show ? 6 : 3}
              fill={COLOR_HEX[dot.color]}
              opacity={dot.show ? 1 : 0.35}
              style={{
                filter: dot.show ? \`drop-shadow(0 0 8px \${COLOR_HEX[dot.color]})\` : "none",
                transition: "all 240ms"
              }}/>
      {dot.show && (
        <circle cx={dot.x} cy={dot.y} r={10}
                fill="none" stroke={COLOR_HEX[dot.color]} strokeWidth="1" opacity="0.7">
          <animate attributeName="r" from="6" to="18" dur="1.2s" repeatCount="indefinite"/>
          <animate attributeName="opacity" from="0.7" to="0" dur="1.2s" repeatCount="indefinite"/>
        </circle>
      )}
    </g>
  ))}
</g>
);
}

function ConnectorLayer({ mode, beat }) {
const active = getActive(mode, beat);
return (
<svg viewBox="0 0 1328 660" className="connectors" preserveAspectRatio="none">
  <ArrowMarkers />
  {Object.entries(SEGMENTS).map(([id, seg]) => (
    <Segment
      key={id}
      id={id}
      d={seg.d}
      color={seg.color}
      label={seg.label}
      active={active.includes(id)}
    />
  ))}
  <PortDots mode={mode} beat={beat} />
</svg>
);
}

Object.assign(window, { ConnectorLayer, FLOW_SCRIPT, SEGMENTS });

</script>
<script type="text/babel">
/* SchemaBrain · Architecture Premium · Main app
Tab controller, beat timeline, captions, control bar. */

const { useState, useEffect, useRef } = React;

const MODES = [
{ id: "mcp",      num: "01", label: "12-Tool MCP Stdio Flow",        kind: "mint",
caption: { ttl: "Stdio JSON-RPC over a 12-tool surface",
           desc: "Agent emits an MCP call. SchemaBrain resolves it against the semantic layer, looks up cosine-similar entities in the local vector store, compiles a strictly parameterized read-only SQL, executes it against Postgres, then writes a hash-chained audit row and returns the structured response." } },
{ id: "pii",      num: "02", label: "PII Refusal & Firewall Path",   kind: "pii",
caption: { ttl: "The database is never touched",
           desc: "Same MCP call. The classifier matches the requested columns against CCPA · GDPR · HIPAA · PCI categories — a hit on contact data triggers an immediate refusal. SchemaBrain returns a structured recovery envelope {status: 'refused', kind: 'pii_blocked', recovery: …} and logs the attempt." } },
{ id: "indexing", num: "03", label: "Indexing & Enrichment Pipeline", kind: "cyan",
caption: { ttl: "Local cron · DB Connector → Profiler → Embedder → SQLite",
           desc: "A scheduled job reads schemas through a read-only connection, a regex-based profiler classifies columns, fastembed BAAI/bge-small-en-v1.5 (~67 MB ONNX) generates 384-dim embeddings locally, and everything is written to the single SQLite registry." } },
{ id: "audit",    num: "04", label: "Cryptographic Audit Chain",     kind: "mint",
caption: { ttl: "Tamper-evident SHA-256 chain · append-only",
           desc: "Every tool call is appended as a ledger row hashed against the previous: H(rowₙ) = SHA256(data ‖ H(rowₙ₋₁)). schemabrain audit verify walks the chain and surfaces any tampered row in milliseconds." } },
];

const BEATS_PER_CYCLE = 7;
const BEAT_MS = 1200; // 1.2s per beat → ~8.4s loop

function App() {
const [mode, setMode] = useState("mcp");
const [beat, setBeat] = useState(0);
const [playing, setPlaying] = useState(true);
const beatRef = useRef(beat);
beatRef.current = beat;

// Beat timer
useEffect(() => {
if (!playing) return;
const id = setInterval(() => {
  setBeat(b => (b + 1) % BEATS_PER_CYCLE);
}, BEAT_MS);
return () => clearInterval(id);
}, [playing, mode]);

// Reset beat when mode changes
useEffect(() => { setBeat(0); }, [mode]);

const activeMode = MODES.find(m => m.id === mode) || MODES[0];

return (
<>
  <TabBarPortal mode={mode} setMode={setMode} />
  <StageContents mode={mode} beat={beat} />
  <FlowCaption mode={mode} active={activeMode} beat={beat} />
  <ControlsPortal playing={playing} setPlaying={setPlaying} mode={mode} beat={beat} />
</>
);
}

function TabBarPortal({ mode, setMode }) {
const bar = document.getElementById("tab-bar");
if (!bar) return null;
return ReactDOM.createPortal(
<>
  {MODES.map(m => (
    <button
      key={m.id}
      className={\`tab \${m.kind} \${mode === m.id ? "active" : ""}\`}
      onClick={() => setMode(m.id)}
    >
      <span className="num">{m.num}</span>
      {m.label}
    </button>
  ))}
</>,
bar
);
}

function ControlsPortal({ playing, setPlaying, mode, beat }) {
const el = document.getElementById("controls-mini");
if (!el) return null;
return ReactDOM.createPortal(
<>
  <span style={{ marginRight: 6 }}>BEAT {String(beat + 1).padStart(2, "0")}/{String(BEATS_PER_CYCLE).padStart(2, "0")}</span>
  <button className={\`ctl \${playing ? "on" : ""}\`} onClick={() => setPlaying(p => !p)}>
    {playing ? "⏸ pause" : "▶ play"}
  </button>
</>,
el
);
}

function StageContents({ mode, beat }) {
const root = document.getElementById("stage-canvas");
if (!root) return null;
return ReactDOM.createPortal(
<>
  <ConnectorLayer mode={mode} beat={beat} />
  <CoreBoundary mode={mode} />
  <AgentCard mode={mode} beat={beat} />
  <MCPServerModule mode={mode} beat={beat} />
  <PIIModule mode={mode} beat={beat} />
  <EmbedderModule mode={mode} beat={beat} />
  <AuditModule mode={mode} beat={beat} />
  <RegistryModule mode={mode} beat={beat} />
  <DatabaseCard mode={mode} beat={beat} />
</>,
root
);
}

// Numbered step badges floating along the active connector
function StepLabels({ mode, beat }) {
const labels = STEP_LABELS[mode] || [];
const current = labels[beat];
if (!current) return null;
return (
<div style={{
  position: "absolute",
  left: current.x, top: current.y,
  transform: "translate(-50%, -50%)",
  display: "flex", alignItems: "center", gap: 8,
  background: "rgba(7, 8, 11, 0.92)",
  border: \`1px solid \${current.color}\`,
  borderRadius: 6,
  padding: "5px 10px 5px 5px",
  fontFamily: "var(--mono)",
  fontSize: 10,
  color: "var(--paper)",
  letterSpacing: "0.04em",
  boxShadow: \`0 0 18px \${current.color}66\`,
  whiteSpace: "nowrap",
  zIndex: 5,
  animation: "fadeStep 240ms ease-out"
}}>
  <span style={{
    width: 18, height: 18, borderRadius: 4,
    background: current.color, color: "#0A0B10",
    display: "flex", alignItems: "center", justifyContent: "center",
    fontSize: 10, fontWeight: 700
  }}>{current.n}</span>
  {current.txt}
</div>
);
}

const C_MINT = "#3DCD8B";
const C_CYAN = "#22D3EE";
const C_RED  = "#EF4444";

const STEP_LABELS = {
mcp: [
{ n: "1", txt: "Discovery · stdio jsonrpc", x: 295, y: 240, color: C_MINT },
{ n: "2", txt: "MCP routes to semantic tool", x: 700, y: 60,  color: C_MINT },
{ n: "3", txt: "Cosine lookup · local vectors", x: 840, y: 184, color: C_CYAN },
{ n: "3", txt: "Resolve canonical join", x: 840, y: 380, color: C_CYAN },
{ n: "4", txt: "Compile parameterized SQL", x: 1040, y: 180, color: C_MINT },
{ n: "5", txt: "Log SHA-256 chain row", x: 870, y: 500, color: C_MINT },
{ n: "6", txt: "Return { ok, data }", x: 280, y: 460, color: C_MINT },
],
pii: [
{ n: "1", txt: "Same MCP call inbound", x: 295, y: 240, color: C_MINT },
{ n: "2", txt: "Tool inspects requested columns", x: 660, y: 60, color: C_MINT },
{ n: "3", txt: "Match · contact + GDPR", x: 490, y: 264, color: C_RED },
{ n: "4", txt: "BLOCKED · DB never touched", x: 800, y: 282, color: C_RED },
{ n: "4", txt: "Build recovery envelope", x: 490, y: 320, color: C_RED },
{ n: "5", txt: "Audit the refusal too", x: 490, y: 380, color: C_RED },
{ n: "6", txt: "Return { pii_blocked }", x: 280, y: 360, color: C_RED },
],
indexing: [
{ n: "1", txt: "Cron 04:00 · read schemas", x: 1040, y: 300, color: C_CYAN },
{ n: "2", txt: "Profiler classifies columns", x: 840, y: 282, color: C_CYAN },
{ n: "3", txt: "fastembed → 384-dim vec", x: 840, y: 380, color: C_CYAN },
{ n: "4", txt: "Write to SQLite registry", x: 840, y: 460, color: C_CYAN },
{ n: "4", txt: "Index 26 metrics",        x: 840, y: 500, color: C_CYAN },
{ n: "4", txt: "Index 4 612 embeddings",  x: 840, y: 540, color: C_CYAN },
{ n: "✓", txt: "Index complete",          x: 840, y: 460, color: C_CYAN },
],
audit: [
{ n: "✓", txt: "Audit ledger · 5 rows shown", x: 490, y: 360, color: C_MINT },
{ n: "1", txt: "Row 14 hashed", x: 490, y: 460, color: C_MINT },
{ n: "2", txt: "Row 15 chained · prev hash mixed in", x: 490, y: 478, color: C_MINT },
{ n: "3", txt: "Row 16 chained", x: 490, y: 496, color: C_MINT },
{ n: "4", txt: "Row 17 chained", x: 490, y: 514, color: C_MINT },
{ n: "5", txt: "Row 18 chained · tip of chain", x: 490, y: 532, color: C_MINT },
{ n: "✓", txt: "schemabrain audit verify · 18/18", x: 490, y: 600, color: C_MINT },
],
};

const STEP_TEXT = {
mcp: [
"Discovery · stdio jsonrpc · agent calls find_relevant_entities()",
"MCP router resolves the call against semantic-layer tool surface",
"Cosine-similarity lookup against 4 612 local vectors",
"Resolve canonical join · user → order → order_item",
"Compile strictly-parameterized read-only SQL for Postgres",
"Append SHA-256 audit row · inputs, metadata, timestamp",
"Return { ok: true, data } envelope to the agent",
],
pii: [
"Same MCP call inbound · agent requests list_entities()",
"MCP router inspects the requested columns",
"PII classifier flags contact / GDPR match",
"REFUSED · database is never touched",
"Build structured recovery envelope · { kind: 'pii_blocked' }",
"Audit the refusal too · tamper-evident chain entry",
"Return { pii_blocked, recovery } back to the agent",
],
indexing: [
"Cron 04:00 UTC · read-only connection opens to Postgres",
"Regex-based profiler classifies columns by content shape",
"fastembed BAAI/bge-small-en-v1.5 → 384-dim ONNX vectors",
"Write 142 table schemas to ./schemabrain.db",
"Write 38 semantic entities + 26 metric definitions",
"Write 4 612 vector embeddings · ready for cosine search",
"Index complete · agent traffic resumes against fresh registry",
],
audit: [
"Audit ledger snapshot · last 5 rows of the chain",
"Row 14 hashed · SHA-256 over data + prev hash",
"Row 15 chained · previous hash mixes into next",
"Row 16 chained · any tampering will cascade",
"Row 17 chained · still verifying",
"Row 18 chained · tip of the chain",
"$ schemabrain audit verify · 18/18 ✓ chain intact",
],
};

function FlowCaption({ mode, active, beat }) {
const stage = document.getElementById("stage-canvas");
if (!stage) return null;
const stepTxt = (STEP_TEXT[mode] || [])[beat] || "";
return ReactDOM.createPortal(
<div className={\`flow-caption \${active.kind === "pii" ? "red" : active.kind === "cyan" ? "cyan" : ""}\`}>
  <span className="num">{active.num}</span>
  <span className="txt">
    <span className="ttl">{active.caption.ttl}</span>
    <span className="desc">{active.caption.desc}</span>
  </span>
  <span className="step-block">
    <span className="step-num">STEP {String(beat + 1).padStart(2, "0")}/{String(BEATS_PER_CYCLE).padStart(2, "0")}</span>
    <span className="step-text">{stepTxt}</span>
  </span>
</div>,
stage
);
}

ReactDOM.createRoot(document.getElementById("react-mount")).render(<App />);

</script>

<script>
// Scale the 1440x920 scene to fit viewport on smaller screens.
function fitScene() {
const scene = document.getElementById('scene');
if (!scene) return;
const vw = window.innerWidth;
const scale = Math.min(1, (vw - 16) / 1440);
scene.style.transform = \`scale(\${scale})\`;
document.getElementById('stage-root').style.minHeight = (1040 * scale + 48) + 'px';
}
window.addEventListener('resize', fitScene);
window.addEventListener('load', fitScene);
fitScene();
</script>

</body>
</html>
`}
        scrolling="no"
        style={{ display: "block", width: "100%", height: "100%", border: "none", background: "#07080B", transformOrigin: "top center", transition: "transform 200ms ease" }}
        title="SchemaBrain Architecture — interactive diagram"
        loading="lazy"
      />
    </div>
  </div>
</div>

How SchemaBrain is put together, the contracts the tool layer keeps, and what
"validated" actually means today.

## The pipeline

```mermaid theme={null}
flowchart LR
    DB[(Postgres /<br/>SQLite)] --> Connector
    Connector --> Profiler
    Profiler -->|stats + PII redaction| Enricher
    Enricher -->|Haiku 4.5 / Sonnet 4.6<br/>via prompt cache| Embedder
    Embedder -->|fastembed<br/>BAAI/bge-small-en-v1.5| Store[(SQLite<br/>store)]

    QueryLog[(pg_stat_statements)] -->|schemabrain mine-queries| Miner
    Miner -->|normalized SQL +<br/>PII tagging| Store

    Store --> Tool1[find_relevant_tables]
    Store --> Tool2[describe_table]
    Store --> Tool3[describe_column]
    Store --> Tool4[suggest_joins]
    Store --> Tool5[get_example_queries]

    Tool1 & Tool2 & Tool3 & Tool4 & Tool5 -->|MCP / stdio| Agent[Agent<br/>Claude Desktop /<br/>Anthropic SDK /<br/>custom]
```

<Note>
  The pipeline is single-process and synchronous. No Celery, no Redis, no Qdrant. The store is one SQLite file you can `cp` to back up, `sqlite3` into to inspect, or `rm` to start over.
</Note>

## Cache-aware re-indexing

Re-running `schemabrain index` against an unchanged schema costs **\$0.00** in
LLM calls. Each column has a content-addressable fingerprint:

```
sha256(name | type | nullable | default | position |
       fk_targets | sample_values | sibling_context | prompt_version)
```

On re-index:

| Change                                                | Behavior                                          |
| ----------------------------------------------------- | ------------------------------------------------- |
| Structural unchanged + semantic unchanged             | Keep cached description (no LLM call)             |
| Semantic changed (new sample values, new FKs)         | Re-enrich                                         |
| Structural changed (column added / removed / retyped) | Re-enrich + mark `schema_changed_at`              |
| Object missing                                        | Set `deprecated_at = now()`. Purge after 30 days. |

The cache key includes `prompt_version` so a prompt change correctly
invalidates everything.

## How retrieval works

`find_relevant_tables` runs cosine similarity between the query embedding
and every stored column embedding. Per-table score = MAX cosine across the
table's columns (sparse-relevance heuristic — one highly-aligned column is
strong evidence). Tables with no matches above zero are dropped; ties break
alphabetically by qualified name.

### Weak matches return data, not errors

If we returned an error for "no good match," the agent couldn't tell *what*
the indexed schema contains — and listing what's available is one of the
most useful things the agent does on adversarial questions ("there's no
payments table; here's what we DO have").

Score thresholds are the agent's call, not the tool's. In our testing
(Claude Haiku 4.5 + Sonnet 4.6), the agent correctly judged "0.74 max
score = the search reaching, not a real hit" and answered honestly. A
future `match_quality` enum may land in v1 if smaller models struggle to
reason about raw scores.

## Query-log mining

Schema introspection tells the agent what *exists*; query-log mining tells
it what *runs*. `schemabrain mine-queries` reads `pg_stat_statements`,
normalizes each SQL text, and stores it alongside an observation count,
first/last seen timestamps, sensitivity tag, and PII category set. The
`get_example_queries` MCP tool surfaces those examples per table so the
agent can pattern-match on real usage instead of guessing column shapes
from names alone.

Mining is opt-in and idempotent. Run it once for a snapshot, or on a
schedule for living usage data. Until it has run, `get_example_queries`
returns `status: empty` with a recovery hint pointing the agent at
`describe_table` — the agent never gets a hard failure for an
unpopulated cache.

## Cost model

Per `index` run, with the default Haiku 4.5 + local embeddings:

| Schema size                                                      | Cost        | Time       | Source       |
| ---------------------------------------------------------------- | ----------- | ---------- | ------------ |
| 12 tables / 84 columns (bundled fixture)                         | \~\$0.034   | \~45 sec   | measured     |
| 15 tables / 87 columns (Pagila, partition children deduplicated) | \$0.0299    | 105 sec    | measured     |
| \~50 tables / \~300 columns                                      | \$0.10–0.15 | 5–8 min    | extrapolated |
| \~200 tables / \~1500 columns                                    | \$0.45–0.55 | 30–40 min  | extrapolated |
| \~500 tables / \~5000 columns                                    | \$1.50–2.50 | 90–130 min | extrapolated |

Both cost and time scale near-linearly with **column count**, not table
count: across the two measured anchors, the per-column cost holds at
\~\$0.00035 and per-column time falls between 1.2–1.6 seconds. The
extrapolated rows assume that ratio continues; production-scale anchors
will tighten or revise it.

<Tip>
  Hard cap configurable via `--max-cost` (default \$1.00). Per-agent-query
  cost depends on tool calls and turns; in our testing, typical questions
  cost \~\$0.005 with Haiku.
</Tip>

### Partitioned tables are deduplicated automatically

Declarative partition children share an identical column structure with
their parent, so enriching each one separately is purely wasted work. The
Postgres connector filters them out at `list_tables()` time using
`pg_class.relispartition`. On Pagila this dropped the index from 22
tables / 129 columns to 15 tables / 87 columns — a 34% cost reduction
and a 50% time reduction vs the naive reflection.

`get_table()` stays permissive: if a caller explicitly asks for a
partition child by name, they still get it. Only bulk listing skips them.

## Eval

Bundled 10-question golden set on the e-commerce fixture, with the local
embedding retriever:

| Metric     | Embedding (default) | Keyword (baseline) |
| ---------- | ------------------- | ------------------ |
| Recall\@1  | **0.65**            | 0.60               |
| Recall\@3  | **0.95**            | 0.95               |
| Recall\@10 | **1.00**            | 0.95               |

Reproduce:

```bash theme={null}
schemabrain eval \
  --source "postgresql+psycopg://postgres:local@localhost:5432/postgres" \
  --store-path ./schemabrain.db
```

The eval harness is generic — it scores any `Retriever` Protocol
implementation against any golden set. Bundled with two examples
(`golden_sets/ecommerce.json` — the `--golden` default — and
`golden_sets/saas.json`); bring your own `golden_sets/<your-schema>.json`
for your real schema. The
BIRD Mini-Dev automated benchmark is on the v0 roadmap for cross-comparable
text-to-SQL execution accuracy.

## What's validated

As of v0.6.0, against two reference anchors — the e-commerce
fixture (8 tables / 39 columns) and the Pagila DVD-rental sample
(15 tables / 87 columns after declarative-partition deduplication;
22 / 129 raw). A SaaS demo pack (12 tables / 84 columns) ships
as the default bundled fixture, exercised by the pre-publish smoke — PII
refusal, audit-chain integrity, and refuse-then-pivot recovery over the
headless Anthropic SDK:

* ✅ Indexes Postgres 16 schema with FK-aware introspection (both anchors)
* ✅ Partitioned tables are deduplicated; only the parent is enriched
* ✅ Partition-parent FKs unioned from children (Pagila pattern) so
  declaratively-partitioned tables aren't seen as relationship-less
* ✅ Junction (M:N) tables are detected structurally; descriptions
  explicitly warn that joining through them multiplies result rows;
  `list_joins` synthesises logical bridges across junctions
* ✅ Generates LLM descriptions via Anthropic Claude (Haiku 4.5 default;
  cryptic columns opt into Sonnet 4.6 via `--enable-sonnet`)
* ✅ Local embeddings via `fastembed` (no second API vendor)
* ✅ All 12 MCP tools tested via Claude Desktop AND headless Anthropic SDK,
  on both anchors. The 12: `list_entities`, `list_metrics`, `list_joins`,
  `describe_entity`, `describe_table`, `describe_column`,
  `find_relevant_tables`, `find_relevant_entities`, `suggest_joins`,
  `resolve_join`, `get_example_queries`, `get_metric`
* ✅ Adversarial questions handled honestly ("not in indexed schema" with
  explicit qualifier) — Pagila negative-question test correctly distinguished
  internal `payment_id` from external payment-processor transaction IDs
* ✅ Multi-hop join discovery via `suggest_joins` (Pagila: rental → customer
  → address path returned correctly)
* ✅ Query-log mining via `pg_stat_statements`; `get_example_queries`
  returns observed SQL with PII tagging
* ✅ Cache-aware re-index (\$0 on unchanged schemas)
* ✅ Fresh-machine quickstart works from a stripped shell
* ✅ Continuous integration (lint + unit + integration with a 99% coverage
  gate over the full test suite on `main`)

<Warning>
  **Not yet validated:**

  * Production-scale schemas (\~200+ tables). Extrapolation from the two measured anchors is in the cost-model table above.
  * Snowflake / BigQuery / MySQL connectors (planned for v1)
  * Long-running serve sessions (no known issues, but no soak test yet)
</Warning>

### M:N caveats are surfaced in junction-table descriptions

Junction (M:N association) tables are detected structurally — composite
primary key with all PK columns being FK sources to ≥2 distinct target
tables — and that detection becomes part of the column-enrichment
prompt. The resulting descriptions explicitly state that joining through
the junction multiplies result rows, surfacing the double-counting risk
downstream agents need.

Example, generated on Pagila's `film_category` (composite PK on
`(film_id, category_id)` with FKs to `film` and `category`):

> **film\_id:** *"Identifier for a film in this junction table that
> links films to their categories; joining through this table
> multiplies rows by category count per film"*

> **category\_id:** *"Identifies the category assigned to a film in this
> M:N junction table; joining through multiplies result rows"*

Whether the calling agent surfaces a separate **Caveat:** block in its
final answer depends on the question:

* **When the user asks about caveats** (*"what should I watch out for"*),
  Claude Haiku 4.5 reliably writes an explicit M:N caveat as the first
  numbered item, names the consequence (*"counted in both category
  totals"*), and suggests `DISTINCT` or business-rule clarification as
  the fix. This is gold-standard behavior.
* **When the user just asks for the SQL** with no priming, surfacing
  varies. Across multiple Haiku runs on identical inputs, sometimes
  the agent mentions M:N inline as a parenthetical, sometimes it omits
  it. The variance is downstream of LLM sampling, not SchemaBrain.

Either way, the warning is present in every relevant column description
and is retrievable via `describe_column` / `describe_table`. Agents
performing serious analysis should be prompted to surface them.

Self-referential association tables (both PK columns FK to the same
target) do not currently qualify as junctions under this heuristic;
revisit if real-world schemas ever rely on the pattern.

Independent SQL validation (per [`Setup`](/setup)) remains the right
backstop for production queries.

## Scalability frontier

SchemaBrain has three known architectural ceilings. None trip on today's
real workloads but each will trip on extreme or adversarial inputs. We
publish them up front because an honest "here is where we will hurt" is a
better trust signal than a vague "scales well."

| Ceiling                     | Where it bites             | Trigger                                 | Containment plan                                                                      |
| --------------------------- | -------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- |
| SQLite store                | Indexed-schema size        | ≥ 50,000 columns or > 1 GB store file   | Swap to embedded DuckDB behind the existing `Store` Protocol                          |
| Serial enrichment           | `index` wall-clock         | ≥ 5 hour `index` run on a single source | Add `--workers N` parallel mode; respect Anthropic 429 backoff                        |
| In-process cosine retrieval | `find_relevant_tables` p95 | ≥ 100,000 column embeddings             | Move to an embedded vector index (`hnswlib`) behind the existing `Retriever` Protocol |

Each swap is localised to one implementation file behind a Protocol that
already exists. The migration risk is bounded; the work is not free.

What this looks like in practice: a 200-table production schema is well
inside today's envelope (the cost-model table above extrapolates to
\~\$0.50 and \~30 minutes). A 5,000-table data warehouse will hit one or
two of these ceilings; the operator should expect to migrate to the
swap path and we should expect to do that work.

The detailed ceiling analysis — triggers, swap plan, risk discussion —
lives in an internal memo that is not part of the OSS distribution.

## What's coming after v0.5

The v0.5 release ships the four load-bearing safety mechanisms — [read-only by
architecture](/mechanism/read-only), the [12-category PII taxonomy](/mechanism/pii-taxonomy),
the [hash-chained audit log](/mechanism/audit-chain), and the
[structured recovery envelope](/mechanism/structured-recovery). The roadmap from
here is incremental hardening + scope expansion, not a re-architecture:

* **More database engines** — MySQL and SQLite source support (currently
  Postgres-only). Same firewall posture, broader applicability.
* **EXPLAIN-based cost cap** — extend the current `--statement-timeout-ms` and
  `--max-rows-per-result` guards with a pre-execution cost estimate, so an
  expensive `get_metric` call is refused before the source is touched.
* **Content-aware PII classification** — extend the name-based classifier with
  sample-value inspection. Catches columns where the rule table would match the
  content (e.g. a free-text `notes` column holding the occasional email or SSN)
  but not the column name.
* **HTTPS transport** — alongside the current stdio transport, for hosts that
  can't launch a subprocess (e.g. ChatGPT Custom Connector).

## Next steps

<CardGroup cols={2}>
  <Card title="MCP tool reference" icon="code" href="/reference/mcp-tools/overview">
    Per-tool signatures, response shapes, and refusal contracts.
  </Card>

  <Card title="Read-only by architecture" icon="lock" href="/mechanism/read-only">
    Why writes are impossible at the type level, not the trust level.
  </Card>

  <Card title="Threat model" icon="shield" href="/threat-model">
    Full attack-surface walk-through with code citations.
  </Card>

  <Card title="Charter v1.2" icon="scroll" href="/agent-ux-charter">
    The contract every MCP tool implements — status taxonomy, envelope shape, recovery hints.
  </Card>
</CardGroup>
