Domain Model: pal-e-docs
Domain Model: pal-e-docs
The knowledge platform's entity model. Notes hold metadata and legacy rendered HTML; blocks are the real content graph (heading/paragraph/list/table/code/mermaid). Tags, projects, boards, and board items provide cross-cutting classification and workflow. This is the what of pal-e-docs — the when (data flow) and where (deployment) live in sibling arch notes.
Diagram
Components
| Component | Purpose | Notes |
|---|---|---|
| <strong>notes</strong> | Page-level metadata + legacy rendered HTML | SQLAlchemy model <code>Note</code>. <code>html_content</code> kept for back-compat; authoritative content lives in <code>blocks</code>. Indexes: PK, unique on <code>slug</code>. No index on <code>note_type</code> (opportunity for partial index — see ticket pal-e-api#252). |
| <strong>blocks</strong> | Atomic structural content units (heading, paragraph, list, table, code, mermaid) | SQLAlchemy model <code>Block</code>. Embedding column is <code>halfvec(2560)</code> with HNSW index for semantic search. Mermaid blocks are intentionally skipped by the embedding worker (diagram source is not semantically meaningful). Unique <code>(note_id, anchor_id)</code> enables surgical <code>get_section</code> reads. |
| <strong>projects</strong> | Top-level grouping for notes, repos, and boards | SQLAlchemy model <code>Project</code>. Every project optionally has a <code>page_note_id</code> pointing at its project-page note. One board per project. |
| <strong>repos</strong> | Forgejo/GitHub repository records associated with a project | SQLAlchemy model <code>Repo</code>. Platform + URL + role metadata. |
| <strong>tags</strong> | Cross-cutting classification of notes (active, sop, template, convention, etc.) | M:N via <code>note_tags</code>. Case-sensitive unique name. |
| <strong>note_links</strong> | Directed graph of note-to-note references | Source → Target edges. Used by link maintenance and orphan detection. |
| <strong>note_revisions</strong> | Full-content history of every note edit | Snapshots <code>html_content</code> per edit. Revision number monotonic per note. Enables rollback and audit. |
| <strong>compiled_pages</strong> | Rendered HTML cache with TOC JSON | One row per note. Invalidated on block changes via <code>content_hash</code>. Serves the read-heavy public browsing path. |
| <strong>boards</strong> | Kanban board per project | One-to-one with project. Slug-addressable. |
| <strong>board_items</strong> | Kanban tickets (issue-backed or note-backed) | Dual-addressing: <code>forgejo_issue_url</code> for issue items, <code>board_note_id</code>/<code>note_slug</code> for note-backed items (plan/phase/repo/project). Labels carry story:X, arch:Y, type:Z traceability triangle. |
| <strong>users</strong> | Authenticated superusers of the API | Email + hashed password + is_approved. Keycloak JWT is the actual runtime auth path; this table is the local user directory. |
Key Decisions
- Blocks are the source of truth, not html_content. The legacy
notes.html_contentcolumn is preserved for back-compat and for the compiled-page cache, but block-first access (get_note_toc→get_section) is the cheap path that agents use. Rationale: token efficiency — one section is ~500 tokens, the full note is ~5000. - Embeddings live on blocks, not notes. Atomic semantic units give better retrieval (a single procedure paragraph inside a 10KB SOP is findable). Mermaid blocks are explicitly skipped — diagram syntax is not embedding-friendly, and the
blocks_embedding_triggerqueue filters them out. - board_items dual-addresses notes and issues. A board item is either note-backed (plan, phase, repo, project, todo) or issue-backed (forgejo_issue_url). This preserves the kanban-over-plans migration: old phase notes still work, new tickets flow through Forgejo. The
item_typeenum enforces which address field is required. - note_type is a varchar, not an enum. Extensibility: new note types (board, project-page, architecture, skill, agent) were added without migrations. The trade-off is no DB-level validation — hooks enforce correctness at write time (see
check-note-template.sh). - Tags are M:N, labels are comma-separated strings. Tags are note-level classification (normalized, queryable). Labels are board-item-level traceability (denormalized for display speed). They serve different query patterns and should not be unified.
- No index on
block_typeornote_type. Current scale (23K blocks, 900 notes) makes seq-scans <5ms so it hasn't mattered. Partial indexes on first-class query paths (block_type='mermaid',note_type='architecture') are scoped in ticket pal-e-api#252 — motivation is semantic self-documentation of query paths, not performance.
Related
project-pal-e-docs— the project this diagram describestemplate-architecture— the triplet model prescribing this notearch-dataflow-pal-e-docs— TODO, the when sibling (sequenceDiagram of MCP → API → DB round-trips)arch-deployment-pal-e-docs— TODO, the where sibling (graph TB of k3s pods, CNPG, ingress)convention-architecture-ids— howarch:labels on board items map to Components table rows