Documentation
System architecture
How Katagami keeps rendering stateless while template data stays durable.
Katagami splits into a durable catalog and a stateless render path. PostgreSQL and S3-compatible storage hold everything that must survive; each server holds only a read-through cache and a supervised Typst worker pool. That split is what lets you run replicas behind a load balancer without a shared working directory.
Runtime topology
The API accepts publish and render requests. The registry is an in-memory LRU of hot published versions — never a full mirror of the catalog. A miss read-throughs PostgreSQL and S3, verifies every object against its manifest SHA-256, then inserts. A process-local version coordinator serializes publish, load, retire, and unretire work for the same version. Different version keys do not share an I/O lock, so slow storage or catalog work for one version does not stop warm reads of another. The worker supervisor maintains a fixed pool of child processes. Each worker handles one compile; a deadline or worker failure kills and reaps that child, then replacement is bounded by the restart budget.
PostgreSQL is the catalog and shared-capacity authority. S3 holds content-addressed
objects under objects/{sha256}. The keyed coordinator is local to one replica.
Cross-replica lifecycle notifications, reconnect handling, periodic reconciliation,
and bounded staleness keep replica registries convergent without eager object loads.
Publication path
Publication is the only way source enters the system, and it is guarded end to end.
-
Accept the pack
An admin request carries a JSON Schema, the Typst source pack, font files, optional static files, and the allowlist of assets a render request may send.
schema · source · fonts · static assets
-
Write objects conditionally
Every object is stored under a key derived from its own content, created with an S3 precondition rather than a check-then-write. An existing object is never overwritten.
objects/{sha256} · If-None-Match: *
-
Verify every hash
Katagami loads the new version and checks each stored object against the SHA-256 recorded in its manifest, before that version is visible to any renderer.
each object vs manifest SHA-256
-
Reserve the version
PostgreSQL reserves shared catalog capacity inside a transaction held under an advisory lock, then records the immutable version. Publishers on separate replicas cannot exceed the shared limit.
PostgreSQL transaction · advisory lock
-
Warm the local registry
After PostgreSQL commits, the publishing replica inserts the verified version into its local cache. PostgreSQL LISTEN/NOTIFY invalidates peers, and freshness-bounded reconciliation repairs missed notifications under ADR 0004.
per-version gate · local LRU
The result is durable storage with a rebuildable render process. A replica can restart, miss-fetch a version on first use, and serve the same published bytes without any shared render directory.
Render path
A render request never touches the host filesystem and never introduces new source.
-
Resolve the version
One replica coordinates each version independently. A miss loads through PostgreSQL and S3, verifies each SHA-256, and inserts under the cache budget. LISTEN/NOTIFY and reconciliation keep replica registries fresh.
keyed gate · registry hit or verified read-through
-
Validate the data
The request body's data is checked against the JSON Schema published with that exact version. Invalid data is rejected before Typst is ever invoked.
published JSON Schema · 422 on failure
-
Check the assets
Named request assets are matched against the allowlist in the published manifest. A render request cannot introduce arbitrary Typst file names.
manifest allowlist · 400 on failure
-
Compile in a worker
The supervisor dispatches each compile to one worker process. On a deadline or worker death it kills and reaps that child; the restart budget controls replacement capacity.
supervised child · deadline · IPC
-
Return the PDF
The response is PDF bytes. A result reaches object storage only when ALLOW_PERSISTED_RESULTS is enabled and the request explicitly asks for it.
application/pdf · optional persist
Katagami does not depend on a host Typst CLI, arbitrary shell process, WASM runtime, host filesystem read, or render working directory. It runs only its own supervised worker binary, which provides hard deadline and crash isolation.
Why versions are immutable
A published version is a fixed unit: schema, source, fonts, static assets, and the
hashes of every object it references. Its content cannot change. Changing a
template means publishing a new version. A version may later be retired — it
stays durable for audit and reproducibility, drops out of the registry and
active-version quota, still counts against MAX_STORAGE_BYTES, and renders answer
410 Gone.
That constraint pays for itself in three places. A caller pinned to v1 cannot be
broken by someone else’s template edit. A rendered document can be reproduced later
from the version it names. And because objects are content-addressed and written with
a precondition rather than a check-then-write, two publishers racing on separate
replicas cannot corrupt each other’s version.
The cost is that mistakes are shipped forward rather than patched in place. Publish
v2; there is no way to fix v1.