ADR-046: Reference detection is scheme-driven, not three hardcoded patterns

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

Context

ADR-006 decided that a referable document family is a [luria.schemes.X] entry rather than a hardcoded prefix, and the vocabulary followed: the acknowledgement verb is inactive-ok, not adr-ok. Rendering honoured it, luria new honoured it (ADR-036), and the configuration reference (ADR-044) described it.

The linter did not. doc_refs.find_refs matched exactly three patterns:

for kind, regex in (("dp", DP_RE), ("adr", ADR_RE), ("issue", ISSUE_RE)):

ADR_RE is a module constant spelling ADR. DP_RE matches the prose spelling — design principles #17 — and has no branch for the code spelling

DP-17. resolve matched the same three kinds, with adrs meaning the ADR scheme’s documents and the fallthrough meaning principles.

Two consequences, both invisible until someone ran a configuration that used them:

  • A configured scheme was never checked. examples/rfcs-and-specs renders an RFC index and a SPEC document, scaffolds with luria new rfc, and cited SPEC-001 from an RFC in plain prose. luria lint passed. The reference was neither linked nor reported — the state the whole package exists to prevent.
  • DP-6 was a documented lie. CLAUDE.md and the scaffolded template both say to write the bare code and let luria link --fix spell the target, naming DP-6 explicitly. A bare DP-6 matched nothing, so it was never linked and never flagged: the worst of the three possible behaviours, because silence reads as approval. Applying the fix to this repository linked 38 references that had accumulated unseen.

Neither was found by review. Both were found in the first minute of running a worked example, which is the argument ADR-045 makes.

Decision

find_refs builds its pattern list from current().schemes, tagging each match with kind="scheme" and the prefix it matched under. Ref gains prefix; kind collapses to "scheme" | "issue" | "remote".

DP_RE still runs, first and separately, because it matches the prose spelling that no scheme pattern covers — design principles #17, and the sibling chain #17 and #18. The code spelling arrives with the schemes, like every other prefix. Remotes still claim their spans before any local pattern, so LU-ADR-013 is never read as a local ADR-013 (ADR-016).

resolve dispatches on the scheme’s render mode: an index-rendered scheme resolves to the document’s own file, a document-rendered one to an anchor in the assembled page. Discovered anchors win over constructed ones where they exist, which keeps working the project whose principles are still one hand-written file with heading-derived anchors (ADR-012).

One guard is load-bearing and was not obvious. A document never links to itself. For an index-rendered scheme that falls out of target == source, because the document is the file being scanned. For a document-rendered one the source is the fragment and the target is the page it assembles into — different files, so the existing test does not fire. Without an explicit check, every principle’s own # DP-001: heading became a link into the assembled document, and check_title then failed on a heading that no longer matched its frontmatter. That is what the first run did, across every principle at once.

Alternatives considered

  • Add RFC_RE, SPEC_RE beside ADR_RE. The same mistake with more constants, and it cannot work: the prefixes are not known until a project’s config is read.
  • One Ref.kind per scheme ("rfc", "spec"). Reintroduces exactly what ADR-006 rejected — ADR as a special name in the vocabulary — and every kind == comparison becomes a lookup against config anyway.
  • Fix DP_RE only, since that was the reported symptom. Would have left every other configured scheme unlinted, and the report was a symptom of the general defect, not the defect.
  • Delete the claim from the guide instead. docs/adopting.md says RFC-7 is a first-class reference that luria link --fix writes and luria lint demands. Making the sentence true by removing it trades a feature the package already promised for an accurate paragraph.
  • Status quo. The cost is not hypothetical: 38 unlinked references in this repository alone, and any adopter with a second scheme running an unchecked record while the badge said clean.

Consequences

Every configured scheme is now linted, linked and reported on equal terms, and cross-scheme references resolve in both directions — an RFC citing a SPEC gets an anchor into the assembled page, a SPEC citing an RFC gets a file link, each from the base where its text renders.

This repository gained 38 links in one pass. Read a sample rather than trusting it wholesale — that advice is already in the adoption guide, and following it is what caught the self-linking headings before they were committed.

An adopter upgrading will see the same effect at their own scale: references their record has been carrying unchecked become violations at once. That is the correct direction — they were always wrong — but it is a one-time luria link --fix pass that wants reading, not a silent upgrade. New warnings are also expected where a scheme code resolves to nothing; two illustrative codes in doc_refs.py’s own prose needed an unresolved-ok directive for precisely this reason.

ADR_RE stays a module constant. The code-comment scan expects that shape, and that scan is a different question from prose references; generalizing it is its own decision, and not one to make while it is not the thing being fixed.