Load-bearing — fixes something an adopting record must write: a frontmatter field, a path on disk, the spelling of a code, or directive syntax in prose. Reversing it does not just change luria’s behaviour, it invalidates records that have already been filed.

20 of 121 decisions. Back to the full index.

#TitleSummaryStatus
ADR-003 v2Status is a closed vocabulary in frontmatter, enforced by lintA decision’s status comes from a closed vocabulary (Active | Proposed | Deferred | Superseded | Rejected, plus an optional em-dash note) and lives in YAML frontmatter alongside tags, date and issue — with the lint enforcing both. The vocabulary is closed because an open one drifts into synonyms: a strata-g audit found ~30 distinct status forms, with Accepted and Active split 44/46 and meaning the same thing. Frontmatter rather than a prose header because the index is generated from these fields (ADR-004), and parsing prose to build it puts a regex between a decision and its own metadata. Deferred earns its place: postponement stated is better than postponement faked as Proposed. Rejected: free-text status (drifted), and keeping the bold **Status:** header alongside frontmatter (two copies to drift, DP-4).Active
ADR-005References in prose are hyperlinks, enforced by lintEvery document code, design principle and issue number cited in prose is a hyperlink, and luria lint fails on one that isn’t; luria link --fix writes exactly the links the lint demands, from the same scanner, so the linter can never demand a rewrite the fixer wouldn’t make (DP-4). In the corpus this was extracted from, 2,246 references across 160 files were bare — and the split between linked and bare was random, which is worse than uniformly bare because a reader can’t learn which references are worth clicking. Non-obvious rules, each found by a wrong rewrite: markdown isn’t parsed inside a raw HTML block (those get an <a href>); backtick pairing is per-paragraph or one stray tick inverts code-vs-prose for a whole file; a low #N is ambiguous and stays bare without an explicit cue; fragment links resolve from the file the collector puts them in. Rejected: absolute URLs everywhere (breaks offline and non-GitHub reading), and a prose rule with no lint (the surface that drifted).Active
ADR-008One directive vocabulary, one scope ruleComment directives share one parser, one shape — <name>[-block|-file]: <args> — <reason> — and one scope rule with no per-directive defaults: bare is the line and the line below, -block the run of non-blank lines it sits in, -file the document. inactive-ok acknowledges a deliberate reference to a retired document; unexempt is the inverse, putting a region the linter skips (a code block) back under it. The directive must OPEN its comment, # noqa style, and directive-SHAPED text is never a citation — both rules exist because documenting the syntax kept invoking it, four separate times. A first pass gave unexempt an implicit block scope so that a plausible example would parse; that bought one example and cost the rule its predictability, and was removed. Rejected: per-directive default scopes, and a wildcard that would mute a whole file including references added later.Active
ADR-013A document’s filename is its code; the title lives in the frontmatterA scheme’s documents are named for their code alone — ADR-013.md, not adr-013-a-documents-filename-is-its-code.md — and the title moves into a title: frontmatter field, which the generated views prefer over the body’s H1. A slug in the filename is a third copy of the title that no tool reads, that a rename plus every inbound link is required to correct, and that therefore never gets corrected. The body H1 stays, because someone opening the file alone needs a heading, so luria lint guards that the two agree — rung 2 of DP-3, since rung 1 isn’t available. Filename decoding, which had accumulated five separate copies, is now Scheme.documents() alone, and it reads legacy slug filenames so adoption isn’t a rename-everything-first proposition. Rejected: dropping the H1 (unreadable on its own), and enforcing the short filename on adopters (a convention choice, not a defect).Active
ADR-016Remotes are public URLs, Luria is its own worked example, and every scheme versionsSupersedes ADR-015, keeping its core — a foreign record is cited as LU-ADR-013, a registered prefix composed with that project’s own code, and the config builds the URL — and dropping the part that bent the tool around one repository. Discovery reads public HTTPS only; the local-clone option is gone, because a resolution that depends on what happens to be on somebody’s disk is not reproducible and quietly made the ancestor’s pre-convention shape Luria’s problem. Luria registers itself as remote LU, which the luria init scaffold cites instead of pasting URLs, so the mechanism is exercised by the package rather than only by its tests. Also: version: becomes standard frontmatter for every scheme, not just principles, shown in the index only when it isn’t 1; and *.stub files are linted, closing a hole where hand-written prose rendered into a page the lint skipped for being generated. Rejected: keeping the clone path behind a flag (the reproducibility problem is the same), and a credentialed fetch.Active
ADR-019A wrong reason is corrected in place and versioned; a changed choice is supersededTwo ways a decision record can be wrong, and only one of them is a supersession. If the choice changes, add a new decision and retire the old one — the rule ADR-001 has always stated. If the choice stands but a reason for it turns out to be wrong, correct the body in place, bump version, and say what changed in history:. Superseding over a bad argument is theatre: it retires a decision still in force, and every citation of it has to be repointed at an identical claim. Leaving the argument standing is worse, because a wrong reason is exactly what gets quoted at the next person. The version field makes the correction visible, which is the whole reason “never rewrite a body” exists — the objection is to silent revision, not to being wrong out loud. Rejected: superseding for any change (churn, and it makes the status vocabulary lie), and correcting silently (indistinguishable from rewriting history).Active
ADR-020The devlog is a journal: dated entries that persist, rendered into booksThe devlog stops being a collected view and becomes a journal: entries live at devlog.d/yyyy/mm/dd/hhmmss.md, persist, and are rendered into one generated book per period plus an index. Identity is the authoring timestamp, so there is no number to assign and no name to collide on; ordering is a pure function of the tree rather than of commit order, which a rebase can change. Entries carry the standard frontmatter — title: is what each book’s contents list shows — and luria lint checks the path agrees with created:. Rejected: a dated file per period appended to directly (two branches in the same month still conflict, which is most of them), a single growing docs/devlog.md (8,281 lines in 40 days at the pilot’s rate), and slugs in the filename (a second name for the thing title: already names, DP-3).Active
ADR-021The read/write boundary: views in docs/, sources in record/The repository layout is split along what a person is doing: docs/ holds everything a reader browses — prose and every generated view — and record/ holds everything a contributor files, each container inside carrying the .d suffix as the visual affordance that you have crossed into the write domain. A scheme gains a separate output so its index renders into docs/ while its sources live in record/; the stub and tags.yaml move to the source side, so a view directory holds only what the generator wrote — which turns “don’t hand-edit” from a comment into a lint. Rejected: marking sources with .d at the top level (four write locations, no single answer to “where do I file”), segregating by project into a meta/ directory (the wrong axis — it split ours-vs-theirs when every repo’s actual confusion is read-vs-write), and burying the write root as a dotfile (contributors work there daily).Active
ADR-024A remote reference is a prefix, a delimiter and a uid — numbers are the special caseA remote need not hold a Luria-shaped record. Give it a uid regex and its references are the prefix, a configurable delim and whatever the pattern matches — an arxiv id, a ticket key — constructed through the url template, which can address the uid’s capture groups by position ({1}.{2}) or take the whole tail as {0}/{uid}. A uid is exact and is never normalised: zero-padding an arxiv id would quietly cite a different paper. One rung only — no lockfile, no code-only convention, and a uid remote without a template constructs nothing rather than guessing. The refactor this forced was overdue anyway: one combined regex with a hardcoded hyphen became per-remote patterns behind references() and parse_code(), so the delimiter and tail shape are spelled in one place. Rejected: overloading schemes for uid families (a scheme implies documents with statuses and an index; an arxiv paper has neither), and auto-linkifying unregistered shapes (an unconfigured prefix must never match).Active
ADR-025Wikilinks: [[CODE]] is a typed reference, and typing it changes the rules[[ADR-013]], [[SG-DP-18]], [[ARXIV-2403.05530|the report]] — wikilink brackets are the author asserting “this is a reference, link it”, and the assertion changes the rules on both sides. Inside the brackets no prose heuristics apply: a bare DP-3 (no #) resolves, a low [[#10]] needs no cue, any registered remote shape works. And an unresolvable wikilink is a lint violation the fixer cannot clear — the one deliberate exception to “the lint never demands what --fix won’t do”, because an explicit request deserves an explicit refusal rather than a silent skip. Wikilinks are consumed: luria link --fix rewrites them to ordinary markdown links (an <a href> inside raw-HTML blocks), because GitHub renders [[…]] as literal brackets and source files must read as plain markdown wherever views aren’t generated. Rejected: expanding at view-generation time (fails everywhere sources are read directly), and heuristic-free auto-linking without brackets (already rejected in ADR-024 — this is the sanctioned way to opt a single reference out of the heuristics).Active
ADR-049Temporary codes at filing; concretized and aliased at the serialization pointA scheme can allocate its numbers at the merge point instead of at filing. With allocate = "merge", luria new adr issues a temporary code — ADR-tmp47fje, visibly provisional and never readable as a number — and the document is first-class on its branch: indexed, linted, citable as [[ADR-tmp47fje]], linked by the fixer like any neighbour. luria concretize, run wherever merges serialize (a merge queue, the merge-to-main job), assigns the next free sequential numbers in merge order, renames the files, rewrites every reference in the tree, and records each temporary code in the document’s formerly: frontmatter — which the resolver honours forever, so a temp code cited in a PR thread, a commit message, or another repository’s LU- reference is never a dead link, only an old name. luria concretize --check is the guard for the trunk: a temporary code on main is always wrong and mechanically fixable, so it fails (ADR-035’s admission rule). The motivating failure: filing-time numbering is a distributed claim on a global counter, so two branches both mint ADR-123 and collide at merge — rarely for one human, structurally for N concurrent agent branches. Rejected: filing-time numbering (the status quo, which lies about order under concurrency); timestamp codes forever (no collision, but trades away the small citable numbers the record’s culture runs on); reserving numbers against main at filing (a coordination round-trip per document, and racy without the serialization it was trying to avoid); and coupling this to an intermediate branch (concretization needs a serialization point, not a branch — a queue or the merge job serializes fine, and the batching question is separable).Active
ADR-051Prose frontmatter is what the generator renders, not what the author preferssummary: was the single frontmatter key treated as prose — scanned for bare references, linked by the fixer, checked by the lint — while everything else was data read by value. origin: broke that split: it is rendered as markdown into a principle’s metadata line, so a reference written there should be followable, but the machinery silently left it as literal text, which is the worst of both (it renders as a link if hand-written, and rots unchecked forever). Decision: a PROSE_KEYS set, currently summary and origin, and the membership rule is stated — a key is prose exactly when the generator renders its value as markdown somewhere. Rejected: making the set configurable, because a project cannot make a field prose by declaring it so; the rendering is what makes it true, and a config knob would let a project ask for links in a field that will display them as raw text.Active
ADR-071Typed edges come from fields, and a superseded document names its successor in superseded_by:The citation graph had one kind of edge, “A mentions B”, while facts the record states in structure went unread: a declared reference field, an influenced_by: list, and the successor a superseded document names. They are now typed edges, the field name being the relation, and the site renders each page’s edges both ways. superseded_by: is a built-in reference field on every scheme — the successor written as structure and read as structure, checked, resolved, an edge — where two drafts of this decision inferred it from the by CODE shape of a status note and were corrected on review: a field is concrete and checkable, and a relation inferred from free text makes the author’s prose conform to a shape the tool happens to recognise. Rejected: that inference; leaving it to Quartz’s untyped backlinks; a relations DSL.Active
ADR-072The status note is its own fieldstatus: Superseded — by FX-ADR-032 was one scalar carrying two types: a word from a closed vocabulary, and a prose note that was rendered, rebased for links, and split off the word in six places. The note is now its own field, status_note:, and a prose key like summary: — a code in it is a citation the fixer links. The successor a superseded document names is a reference field, superseded_by: (ADR-071); the note is for what the field cannot say. A note still riding in status: is a lint finding that luria index repairs, the way it fills created: from a path (ADR-031). Amends ADR-003, which placed the note after an em-dash. Rejected: leaving the scalar and parsing it forever, which keeps the field’s type a lie; and scanning status: as prose without splitting it.Active
ADR-087Identity is a field; the filename is a projection of itA scheme document carries number:, and its filename is derived from it — the model journals have always used for created:, applied to schemes. Identity in the filesystem was the obstacle to expressive filenames and aliases, because anything the name encoded became load-bearing. A counter rather than a UUID: the sequence carries the order documents were filed, and merge-safety is already solved by temporary codes.Active
ADR-093Fixture schemes come from a reserved namespaceFX is reserved for fixture codes that RESOLVE (ADR-034); the names under it — FXL, and FXM where a migration fixture needs a second — are reserved for a fixture project’s own SCHEMES, the codes that must resolve to nothing and be claimed by nobody. A leading-prefix reservation, checked by luria lint at the config rather than at each citation, so a project cannot quietly take the guarantee away from itself. Retires the three unlinted-file: opt-outs that stood in for it, which blanked whole suites to excuse a handful of specimen codes and are the one suppression the reports cannot converge past (ADR-033). Rejected: masking string literals in the tree-sitter grammar (narrows what a directive governs, against the stated invariant, and CI has no grammar installed, so it would be inert exactly where the codes are), one reserved name rather than a namespace (a migration fixture needs two prefixes, and the second would be invisible only by luck), a per-site unresolved-ok: (93 acknowledgement directives already live in 21 test files), and refusing the prefix at config load (fixtures declare it deliberately, so the refusal would break the mechanism it protects).Active
ADR-095An acknowledgement can carry its own deadlineA directive may be written until <YYYY-MM-DD>; after that date find stops returning it, so every check behaves as if it were never written, and luria lint reports what expired. Rejected: a relative duration, which needs an anchor the file does not carry; and a version milestone, which needs a project-version concept luria does not have.Active
ADR-098One config file, one format, vocabularies declared onceConfig was TOML, vocabularies were YAML files beside each scheme’s records, and the lockfile was JSON — three formats for one system, with no reason any of them could state. Worse, a vocabulary two schemes share had to be two files: in the corpus that motivated this, ten of thirteen entries had silently drifted. Moves to one luria.yaml with a central vocabularies: table, omegaconf underneath the existing validation rather than in place of it. Rejects keeping vocabularies local, and rejects letting structured configs replace the semantic checks.Active
ADR-104A directive’s argument list is syntax; its reason is proseActive
ADR-105A mention is not a citation, and says nothing about statusActive