ADR-067: A code relates to named URIs through one template vocabulary
Status Active · Filed 2026-08-28 · Issue #135
Context
The remote machinery grew one mechanism per relation between a code and a
URI. The reader’s link had four: url templates at two levels, the
document/anchor shape (ADR-023), and a hardcoded GitHub blob
construction fed by the discovered filename map (ADR-016). Content pins
(ADR-066) added two more: pin_url templates duplicating the whole
precedence story in a parallel method, and — worst — a regex that parsed
the already-rendered blob URL back apart to derive its raw counterpart,
which meant Luria constructed a URL from parts it held and then read the
parts back out of its own output, for one forge only.
Every future relation — another forge’s raw scheme, an edit view, an API endpoint — would have meant another field, another mirrored method, another special case. That is the drift DP-4 names, one axis up: not five copies of one glob, but five mechanisms for one idea.
Decision
A code relates to a set of named URIs. read is where a reader lands;
bytes is what a content pin hashes; a project may declare further names
in a uris table on a remote or one of its schemes, and they render
through Remote.uri — one call, one template vocabulary: {code}, {number},
{prefix}, {repo}, {ref}, {dir}, {document}, {anchor}, {filename}. url
and pin_url remain the short spellings of uris.read and uris.bytes,
folded together at load; setting both spellings to different values is a
config error rather than a silent winner.
Three properties are load-bearing:
- The forge lives in data. GitHub’s blob/raw pairing is the shipped
default pair of
read/bytestemplates for a remote with arepo— the rebase regex is deleted, and a different forge is a different pair of template lines. - The lockfile’s authority is variable availability, not control flow. {filename} comes from the discovered map: no map, and the code-only convention fills it; a map whose keys omit the code, and the variable is unavailable, so any construction needing it renders "" — the old veto, now reaching custom templates too. A template that never mentions {filename} never consults the map, which is the old rule “the lockfile covers exactly what discovery can see” stated once instead of enforced twice.
- A chosen template renders or fails visibly. Precedence picks a template first and renders second; a template that cannot fill a variable yields "" rather than falling through, so a misspelled variable surfaces as a dangling reference instead of hiding behind a working convention (DP-1).
Precedence for read is unchanged from ADR-023: the scheme’s template,
then the scheme’s shape, then the remote’s template, then the remote-level
construction. For every other name a declaration beats a derivation at
either level (ADR-066), and a derived default exists only where read
itself resolves by construction — a remote whose read is a template has
said nothing about its bytes, so none are guessed.
Alternatives considered
- A field and a method per relation — the status quo, and the path of least resistance each time. Two relations in, it had already produced a mirrored precedence implementation and a regex reading Luria’s own output; every further relation would compound it.
- Keep the rebase regex — it made bytes free for any URL that happened
to render blob-shaped, including a
urltemplate pointing at GitHub. That freeness was the problem: bytes derived from parsing a rendered page’s address is a guess wearing a construction’s confidence, and it bound the derivation to one forge’s URL anatomy forever. The one behavior this decision changes: a blob-shapedreadtemplate no longer implies bytes — declaringuris.bytesdoes. - Per-forge builtins (
forge = "gitlab") — solves only the forge case, adds a registry Luria must maintain, and answers “what about my edit URI?” with another mechanism. Templates subsume it; a forge shorthand can still be added later as sugar that expands to templates. - Status quo — each new relation costs a subsystem, and the sixth looks like the fifth.
Consequences
Nothing a project wrote changes meaning: url, pin_url, document,
anchor, dir and the lockfile keep their spellings and semantics, and
the whole prior test suite passes unchanged on the unified renderer.
Custom templates gain {filename} — a GitLab raw scheme with slug filenames
now works, authority semantics included. A declared name without a
consumer renders and waits; luria remotes --check still probes read,
pins hash bytes. The history:-style relations this enables are a name
away rather than a design away.