ADR-060: Schemes declare their own shape — where the vocabulary lives, and what a field means

Status Active · Filed 2026-08-24

Context

An anthology of ML training practice adopted this package with four schemes: LIT (one note per paper), SOTA (one document per recommendation, citing the note it came from), plus ADR and DP. The scheme machinery handled that without complaint. Two things about the model had nowhere to go.

The shared vocabulary was written four times. Twelve topic categories, seven of them carried by both content schemes. They appear in two tag_groups lists in luria.toml, and again with labels and blurbs in two tags.yaml files — roughly eighty lines of configuration for one twelve-term vocabulary. Nothing relates the four copies, so nothing noticed when they disagreed, and they already did before the migration merged: the same tag carried a different blurb in each scheme, written days apart by one author.

That is this project’s own founding observation happening inside this project’s own configuration surface. tags.yaml exists so a browsing vocabulary is stated where it is defined; a second scheme wanting the same vocabulary had no way to say so.

The relationship between the schemes could not be stated at all. The rule that justified splitting them — a practice names the paper behind it — was written as requires = ["source"], and a design principle was written about it. Tested against the live record by editing one document and re-linting, requires enforces less than it appears to:

source:result
LIT-001passes — correct
ADR-001passes silently — a practice citing a decision as its evidence
'a paper I read once'passes silently — not a code at all
LIT-999a warning, from the generic dangling-code check

requires asks whether a field is truthy. It has no way to ask what the field means, so the rule the two-scheme split existed to enforce was enforced only in the sense that the field was not blank.

Decision

Let a scheme say both things.

A scheme may name where its vocabulary lives, so two schemes can share one file:

[luria.schemes.SOTA]
tags = "record/topics.yaml"

A tag may say which schemes carry it as a primary, where the tag is defined — which removes the tags list from the group entirely, and lets one file give two schemes different primaries without repeating the shared part:

training-optimization:
  label: Training optimization
  primary_for: [LIT, SOTA]
generative-modeling:
  label: Generative modeling
  primary_for: [LIT]          # the reading list only

A group that lists no tags derives its membership from those keys. An inline list still wins where one is written, so nothing existing changes.

A scheme may declare what a reference field holds:

[luria.schemes.SOTA.references]
source = { scheme = "LIT", required = true }

Four checks where requires gave one: present, shaped like a code, belonging to the named scheme, and resolving to a document. A reference naming a scheme that is not declared is a config error, raised at load like the other family validations.

Alternatives considered

  • Leave it as requires and document the limit. Cheapest, and it leaves a design principle in a downstream record asserting a guarantee the tool does not provide. The gap is not a documentation gap; a field that accepts any truthy value is not a citation.
  • Validate reference fields with a regex per scheme. A pattern key would catch the arbitrary-sentence case and not the wrong-scheme case, and it would put the scheme’s own naming convention into a second place — which is the duplication half of this decision, reintroduced on the other axis.
  • Scheme inheritance or a defaults table. Would also shrink the config — our four schemes repeat render, active, and an output convention — and it shrinks it without letting the config say anything it could not say before. Duplication that a reader can see and check is not the problem here; duplication that nothing relates is. Not doing it.
  • Derive the vocabulary from usage. Collect the tags documents actually carry and skip the declaration. That is already the behaviour for unlisted tags, and it cannot express “this term is a primary for that scheme and not this one”, which is the distinction being drawn.
  • Generated backlinks in the same change. A declared reference makes “practices drawn from this paper” derivable, and the downstream record left that out precisely because a hand-maintained list drifts. It is the obvious next step and it is a rendering change rather than a configuration one, so it is deliberately not here.

Consequences

Both additions are opt-in and inert until declared. A scheme with no tags key reads the collocated file; a group with an inline list behaves as before; a scheme with no references table is unchanged.

requires and references now overlap for the required case. That is worth being explicit about rather than deprecating one: requires is the right tool for a field that is simply mandatory (arxiv: on a paper note is not a reference to anything in the record), and references for a field that names a document. A field listed in both is checked twice and reports twice, which is noise — the configuration reference should say so.

A shared vocabulary means one tags.yaml can be edited from two schemes’ directories, which is a small loss: the file no longer sits beside the only documents that use it. That is the trade the duplication was buying, and the measured drift says it was not worth its price.