ADR-021: The read/write boundary: views in docs/, sources in record/
Status Active · Filed 2026-08-04 · Issue #3 · Influenced by ADR-012 · ADR-013 · Influenced ADR-027 · ADR-042 · ADR-044 · DP-009
Context
The layout had grown one decision at a time, and the accumulated result mixed the two things a project-memory repo contains — ground truth someone files and views someone reads — at every level:
changelog.d/anddevlog.d/sat at the top level, sources exposed as if they were reading surfaces, while the other two source containers (docs/decisions/,docs/principles/) carried no.dmarking at all.docs/design-principles.md— generated — was a sibling of the sources it is generated from.- The decision index was buried under its own scheme’s
ADR-sort, sitting beside theREADME.stubit is rendered from. README.mdmeant opposite things in different directories: hand-written and lint-required indocs/, generated and lint-forbidden indecisions/anddevlog/. Same filename, inverted affordance.- The devlog’s entrypoint sat two clicks deep and read as an archive.
Every rule about which of these files a person may edit lived in HTML comments and lint errors — discovered after landing in the wrong place. The record already had the load-bearing distinction (ADR-012: views are a pure function of sources that persist); the tree just didn’t express it.
An earlier attempt sliced the same discomfort along the wrong axis: moving
Luria’s own record into a meta/ directory, separating whose content it was
rather than what a person does with it. It worked, and it was closed
unmerged, because the deliverable was already the template/ directory —
the whole repo is implicitly meta — and the actual confusion it surfaced
(sources and views interleaved) applies to every adopting project, not just
this one.
Decision
One boundary, stated as a browsing property: browsing lands you on views; only a link or a deliberate act of filing lands you in sources.
docs/ READ — prose + every generated view
project-memory.md authored prose
decisions/README.md generated index
decisions/tags/<tag>.md generated
design-principles.md generated
devlog/ generated books + index
record/ WRITE — all ground truth
decisions.d/ ADR-*.md, _template.md, README.stub, tags.yaml
principles.d/ DP-*.md, _template.md, README.stub
changelog.d/ fragments, collected then consumed
devlog.d/ yyyy/mm/dd/hhmmss.md, persist
The mirror is the teachable part: what you read at docs/X you write at
record/X.d. The .d suffix inside record/ is deliberate redundancy — a
visual affordance that you have entered a special domain of the tree, kept
even though the root already says so, because a deep link drops you inside
the tree where the root isn’t visible.
Four consequences that are mechanism, not just placement:
- A scheme’s
outputis separate from itsdir. The index and tag pages render into the view; the sources stay put. Unset, the two coincide — the collocated layout every pre-existing project has — so adoption never begins with a move. - The stub and
tags.yamllive with the sources. They are authored, so they belong on the write side; the stub’s links resolve from where the index renders, andlink_baseknows it. - A view directory holds only what the generator wrote. Anything else in one is a lint failure. This is the payoff: “don’t hand-edit” stops being a comment and becomes a checkable property with the right failure polarity (DP-3).
- The journal’s front page leads with the current book’s contents. Hot on top, the shelf below — without it the newest writing sat behind two clicks and the whole journal read as an archive.
CHANGELOG.md stays at the repository root: it is the one view whose location
is a universal convention that outranks this layout.
Alternatives considered
- Generalize
.dat the top level (decisions.d/,principles.d/besidechangelog.d/). Preserves the familiar convention, but it is four write locations that don’t even sort adjacently, and it answers “where do I file?” with a naming rule rather than a place. The suffix was doing exactly the job a single write root does better — so the root does the segregating and the suffix stays on as the local warning sign. - Segregate by project instead — ours in
meta/, the deliverable everywhere else. Built, measured, closed unmerged. The wrong axis: it fixed which audience met which content while leaving sources and views interleaved inside both halves. - A dot-directory write root (
.luria/). Maximum “harder to land on accidentally”, but contributors file there every day, and hiding the daily workspace punishes the person the layout exists to serve.record/is visible and self-describing; the prose already calls this “the record”. - Per-document generated view pages (a
docs/page per ADR, so links never leave the read tree). Doubles every document for no reader benefit — a source arrived at by link is the reading experience; the problem was only ever what browsing lands on. - Status quo with better comments. The comments existed. The inventory above is what they achieved.
Consequences
- A visitor browsing the repo meets
README.md,docs/,record/,template/— anddocs/now reads top-to-bottom as prose and entrypoints, with the generated indexes carrying their own navigation. - Links from views into
record/are long (../../record/decisions.d/…). Accepted: they are generated, and nobody types them. - The decisions index is no longer buried — its directory contains only the index and its tag pages.
- Fixture code that spelled
docs/decisionshad to learn to ask the config (DP-4); tests now assert resolution rules rather than current addresses, which is what lets the next move — if there is one — cost a config edit. - The standing value this applies — structure is read before text, so affordances are spent deliberately — is DP-9; this layout is its worked application, not the principle itself.