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/ and devlog.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 .d marking 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 the README.stub it is rendered from.
  • README.md meant opposite things in different directories: hand-written and lint-required in docs/, generated and lint-forbidden in decisions/ and devlog/. 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 output is separate from its dir. 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.yaml live with the sources. They are authored, so they belong on the write side; the stub’s links resolve from where the index renders, and link_base knows 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 .d at the top level (decisions.d/, principles.d/ beside changelog.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/ — and docs/ 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/decisions had 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.