Architecture decision records¶
Start here to find a decision without loading the whole archive. This is a reading index, not another specification. Each record owns its rationale, status and amendment history.
Reading protocol¶
- Read the relevant specification chapter
and check open questions. Repository
invariants are in
CLAUDE.md; an unresolved conflict is not permission to change a product rule. - Pick a topic below. Read the record's Status before its Decision, then follow its named amendments. A later record overrides only the scope it explicitly changes; a higher number alone establishes no precedence.
- Consolidated records put the current rule first and link to historical
snapshots only for rationale or old section references. Do not load
history/by default. In other records, follow the Reading map when present. Cite the full file and section when handing work to another agent. - Accepted is a decision state, not a deployment state. Check the code,
tests and merge progress for implementation. Imported
records describe heig-classroom until their Quiz status/addendum adapts them;
its
GR-,GH-,AU-andNFR-IDs are not Quiz requirement IDs.
Topics¶
Each record appears once below. Cross-topic dependencies live in the records, so this index does not duplicate their status or maintain a second dependency graph.
Platform and operations¶
- ADR-001 — Modular monolith, single process, optional
WORKER_MODEsplit - ADR-002 — Node.js + TypeScript + Fastify backend
- ADR-003 — PostgreSQL as the only stateful component, access through an isolated Drizzle ORM
- ADR-004 — pg-boss job queue on Postgres
- ADR-005 — SSE rather than WebSocket, without
Last-Event-IDreplay - ADR-006 — Deadlines through a single ticker-sweeper, no scheduled one-shot job
- ADR-008 — React + Vite SPA front end, own accessible primitives, no SSR
- ADR-094 — One scale for form controls: pill for one line, soft square for content
- ADR-104 — One state grammar for the staff's badges: green open, amber awaited, blue scheduled, outline finished
- ADR-009 — Deployment on a single VM, Docker Compose, Caddy, provider and off-site backups
- ADR-010 — Secrets outside the repository and outside the database, in an encrypted institutional vault
- ADR-011 — Reconciliation reuses the idempotent webhook handlers
- ADR-016 — The code runner on the codespace VM, behind Caddy, with a shared token
- ADR-101 — The runner's capacity is budgeted: a budget per request, aged grading, a cap per evaluation
- ADR-028 — A staging environment on the production VM, and promotion by sha
- ADR-030 — Notification channels and delivery rules
- ADR-087 — What's new: one entry file per pull request, shown once after an update
- ADR-105 — The training course: one per teacher, four missions on real screens, a progress card derived from the data
- ADR-093 — Festive touches: the logo dresses up on a few days of the year
- ADR-055 — The system status: one registry of health checks, a narrow
/healthz, a backup report - ADR-107 — Platform activity for the administrator: hours of activity per person, read in aggregate only, kept eight days
- ADR-065 — Connection recovery without reloading student work
- ADR-110 — The client's version after a deploy: old chunks kept, the new build taken at a safe moment
Identity, access and exam confinement¶
- ADR-013 — Pool sharing: three roles, one resolution order, succession by seniority
- ADR-095 — Public pool catalogue: subscriptions, read-only course links, unpublishing
- ADR-018 — The real student view and stateless preview
- ADR-022 — Personal API tokens, and an MCP server that is one more client of the API
- ADR-023 — The portal is its own OAuth 2.1 server, so claude.ai and ChatGPT can sign in
- ADR-113 — Scopes of personal API tokens: MCP by default, the whole API only when asked for and always expiring
- ADR-027 — Launch tickets and typed sessions (Safe Exam Browser first)
- ADR-034 — Acting as a student: a one-time link, an
impersonationsession, read-only in production - ADR-051 — Attested kiosk stations, paired from the student's phone, beside Safe Exam Browser
- ADR-089 — Kiosk stations for the online workspace: the station stays on Quiz and frames the portal
- ADR-053 — Removing the entry codes: the classroom join code and the evaluation access code
- ADR-054 — Super Powers: an admin reaches everyone's content for one hour, on request
- ADR-068 — Course staff roles: owner and assistant
- ADR-061 — Login adoption of the accounts imported from heig-classroom
- ADR-111 — Login admission: an account is created only for a member of a listed institution or an invited person
- ADR-112 — A person's data export: their own student views gathered in one file, plus three account rows
Question authoring and types¶
- ADR-015 — The browser executes, the server grades: a
runtimeper code question, a command line per case - ADR-017 — Moving a question to another pool
- ADR-019 — Grading a schematic by simulation, not by topology
- ADR-075 — The workspace question: an advanced exam question worked in the online workspace, collected by the server
- ADR-021 — Code image: a variant of
code, a target in the config, stdout graded whatever the exit - ADR-024 — One locked editor, three student tools, a visible cooldown
- ADR-096 — Extra files and compiler flags of a program question are public program inputs
- ADR-036 — Categorize: a new question type, sorting cards into columns
- ADR-040 — Favourite stars on questions: a second per-user preference table
- ADR-046 — Diagram: one editor engine, one question type, eight notations
- ADR-056 — Parameterized questions: variables drawn per attempt
- ADR-097 — Transmitted answer and stored answer: a student exchanges choices by displayed position
- ADR-081 — Concepts replace tags: one instance-wide, bilingual vocabulary, curated by the admin
- ADR-114 — Proposed relations between concepts: a deterministic pre-pass, then the model, accepted by selection
- ADR-116 — The concept map of a pool: a concept counts what its click opens, one level of the hierarchy per view
- ADR-083 — Frequency-domain stimuli for circuit
- ADR-099 — Regex matchers on RE2: linear matching, bounded programs, refused at publication only
- ADR-102 — Mathematical expression answers: a math field, numerical equivalence, no CAS
- ADR-103 — Blocks: a block-programming question type, a warehouse robot graded by a pure interpreter
- ADR-109 — Each question type draws its own answer key for the debrief
Evaluations, grading and practice¶
- ADR-012 — Grade freezing: receipt time written synchronously, two-step freeze
- ADR-014 — Live polls
- ADR-071 — The brainstorm poll: short ideas, a bubble cloud, moderated by the teacher
- ADR-072 — AI assistance for a brainstorm: a model moderates, corrects and groups ideas live (superseded, folded into ADR-071)
- ADR-020 — Presence is a body in the room, and the Results switch colours it now
- ADR-025 — Several attempts on an exercise
- ADR-026 — Negative marking, per evaluation, with the total floored at 0
- ADR-031 — Evaluation templates at course level
- ADR-032 — Per-user display state lives in
user_course_prefs - ADR-033 — The correction projection reads the validated gradings, per attempt
- ADR-037 — Teacher-only material in a solution
- ADR-041 — The drill: spaced practice, scheduled by FSRS, rated by correctness and time
- ADR-044 — The grading table: one question at a time, anonymous by default
- ADR-050 — Publishing the correction of an exercise that is still running
- ADR-052 — Bonus questions replace the threshold grade scale
- ADR-108 — How an item counts: normal, bonus or neutralized, changeable at grading time
- ADR-066 — A wide question beside a pinned rail
- ADR-067 — Grading an exercise at hand-in
- ADR-069 — A calculator provided on the student's screen
- ADR-090 — A notepad provided on the student's screen
- ADR-076 — An attempt starts by an explicit Start, not by opening the link
- ADR-079 — The conditions of an evaluation: announced by the teacher, imposed by the platform
- ADR-084 — A text before an item: the intro of an evaluation item
- ADR-085 — Confidence in the drill: stated beside the review, never part of a score
- ADR-098 — Student concept mastery: a level per course concept, from reviewed cards only
- ADR-115 — Targeted revision by concept: extra practice on the student's own cards, outside FSRS and the level
- ADR-086 — Scheduled or Live: who drives the clock, a safety deadline, and a limit cut at the window's end
- ADR-088 — The exam integrity journal: leaving the page and pasting from outside, recorded lightly, never proof
- ADR-091 — Partial retake of an exercise
- ADR-092 — Changing the mode of an evaluation: only the mode moves, until students are let in
Question analytics¶
- ADR-038 — Question statistics in the pool: success rate, and a reset
- ADR-039 — Time spent per question: a dwell measured by the server
- ADR-042 — The discrimination index of a question
- ADR-043 — The distractors of a multiple-choice question
LLM services¶
- ADR-045 — The LLM grading service, and its development stub (superseded, folded into ADR-063)
- ADR-058 — The LLM gateway: one institutional key, a daily cap, a call log
- ADR-059 — "Generate": the wand of the question editor
- ADR-060 — The LLM review of the published questions
- ADR-063 — LLM grading of essays and diagrams
- ADR-080 — The teacher assistant: ask the documentation and your own data from any screen
- ADR-082 — The AI card of the question editor
Classroom merge, projects and journal¶
- ADR-007 — Ephemeral self-hosted runners for grading, sized by the freeze
- ADR-029 — A UI library shared by Quiz and Classroom (superseded by ADR-035)
- ADR-035 — Merging heig-classroom into Quiz: one platform, one roster, activities of several kinds
- ADR-047 — Online workspace: no student credential, therefore no write access
- ADR-078 — The workspace's git relay: Quiz issues a token scoped to one repository, the App key stays on the app VM
- ADR-048 — Group assignments: groups per assignment, formed by the staff, delivered in three lots (superseded, folded into ADR-070)
- ADR-070 — Group sets: a classroom's reusable groups, which a project follows until its deadline
- ADR-077 — A staff seat accepts a project: the teacher's test repository
- ADR-049 — The classroom journal: GitHub holds the content, Postgres holds a read model (superseded, folded into ADR-057)
- ADR-057 — The journal in two modes: in Quiz, or in a GitHub repository
- ADR-062 — Building a project's distribution repository, and never deleting on GitHub
- ADR-064 — A project's deadline: claims, leases and per-repository markers
- ADR-073 — Syncing a project's source: a lease, one pull request per branch, the handed-out sha
- ADR-074 — Gradebook: stored marks beside derived absence
- ADR-100 — GitHub organizations are granted to courses: who may connect, what the staff may read
- ADR-106 — Grading a project by criteria on the platform: the criteria as data, the evidence run in
apps/runner, an LLM pass, a validated report
Maintaining a record¶
Use the template for new decisions. Keep one decision per record; put requirements in the spec, operational commands in the runbook and delivery progress in the merge task cards. Link to those sources instead of copying them.
Keep existing IDs, paths and heading anchors stable, except to resolve a
duplicate number, noted in the record's Status (2026-10-09: an anchor list into history/ may be reduced to a correspondence
table of the cited sections plus bare compatibility anchors). Select an unused ID after
checking both the repository and concurrent branches/PRs; rebase before
merging, and renumber yours if that ID landed meanwhile. For a changed decision,
name the affected section in the new record and add a reciprocal link in the old
record's Status. Use superseded only for a whole decision that no longer
applies; otherwise say amended, with its scope. A record folded into its
successor keeps its number and path as a short stub whose Status names the
successor and where each living rule went; the successor carries a
correspondence table for the sections code and documents cite, and this
index marks the stub "(superseded, folded into ADR-NNN)". Keep the old
rationale, and mark obsolete instructions where they occur if a reader could
retrieve them alone.
Do not append another implementation diary to an already long record.
Amended 2026-10-09 (rules carried from the archived 2026-10-02 audit):
- A record never restates another record's decision: it links to the record that owns it and names the affected points, so the copy cannot drift.
- Records on one topic that settle different trade-offs stay separate (project CI runners, ADR-007, and the question runner, ADR-016; the freeze, ADR-012, and its application to projects, ADR-064); a repeated reference to an access invariant is local rationale, not duplication. Fold only a record whose living rules have all moved.
- Consolidating keeps every stated limit explicit — an at-most-once loss window, a privacy exception, what a preview does not exercise, a change a notification can miss — and never replaces it with a stronger promise.
Add a link under one topic here. The site's Decisions entry points to this index; there is no separate per-ADR navigation list to update. Build the documentation and check links before opening the PR. The 2026-10-02 audit, archived, records the reasons for this organization.