ADR Index
Every architecture decision the curriculum asks you to record, with where it is made and what it supersedes.
These are documents you write, in docs/adr/, at the moment of each decision. A reconstructed
rationale is fiction, and the value of an ADR is almost entirely in the rejected options — which
is the part you cannot reconstruct.
The Format
# ADR-00N: <the decision, in the imperative>
## Context
What forced the decision. What was known and unknown at the time.
## Options
A, B, C — each with its REAL cost. A strawman option is worse than no ADR.
## Decision
One sentence, active voice: "We use X."
## Consequences
What this makes easy, what it makes hard, and the observation that would make us revisit.
Three rules: write it at the time, record the rejected options with their real costs, and never edit a decision — supersede it.
The Core Fourteen
| # | Decision | Written in | Rejected, notably |
|---|---|---|---|
| 001 | One crate, not a workspace | Workspace | A crate per subsystem — longer builds, accidental public API |
| 002 | Stack VM, not register | Lab 12 / chapter | Register (Lua's) — fewer dispatches, a register allocator |
| 003 | Keep the tree walker forever as the oracle | Lab 8 | Delete it — saves 1,000 lines, loses the guarantee |
| 004 | Value is a 16-byte Rust enum | Lab 4 / chapter | NaN boxing — 8 bytes, requires unsafe |
| 005 | Lua 5.4's integer/float split | Lab 4 | Floats only — silent precision loss at 2^53 |
| 006 | Tracing mark-and-sweep over handles, no unsafe | Lab 15 | Rc (cannot collect cycles); raw pointers (UB on a bug) |
| 007 | Interning added only after a benchmark | Lab 16 | Intern everything up front — regresses concat-heavy code |
| 008 | Insertion-ordered table iteration | Lab 13 | Lua's unspecified order — flaky tests, non-reproducible output |
| 009 | Byte-wise string ordering, not strcoll | Lab 4 | Lua's locale-dependent ordering — different sorts per machine |
| 010 | Host-controlled module resolution | Lab 22 | A filesystem search path — ambient authority, traversal |
| 011 | Engine is !Send + !Sync | Lab 19 | Send via Arc (atomics everywhere); Sync via a lock (worse than one per thread) |
| 012 | Globals are a Table, not _ENV upvalues | Scope and Environments | Full _ENV — more flexible, needs upvalues first |
| 013 | Production profile: partially-trusted, in-process | Lab 23 | Claiming hostile multi-tenant — not achievable in-process |
| 014 | Cranelift, not hand-emitted machine code | Lab 30 | Hand-emitted x86-64 — weeks on encoding, never reaching deopt |
Also Recorded Along the Way
Not numbered in the plan, and each still deserves an ADR when you make it:
| Decision | Where |
|---|---|
Byte strings, not UTF-8 String | Lab 16 |
| Absolute jump targets, not relative | Opcode reference |
Op as a typed enum, not a packed word | Constant Pools and Encoding |
| No string→number coercion in arithmetic | Lab 4 |
| Left-to-right operand evaluation, guaranteed | Lab 3 |
No __gc, __mode, __close, __metatable | Metatables |
pcall does not catch Limit or Host errors | Lab 21 |
| The JIT is off by default | JIT Architecture |
From the Capstone Projects
Each supersedes or confirms an earlier one. Confirming is a fine outcome — and it is now backed by a measurement rather than a preference.
| # | Decision | Supersedes |
|---|---|---|
| 015 | Register VM: keep or reject, with numbers | 002 |
| 016 | NaN boxing: keep or reject, with numbers and an unsafe line count | 004 |
| 017 | Incremental GC: keep or reject, with a pause distribution | 006 |
| 018 | Coroutines: the per-coroutine/shared split | — |
The Decision That Keeps Recurring
Determinism decided five separate questions, from five different directions:
| Question | Determinism's answer |
|---|---|
| Recursion limit: counted depth or a real stack probe? | Counted — a probe makes the failure point machine-dependent |
| Execution limit: instructions or wall clock? | Instructions — a deadline fails differently on a loaded machine |
| Table iteration order? | Insertion — unspecified order makes pairs tests flaky and output irreproducible |
| String ordering? | Byte-wise — strcoll sorts differently per locale |
math.random in SAFE? | No — the host supplies a seed |
When one criterion keeps deciding things, promote it to a written principle. Ember's lives in
docs/architecture.md, and it is the reason all five answers are consistent rather than
independently reasonable.
Self-check
- Why must an ADR be written at the time of the decision?
- Which part of an ADR is the part you cannot reconstruct later?
- Why supersede rather than edit?
- Which five design questions did determinism decide, and what does that tell you to do?
- Pick three ADRs at random and state the rejected option and its real cost, without looking.
Next: Lua Differences.