Every AVIAN and Renkara team produces finished documents that need to reach real readers: architecture decisions, ADRs, audits, runbooks, patent-adjacent design docs, one-off AI-generated artifacts. Before Lectern, those lived scattered across repos, Slack threads, and ad hoc S3 links — readable only by whoever happened to have the right local checkout, and editable by anyone who did. We wanted a single, secure place to read the fleet's finished work, with one property none of our other tools guarantee: nothing published to it can ever be changed.
Three Laws, Not Three Guidelines
Lectern's whole design rests on three rules that hold structurally, not by convention:
- Read-only. No content-mutation endpoint exists on the reader surface, no edit or upload affordance exists anywhere in the UI, under any framing. Publishing lives exclusively behind
/api/v1/admin/*, gated by an admin bearer key that never reaches a browser bundle. - Immutable. Versions are append-only and content-addressed — every version's S3 key is derived from its own sha256 hash, so an object can never be silently overwritten. Documents are retired, never deleted; there is no hard-delete anywhere in the lifecycle.
- Byte-identical. An HTML artifact published to Lectern is served exactly as published, sha256-verified. Lectern never "fixes," reformats, or normalizes a published file — fidelity is a structural guarantee, not a best-effort promise.
Those laws are why the tool is named after a reading stand rather than a library or a wiki: you read from it, you never write on it.
Sandboxed Rendering, One Trust Boundary at a Time
The interesting engineering problem in Lectern is rendering arbitrary published HTML safely. Every document renders inside an <iframe sandbox="allow-scripts"> built from a Blob URL — deliberately never allow-same-origin. It's the same containment model claude.ai uses for artifacts: scripts inside the frame can run, but they execute in a null origin with no access to the reader's auth token, no cookies, no reach into the parent DOM. A hostile or simply buggy artifact can't exfiltrate a session or escape its frame.
Everything else follows the same one-boundary-at-a-time discipline. Reading requires a real renkara-auth JWT, verified RS256 against a Valkey-cached JWKS document. Publishing requires a completely separate admin bearer key that only ever travels between the founder's workstation and the API — there is no user-JWT path into the admin surface at all, by design, so a compromised reader session can never touch the write path. Documents and whole collection subtrees can be scoped to a named audience, and one shared visibility predicate — not a per-endpoint reimplementation — governs the catalog, search, and content fetch alike. Every view, download, and search query writes an attributed, append-only audit event, so "who read what, and when" is always answerable.
Publishing Is a Workstation-Only Pipeline
There's exactly one way content enters Lectern: the founder runs lectern_publish.py or an MCP tool from the workstation. Both are thin wrappers over the identical admin API, so the CLI and the AI-assisted lane can never drift apart. A sync is idempotent — re-running it against an unchanged source reports 100% unchanged, and nothing new is written unless the content actually changed. Every mutation is reversible without ever deleting a version row or an S3 object: point the current version at an older one, retire a document, or restore it.
Markdown renders to a fully self-contained artifact at publish time — inlined CSS, Pygments-highlighted code in both light and dark themes, embedded images, and client-side Mermaid diagrams — so a reader's browser never makes an external request to display a document. HTML artifacts pass through byte-identical. PDFs get best-effort text extraction for search, served as-is otherwise.
38 MCP Tools
The MCP server exposes the entire reading and publishing surface to Claude Code: health and diagnostics (4 tools), reading and full-text search (9 tools), publishing and governance — publish, retire, rollback, collection and audience management, share-link minting (17 tools), and audit and integrity — access-log queries, per-document analytics, S3-vs-registry drift detection, search reindexing (5 tools) — plus a read-only admin reporting UI backed by the exact same query logic, so the browser and the machine can never disagree about the numbers.
Key Specs
| Spec | Detail |
|---|---|
| Frontend | React 19, TypeScript, Vite 8, sandboxed iframe artifact viewer |
| Backend | FastAPI, SQLAlchemy 2.0 async, PostgreSQL, Postgres full-text search |
| Content store | S3, KMS-encrypted, IAM-identity access only — no bucket policy at all |
| Auth | renkara-auth RS256 JWT (readers) + constant-time admin key (publishing) |
| Isolation | Blob URL + iframe sandbox="allow-scripts", never allow-same-origin |
| MCP tools | 38 (health/ops, reading, publishing, collections, audiences, audit) |
| Rate limiting | Per-IP buckets on content, search, and the admin/MCP surface |
| Theme | Light and dark mode, mobile-safe to 375px |
Integration Points
Lectern signs readers in through the shared renkara-auth OIDC flow — there's no separate account to create. It streams content through its own API rather than presigned S3 URLs, which keeps the audit log complete by construction and the content bucket reachable by exactly one IAM role. And the MCP server plugs directly into Claude Code sessions through Roost, so publishing a new architecture doc or ADR can happen from the same session that wrote it, without ever leaving the terminal.