Context
The lint’s reference vocabulary — unresolved-codes, broken-targets,
retired-citations — asks one question in three shapes: does this pointer
resolve? remote-drift asks a fourth: have the bytes behind an endorsed pin
changed since a human vouched for them?
None of them asks whether an identifier and the document carrying it were
ever about the same thing. In anthology-of-the-sota, 53 of 139 arxiv:
identifiers did not resolve to the paper the note named: quantum secret
sharing filed as the PaLM 2 technical report, electron losses in hypersonic
flows filed as checkpointing advice, chromatic number and Hamiltonicity filed
as compiler optimization.
None of these was a typo. Every one was syntactically valid, in a plausible
year range, and in the right format — 2111.09432 reads as a late-2021
systems paper unless you fetch it. That is why two years of review missed
them, and why the failure is worth a check rather than a convention.
The gap also falsifies a claim that record makes about itself, in the principle this whole mechanism serves: that the remotes “turn those ids into resolvable references rather than strings, so ‘cite the paper’ and ‘the citation resolves’ are the same check rather than two”. They were two. Resolution was available and never performed.
Decision
A remote declares how to ask what one of its identifiers is:
[luria.remotes.ARXIV]
uris.title = "https://export.arxiv.org/api/query?id_list={1}.{2}"
title_re = "<entry>.*?<title>(.*?)</title>"
uris.title is one more name in the URI table read and bytes already
live in — the table’s docstring anticipated this: “a relation Luria does not
ship yet is one more name”. title_re’s first capture group is the title.
luria remotes --resolve fetches every one and records what came back in
remotes.lock.json. luria lint compares against the document’s own
title:, and may ask about what the lockfile cannot answer:
[luria.lint]
network = "auto" # default: ask about what is not already known
# "never" # answer only from the lockfile — the hermetic build
# "require" # not being able to ask is a finding
The lockfile is a cache with an endorsement in it, not the boundary of
what may be known. Under auto the common case touches no network at all —
every identifier is already answered — and the case that does is the one
citation a contribution just added, which is precisely the one worth asking
about. Disagreement is source-mismatch; an identifier nothing has verified
is source-unchecked; source-ok: acknowledges a deliberate disagreement.
Failure kinds are distinguished by HTTP status, because they mean opposite things: 404/410 is upstream saying the identifier names nothing — an answer, recorded as such and not retried — while 429/503 is upstream declining to say, which is retried with backoff and, if it persists, reported as unchecked rather than written down as an absence.
Which fields hold identifiers needs no new configuration: a frontmatter key
equal to a remote’s prefix, lowercased. A project that declared ARXIV has
already said what arxiv: means.
Alternatives considered
Make the lockfile the boundary: never fetch during lint. This is what
shipped first, on a reading of ADR-016’s “a check that reaches the network
is a check that fails on a train” as a prohibition rather than as a
requirement that the build stay possible offline. It has a hole big enough
to defeat the check’s purpose: an identifier the lockfile had never seen was
treated as exempt, so a freshly typed wrong citation passed silently until
somebody remembered to run --resolve. That is the same dependence on ritual
that let 53 bad identifiers survive two years, reintroduced by the guard
meant to end it.
The synthesis is that determinism matters where a check fails, not where it
asks. never keeps the hermetic build exactly. auto asks only about what is
unknown, falls back to reporting it unchecked, and writes what it learns back
— so the next run is offline and a reviewer can see in the diff what upstream
said. require is the CI posture: a green build means the references were
verified rather than remembered.
Guess the metadata API from the host. arxiv.org → the Atom API,
doi.org → Crossref, and a project would configure nothing. Rejected for the
reason pin_url is declared rather than derived: only the project can vouch
that a URL serves what it appears to, and a guess that silently stops
matching when a provider changes its response shape fails closed — the
lockfile gains no entry, and no entry reads exactly like agreement. A check
that quietly stops checking is worse than one that was never installed.
Fuzzy matching on similarity. Would forgive the 22 cosmetic cases in that record automatically — a dropped subtitle, a nickname the project prefers. Rejected because it converts a mechanical check into a judgement call, and the standing rule is that a check joins the lint only when the violation is always wrong and mechanically fixable. “The recorded title is not the resolved title” is mechanical; “the recorded title is 0.82 similar” is a threshold argument in every future review. The directive puts the judgement on a person, once, with a reason, where the disagreement is.
Leave unresolved identifiers silent. Rejected, having shipped it once.
The argument was that a project which never ran --resolve would see every
document become a finding, teaching people to run a command to silence the
lint. It is backwards: running the command is the remedy, it is mechanical,
and under auto the lint runs it for you on the one identifier that needs
it. What the silence actually bought was a check that could not see the case
it exists for.
Consequences
No project’s output changes until it declares uris.title on a remote: with
no way to ask, the check has no opinion, and network governs nothing. Once
declared, auto means luria lint may make a request for an identifier it
has no answer for — bounded by how many citations a contribution adds, and
zero on a record whose lockfile is current. A project that wants the old
behaviour sets network = "never".
The lockfile grows a titles section, which write_lock preserves like the
others, and which the lint now writes to as well as reads.
The check is deliberately shallow: it compares titles, not authors or years, because the title is the field a record already carries for its own reasons and therefore the one that cannot drift out of maintenance. A record that wants author and date checked can add them later against the same lockfile entries; the fetch already has them in hand.