ADR-047: A declared family replaces the default; settings still merge

Status Active · Filed 2026-08-11 · Influenced by ADR-006 · ADR-045 · Influenced ADR-048

Context

ADR-006 made document families configuration, and ADR-045’s executable examples measured how far that promise actually reached. Two of the limits they pinned shared one root cause: configuration merged over DEFAULTS with a single per-key rule, everywhere.

  • The shipped ADR scheme could not be removed. A record of RFCs and specs still carried an ADR scheme pointing at record/decisions.d and still rendered an empty decision index.
  • A declared scheme’s omitted key inherited the default entry’s value. [luria.schemes.ADR] dir = "decisions" with no output — the natural way to adopt without moving files — inherited output = "docs/decisions" and silently relocated the index.

Both were documented in the configuration reference under “What is not configurable”, asserted by tests, and worked around in examples/collocated/ with an output = "decisions" line whose only job was to defeat the inheritance. A limit that has to be that well-documented is a bug with good paperwork.

Decision

Two merge rules, split by what a table is.

Settings tables merge per key: paths, code, lint, site, and the top-level scalars. Their keys are Luria’s vocabulary; a project setting one of them is overriding, and clearing the others would turn every partial override into a broken config.

Family tables replace whole when declared: schemes, fragments, journals, remotes, listed in config.FAMILIES. Their entries are the project’s vocabulary — the table’s keys are names the project chose — so declaring the family is authorship, not adjustment. Declared, the family is exactly what was written; undeclared, the shipped default stands.

The consequence that pays for the change: omission is meaningful again inside a declared family. An output left out is genuinely unset, because no default entry survives to be inherited from. The Scheme.output docstring said omission collocates the view; it is now true without an asterisk.

Alternatives considered

  • A removal sentinelADR = false, or enabled = false per entry. Works, and invents a second vocabulary whose only purpose is to un-say what the merge rule said. A config format where absence means “default”, presence means “mine”, and false means “actually absent” has three states for a two-state question.
  • Replace on any subtable write. One rule instead of two, but the wrong one for settings: [luria.site.theme.light] would clear the rest of the site table, and every partial override becomes a trap. The two-rule split is the finding, not a complication of it.
  • Status quo. Two permanent warning labels in a generated reference, a workaround line in an example, and every RFC-only adopter rendering a decision index for a scheme they never asked for.

Consequences

The two pinned limits inverted, and their tests fired when the rule changed — which is what the pins were for. examples/rfcs-and-specs now has no ADR scheme at all, examples/collocated collocates by omission with the workaround line retired, and examples/many-journals demonstrates the other half: an undeclared family keeps its defaults.

The migration cost is real and narrow: a project that declared part of a family expecting the rest from the defaults — [luria.schemes.DP] alone, counting on ADR to persist — now owns the family it declared and must write the missing entry. The shipped template and this repository both declare their families in full, so the shape luria init has ever produced is unaffected; the changelog carries the upgrade note.

doc_refs.adr_paths() now answers {} for a project with no ADR scheme, and the index-stub fallback names the scheme it renders — both hardened by the same examples that found the limits.