# Architecture Overview ## System Context Cairnquire is a self-hosted documentation platform. A single Go binary serves HTTP requests, manages a libsql database, watches a filesystem directory, and synchronizes content with connected web clients. ## Component Diagram ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Client Layer │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ Browser │ │ File Editor │ │ Mobile / PWA │ │ │ │ (Preact SPA) │ │ (VS Code, │ │ (Responsive Web) │ │ │ │ │ │ Vim, etc.) │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ └────────────┬─────────────┘ │ │ │ │ │ │ │ │ HTTP/WS │ File System │ HTTP/WS │ └─────────┼──────────────────┼────────────────────────┼────────────────┘ │ │ │ ┌─────────┼──────────────────┼────────────────────────┼────────────────┐ │ │ │ Go Application Server │ │ │ │ │ │ ┌──────▼──────┐ ┌──────▼──────┐ │ │ │ HTTP Router │ │ File Watcher │ │ │ │ (chi) │ │ (fsnotify) │ │ │ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ ┌──────▼──────────────────▼──────┐ │ │ │ Request Processing │ │ │ │ ┌────────────┐ ┌───────────┐ │ │ │ │ │ Auth Layer │ │ Markdown │ │ │ │ │ │(WebAuthn) │ │ Renderer │ │ │ │ │ └────────────┘ └───────────┘ │ │ │ │ ┌────────────┐ ┌───────────┐ │ │ │ │ │ Sync Engine│ │ Search │ │ │ │ │ │(WebSocket) │ │ (FTS5) │ │ │ │ │ └────────────┘ └───────────┘ │ │ │ │ ┌────────────┐ ┌───────────┐ │ │ │ │ │ Email │ │ File │ │ │ │ │ │ (Postmark) │ │ Store │ │ │ │ │ └────────────┘ └───────────┘ │ │ │ └──────────┬─────────────────────┘ │ │ │ │ │ ┌──────────▼─────────────────────┐ │ │ │ Data Layer │ │ │ │ ┌────────────┐ ┌───────────┐ │ │ │ │ │ libsql │ │ Content- │ │ │ │ │ │ (SQLite) │ │ Addressed │ │ │ │ │ │ │ │ Files │ │ │ │ │ └────────────┘ └───────────┘ │ │ │ └────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────┘ ``` ## Component Descriptions ### 1. HTTP Router (chi) - Serves static assets (CSS, JS, fonts) - Routes API requests to handlers - Serves server-rendered HTML for document pages - Enforces middleware chain: logging, recovery, CORS, CSP, auth ### 2. File Watcher (fsnotify) - Watches configured directory for .md file changes - Debounces rapid changes (1 second) - Computes SHA-256 hash of new/changed files - Triggers sync events to connected clients ### 3. Authentication Layer - WebAuthn/passkey primary (FIDO2 compliant) - Argon2id password fallback - Session cookies: signed, HttpOnly, Secure, SameSite=Strict - RBAC middleware checks permissions on every request ### 4. Markdown Renderer - Goldmark parser with custom extensions: - Wiki-links: `[[Page Name]]` and `[[Page Name|Display Text]]` - Tags: `#tagname` parsed and linked - Admonitions: `> [!NOTE]`, `> [!WARNING]`, etc. - Mermaid diagrams: fenced code blocks with `mermaid` language - Math: `$inline$` and `$$block$$` via KaTeX - Server-side rendering produces semantic HTML - Client-side hydration adds interactivity (folding, copy buttons) ### 5. Sync Engine (WebSocket) - Implements SimpleSync protocol (see spec) - Bidirectional: server pushes changes, clients request/pull - Content-addressed: files identified by SHA-256 hash - Conflict detection: three-way merge or manual resolution - Offline queue: client stores pending changes in IndexedDB ### 6. Search (FTS5) - SQLite FTS5 virtual table indexes document content - Server provides search API for initial load - Client syncs index to IndexedDB for offline search - Index rebuilds incrementally on file changes ### 7. Email (Postmark) - Go adapter using Postmark HTTP API - Plain-text emails only (no HTML) - Threading via Message-ID / In-Reply-To headers - Webhook endpoint receives email replies, converts to comments - Templates: notification, digest, conflict alert ### 8. File Store (Content-Addressed) - Files stored by SHA-256 hash: `store/ab/cd/abcdef1234...` - Immutable: once written, never modified - Deduplication: identical content stored once - Metadata in database maps filenames to current hash - Attachments served with strict content-type validation ## Data Flow: Document Rendering ``` 1. Request: GET /docs/getting-started │ ▼ 2. Auth Middleware: Check session, verify read permission │ ▼ 3. Document Lookup: Query DB for current hash of "getting-started.md" │ ▼ 4. Cache Check: Is rendered HTML in cache? (TTL: 1 minute for dynamic) │ ├── Yes → Serve cached HTML │ └── No → Continue │ ▼ 5. File Read: Read content from `store/ab/cd/...hash...` │ ▼ 6. Markdown Parse: Goldmark with extensions → AST │ ▼ 7. Transform AST: Resolve wiki-links, extract tags, validate admonitions │ ▼ 8. Render HTML: AST → html/template with layout │ ▼ 9. Response: HTML with CSP nonce, Preact hydration marker ``` ## Data Flow: File Sync ``` User saves file on laptop │ ▼ File Watcher detects change │ ▼ Compute SHA-256 hash │ ▼ Store in content-addressed filesystem │ ▼ Update database: filename → new hash │ ▼ Broadcast to WebSocket subscribers │ ▼ Connected clients receive update notification │ ▼ Client requests new content by hash (if not cached) │ ▼ Server sends file content │ ▼ Client verifies hash, updates UI ``` ## Database Schema (Simplified) ```sql -- Documents: current state and metadata CREATE TABLE documents ( id TEXT PRIMARY KEY, path TEXT UNIQUE NOT NULL, -- relative path like "getting-started.md" current_hash TEXT NOT NULL, -- SHA-256 of current content title TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, updated_by TEXT REFERENCES users(id), permission_read TEXT NOT NULL DEFAULT 'public', -- public, authenticated, private permission_write TEXT NOT NULL DEFAULT 'authenticated' ); -- Document versions: immutable history CREATE TABLE document_versions ( id TEXT PRIMARY KEY, document_id TEXT NOT NULL REFERENCES documents(id), hash TEXT NOT NULL, -- content hash previous_hash TEXT, -- for diffing created_at DATETIME NOT NULL, created_by TEXT REFERENCES users(id), change_summary TEXT, -- auto-generated or user-provided signature TEXT -- optional Ed25519 signature ); -- Users: minimal, no PII beyond email CREATE TABLE users ( id TEXT PRIMARY KEY, email TEXT UNIQUE NOT NULL, display_name TEXT, password_hash TEXT, -- NULL if passkey-only passkey_credential_id TEXT, -- WebAuthn credential created_at DATETIME NOT NULL, last_seen_at DATETIME ); -- Sessions: server-side state CREATE TABLE sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id), token_hash TEXT UNIQUE NOT NULL, -- SHA-256 of session token created_at DATETIME NOT NULL, expires_at DATETIME NOT NULL, ip_address TEXT, user_agent TEXT ); -- Comments: linked to specific versions CREATE TABLE comments ( id TEXT PRIMARY KEY, document_id TEXT NOT NULL REFERENCES documents(id), version_hash TEXT NOT NULL, -- which version this comments on parent_id TEXT REFERENCES comments(id), -- threading author_id TEXT NOT NULL REFERENCES users(id), content TEXT NOT NULL, created_at DATETIME NOT NULL, resolved_at DATETIME, resolved_by TEXT REFERENCES users(id) ); -- Notifications: user preferences and queue CREATE TABLE notification_settings ( user_id TEXT PRIMARY KEY REFERENCES users(id), global_enabled BOOLEAN NOT NULL DEFAULT true, digest_mode TEXT CHECK(digest_mode IN ('instant', 'hourly', 'daily')) DEFAULT 'instant', email_enabled BOOLEAN NOT NULL DEFAULT true ); CREATE TABLE document_watchers ( user_id TEXT REFERENCES users(id), document_id TEXT REFERENCES documents(id), folder_path TEXT, -- watch entire folder created_at DATETIME NOT NULL, PRIMARY KEY (user_id, document_id, folder_path) ); -- FTS5 Search Index CREATE VIRTUAL TABLE document_search USING fts5( content, document_id UNINDEXED, tokenize='porter' ); -- Audit Log: everything CREATE TABLE audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, actor_id TEXT REFERENCES users(id), action TEXT NOT NULL, -- read, write, delete, login, logout, sync resource_type TEXT NOT NULL, -- document, user, comment, etc. resource_id TEXT NOT NULL, details TEXT, -- JSON metadata ip_address TEXT ); ``` ## Deployment Architecture ```yaml # docker-compose.yml (simplified) version: "3.8" services: app: build: . ports: - "8080:8080" volumes: - ./data:/data # Database + attachments - ./notes:/notes:ro # Source markdown files (optional) environment: - DB_PATH=/data/db.sqlite - FILESTORE_PATH=/data/files - NOTES_PATH=/notes - POSTMARK_API_KEY=${POSTMARK_API_KEY} - DOMAIN=${DOMAIN} healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"] ``` ## Scaling Considerations This architecture is intentionally single-node: - **SQLite** handles read-heavy workloads excellently; write concurrency managed via WAL mode - **Content-addressed files** are immutable and cache-friendly - **Server-rendered HTML** reduces client compute If horizontal scaling becomes necessary: 1. libsql replication (read replicas) 2. NFS or object storage for content-addressed files 3. Load balancer with sticky sessions for WebSocket These are documented as future enhancements, not current requirements.