ADR-044: The configuration reference is generated from the config schema

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

Context

Luria’s capability surface is luria.toml. A scheme, a fragment directory, a journal, a remote and the site are five composable primitives, and the record this package keeps for itself — decisions, principles, a changelog, a devlog — is one instantiation of them. ADR-006 decided that in the abstract, and the code has been honest to it ever since: luria new’s kinds derive from the config, Scheme carries a prefix, and the directive vocabulary says inactive-ok rather than adr-ok precisely so a second scheme costs a table and nothing else.

None of that reached a reader. The configurable surface was documented in three places, all of them arrived at too late:

  • config.py’s docstrings, which are in practice the configuration reference — every dataclass carries prose plus a worked TOML example.
  • The decision recordADR-006, ADR-024, ADR-028, ADR-036. This is the archaeology layer: it answers why, and a reader consults it after already knowing the thing exists.
  • template/luria.toml’s comments, which are good, and which a project sees after luria init — that is, after the adoption decision is made.

The read surface had no reference layer at all: docs/ contained no configuration page and no mention of luria.toml outside generated ADR summaries. The predictable result is that the shipped four layers read as Luria’s parts. That is a documentation failure with a design cost, because it suppresses the question — “could this hold RFCs, a meeting log, arXiv citations?” — whose answer has been yes the whole time.

Decision

luria index renders docs/configuration.md, from the schema rather than from prose kept in step by hand.

Two derivations, both mechanical. Section prose comes from inspect.getdoc() on each config dataclass, so those docstrings stay the single source and their indented TOML examples are re-fenced for a page. The key tables come from dataclasses.fields().

The key tables are read, never listed. This is the invariant a future change must not break. A field added to Site appears in the reference on the next build, with its type and default, because nothing anywhere holds a list of keys that someone could forget to extend. Where classification is hand-held — which keys Luria fills from the table’s own name, which derive from issue_url — it is presentation only, and a field missing from those sets still gets a row, merely labelled *required*. Being wrong in that direction produces a row with a poor label; being wrong in the other direction produces a key nobody can discover, which is the failure this exists to prevent.

The page registers in Config.is_generated, which does double duty: luria index --check and luria lint cover its staleness like any other view, and doc_refs.doc_files filters on the same flag, so a page built out of example codes (ADR-012, RFC-7, LU-ADR-013) is skipped by the bare-reference lint and left alone by luria link --fix. The status reports established that pattern; this follows it.

The prose that frames the page lives in the module, not in a per-project stub — see the summary’s rejection of the alternative.

Alternatives considered

  • Write the page by hand. The obvious one, and the reason the page did not already exist. It is the second copy of a schema that changes — the hand-maintained projection DP-3 names — and it would drift in the worst direction: silently, and specifically for newly added keys, which are the ones a reader most needs documented. A doc that is right today is exactly what a hand-written one looks like.
  • Leave it in the docstrings. They are already good, which makes this tempting. But they are not browsable, not linkable from a badge or an index, invisible to the published site, and reaching them requires knowing which module to open — the precise knowledge a newcomer lacks.
  • A per-project README.stub for the prose. Consistent with how schemes work, and wrong here: a scheme’s stub exists because a project’s decision index needs its own preamble. The configuration reference is Luria describing Luria’s schema, identical in every repository that installs it.
  • A general autodoc dependency. Heavyweight, and it renders module layout when ADR-030 says the surface is the workflows.
  • Status quo. Costs the question nobody asks. Every adopter who wanted RFCs, a second journal, or linted arXiv citations had to read source to find out the tool already did it.

Consequences

The docs gain a reference layer, and docs/README.md gains its entry — which luria lint’s docs-index check demanded immediately, unprompted, on the first run after the page appeared.

What this obliges: the config dataclass docstrings are now published, not internal notes. Rewording one changes a page a reader browses, and a change to either the docstrings or the fields makes the committed view stale — so luria index is now part of changing config.py, not only part of changing the record. That is the intended coupling; it is also a new way to see a red build, and worth knowing before it surprises someone.

Both guards were fired once before being trusted (DP-6), and the devlog entry for this work records both. A hand-edit to the page makes luria index --check exit 1 and name it. A throwaway field added to Site appeared as a documented row without the generator being touched, and made the committed page stale — which is the whole design demonstrated in one command.

What this does not fix: adding a scheme costs one table, but renaming one, or moving its documents, is still a manual pass. The reference says so under “What is not configurable” rather than leaving a reader to discover it, and ADR-040 is the decision that would give it a command.