The lint’s warning classes are judgement calls: a citation of a superseded decision might be exactly right (history, or a rejection worth pointing at), and only a human can say so. Directives are how a judgement, once made, is written down where the finding is — so the check stops asking, the reason survives, and the report still counts what was acknowledged.
Shape
A directive lives in a comment — HTML comments in markdown, # comments
in a record document’s YAML frontmatter, #, //, /* */ or --
comments in code — and reads:
<!-- inactive-ok: ADR-012 — the rejection is the point being made -->
That is: a name, optional scope suffix, a colon, arguments (codes, paths, or keywords, separated by spaces or commas), and after an em-dash, the reason. The reason is prose for the next reader; write one.
Giving one a deadline
An acknowledgement is a promise about the future — this reference to a
Proposed decision is deliberate. Some of those promises are permanent
(this page is that history) and some are not (circle back after the
release). The ones that are not go stale silently, which is the exact
failure directives exist to prevent, one level up.
So a directive can carry an expiry:
<!-- inactive-ok: ADR-028 until 2026-10-01 — revisit when the API settles -->
After that date luria behaves as if the directive were never written: the
check it silenced starts reporting again. The date is inclusive — good on
the 1st, gone on the 2nd — because somebody writing until 2026-10-01 means
the last day it holds, not the first day it does not.
until <date> works on every directive, not just inactive-ok; it is part of
the shape, like the scope suffix (ADR-095). An ISO date and nothing else — a duration
(“two weeks”) would need an anchor the file does not carry, and the date is
that anchor, written down.
Two things it does not do quietly:
- An expired directive is reported.
luria lintnames the file, the directive, the date and the author’s own reason, underexpired-directives. Silently dropping it would leave a check failing again with the acknowledgement still sitting above it, which is a puzzle rather than a report. - A date it cannot read is reported, and the directive stays live.
until nextweekis a typo, and dropping a suppression over a typo breaks a build for a reason the failure message would not explain. The report is the correction.
Scope is how much text the directive governs:
| spelling | governs |
|---|---|
name: | its own line and the next line |
name-block: | the paragraph (blank-line-delimited block) it sits in, or the following block when the comment stands alone |
name-file: | the whole file |
A blank-line-delimited block is a guess at where a block ends, and every language has a construct that holds a blank line — a Python docstring, a YAML mapping, a function body. Install the optional extra:
pip install luria[syntax]
and -block reads the real one from a tree-sitter grammar instead: the
smallest syntactic unit that starts where the block’s content starts and
holds the whole block. It is the same rule in every language the grammar
pack supports, and it can only extend what the blank-line block already
governed — a directive that works without the extra works the same with
it. Without the extra nothing changes, and LURIA_TREE_SITTER=0 turns it
off without uninstalling it.
So this reaches a citation in a docstring’s third paragraph, which the blank-line rule cannot:
# inactive-ok-block: ADR-012 — the decision this function replaced
def apply(...):
"""First paragraph.
A later paragraph citing ADR-012."""With a grammar the extra also stops reading a comment marker inside a
string literal as a comment — echo 'see # this' in a shell script is one
string, not a directive site.
A citation site can be a frontmatter field: superseded_by: naming a
document that was itself later retired is reported at that line like any
sentence. The line-scoped form answers it in place, as a YAML comment
directly above the field:
# inactive-ok: ADR-012 — the successor was itself later rejected; the chain is deliberate
superseded_by: ADR-012Only a whole-line # comment inside the frontmatter counts; a # inside
a value is data, and a # in the body is a heading.
A directive that no longer matches anything — the document went Active
again, the hand-written URL was fixed, the argument has a typo — is itself
reported under stale-directives, so acknowledgements cannot quietly
outlive what they excuse.
The vocabulary
| directive | acknowledges | argument |
|---|---|---|
inactive-ok: | a citation of a document that is not in force (retired-citations) | the code(s) |
unresolved-ok: | a code that resolves to no document, kept deliberately (unresolved-codes) | the code(s) |
mention-ok: | a code that is named, not cited — the text makes no claim about the document’s state, so this covers both findings above and does not retire when that state changes (ADR-105) | the code(s) |
url-ok: | a remote code linked to a hand-written URL instead of the constructed one (hand-written-urls) | the code(s) |
target-ok: | a relative link target that resolves to nothing from where the prose renders (broken-targets) | the exact target |
source-ok: | an identifier whose upstream title is not the one recorded (source-mismatch) — a preferred nickname, a trimmed subtitle, a title that changed between versions | the identifier(s) |
broad-ok: | a term flagged by narrow-titles, used in a legitimately broad sense | the term(s) |
unlinted-file: | opts the entire file out of reference checking — the blunt tool for fixture-heavy or vendored pages. File-scoped by design; counted in the report rather than hidden | — |
unexempt: | the reverse of an exemption: makes the linker treat code regions as prose again, for pages about the reference syntax | codeblock, inline-code |
pin: | not an acknowledgement — a registration: marks a cited URL for content pinning, so luria remotes --pin endorses its bytes and the lint reports when they change (remote-drift) | the URL(s) |
Each acknowledgement covers findings at its own site only — that locality is the point. Vouching for one citation of a retired decision says nothing about the next one, which gets its own look and its own reason.
One warning class is acknowledged without a directive on purpose:
remote-drift (a pinned document whose content changed upstream) is
cleared by re-endorsing — luria remotes --pin CODE after reviewing the
change — because the judgement lives in the lockfile, not in prose at a
citation site.
pin: runs the other way from every acknowledgement: it arms a check
rather than quieting one. Flag a URL where it is cited —
<!-- pin: https://spec.example/v1.html — the spec this implements -->
We follow [the spec](https://spec.example/v1.html).
— and luria remotes --pin endorses its content by hash; from then on the
lint reports when the bytes change. The URL must appear in text the
directive governs (its own comment doesn’t count), so a flag whose
citation was deleted reports itself under stale-directives like any
other directive. Deleting the flag retires the pin — the next bare
luria remotes --pin prunes it — so a pin that fires too often costs one
removed comment, and the URL goes back to being an ordinary, unwatched
link. Foreign codes need no flag: luria remotes --pin LU-ADR-013
already covers them.
Mentioning a code without citing it
Backticks are the lighter tool and usually the right one: a code inside
` inline code ` or a fenced block is masked from the reference
machinery entirely — not linked, not counted, not checked. That is how
documentation (including this page) shows codes as syntax. A directive is
for the cases where the code should stay bare prose and still be excused.
Fixture codes
A mention is not a citation. Some references name a code without asserting
anything about it — a specimen quoted as evidence, prose about a code’s literal
spelling, a demonstration of what a moved address looks like. mention-ok: is
the word for those, and the reason it exists is that the others retire on a
change it should survive: unresolved-ok claims the code resolves to nothing,
which stops being true the day somebody allocates that number, and the
annotation then goes stale for a reason that has nothing to do with why it was
written.
It is used while the code it names is cited in its scope, whatever the document’s state, and reported stale the one way that is still about the annotation: when nothing in scope names the code any more.
Prefer the FX prefix below where you control the spelling — it needs no
acknowledgement at all. Reach for mention-ok: when the spelling is the point,
as in prose about a code that a regex once failed to match.
Documentation and tests need example codes that look real but
deliberately resolve to nothing in this record. Rather than sprinkle
unresolved-ok: everywhere, this project reserves the FX remote prefix
for them: a code like FX-ADR-001 is declared, resolves by a fixed URL
template (to this section), and reads unambiguously as “an example, not a
citation”. Any project can adopt the same convention with a one-table
remote:
remotes:
FX:
name: fixtures
url: https://github.com/dmarx/luria/blob/main/docs/directives.md#fixture-codesThat covers a fixture code that should resolve somewhere harmless. A test
suite usually wants the opposite: a code belonging to a scheme it declares
itself, in a tmp_path project, which the project running the suite has never
heard of. So the whole FX prefix is a reserved namespace, and no
project declares a scheme whose prefix begins with it:
FXLis the local fixture scheme by convention.FXMis the second one, for a fixture that needs two prefixes at once — arename_schememigration, say.- Any other
FX…name is available for the same purpose.AFXis not reserved: the match is on the leading prefix, not a substring.
luria lint refuses a declared scheme in the namespace, at the config rather
than at each citation, so the guarantee cannot be taken away by accident.
See ADR-093.
Design notes
- Directives are found by tokenising real comment syntax per file type, so a string that merely looks like a directive in running prose does not fire.
- Arguments are validated: a directive naming an unknown code, an unknown region keyword, or nothing at all is reported rather than silently inert.
- The reports remain a complete account. Every acknowledgement is counted next to what it silenced — the bargain is quiet checks, never hidden findings.