quickable

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.

PackageBoundaryResponsibility
@quickable/contractlibraryThe 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/patchlibraryBlock-addressed patch engine: split files into anchored blocks (oxc-parser), apply anchor ops, verify.
@quickable/buslibraryNATS 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/authlibraryThe identity plane: programmatic operator/account/user JWT minting (nkeys.js + @nats-io/jwt), resolver push, namespace allocator.
@quickable/obsvlibraryThe one observability wiring point: otel sink, pino→VictoriaLogs transport, prom-client registry.
@quickable/coreserviceEvery application flow — signup, app, material, session, realtime — served as pumped-fn lite flows through bus.serve(). Stateless.
@quickable/transformserviceThe build worker: material → browser bundle via Vite 8 programmatic build. Never executes user code.
@quickable/gatewayserviceREST /v1 + MCP /mcp generated from the registry, plus static shell/bundle reads. Thin; holds no logic and no authority.
@quickable/runnerbrowserThe parent shell: holds the only credential and the only socket, boots the sandboxed iframe, relays validated messages.
@quickable/qctlCLIOps probes: the isolation denial matrix, gateway authority, app lifecycle, material/transform, sandbox-rt, plus the edge smoke e2e.
apps/docsstatic siteThis site — fumadocs, statically exported, with the API reference generated from the contract.
deploy/infracompose 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.
  • cmdId is 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.
A3One 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).
A5Material mutation is accepted only on api.material.> from material/owner creds; bridge creds carry a broker-level deny on it.
A6The 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.
A11All 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.
A13User JWTs are signed by account signing keys; namespace identity seeds are destroyed after mint; browser credentials are always bearer_token: true with exp ≤ 1h.
A14pumped-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.
A16Browser runtime deps are inline blob modules injected into the srcdoc — nothing external is fetched inside the sandbox.

On this page