ADR-066: Remote content endorsed by hash, drift compared offline
Status Active · Version 2 · Filed 2026-08-27 · Issue #135
Context
A local citation is checked against the document’s own frontmatter: retire a decision and every unacknowledged citation of it is reported. A foreign reference has nothing to check against — the remote may supersede the document tomorrow, and the reference here would keep presenting it as justification indefinitely (#135). Status is upstream’s to declare and this project cannot read it; but “the content I endorsed is still the content there” is a claim about bytes, and bytes can be hashed.
The constraint that shaped the mechanism is the lockfile’s own (ADR-016): CI,
an offline checkout and a laptop must answer the same question the same way,
so luria lint never opens a socket.
Decision
Store two hashes per pinned document in remotes.lock.json. luria remotes --pin fetches a cited document’s raw bytes and records their hash as
endorsed — a human’s claim, which only --pin may move. luria remotes --refresh re-fetches every pinned document and records what upstream serves
as seen, never touching the endorsement. The lint compares the two
committed hashes offline and reports each disagreement under the
remote-drift warning class — reported by default, promotable through
fail_on like every other class (ADR-035).
Re-endorsing is the acknowledgement: review the change upstream, run
luria remotes --pin CODE, and both hashes agree again. No comment directive
exists for the class, deliberately — the judgement lives in the lockfile, not
in prose at a citation site, so there is no second place for it to go stale.
What gets hashed is a construction’s stable bytes, never the page a
reader lands on, resolved through an ordered table of sources
(luria.pins.SOURCES — adding a source-specific case is one function and
one entry). A declared pin_url template wins: only the project can vouch
that a URL is content-stable, so arXiv’s immutable e-print archive can stand
behind the abstract page a reader sees. Otherwise a GitHub file construction
qualifies on its own, re-based onto raw.githubusercontent.com so the
fetched bytes are the document rather than the page around it.
Every pin has a registration — the thing that says it should exist, and
whose removal retires it — and there is one kind per judgement site.
pin = true in config, on a remote or one of its schemes, registers a whole
code family: every cited reference is pinned automatically, and the lint
reports any the lockfile has not endorsed yet, because the judgement “this
record is knowledge we lean on” is made per source, not per citation. A
pin: comment directive registers one arbitrary URL where it is cited
(<!-- pin: https://… — why -->) — a spec, a dataset card, no code family
behind it. An explicit luria remotes --pin CODE registers one ad-hoc pin,
whose lockfile entry is its own registration. A bare --pin syncs the
lockfile to the registrations, and a pin whose registration is gone — the
config line deleted, the flag removed, the citation dropped — is reported
and then pruned: committed state that governs nothing is the lockfile’s
version of a stale directive, and retiring a noisy pin costs one removal.
One rule keeps the sweep honest: a bare --pin never moves an endorsed
hash that upstream has drifted from. It records the observation (as
--refresh would) and names the explicit command — otherwise one scheduled
sweep would quietly launder every drift finding the lint was about to raise,
and the human review the two-hash design exists to force would never happen.
Alternatives considered
- Fetch and compare in the lint — the obvious one. It makes drift visible the moment it happens, and it makes the lint a check that fails on a train and answers differently in CI than on a laptop, which is the exact failure the lockfile exists to prevent (ADR-016). Every network observation is an explicit command whose output is committed; the lint only ever reads.
- Mirror upstream status — read the remote document’s frontmatter and
cache its
status:locally. It answers the real question directly, but the cache is a hand-me-down projection of somebody else’s record (DP-3): stale the moment upstream edits, wrong for remotes that are not Luria-shaped, and unreadable for private ones. A content hash makes the weaker claim that is actually verifiable — something changed — and hands the judgement to a person, which is where every status judgement in this record already lives (ADR-035). - Hash whatever URL the reference constructs — an arXiv abstract or a
Jira ticket is a rendered page whose markup churns under identical content,
so the pin would cry wolf on the site’s deploy schedule, and a guard that
cries wolf is a guard nobody reads (ADR-016). The command refuses by default
and says why; a remote that does have stable bytes somewhere declares
the location (
pin_url), which moves the judgement to the party that can actually make it — the same bargain as aurltemplate, and the config line is the project taking responsibility for the claim. - Status quo — foreign references stay reachability-checked only
(
--check), and a superseded upstream decision keeps being presented as justification until a reader happens to click through and notice.
Consequences
Endorsement is opt-in per project and per document; nothing changes for a
record with no pins, and the lockfile’s shape is unchanged until the first
--pin writes a pins section. Drift detection is only as fresh as the last
--refresh — a scheduled CI job that runs it and opens a PR with the
lockfile diff is the natural companion, and is left to projects. A pin on a
document-scheme code covers the whole document the anchor lands in, so one
upstream edit can flag several pinned anchors at once; that is conservative
in the right direction. The seen hash moves only on successful fetches —
an unreachable document keeps its last observation rather than inventing a
change. The migration sweep (ADR-040) never touches the lockfile as text:
its JSON nests a remote’s prefix away from its tails, so the composed-span
mask cannot tell a foreign key from a local code. A claimed mirrored
remote’s pins instead travel structurally — re-keyed to the new spelling
with both hashes intact, because the endorsement is of content a rename
does not change, and prune-and-re-endorse would silently vouch for
unreviewed drift. The claimed remote’s discovered filename map is dropped
for re-discovery (its keys and values both spell the old world), and when
upstream’s own rename lands, the swept documents’ changed bytes surface as
ordinary drift for review.