Pablan, as it stands

Self-hosted knowledge management for SMEs: a split-screen Markdown editor
whose sections an LLM refines while you write, and RAG question answering
over the documents that result. FastAPI + Postgres/pgvector on the back,
SvelteKit on the front, everything OpenAI-compatible and self-hostable.

Squashed into a single commit; the development history stays local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
This commit is contained in:
ProfessorNova
2026-09-04 09:21:37 +02:00
co-authored by Claude Opus 5
parent 68d3a43191
commit 784b76baf7
346 changed files with 43430 additions and 0 deletions
+381
View File
@@ -0,0 +1,381 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1290 1090" width="1290" height="1090" role="img" aria-label="Pablan data model">
<title>Pablan data model</title>
<style>
:root {
--bg: #fbfaf9;
--surface: #ffffff;
--sunken: #f2f0ed;
--ink: #1a1815;
--muted: #63605a;
--line: #c5bfb6;
--line-soft: #e5e1db;
--accent: #b8770a;
--secondary: #b04a00;
--note: #faf3e6;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f0e0d;
--surface: #1a1817;
--sunken: #232120;
--ink: #efece8;
--muted: #a49d94;
--line: #4a443d;
--line-soft: #332f2b;
--accent: #e6b422;
--secondary: #e08030;
--note: #241f16;
}
}
text { font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; fill: var(--ink); }
.t-title { font-size: 15px; font-weight: 650; }
.t-sub { font-size: 11.5px; fill: var(--muted); }
.t-node { font-size: 12.5px; font-weight: 600; }
.t-body { font-size: 11.5px; }
.t-muted { font-size: 11px; fill: var(--muted); }
.t-edge { font-size: 10.5px; fill: var(--muted); }
.t-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; }
.box { fill: var(--surface); stroke: var(--line); stroke-width: 1.2; }
.box-sunken { fill: var(--sunken); stroke: var(--line-soft); stroke-width: 1; }
.box-accent { fill: var(--surface); stroke: var(--accent); stroke-width: 1.6; }
.note { fill: var(--note); stroke: var(--line-soft); stroke-width: 1; }
.group { fill: none; stroke: var(--line-soft); stroke-width: 1.2;
stroke-dasharray: 5 4; }
.lifeline { stroke: var(--line); stroke-width: 1; stroke-dasharray: 4 4; }
.edge { stroke: var(--line); stroke-width: 1.3; fill: none; }
.edge-accent { stroke: var(--secondary); stroke-width: 1.5; fill: none; }
.edge-soft { stroke: var(--line); stroke-width: 1.1; fill: none;
stroke-dasharray: 5 4; }
.edge-soft-accent { stroke: var(--secondary); stroke-width: 1.3; fill: none;
stroke-dasharray: 5 4; }
.planned { stroke-dasharray: 6 4; opacity: 0.62; }
</style>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="var(--line)"/>
</marker>
<marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="var(--secondary)"/>
</marker>
<marker id="arrow-open" viewBox="0 0 10 10" refX="9" refY="5"
markerWidth="8" markerHeight="8" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9" fill="none" stroke="var(--line)"
stroke-width="1.3"/>
</marker>
<marker id="many" viewBox="0 0 12 12" refX="11" refY="6" markerWidth="11"
markerHeight="11" orient="auto">
<path d="M 1 1 L 11 6 M 1 6 L 11 6 M 1 11 L 11 6" fill="none"
stroke="var(--line)" stroke-width="1.2"/>
</marker>
<marker id="one" viewBox="0 0 12 12" refX="2" refY="6" markerWidth="11"
markerHeight="11" orient="auto">
<path d="M 3 1 L 3 11" fill="none" stroke="var(--line)" stroke-width="1.4"/>
</marker>
</defs>
<rect width="1290" height="1090" fill="var(--bg)"/>
<text x="30" y="32" class="t-title" text-anchor="start">Data model</text>
<text x="30" y="52" class="t-sub" text-anchor="start">Markdown in documents.content_md is the source of truth; chunks are a disposable derivative.</text>
<path d="M 90 167 V 200" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="98" y="187.5" class="t-edge" text-anchor="start">employs</text>
<path d="M 90 356 V 404" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="98" y="384" class="t-edge" text-anchor="start">logs in</text>
<path d="M 130 356 V 520" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="138" y="442" class="t-edge" text-anchor="start">starts</text>
<path d="M 90 625 V 653" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="98" y="643" class="t-edge" text-anchor="start">contains</text>
<path d="M 530 303 V 404" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="538" y="374.5" class="t-edge" text-anchor="start">grants</text>
<path d="M 590 303 V 520" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="598" y="441" class="t-edge" text-anchor="start">audited by</text>
<path d="M 650 303 V 724" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="658" y="543" class="t-edge" text-anchor="start">derived into</text>
<path d="M 410 136 H 422 V 130 H 470" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="462" y="130" class="t-edge" text-anchor="end">owns</text>
<path d="M 410 158 H 434 V 464 H 470" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="462" y="152" class="t-edge" text-anchor="end">granted to</text>
<path d="M 410 284 H 446 V 182 H 470" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="462" y="278" class="t-edge" text-anchor="end">authors</text>
<path d="M 410 328 H 470 V 658 H 470" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="462" y="322" class="t-edge" text-anchor="end">acted</text>
<path d="M 30 306 H 18 V 950 H 470" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text x="26" y="940" class="t-edge" text-anchor="start">is asked / answers</text>
<path d="M 850 270 H 866 V 950 H 850" class="edge" marker-end="url(#many)" marker-start="url(#one)"/>
<text transform="rotate(-90 870 700)" x="870" y="700" class="t-edge" text-anchor="middle">is questioned by</text>
<rect x="30" y="96" width="380" height="71" rx="8" class="box" />
<rect x="30" y="96" width="380" height="28" rx="8" class="box-sunken" />
<text x="42" y="115" class="t-node" text-anchor="start">departments</text>
<text x="42" y="137" class="t-mono" text-anchor="start">id</text>
<text x="182" y="137" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="137" class="t-muted" text-anchor="start">PK</text>
<text x="42" y="154" class="t-mono" text-anchor="start">name</text>
<text x="182" y="154" class="t-mono" text-anchor="start">string</text>
<text x="245" y="154" class="t-muted" text-anchor="start">unique</text>
<rect x="30" y="200" width="380" height="156" rx="8" class="box" />
<rect x="30" y="200" width="380" height="28" rx="8" class="box-sunken" />
<text x="42" y="219" class="t-node" text-anchor="start">users</text>
<text x="42" y="241" class="t-mono" text-anchor="start">id</text>
<text x="182" y="241" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="241" class="t-muted" text-anchor="start">PK</text>
<text x="42" y="258" class="t-mono" text-anchor="start">email</text>
<text x="182" y="258" class="t-mono" text-anchor="start">string</text>
<text x="245" y="258" class="t-muted" text-anchor="start">unique</text>
<text x="42" y="275" class="t-mono" text-anchor="start">name</text>
<text x="182" y="275" class="t-mono" text-anchor="start">string</text>
<text x="42" y="292" class="t-mono" text-anchor="start">role</text>
<text x="182" y="292" class="t-mono" text-anchor="start">string</text>
<text x="245" y="292" class="t-muted" text-anchor="start">member | admin</text>
<text x="42" y="309" class="t-mono" text-anchor="start">locale</text>
<text x="182" y="309" class="t-mono" text-anchor="start">varchar</text>
<text x="245" y="309" class="t-muted" text-anchor="start">de | en, null = browser</text>
<text x="42" y="326" class="t-mono" text-anchor="start">password_hash</text>
<text x="182" y="326" class="t-mono" text-anchor="start">string</text>
<text x="245" y="326" class="t-muted" text-anchor="start">argon2</text>
<text x="42" y="343" class="t-mono" text-anchor="start">department_id</text>
<text x="182" y="343" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="343" class="t-muted" text-anchor="start">FK, null, SET NULL</text>
<rect x="30" y="404" width="380" height="88" rx="8" class="box" />
<rect x="30" y="404" width="380" height="28" rx="8" class="box-sunken" />
<text x="42" y="423" class="t-node" text-anchor="start">auth_sessions</text>
<text x="42" y="445" class="t-mono" text-anchor="start">id</text>
<text x="182" y="445" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="445" class="t-muted" text-anchor="start">PK, = cookie token</text>
<text x="42" y="462" class="t-mono" text-anchor="start">user_id</text>
<text x="182" y="462" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="462" class="t-muted" text-anchor="start">FK, CASCADE</text>
<text x="42" y="479" class="t-mono" text-anchor="start">expires_at</text>
<text x="182" y="479" class="t-mono" text-anchor="start">timestamp</text>
<text x="245" y="479" class="t-muted" text-anchor="start">TTL 14d, configurable</text>
<rect x="30" y="520" width="380" height="105" rx="8" class="box" />
<rect x="30" y="520" width="380" height="28" rx="8" class="box-sunken" />
<text x="42" y="539" class="t-node" text-anchor="start">conversations</text>
<text x="42" y="561" class="t-mono" text-anchor="start">id</text>
<text x="182" y="561" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="561" class="t-muted" text-anchor="start">PK</text>
<text x="42" y="578" class="t-mono" text-anchor="start">mode</text>
<text x="182" y="578" class="t-mono" text-anchor="start">string</text>
<text x="245" y="578" class="t-muted" text-anchor="start">query | insight</text>
<text x="42" y="595" class="t-mono" text-anchor="start">status</text>
<text x="182" y="595" class="t-mono" text-anchor="start">string</text>
<text x="245" y="595" class="t-muted" text-anchor="start">active | completed</text>
<text x="42" y="612" class="t-mono" text-anchor="start">user_id</text>
<text x="182" y="612" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="612" class="t-muted" text-anchor="start">FK, CASCADE</text>
<rect x="30" y="653" width="380" height="122" rx="8" class="box" />
<rect x="30" y="653" width="380" height="28" rx="8" class="box-sunken" />
<text x="42" y="672" class="t-node" text-anchor="start">messages</text>
<text x="42" y="694" class="t-mono" text-anchor="start">id</text>
<text x="182" y="694" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="694" class="t-muted" text-anchor="start">PK</text>
<text x="42" y="711" class="t-mono" text-anchor="start">conversation_id</text>
<text x="182" y="711" class="t-mono" text-anchor="start">uuid</text>
<text x="245" y="711" class="t-muted" text-anchor="start">FK, CASCADE</text>
<text x="42" y="728" class="t-mono" text-anchor="start">role</text>
<text x="182" y="728" class="t-mono" text-anchor="start">string</text>
<text x="245" y="728" class="t-muted" text-anchor="start">user/assistant/system</text>
<text x="42" y="745" class="t-mono" text-anchor="start">content</text>
<text x="182" y="745" class="t-mono" text-anchor="start">text</text>
<text x="42" y="762" class="t-mono" text-anchor="start">meta</text>
<text x="182" y="762" class="t-mono" text-anchor="start">jsonb</text>
<text x="245" y="762" class="t-muted" text-anchor="start">citations, fallback code</text>
<rect x="470" y="96" width="380" height="207" rx="8" class="box" />
<rect x="470" y="96" width="380" height="28" rx="8" class="box-sunken" />
<text x="482" y="115" class="t-node" text-anchor="start">documents</text>
<text x="482" y="137" class="t-mono" text-anchor="start">id</text>
<text x="622" y="137" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="137" class="t-muted" text-anchor="start">PK</text>
<text x="482" y="154" class="t-mono" text-anchor="start">title</text>
<text x="622" y="154" class="t-mono" text-anchor="start">string</text>
<text x="482" y="171" class="t-mono" text-anchor="start">status</text>
<text x="622" y="171" class="t-mono" text-anchor="start">string</text>
<text x="685" y="171" class="t-muted" text-anchor="start">draft | published | archived</text>
<text x="482" y="188" class="t-mono" text-anchor="start">is_builtin</text>
<text x="622" y="188" class="t-mono" text-anchor="start">bool</text>
<text x="685" y="188" class="t-muted" text-anchor="start">shipped help page</text>
<text x="482" y="205" class="t-mono" text-anchor="start">visibility</text>
<text x="622" y="205" class="t-mono" text-anchor="start">string</text>
<text x="685" y="205" class="t-muted" text-anchor="start">public | department |</text>
<text x="685" y="222" class="t-muted" text-anchor="start">restricted</text>
<text x="482" y="239" class="t-mono" text-anchor="start">content_md</text>
<text x="622" y="239" class="t-mono" text-anchor="start">text</text>
<text x="685" y="239" class="t-muted" text-anchor="start">THE source of truth</text>
<text x="482" y="256" class="t-mono" text-anchor="start">meta</text>
<text x="622" y="256" class="t-mono" text-anchor="start">jsonb</text>
<text x="685" y="256" class="t-muted" text-anchor="start">template, context, summary</text>
<text x="482" y="273" class="t-mono" text-anchor="start">author_id</text>
<text x="622" y="273" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="273" class="t-muted" text-anchor="start">FK, null, SET NULL</text>
<text x="482" y="290" class="t-mono" text-anchor="start">department_id</text>
<text x="622" y="290" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="290" class="t-muted" text-anchor="start">FK, null, SET NULL</text>
<rect x="470" y="404" width="380" height="88" rx="8" class="box" />
<rect x="470" y="404" width="380" height="28" rx="8" class="box-sunken" />
<text x="482" y="423" class="t-node" text-anchor="start">doc_permissions</text>
<text x="482" y="445" class="t-mono" text-anchor="start">document_id</text>
<text x="622" y="445" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="445" class="t-muted" text-anchor="start">PK, FK, CASCADE</text>
<text x="482" y="462" class="t-mono" text-anchor="start">department_id</text>
<text x="622" y="462" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="462" class="t-muted" text-anchor="start">PK, FK, CASCADE</text>
<text x="482" y="479" class="t-mono" text-anchor="start">level</text>
<text x="622" y="479" class="t-mono" text-anchor="start">string</text>
<text x="685" y="479" class="t-muted" text-anchor="start">read</text>
<rect x="470" y="520" width="380" height="173" rx="8" class="box" />
<rect x="470" y="520" width="380" height="28" rx="8" class="box-sunken" />
<text x="482" y="539" class="t-node" text-anchor="start">document_events</text>
<text x="482" y="561" class="t-mono" text-anchor="start">id</text>
<text x="622" y="561" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="561" class="t-muted" text-anchor="start">PK</text>
<text x="482" y="578" class="t-mono" text-anchor="start">document_id</text>
<text x="622" y="578" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="578" class="t-muted" text-anchor="start">FK, CASCADE</text>
<text x="482" y="595" class="t-mono" text-anchor="start">actor_id</text>
<text x="622" y="595" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="595" class="t-muted" text-anchor="start">FK, null, SET NULL</text>
<text x="482" y="612" class="t-mono" text-anchor="start">action</text>
<text x="622" y="612" class="t-mono" text-anchor="start">string</text>
<text x="685" y="612" class="t-muted" text-anchor="start">created | edited | published |</text>
<text x="685" y="629" class="t-muted" text-anchor="start">archived | visibility_changed |</text>
<text x="685" y="646" class="t-muted" text-anchor="start">review_requested / _resolved</text>
<text x="482" y="663" class="t-mono" text-anchor="start">content_md</text>
<text x="622" y="663" class="t-mono" text-anchor="start">text</text>
<text x="685" y="663" class="t-muted" text-anchor="start">snapshot AFTER the event</text>
<text x="482" y="680" class="t-mono" text-anchor="start">title / visibility / meta</text>
<text x="685" y="680" class="t-muted" text-anchor="start">snapshot</text>
<rect x="470" y="724" width="380" height="156" rx="8" class="box" />
<rect x="470" y="724" width="380" height="28" rx="8" class="box-sunken" />
<text x="482" y="743" class="t-node" text-anchor="start">chunks</text>
<text x="482" y="765" class="t-mono" text-anchor="start">id</text>
<text x="622" y="765" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="765" class="t-muted" text-anchor="start">PK</text>
<text x="482" y="782" class="t-mono" text-anchor="start">document_id</text>
<text x="622" y="782" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="782" class="t-muted" text-anchor="start">FK, CASCADE</text>
<text x="482" y="799" class="t-mono" text-anchor="start">chunk_index</text>
<text x="622" y="799" class="t-mono" text-anchor="start">int</text>
<text x="685" y="799" class="t-muted" text-anchor="start">unique per document</text>
<text x="482" y="816" class="t-mono" text-anchor="start">content</text>
<text x="622" y="816" class="t-mono" text-anchor="start">text</text>
<text x="482" y="833" class="t-mono" text-anchor="start">embedding</text>
<text x="622" y="833" class="t-mono" text-anchor="start">vector</text>
<text x="685" y="833" class="t-muted" text-anchor="start">1024 bge-m3, HNSW cosine</text>
<text x="482" y="850" class="t-mono" text-anchor="start">tsv</text>
<text x="622" y="850" class="t-mono" text-anchor="start">tsvector</text>
<text x="685" y="850" class="t-muted" text-anchor="start">generated german, GIN</text>
<text x="482" y="867" class="t-mono" text-anchor="start">meta</text>
<text x="622" y="867" class="t-mono" text-anchor="start">jsonb</text>
<text x="685" y="867" class="t-muted" text-anchor="start">heading path, filters</text>
<rect x="470" y="904" width="380" height="156" rx="8" class="box" />
<rect x="470" y="904" width="380" height="28" rx="8" class="box-sunken" />
<text x="482" y="923" class="t-node" text-anchor="start">review_requests</text>
<text x="482" y="945" class="t-mono" text-anchor="start">id</text>
<text x="622" y="945" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="945" class="t-muted" text-anchor="start">PK</text>
<text x="482" y="962" class="t-mono" text-anchor="start">document_id</text>
<text x="622" y="962" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="962" class="t-muted" text-anchor="start">FK, CASCADE</text>
<text x="482" y="979" class="t-mono" text-anchor="start">requester_id</text>
<text x="622" y="979" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="979" class="t-muted" text-anchor="start">FK, null, SET NULL</text>
<text x="482" y="996" class="t-mono" text-anchor="start">reviewer_id</text>
<text x="622" y="996" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="996" class="t-muted" text-anchor="start">FK, null, grants read+edit</text>
<text x="482" y="1013" class="t-mono" text-anchor="start">question</text>
<text x="622" y="1013" class="t-mono" text-anchor="start">text</text>
<text x="685" y="1013" class="t-muted" text-anchor="start">null, "still 14 days?"</text>
<text x="482" y="1030" class="t-mono" text-anchor="start">resolved_at</text>
<text x="622" y="1030" class="t-mono" text-anchor="start">timestamp</text>
<text x="685" y="1030" class="t-muted" text-anchor="start">null while open</text>
<text x="482" y="1047" class="t-mono" text-anchor="start">resolved_by_id</text>
<text x="622" y="1047" class="t-mono" text-anchor="start">uuid</text>
<text x="685" y="1047" class="t-muted" text-anchor="start">FK, null, who checked it</text>
<rect x="890" y="96" width="380" height="105" rx="8" class="box" />
<rect x="890" y="96" width="380" height="28" rx="8" class="box-sunken" />
<text x="902" y="115" class="t-node" text-anchor="start">templates</text>
<text x="902" y="137" class="t-mono" text-anchor="start">id</text>
<text x="1042" y="137" class="t-mono" text-anchor="start">uuid</text>
<text x="1105" y="137" class="t-muted" text-anchor="start">PK</text>
<text x="902" y="154" class="t-mono" text-anchor="start">name</text>
<text x="1042" y="154" class="t-mono" text-anchor="start">string</text>
<text x="902" y="171" class="t-mono" text-anchor="start">version</text>
<text x="1042" y="171" class="t-mono" text-anchor="start">string</text>
<text x="1105" y="171" class="t-muted" text-anchor="start">schema version, e.g. 1.0</text>
<text x="902" y="188" class="t-mono" text-anchor="start">config</text>
<text x="1042" y="188" class="t-mono" text-anchor="start">jsonb</text>
<text x="1105" y="188" class="t-muted" text-anchor="start">authoring template</text>
<rect x="890" y="226" width="380" height="173" rx="8" class="box" />
<rect x="890" y="226" width="380" height="28" rx="8" class="box-sunken" />
<text x="902" y="245" class="t-node" text-anchor="start">llm_settings</text>
<text x="902" y="267" class="t-mono" text-anchor="start">id</text>
<text x="1042" y="267" class="t-mono" text-anchor="start">uuid</text>
<text x="1105" y="267" class="t-muted" text-anchor="start">PK</text>
<text x="902" y="284" class="t-mono" text-anchor="start">role</text>
<text x="1042" y="284" class="t-mono" text-anchor="start">string</text>
<text x="1105" y="284" class="t-muted" text-anchor="start">unique: chat/utility/embed</text>
<text x="902" y="301" class="t-mono" text-anchor="start">base_url</text>
<text x="1042" y="301" class="t-mono" text-anchor="start">string</text>
<text x="1105" y="301" class="t-muted" text-anchor="start">seeded from env at start</text>
<text x="902" y="318" class="t-mono" text-anchor="start">model</text>
<text x="1042" y="318" class="t-mono" text-anchor="start">string</text>
<text x="1105" y="318" class="t-muted" text-anchor="start">seeded from env at start</text>
<text x="902" y="335" class="t-mono" text-anchor="start">api_key</text>
<text x="1042" y="335" class="t-mono" text-anchor="start">text</text>
<text x="1105" y="335" class="t-muted" text-anchor="start">plaintext, never returned</text>
<text x="902" y="352" class="t-mono" text-anchor="start">base_url_from_env</text>
<text x="1042" y="352" class="t-mono" text-anchor="start">bool</text>
<text x="1105" y="352" class="t-muted" text-anchor="start">provenance, drives reset</text>
<text x="902" y="369" class="t-mono" text-anchor="start">model_from_env</text>
<text x="1042" y="369" class="t-mono" text-anchor="start">bool</text>
<text x="1105" y="369" class="t-muted" text-anchor="start">provenance, drives reset</text>
<text x="902" y="386" class="t-mono" text-anchor="start">api_key_from_env</text>
<text x="1042" y="386" class="t-mono" text-anchor="start">bool</text>
<text x="1105" y="386" class="t-muted" text-anchor="start">provenance, drives reset</text>
<rect x="890" y="430" width="380" height="88" rx="8" class="box" />
<rect x="890" y="430" width="380" height="28" rx="8" class="box-sunken" />
<text x="902" y="449" class="t-node" text-anchor="start">prompt_settings</text>
<text x="902" y="471" class="t-mono" text-anchor="start">id</text>
<text x="1042" y="471" class="t-mono" text-anchor="start">uuid</text>
<text x="1105" y="471" class="t-muted" text-anchor="start">PK</text>
<text x="902" y="488" class="t-mono" text-anchor="start">key</text>
<text x="1042" y="488" class="t-mono" text-anchor="start">string</text>
<text x="1105" y="488" class="t-muted" text-anchor="start">unique, e.g. query_system</text>
<text x="902" y="505" class="t-mono" text-anchor="start">content</text>
<text x="1042" y="505" class="t-mono" text-anchor="start">text</text>
<text x="1105" y="505" class="t-muted" text-anchor="start">full replacement text</text>
<rect x="890" y="530" width="380" height="156" rx="8" class="box" />
<rect x="890" y="530" width="380" height="28" rx="8" class="box-sunken" />
<text x="902" y="549" class="t-node" text-anchor="start">jobs</text>
<text x="902" y="571" class="t-mono" text-anchor="start">id</text>
<text x="1042" y="571" class="t-mono" text-anchor="start">uuid</text>
<text x="1105" y="571" class="t-muted" text-anchor="start">PK</text>
<text x="902" y="588" class="t-mono" text-anchor="start">type</text>
<text x="1042" y="588" class="t-mono" text-anchor="start">string</text>
<text x="902" y="605" class="t-mono" text-anchor="start">payload</text>
<text x="1042" y="605" class="t-mono" text-anchor="start">jsonb</text>
<text x="902" y="622" class="t-mono" text-anchor="start">status</text>
<text x="1042" y="622" class="t-mono" text-anchor="start">string</text>
<text x="1105" y="622" class="t-muted" text-anchor="start">pending/running/done/failed</text>
<text x="902" y="639" class="t-mono" text-anchor="start">run_after</text>
<text x="1042" y="639" class="t-mono" text-anchor="start">timestamp</text>
<text x="1105" y="639" class="t-muted" text-anchor="start">idx (status, run_after)</text>
<text x="902" y="656" class="t-mono" text-anchor="start">attempts</text>
<text x="1042" y="656" class="t-mono" text-anchor="start">int</text>
<text x="902" y="673" class="t-mono" text-anchor="start">last_error</text>
<text x="1042" y="673" class="t-mono" text-anchor="start">text</text>
<text x="1105" y="673" class="t-muted" text-anchor="start">sanitized, never content</text>
<rect x="890" y="904" width="380" height="156" rx="8" class="note" />
<text x="904" y="926" class="t-node" text-anchor="start">Reading the lines</text>
<text x="904" y="945" class="t-muted" text-anchor="start">A bar is the one side, a fan the many side.</text>
<text x="904" y="958" class="t-muted" text-anchor="start">Optionality is on the column instead: a note</text>
<text x="904" y="971" class="t-muted" text-anchor="start">saying null (and SET NULL vs CASCADE) is what</text>
<text x="904" y="984" class="t-muted" text-anchor="start">actually matters when a row is deleted.</text>
<text x="904" y="997" class="t-muted" text-anchor="start">templates, llm_settings, prompt_settings and</text>
<text x="904" y="1010" class="t-muted" text-anchor="start">jobs stand alone: config and work, not knowledge.</text>
<text x="904" y="1029" class="t-muted" text-anchor="start">An open review_request is not a document status:</text>
<text x="904" y="1042" class="t-muted" text-anchor="start">it can hang on a draft or on one published long ago,</text>
<text x="904" y="1055" class="t-muted" text-anchor="start">and it marks the document everywhere it appears.</text>
</svg>

After

Width:  |  Height:  |  Size: 26 KiB