# 07 · Stack ## Choices **Rust + axum** for the server; **maud** (or askama, if templates-as-files are preferred) for HTML — maud's compile-time fragments pair naturally with HTMX's fragment-swap model. **HTMX 2.x** from CDN (major-pinned), no build step. **SQLite** via sqlx for the AppView index, with a clean seam to Postgres. **jacquard** for atproto (verified current as of mid-2026; note it is pre-1.0 and its maintainer warns of breaking changes between minors — pin versions and read the changelog on upgrade): | Crate | Role | |---|---| | `jacquard` | Agent, sessions, re-exports | | `jacquard-oauth` | OAuth + DPoP, auto token refresh, `SessionRegistry` | | `jacquard-derive` | `#[lexicon]` (adds `extra_data` for unknown fields), `#[open_union]`, `#[derive(IntoStatic)]` — custom `app.ideonomics.*` record types as plain Rust structs | | `jacquard-identity` | handle→DID→PDS resolution chains | | `jacquard-axum` | server-side XRPC extractors, if we expose our own XRPC endpoints (note: was temporarily out of workspace during a redesign — check status) | Key jacquard idioms: types are generic over a backing string (`S: BosStr`, default `SmolStr`); use `.into_output()` for owned results; use `AgentSessionExt::create_record` / `get_record` with the `Collection` trait (`const NSID`) for typed record I/O; `Data` (never `serde_json::Value`) for untyped atproto values. Custom record structs get `#[lexicon]` + a `Collection` impl with `NSID = "app.ideonomics.completion"` etc. Canonical lexicon JSON schemas should also be authored under `lexicons/` and published via the standard DNS-based lexicon resolution for the `app.ideonomics` authority. Alternative considered: **atrium** (`atrium-api`, `atrium-oauth`) — mature, wider adoption, but custom lexicons and OAuth involve substantially more boilerplate. Fallback if jacquard's pre-1.0 churn bites. **Jetstream** consumer for ingestion (a small tokio task; the `rocketman` crate exists if hand-rolling the websocket is unappealing), filtered to `wantedCollections=app.ideonomics.*`, cursor persisted in SQLite. **Interpreter LLMs** behind a trait (`Glosser`) with an HTTP implementation configured by env; ensemble = Vec. Never called in the request path (04). ## Process shape One binary, three tokio task groups: (1) axum server, (2) Jetstream consumer + gloss worker (mpsc queue between them), (3) cron-like slow-loop scheduler. Vertical monolith until measurement says otherwise; the loops are decoupled by design (04), so splitting into services later is a deployment change, not an architecture change. ## Routes ``` GET / landing; handle-input login GET /client-metadata.json OAuth client metadata (public URL required) POST /oauth/login resolve handle → PAR → redirect to PDS auth GET /oauth/callback code exchange; cookie session → jacquard SessionRegistry POST /oauth/logout GET /session/next HTMX fragment: next square (fast loop) POST /session/complete write completion to user PDS; return next fragment POST /session/resonate write resonance record; return fragment GET /sky/{handle} full constellation page (SSR SVG) GET /sky/{handle}/term/{t} term-focus fragment GET /sky/{handle}/card.png OG/badge render (same renderer, card size) GET /compare/{a}/{b} shared-term contrast (consent-gated) GET /atlas population view GET /export the user's records + current derived state, JSON POST /snapshot explicit publish: write app.ideonomics.snapshot ``` Session cookies are opaque server-side IDs mapping to jacquard OAuth sessions (per-DID) in a SQLite-backed auth store; jacquard handles DPoP nonces and token refresh. ## AppView schema (sketch) Tables: `completions`, `squares`, `glosses`, `resonances` (mirrors of records, keyed by AT-URI, indexed by DID); `terms`, `edges` (the per-user graph, edge = canonical relation with weight/sign/gloss-variance); `posteriors` (per-user mixture weights, updated by medium loop); `quilts`, `discrimination` (slow-loop outputs); `layouts` (versioned constellation cache); `pairings` (comparison consent); `cursor` (Jetstream). ## Order of construction 1. OAuth flow + write/read one `completion` record end-to-end (thin slice through the riskiest dependency). 2. Square bank generation offline; serve static-bank sessions with chaining (no inference — random-containing-your-last-term selection). 3. Jetstream consumer + AppView mirror tables. 4. Gloss worker + canonicalization; term-graph construction. 5. Constellation SSR (renders honestly even from crude graphs). 6. Hierarchical model + adaptive selection + evaluation harness (05) — last, because everything upstream produces data worth having even while selection is naive. ## Caveats recorded during research Version facts checked July 2026: jacquard ~0.11–0.12 across crates, active weekly releases; atrium-api ~0.25.x; both ecosystems move fast — re-verify at implementation time. jacquard-axum's extractor redesign status should be checked before committing to server-side XRPC endpoints (we may not need any: the app's own HTTP surface is plain axum + HTMX, and XRPC is only needed if we expose the AppView to other clients — which the philosophy says we eventually should).