Architecture
The containers, the NATS-sole-channel rule, the auth model, the sandbox boundary, the realtime protocol, and the 16 frozen invariants.
The architecture is a frozen decision record (docs/architecture/ in the repository: v1 as
corrected by v2 and v3). This page explains it plainly; the invariants at the bottom are
the frozen set the code is continuously audited against.
The monorepo
One pnpm workspace; one package per container.
| Package | Boundary | Responsibility |
|---|---|---|
@quickable/contract | library | The single source of type truth: every address (subject + zod req/rep + authz meta), the reply envelope, patch and bridge schemas. Zero runtime deps besides zod. |
@quickable/patch | library | Block-addressed patch engine: split files into anchored blocks (oxc-parser), apply anchor ops, verify. |
@quickable/bus | library | NATS binding: connection/JetStream/KV/objstore atoms, serve(addr, flow) and call(addr, input) — the only way an address becomes a handler or a request. |
@quickable/auth | library | The identity plane: programmatic operator/account/user JWT minting (nkeys.js + @nats-io/jwt), resolver push, namespace allocator. |
@quickable/obsv | library | The one observability wiring point: otel sink, pino→VictoriaLogs transport, prom-client registry. |
@quickable/core | service | Every application flow — signup, app, material, session, realtime — served as pumped-fn lite flows through bus.serve(). Stateless. |
@quickable/transform | service | The build worker: material → browser bundle via Vite 8 programmatic build. Never executes user code. |
@quickable/gateway | service | REST /v1 + MCP /mcp generated from the registry, plus static shell/bundle reads. Thin; holds no logic and no authority. |
@quickable/runner | browser | The parent shell: holds the only credential and the only socket, boots the sandboxed iframe, relays validated messages. |
@quickable/qctl | CLI | Ops probes: the isolation denial matrix, gateway authority, app lifecycle, material/transform, sandbox-rt, plus the edge smoke e2e. |
apps/docs | static site | This site — fumadocs, statically exported, with the API reference generated from the contract. |
deploy/ | infra | compose file, Caddyfile, nats server config, Victoria stack. One public port. |
NATS is the sole channel (A2′)
Every state-changing operation travels as a NATS request/reply on an address from the
contract registry. The only HTTP surfaces are enumerated infra: the Caddy edge, the
gateway's generated projections (/v1, /mcp), static reads (shells, bundles, this docs
site), the /nats websocket proxy, health probes and telemetry to Victoria. The gateway's
route table is sealed at boot and asserted against a complete classified inventory — a
hand-added route fails the boot, not just a test (A12′).
caddy (the ONLY public port)
/nats /v1 /mcp /a /bundles /healthz /docs /o11y/*
│ │ │ │
nats ◄───── gateway ─────► NATS static export victoria
▲ (this site) logs/metrics
│
core / transform (queue groups, stateless, creds-authenticated)One account per namespace (A3)
Signup mints a dedicated NATS account per namespace — not a subject prefix inside a shared account. Accounts are hard broker-level isolation: a subject in namespace A does not exist in namespace B; cross-namespace reach is unroutable rather than merely forbidden.
The platform account (QSVC) exports two subject spaces; each namespace account imports
exactly its own slice, pinned by the broker (account_token_position: 3 — the token is the
importing account's public key, validated by the server, never by payload):
q.api.<acct>.> (service) → imported as local api.>
q.evt.<acct>.> (stream) → imported as local evt.>Core serves the wildcard and reads the caller's account public key from the subject the broker routed — identity is broker-captured, payload identity does not exist. See Auth & namespaces for the credential classes and the bearer model.
The sandbox boundary — honest (A4′/D4)
User bundles run in an iframe with sandbox="allow-scripts" only, a default-src 'none'; connect-src 'none' CSP, and a null origin. Runtime deps (react, the bridge stub) are
injected as inline blob modules (A16) — nothing is fetched. The only channel out is a
boot-time MessageChannel to the parent shell, which zod-validates every message, forwards
only bridge-grant subjects, and holds the only browser credential (a short-lived bearer
JWT that never crosses into the iframe).
What this is: the iframe cannot reach server NATS, cannot obtain the credential, and
in-iframe fetch/WebSocket/EventSource are denied.
What this is not: a full network-egress seal. Script-enabled content can navigate its
own frame (location.replace(...)), which can exfiltrate via a URL. The consequence is
deliberately bounded (D4): anything the platform delivers into an app's iframe is treated
as readable by that app's author, so an iframe only ever receives that one app's
realtime state and events — never another app's or namespace's data, never a credential.
Multiplayer state is shared by that app's design; cross-user secrets are structurally
absent.
Realtime — optimistic CAS, server-authoritative (A10′/D2)
Clients never write state. State changes flow through one primitive:
client ── api.rt.<app>.intent {cmdId, type, payload} ──► core replica
core: read KV state+rev → reduce (pure) → kv.update(CAS on rev)
└─ CAS conflict? re-read and retry (bounded)
reply {rev, result, dedup} AFTER the commit
then: publish evt.<app>.rt.state {delta + full state} (best-effort hint)- One KV key per app is the single linearization point — no lost acknowledged move, any number of core replicas.
cmdIdis a client idempotency key: a retry within the dedup window returns the recorded result without re-applying (crash-before-reply safe; the guarantee is idempotency within the window, not absolute exactly-once).- Events are hints; KV is the truth. A client that saw a revision gap calls
api.rt.<app>.resume {fromRev}and gets current state plus the missed deltas. - Cursor-class traffic (
api.rt.<app>.cursor) is fire-and-forget fanout with no KV write.
The transform seal (A6)
The build worker turns material (an in-memory virtual fs read from KV) into a bundle and
writes it to the object store — it transforms but never executes user code: no
import(), no eval, no user-supplied build config. Its container is hardened: read-only
rootfs, tmpfs scratch, and a dedicated nats-only network — no egress and no lateral
container traffic. Its internal subject (q.int.transform.build) has no account export,
so no namespace credential can ever reach it.
The 16 frozen invariants
| # | Plainly |
|---|---|
| A1′ | Every externally reachable application operation is a contract address served as a lite flow. The enumerated non-address HTTP routes (static reads, signup bootstrap, ws proxy, telemetry) change no application state and are inventoried. |
| A2′ | NATS is the sole application/state-changing channel. The infra exceptions (Caddy edge, telemetry to Victoria) are enumerated and non-product. |
| A3 | One NATS account per namespace; cross-account reach exists only via the platform's two exports, pinned by the broker at token position 3. Identity is the broker-captured subject token, never payload. |
| A4′ | The sandbox boundary is honest: connection-sealed (no NATS, no credential, connect-src 'none'), not claimed egress-sealed; scope what enters the iframe instead (D4). |
| A5 | Material mutation is accepted only on api.material.> from material/owner creds; bridge creds carry a broker-level deny on it. |
| A6 | The transform worker transforms but never executes user code, holds no user credentials, and reaches nothing but NATS. |
| A7′ | Every capability handler is a flow that emits log+metric+span via scope-level extensions; edge concerns are observed via access logs and broker authorization-error advisories. |
| A8′ | Durable truth is exactly two things: the JetStream store and /creds. Services are restart-stateless; backup is those two, verified by full-history diff. |
| A9′ | Material writes are block patches with 128-bit content-hash anchors and CAS on baseRev; one patch = one file = one KV key. Ambiguous anchors are rejected, never guessed. |
| A10′ | Realtime state is server-authoritative via single-key optimistic CAS; clients send intents and hold no write path; down-events are hints over KV truth. |
| A11 | All identities are minted programmatically and distributed through the resolver; no tenant appears in static server config. |
| A12′ | The AG-7 gate is a complete classified HTTP route inventory, asserted at gateway boot and sealed — not just generated handlers. |
| A13 | User JWTs are signed by account signing keys; namespace identity seeds are destroyed after mint; browser credentials are always bearer_token: true with exp ≤ 1h. |
| A14 | pumped-fn LITE line only — @pumped-fn/lite on server and in-sandbox, lite-react in the browser shell. |
| A15′ | The gateway never opens a platform (QSVC) connection for user traffic — it forwards the caller's own broker-verified bearer. Core derives identity only from the broker-captured subject token. |
| A16 | Browser runtime deps are inline blob modules injected into the srcdoc — nothing external is fetched inside the sandbox. |