Architecture decisions
Generated from docs/decisions/*.md
Project-wide decisions for ulams, in MADR format. Decisions that only
concern one application live next to it (api/docs/adr, admin/docs/adr, front/docs/adr; those
are retroactive records mined from the pre-monorepo history).
Workflow (see CLAUDE.md): the agent proposes an ADR with status Proposed; the product owner
approves it, which changes the status to Accepted. Superseded records stay and link to their
replacement.
The index below is generated from each record’s title and Status: line: run node scripts/adr-index.mjs
(or yarn adr:index) after adding or changing a record, never edit the rows by hand. CI fails when it is stale.
This list is built from the files in docs/decisions on every build, so a new ADR appears here with its status as soon as it is merged. How to write one: Decisions and documentation.
| # | Decision | Status | Date |
|---|---|---|---|
| 0001 | Monorepo with vendored packages | Accepted (2026-10-08) | 2026-10-08 |
| 0002 | Rename EscolaLMS / Wellms to ulams | Accepted (2026-10-08) | 2026-10-08 |
| 0003 | H5P as an isolated GPL service (Lumi) | Accepted (2026-10-08) | 2026-10-08 |
| 0004 | Theming with CSS custom properties, no styled-components | Accepted (2026-10-08) | 2026-10-08 |
| 0005 | Turborepo and Yarn workspaces | Accepted (2026-10-08) | 2026-10-08 |
| 0006 | Remove the recommender package | Accepted (2026-10-08) | 2026-10-08 |
| 0007 | Tenancy: database per tenant, provisioned by the tenancy package | Accepted (2026-10-08) | 2026-10-08 |
| 0008 | Reference frontend: Astro SSR, framework-free SDK, schema-described UI catalogue | Accepted (2026-10-09) | 2026-10-08 |
| 0009 | LLM layer: an ai package on the official Anthropic SDK |
Accepted (2026-10-09) | 2026-10-08 |
| 0010 | Course Builder: a versioned Course Blueprint applied through domain services | Accepted (2026-10-09) | 2026-10-08 |
| 0011 | Builder streaming: AG-UI events over SSE from Laravel, carrying A2UI surfaces | Accepted (2026-10-09) | 2026-10-08 |
| 0012 | LTI 1.3: one lti package, first-party platform side, packbackbooks tool side |
Accepted (2026-10-09) | 2026-10-09 |
| 0013 | Adapt Path B: JSON sources in the API, builds in an isolated GPL-3.0 worker | Accepted (2026-10-09) | 2026-10-09 |
| 0014 | Third-party packages run on a per-tenant content origin, files served through the API | Accepted (2026-10-09) | 2026-10-09 |
| 0015 | H5P service per tenant: derived internal token, platform-only libraries, least-privilege mounts | Accepted (2026-10-09) | 2026-10-09 |
| 0016 | LiaScript: versioned Markdown documents played without a SCORM package | Accepted (2026-10-09) | 2026-10-09 |
| 0017 | One upload guard and safe extractor for every upload path | Accepted (2026-10-09) | 2026-10-09 |
| 0018 | External content completes topics; completion events fire after progress is saved | Accepted (2026-10-09) | 2026-10-09 |
| 0019 | Conformance against real LMSs and builders in an opt-in nightly workflow | Accepted (2026-10-09) | 2026-10-09 |
| 0020 | Public comparison with other learning platforms: sourced data, neutral values | Accepted (2026-10-09) | 2026-10-09 |
| 0021 | High-availability reference architecture | Accepted (2026-10-09) | 2026-10-09 |
| 0022 | Course Builder studio in the reference web app, with its own author session | Accepted (2026-10-09) | 2026-10-09 |
| 0023 | A2UI surfaces travel as AG-UI activity snapshots (a2ui-surface) |
Accepted (2026-10-09) | 2026-10-09 |
| 0024 | Fake LLM driver: normalised cassettes and synthetic answers | Accepted (2026-10-09) | 2026-10-09 |
| 0025 | How a Course Blueprint maps to LMS entities | Accepted (2026-10-09) | 2026-10-09 |
| 0026 | A first-party DOCX converter instead of PhpWord | Accepted (2026-10-09) | 2026-10-09 |
| 0027 | Course Builder access: one permission, author acts, admins look | Accepted (2026-10-09) | 2026-10-09 |
| 0028 | Generation stages, grounding check and quiz support check | Accepted (2026-10-09) | 2026-10-09 |
| 0029 | The SSE endpoint wakes on a cache key, not Valkey pub/sub | Accepted (2026-10-09) | 2026-10-09 |
| 0030 | Living Course: source revisions and update proposals on top of the Course Blueprint | Accepted (2026-10-09) | 2026-10-09 |
| 0031 | Fragment-level change detection is deterministic | Accepted (2026-10-09) | 2026-10-09 |
| 0032 | Source connectors as plugins; Git through host APIs; one SSRF-safe HTTP client | Accepted (2026-10-09) | 2026-10-09 |
| 0033 | Progress preservation rules for content updates | Accepted (2026-10-09) | 2026-10-09 |
| 0034 | A tamper-evident audit trail for Living Course | Accepted (2026-10-09) | 2026-10-09 |
| 0038 | Brand identity: Orbital Folio | Proposed (2026-10-09) | 2026-10-09 |
| 0039 | Author preview of draft courses runs on its own routes with the author’s token | Proposed | 2026-10-09 |
| 0040 | PostgreSQL 17 with a tested dump-and-restore upgrade | Proposed | 2026-10-09 |
| 0041 | SeaweedFS replaces MinIO; per-tenant S3 identities; server-side reads use the internal endpoint | Proposed | 2026-10-09 |
| 0042 | No WebSocket server: Soketi and Pusher removed, Reverb only when a feature needs push | Proposed | 2026-10-09 |
| 0043 | An enforced morph map with stable aliases for polymorphic types | Proposed | 2026-10-09 |
| 0044 | Content Security Policy: report collector, enforcement, tool origins from the API | Proposed | 2026-10-09 |
| 0045 | H5P learner state through the BFF; no API token in the browser | Proposed | 2026-10-09 |
| 0046 | cmi5 on the content origin with a one-time launch token and an LRS-only session token | Proposed | 2026-10-09 |
| 0047 | OpenAPI as PHP attributes; doctrine/annotations removed; spec snapshot test | Proposed | 2026-10-09 |
| 0048 | Course sites: publish into the current site by default; new sites through provisioning and session transfer | Proposed | 2026-10-09 |
| 0049 | A CommerceProvider interface before Sylius, with a Wellms cart adapter | Proposed | 2026-10-09 |
| 0050 | Lesson content-type registry: deterministic LiaScript rendering and allow-listed H5P libraries | Proposed | 2026-10-09 |
| 0051 | Critic loop with a retry budget and an isolated Playwright solvability runner | Proposed | 2026-10-09 |
| 0052 | Learner layouts as a Layout topic type rendered from the catalogue | Proposed | 2026-10-09 |
| 0053 | Simulations: single-file HTML on the content origin, sandboxed, typed postMessage, off by default | Proposed | 2026-10-09 |
| 0054 | Component playground in the docs site instead of Storybook | Proposed | 2026-10-09 |
| 0055 | Course experiments with delayed retention, surveys and a tenant-level consent model | Proposed | 2026-10-09 |
| 0056 | Admin and legacy front served by nginx-unprivileged with runtime JSON config | Accepted | 2026-10-09 |
| 0057 | learner-insights package: an append-only signal stream keyed by blueprint element IDs | Proposed | 2026-10-09 |
| 0058 | Rule-based risk scoring with reasons behind a RiskScorer interface | Proposed | 2026-10-09 |
| 0059 | Personal remediations: learner-scoped, grounded, cached per struggle pattern | Proposed | 2026-10-09 |
| 0060 | AI tutor: course-scoped full-text retrieval, citations and an attempt guard | Proposed | 2026-10-09 |
| 0061 | Personalisation privacy: off by default, opt-out or consent, minimal data, explainable, erasable | Proposed | 2026-10-09 |
| 0062 | Adaptive interface as a presentation profile over catalogue components | Proposed | 2026-10-09 |
| 0063 | Tenants inherit the platform AI settings, with per-tenant overrides | Proposed | 2026-10-09 |
| 0064 | The studio reports “applied” only from authoritative state | Proposed | 2026-10-09 |
| 0065 | Demo login supports the tutor role | Proposed | 2026-10-09 |
| 0066 | init.sh never regenerates an existing APP_KEY | Proposed | 2026-10-09 |
| 0067 | The quiz time limit default is read from ulams_gift_quiz.max_quiz_time |
Proposed | 2026-10-09 |
| 0068 | The scheduler loop claims each minute with a shared cache lock | Proposed | 2026-10-09 |
| 0069 | CI typechecks, lints and tests the web app, ui and sdk | Proposed | 2026-10-09 |
| 0070 | The admin runs umi/max on Node 24 through a small shim | Proposed | 2026-10-09 |
| 0071 | API security hardening: auth on admin routes, allow-listed payment input, bounded group walks | Proposed | 2026-10-09 |
| 0072 | The agent-first ulams CLI: one command registry generates the parser, help, describe, MCP tools and docs |
Proposed | 2026-10-09 |
| 0073 | CLI machine contract: JSON envelope, NDJSON, exit codes and error codes | Proposed | 2026-10-09 |
| 0074 | Scoped personal access tokens with an agent audit log and Idempotency-Key |
Proposed | 2026-10-09 |
| 0075 | Device login with our own RFC 8628 flow approved in the web app; Passport’s device grant stays off | Proposed | 2026-10-09 |
| 0076 | ulams mcp: a local MCP server (spec 2026-07-28, SDK v2) generated from the CLI registry |
Proposed | 2026-10-09 |
| 0077 | CLI distribution: npm, bun-compiled binaries and a Docker image; no telemetry | Proposed | 2026-10-09 |
| 0078 | A platform-only HTTP API for tenant management | Proposed | 2026-10-09 |
| 0079 | Course-as-code: Markdown with directives + YAML, Blueprint v2 and a committed sync base | Proposed | 2026-10-09 |
| 0080 | Interactive preview in the studio: learner pages from the blueprint, in a frame | Proposed | 2026-10-09 |
| 0081 | ulams:upgrade: an idempotent per-tenant upgrade command with a step registry |
Proposed | 2026-10-09 |
| 0082 | Quiz attempt deadline holds on every queue driver | Proposed | 2026-10-09 |
| 0083 | Course Builder: a stage change is one transaction under the run lock | Proposed | 2026-10-09 |
| 0084 | CLI and MCP commands for the course builder and Living Course | Proposed | 2026-10-09 |
| 0085 | The platform tenant API: operations, permission and what stays off | Proposed | 2026-10-09 |
| 0086 | Interactive topic type: author-uploaded JavaScript packages in an opaque sandbox on the content origin | Proposed | 2026-10-09 |
| 0087 | The ulams-ix bridge protocol and the @ulams/interactive-bridge library (MIT) |
Proposed | 2026-10-09 |
| 0088 | Content packages under demo-content/, played only as sandboxed content |
Proposed (amended 2026-10-09) | 2026-10-09 |
| 0089 | Six demo academies: three free interactive courses, one theme preset each, sourced content, EN/PL as two courses | Proposed | 2026-10-09 |
| 0090 | Living Course: choices made during implementation | Proposed | 2026-10-09 |
| 0091 | Shared hosting: cron-driven workers and an operator-created tenant database | Proposed | 2026-10-09 |
| 0092 | Production reference: one VPS behind Cloudflare, flat tenant hosts, a tunnel and R2 | Proposed | 2026-10-09 |
| 0093 | A public showcase endpoint and hero interactives on the demo landings | Proposed | 2026-10-09 |
| 0094 | Interactive demo courses are written as Markdown module files and seeded through the domain services | Proposed | 2026-10-10 |
| 0095 | The Ulam course: every statement traced to a fact sheet, sources numbered, four licensed photographs | Proposed | 2026-10-10 |
| 0096 | The product landing in Polish and Simplified Chinese: Astro i18n routing, one document per language | Proposed | 2026-10-10 |
Per-application history
Section titled “Per-application history”Retroactive records mined from the history of each application before the monorepo:
- API history: 16 records from
api/docs/adr - Admin history: 16 records from
admin/docs/adr - Old front history: 16 records from
front/docs/adr