Configuration

Generated by luria index from the dataclasses in luria/config.py — built, not edited. A key that exists in the schema is a row here whether or not anyone remembered to describe it.

Everything Luria does is driven by one file at the project root, luria.toml. Every key has a default, so a project with the conventional layout needs only issue_url — and Luria runs without the file at all, on defaults alone, which is what lets luria init bootstrap into an empty repository.

The shape of the file

The tables below are the whole schema. Four of them — schemes, fragments, journals and remotes — are families: you name the entries, and the name you choose becomes part of the vocabulary. [luria.schemes.RFC] is how a project gets RFCs; [luria.journals.incidents] is how it gets a second journal. No code path spells ADR.

That is worth stating plainly, because this package ships one instantiation of its own machinery — decisions, principles, a changelog and a devlog — and it is easy to read those four as Luria’s parts. They are its defaults. A record made of RFCs, specs and a meeting log is the same engine with different tables.

Two merge rules follow from that split (ADR-047). A settings table — paths, code, lint, site — merges per key: setting docs does not clear reports. A family table is replaced whole the moment you declare it: a project that writes [luria.schemes.RFC] and nothing else has exactly one scheme, and the shipped ADR is simply absent. Declare a family and it is yours entirely; leave it undeclared and the default stands. This is also what makes omission meaningful inside a declared family — an output you leave out is genuinely unset, because there is no default entry left for it to inherit from.

tablewhat it configuresfamilies
[luria]issue links, staleness horizon
[luria.paths]where the read and write surfaces live
[luria.schemes.X]a referable document family: ADR, RFC, SPECyes, keyed by prefix
[luria.fragments."dir"]many small files assembled into one documentyes, keyed by directory
[luria.journals.X]dated entries that persist, rendered into booksyes, keyed by name
[luria.remotes.X]another project’s codes, cited from this oneyes, keyed by prefix
[luria.code]which source files are scanned for stale references
[luria.lint]which warning classes fail the build
[luria.site]publishing the record as a browsable site

Top level — [luria]

keydefaultwhat it does
issue_urlunsetA template for issue links, with {n} for the number. Writing it also tells Luria which GitHub repository this is, which is where every [luria.site] default comes from. Unset, issue numbers stay bare rather than linking somewhere wrong.
stale_days90How long a document may sit undecided before the pending-decisions report calls it out.

Paths — [luria.paths]

The read/write boundary: docs/ is where a reader browses, record/ is where a contributor files.

keydefaultwhat it does
docs"docs"The read surface — prose plus every generated view.
decisions"record/decisions.d"The default scheme’s source directory. Kept as its own key for projects that predate [luria.schemes.X].
design_principles"docs/design-principles.md"Where the principles document renders.
reports"docs/reports"Where the status reports land. Committed views rather than build artifacts, so a README badge has a real page to point at.

Code scanning — [luria.code]

Which files outside the record are checked for references to retired documents. A decision number in a code comment is the strongest form of the claim being checked — it is the stated reason the code is shaped that way.

keydefaultwhat it does
globsunsetSource files to scan, as glob patterns.
historical['CHANGELOG.md']Files that are true about the day they were written and never updated — scanning them produces permanent, unactionable noise. Journals are covered automatically.

Enforcement — [luria.lint]

The dial between reported and enforced. Status findings are warnings by default; a class named here fails luria lint instead. Acknowledgement directives keep working under enforcement, because only unacknowledged rows ever reach a class.

keydefaultwhat it does
fail_onunsetWarning classes promoted to failures.
narrow_termsunsetThis project’s own concrete nouns. A title in a scheme marked titles_generalize that names one is reported as narrow-titles. Luria ships none — the words are yours, and empty means the class never fires.

Schemes — [luria.schemes.X]

One entry per referable document family. The key is the prefix codes carry, so [luria.schemes.RFC] makes RFC-7 a first-class reference: luria link --fix writes its link, luria lint demands one, and luria new rfc scaffolds the next free number — none of which needs anything beyond the table.

A family of referable documents — ADR-012, RFC-7, SPEC-3.

Luria ships with one, and knowing that one is not built in is the point: the annotation vocabulary says inactive-ok, not adr-ok, and a code carries its prefix, so a second scheme is an entry here (ADR-006).

keytypedefault
prefixstrthe table’s own name
dirPathrequired
activestr"Active"
renderstr"index"
outputPath | Noneunset
allocatestr"filing"
titles_generalizeboolFalse
requirestuple[str, ...]unset
tag_groupstuple[TagGroup, ...]unset
tags_filePath | Noneunset
field_groupstuple[FieldGroup, ...]unset
referencestuple[Reference, ...]unset
vocabulariestuple[Vocabulary, ...]unset
uniform_okstr | Noneunset

Fragment directories — [luria.fragments."dir"]

One entry per directory whose files are assembled into a single document and then consumed. The changelog is the shipped instance; the mechanism is not changelog-shaped.

One fragment directory: where its pieces assemble to, and in what shape.

[luria.fragments]
"record/changelog.d" = "CHANGELOG.md"       # the append style
[luria.fragments."record/changelog.d"]      # or, spelled as a table:
file  = "CHANGELOG.md"
style = "changelog"

append is the narrative shape: bodies oldest-first, inserted before the marker, so the marker stays at the end and the log reads top-down. changelog is the release shape: each collection is one dated batch, inserted right after the marker so the newest batch reads first, fragments newest-first within it. The shape is configuration because the fragment convention is the contract, not the collector (ADR-028) — the same directory-of-fragments serves either reading order.

keytypedefault
targetPathrequired
stylestr"append"

Journals — [luria.journals.X]

One entry per stream of dated entries that persist. This is a family like any other — a project can run a devlog, a meeting log and an incident log side by side, each with its own granularity and output.

Dated entries that persist, rendered into books (ADR-020).

[luria.journals.devlog]
dir         = "devlog.d"        # entries, partitioned yyyy/mm/dd/
output      = "docs/devlog"     # a directory of books plus an index
granularity = "month"           # year | month | day
title       = "Development log"
blurb       = "…"               # optional prose for the index

The difference from a fragment directory is that nothing is consumed: an entry was true when written and stays true, so the view is generated from sources that persist rather than collected from sources that are deleted.

keytypedefault
namestrthe table’s own name
dirPathrequired
outputPathrequired
granularitystr"month"
titlestr"Journal"
blurbstrunset

Remotes — [luria.remotes.X]

One entry per foreign project whose codes this record cites. The uid key is the general case: with a regex and a URL template, a remote need not hold a Luria-shaped record — or any record — at all, which is how arXiv identifiers, ticket keys and CVE numbers become linted references.

Another project’s record, cited from this one.

A reference to it composes the remote’s prefix with the foreign scheme’s own code — LU-ADR-013 — so the namespace is explicit at the point of use and nothing has to guess which project an unprefixed code meant (ADR-016).

[luria.remotes.LU]
name = "luria"
repo = "dmarx/luria"             # GitHub owner/name
ref  = "main"                    # branch or tag the links point at
dir  = "record/decisions.d"      # where its decisions live
url  = "https://…/{code}.md"     # optional: overrides construction

Everything but repo (or url) has a default, because the defaults are Luria’s own conventions — a remote that uses them needs one line. A code family with a different shape gets a schemes entry (RemoteScheme), which wins over these remote-level settings for its own prefix.

A remote need not hold a Luria-shaped record at all (ADR-024). Give it a uid pattern and its references are the prefix, the delimiter and whatever the pattern matches — an arxiv id, a ticket key — constructed through the url template, which can index the uid’s capture groups by position:

[luria.remotes.ARXIV]
uid = "(\d{4})[.:](\d{4,5})"
url = "https://arxiv.org/abs/{1}.{2}"   # {0} or {uid} is the whole tail

A pin_url template names where the remote’s stable bytes live, which is what lets luria remotes --pin endorse content that has no GitHub file behind it (#135) — arXiv’s e-print archive is the paper where its abstract page is a rendering:

pin_url = "https://arxiv.org/e-print/{1}.{2}"

Both are the short names of a general table: a code relates to a SET of named URIs, each through a template over one vocabulary — {code}, {number}, {prefix}, {repo}, {ref}, {dir}, {document}, {anchor}, and {filename}, which the discovered lockfile map fills. url is uris.read, pin_url is uris.bytes, and a relation Luria does not ship yet is one more name:

[luria.remotes.LU.uris]
bytes   = "https://gitlab.example/{repo}/-/raw/{ref}/{dir}/{filename}"
history = "https://github.com/{repo}/commits/{ref}/{dir}/{filename}"

GitHub’s blob/raw pair is simply the shipped default pair of read and bytes templates for a remote with a repo — a different forge is a different pair of lines, not a different subsystem.

keytypedefault
prefixstrthe table’s own name
repostrunset
refstr"main"
dirstr"record/decisions.d"
namestrunset
urlstrunset
delimstr"-"
uidstrunset
pin_urlstrunset
pinboolFalse
urisdict[str, str]unset
schemesdict[str, RemoteScheme]unset

Per-scheme remote construction — [luria.remotes.X.schemes.Y]

Optional, and only for a remote whose code families do not all construct the same way — a file per code in one, sections of a single page in another.

How one of a remote’s code families constructs (ADR-023).

A remote is not one directory of files — it is a project, and different schemes in it have different shapes. Each entry names one construction:

[luria.remotes.SG.schemes.ADR]
dir = "docs/decisions"                 # file per code
[luria.remotes.SG.schemes.DP]
document = "docs/design-principles.md" # sections of one file…
anchor = "dp-{number}"                 # …at Luria's stable anchors

anchor defaults to the prefix lowercased plus the number — dp-18 — which is the anchor shape Luria’s own document render emits, so a remote on current conventions needs only the document line. A url template overrides both, and a uris table names further relations for this family alone — both with the full template vocabulary (see Remote).

keytypedefault
prefixstrthe table’s own name
dirstrunset
documentstrunset
anchorstrunset
urlstrunset
pin_urlstrunset
pinboolFalse
urisdict[str, str]unset

The site — [luria.site]

Publishing the record as a browsable vault. Every key derives from issue_url for a GitHub project, so the conventional case needs no table at all.

How the record publishes as a browsable site (ADR-042).

[luria.site]
title      = "Luria"
base_url   = "dmarx.github.io/luria"
source_url = "https://github.com/dmarx/luria/blob/HEAD"
exclude    = ["template/**"]

Only exclude is genuinely per-project: the rest default off issue_url, because a project that told Luria where its issues live has already told it which GitHub repository it is (DP-3 — derive the projection).

source_url is where a link lands when its target is a repository file the site does not publish: a workflow, a template, the licence. Empty means “leave those links alone”, and luria site says how many it left.

The branding keys are the project’s own artwork, cited by path:

icon      = "assets/brand/icon.svg"    # favicon, any square image
logo      = "assets/brand/lockup.svg"  # shown in place of the title
logo_dark = "assets/brand/lockup-inverted.svg"   # optional
[luria.site.theme.light]
light = "#f4f1e8"                      # any of Quartz's colour names

logo_dark is only needed when the artwork can’t invert itself. A logo whose SVG exposes a --luria-ink custom property — the convention this project’s own kit uses — is re-inked to the theme automatically, and one that doesn’t is used as it is in both modes.

keytypedefault
titlestrderived from issue_url
base_urlstrderived from issue_url
source_urlstrderived from issue_url
excludetuple[str, ...]unset
iconPath | Noneunset
logoPath | Noneunset
logo_darkPath | Noneunset
themedictunset

Environment variables

Two, both escape hatches rather than configuration — they change how a run behaves, never what the record is, which is why they are not in the file.

variableeffect
LURIA_ROOTthe project root, overriding the search for luria.toml / .git. What lets the test suite run against fixture trees, and what a CI job uses when it checks out a project somewhere other than the working directory.
LURIA_JOBScaps the thread pool that renders views, scans files and probes remote URLs. LURIA_JOBS=1 is the serial escape hatch — the one to reach for when a failure needs a deterministic ordering to reproduce.

Running one module without the package

Every command is a plain function, and the modules stay runnable standalone:

python -m luria.ref_status --all

That is for a project that vendors a single file rather than installing Luria. The CLI is the supported surface; this is the door left unlocked.

What is not configurable

Said plainly, because a reference that only lists dials reads as though everything is one:

  • The lint checks themselves. They are functions, not a registry — a project cannot add a check or switch one off. What it can do is promote warning classes to failures with [luria.lint] fail_on, and acknowledge individual findings with the comment directives. A check earns its place by being always wrong and mechanically fixable; anything else is a report.
  • The frontmatter vocabulary. status:, title:, tags:, date:, version:, history: are fixed field names. Which statuses count as in force is per-scheme (active), but the field they live in is not.
  • Renaming a scheme in place. Adding [luria.schemes.RFC] costs one table; renaming an existing scheme, or moving its documents, is currently a manual pass — there is no migration command. The decision that would give it one is still Proposed.

Two earlier entries in this list are gone because they stopped being true (ADR-047): the shipped ADR scheme can be removed — declare the schemes family without it — and a declared scheme’s output is unset by omission, because a declared family no longer inherits from the defaults. Both are demonstrated and pinned by examples/ in the Luria repository, which CI runs.