ADR-012: Principles are fragments too, rendered as a document
Status Active · Version 2 · Filed 2026-08-03 · Influenced ADR-020 · ADR-021 · ADR-026 · ADR-028 · DP-009
Context
docs/design-principles.md was one hand-maintained file. Every principle added
or revised edited it in the same place, which makes it the lock
DP-2 names — the same shape as the changelog and the
decision index before them.
It also could not answer two questions the principles themselves raise. Which decisions produced this? — the evidence that stops a principle reading as taste, per ADR-009. And has this been revised? — DP-2 and DP-3 were both first written scoped too narrowly and only generalized after a second instance forced it, which is the single most useful thing either of them teaches, and a flat document had nowhere to say so.
Decision
One fragment per principle, in docs/principles/, with frontmatter; the
document is generated from them.
status: Active
version: 2
influenced_by: [ADR-002, ADR-004]
origin: >-
Fragments assembled into a changelog; then the identical conflicts recurring
on the narrative log months later; then the decision index.versionbelabours the point that principles are living documents. A revised principle says it was revised, andhistory:records what changed.influenced_byis the inverse of the citation direction: decisions cite principles, and this names the decisions whose experience produced the principle. Rendered as followable backlinks.statusbrings principles under the same vocabulary as decisions (ADR-003), so a retired principle still cited is reportable exactly like a superseded decision.
A scheme now declares how its view is rendered:
render | output | right when |
|---|---|---|
index | a table of links, plus per-tag pages | documents are browsed and read one at a time |
document | bodies concatenated into one page | the set is read as a whole |
Principles are read as a whole — people cite “DP-3” and then read it among its neighbours — so they render as a document. Decisions are read one at a time, so they render as an index. This is the first exercise of ADR-006’s claim that a second scheme is a config entry and a directory, and it held: no scanner changed.
Generated, not collected — and the frontmatter is not the difference
The obvious objection is that this is what the changelog collector already does: concatenate fragment bodies into a view. Nearly. The difference is not that one strips frontmatter — it is whether the sources survive, and it is already the distinction ADR-002 draws:
- A collected view (the changelog) consumes its fragments. They are
deleted; the view accumulates; it can only ever be appended to.
CHANGELOG.mdcannot be rebuilt, because the fragments that produced last month’s entries no longer exist. - A generated view (decision index, principles document) is a pure function
of sources that persist. It is rebuilt from scratch every time, which is the
only reason
luria lintcan tell that one is stale.
Principles must be generated: they are durable, numbered, versioned and revisable, and the frontmatter is precisely what makes them so. Collecting them would delete the fragments — taking the version history with them — and would make a hand-edit to the assembled document undetectable, which is the property ADR-004 exists for.
The two conventions encode that difference honestly, which is worth noticing
rather than tidying away: a collected view keeps an insert marker in its
output, because collection appends to it again next time; a generated view uses
a {placeholder} in a stub, which does not survive into the output,
because generation rewrites wholesale. Same idea, opposite persistence, and each
convention is shaped by which one it is.
Alternatives considered
- Collect principles like a changelog. The shape matches; the semantics don’t. See above — deleted sources, no staleness detection, no version history.
- Leave the document hand-maintained. A lock and a drifting projection at once, which is exactly the pair ADR-004 removed from the decision index.
- Keep the frontmatter in the rendered document. Honest, and unreadable —
the document’s job is to be read start to finish. The metadata is rendered as
one italic line per principle instead: version, backlinks, origin, and the
status when it isn’t
Active. - A separate
luria principlescommand. A second command for the same operation, which is DP-4’s definition of a latent divergence.luria indexregenerates every scheme’s view, so a newly configured scheme is covered by the staleness check the moment it exists.
Consequences
- Adding or revising a principle is one file, and the assembled document can never silently disagree with its sources.
link_basehad to learn a second kind of fragment: a document-rendered scheme’s sources assemble into a file one directory up, so links written in them resolve from there — the same trap ADR-005 records for changelog fragments, arriving from a direction nobody was watching. Two links in the first eight fragments were already wrong; the lint caught both.- The docs-index check had to stop demanding index entries for scheme sources. A reader opens the view, not the fragments.
versionis hand-maintained, and therefore drifts by DP-3’s own argument. It is fail-loud rather than fail-stale — a stale version number is visible in the rendered line, right next to the text it describes — but it is a hand-maintained field and the honest thing is to say so here rather than pretend otherwise.