ADRs tagged process

How the machinery is adopted, run, and reported on.

25 of 76 decisions. Back to the full index.

#TitleSummaryStatus
ADR-007 v2Document status is reported, not enforcedTwo standing reports — reference status (references to retired documents) and pending decisions (undecided, by age AND citation count) — are WARNINGS that never fail a build, with one summary line each from luria lint. Citing a Rejected decision is often exactly right, and a decision can be legitimately open for months, so only a human can judge a row; a guard that’s wrong most of the time gets suppressed, and then the times it’s right go unread. A deliberate reference is silenced by an inactive-ok: comment (line, -block or -file scoped), counted in the report rather than hidden, and reported when it stops applying. luria reports writes both in full for a CI artifact, because a warning with nowhere to be read is a warning nobody reads. Rejected: a lint error (fails CI on correct citations), a bot that auto-updates statuses (invents decisions nobody made), and a ratchet on a checked-in baseline (a file every contribution touches — DP-2).Superseded — by ADR-035
ADR-009Extracted with provenance, not importedLuria’s decisions are renumbered from 001 and rewritten as decisions about Luria, each naming its strata-g ancestor as provenance rather than importing the original number. Porting the eight ancestor decisions verbatim would have carried a numbering with gaps that mean nothing here, bodies arguing about a graph tool, and cross-references to decisions that were never extracted. The evidence is kept — every principle and decision names the incident that earned it, because a rule whose evidence is missing reads as taste and gets re-litigated. Luria also runs its own machinery on its own record, so the first consumer to hit a bug is this repo. Rejected: importing numbers verbatim (dangling cross-references), and starting clean with no provenance (throws away the only thing that makes the rules persuasive).Active
ADR-010Name the project chester, after Chesterton’s FenceName the package chester, after Chesterton’s Fence — the parable that a fence should not be removed until you know why it was put there, which is what a decision record is for. Superseded the same day by ADR-011: the allusion names only the narrowest slice of what the record does (defending existing constraints), it survives being shortened to “chester” only for readers who already know the parable, and it frames the record defensively — as an argument against change — when its actual job is to make change cheap. Kept for the record, and as the corpus’s worked example of supersession.Superseded — by ADR-011
ADR-011Name the project Luria, after The Mind of a MnemonistName the package Luria, after Alexander Luria and his case study The Mind of a Mnemonist — a man who could not forget. The name points at the faculty the package supplies (memory that survives the session) rather than at one failure it prevents, and the book carries its own cautionary half: Shereshevsky’s total recall was a burden, because a record that never forgets and never abstracts becomes unusable. That tension is the design brief, not a flaw in the allusion. Supersedes ADR-010 (chester, after Chesterton’s Fence), whose allusion named only the narrowest slice of the job, survived shortening only for readers who already knew the parable, and framed the record defensively when its actual job is to make change cheap.Active
ADR-017A reference may name a document before its URL resolvesSG-ADR-032 stays in the prose even though that URL 404s today: strata-g’s filenames still carry title slugs, and the code-only convention Luria builds against is what strata-g will use once its record is ported. The reference is therefore correct and merely early. Naming a foreign document is the durable half — which project, which decision — and it survives whatever the URL does; dropping the citations to avoid a broken link would throw away the meaning to protect the plumbing. Two remotes are registered, SG and LU, and their difference is the point: LU is public and already on the convention, so luria remotes --check verifies it for real, while SG reports as unverifiable and stays that way until the port. Rejected: a hand-authored lockfile (a drifting projection with no guard, since the repo is private), and de-prefixing the citations back to prose (unresolvable and unnameable).Active
ADR-019A wrong reason is corrected in place and versioned; a changed choice is supersededTwo ways a decision record can be wrong, and only one of them is a supersession. If the choice changes, add a new decision and retire the old one — the rule ADR-001 has always stated. If the choice stands but a reason for it turns out to be wrong, correct the body in place, bump version, and say what changed in history:. Superseding over a bad argument is theatre: it retires a decision still in force, and every citation of it has to be repointed at an identical claim. Leaving the argument standing is worse, because a wrong reason is exactly what gets quoted at the next person. The version field makes the correction visible, which is the whole reason “never rewrite a body” exists — the objection is to silent revision, not to being wrong out loud. Rejected: superseding for any change (churn, and it makes the status vocabulary lie), and correcting silently (indistinguishable from rewriting history).Active
ADR-022url-ok acknowledges foreign codes onlyThe url-ok directive — and the warning it acknowledges — applies only to links whose label is a composed foreign code (SG-DP-18), never to local codes or arbitrary hand-targeted links. Three reasons: a warning needs a well-defined wrong, and only the remote namespace has one (a foreign code has exactly one constructed URL, so “differs” is a meaningful binary; a local code has a family of legitimate targets, so the same check would flag correct links until acknowledgement became reflex); the risk profiles differ (a local link fails loudly when its target moves, a remote hand URL fails silently, and the directive exists to compensate for the silent case); and the local want has a better answer the repo already models — register yourself as a remote, the way Luria registers LU. The underlying asymmetry: a local hand URL restates a fact the machinery already owns, so the remedy is mechanical, while a foreign hand URL carries a fact the construction has no home for. That reframes the directive’s role: each url-ok reason describes a gap in what remote config can express, and a recurring shape is the cue to grow the config — per-scheme remote mappings are the known first candidate — not to keep annotating.Active
ADR-027Published to PyPI via trusted publishing; the scaffold ships inside the packageLuria publishes to PyPI through GitHub’s trusted publishing: a publish.yml workflow whose pypi environment’s OIDC identity is the whole credential — no token exists to leak, rotate or forget. Build and publish are separate jobs; the artifact that ships is the artifact that passed a cold-install smoke test (pip install dist/*.whl, then init → index → journal new → lint in an empty directory), which is the guard against shipping a luria init that only works from a checkout. The scaffold ships inside the package (luria/template/ in the wheel, via hatchling force-include) while staying top-level in the repository where a visitor browses it (ADR-021) — the setuptools parent-relative package-data it replaces leaked a bare template/ into site-packages, functional only by collision-prone accident. Publishing fires on a GitHub release (plus manual dispatch); the sdist carries build inputs, not the record — the record’s browsing surface is the repository.Active
ADR-029 v2A generated view must be committed by something; the remedy says so where it is readGenerated views are committed artifacts, and who commits them is open: the author can regenerate and commit, or a generation job can run the generator and push — the latter is the better default, since a view a human rebuilds by hand is still a hand-maintained projection. What breaks is the shape in between, the generator dropped into a checking job committing nothing: the output dies with the runner and a following luria lint compares the generator against itself, so it can no longer fail. The generation route ships as machinery — actions/generate and actions/lint composite actions holding the one authoritative implementation, the luria init template workflow built from them (it previously scaffolded verify-only, handing every adopter the incident-shaped setup), and this repo’s own ci.yml running the same actions by local path. The staleness remedy is context-aware (names both routes, warns against the broken shape) and bare luria badges says on stderr that it only printed. Rejected: banning writes from CI (the first draft — it outlaws generation jobs, including this repo’s own), an always-on write-in-CI warning (noise on every correct generation-job run), and folding README.md into outputs() (ADR-018’s reason still holds).Active
ADR-030The CLI surface is the workflows, not the module layoutTrim the CLI from eleven commands to a tiered eight: six contributor commands (lint, link, index, journal, remotes, init) and two CI commands (reports, collect), named as such in the help text. badges, ref-status and pending are removed outright — each was already subsumed (luria index writes the badges and luria lint checks them per ADR-029; the two reports print as lint warnings per ADR-007 and land in full via luria reports) — and a removed name is an ordinary unknown command: the library is days old with one user, so a deprecation shim would be an affordance for a workflow nobody has. The modules keep their main()s, so python -m luria.ref_status --all still exists for an interactive dig. Rejected: keeping one command per module (mirrors the package, not any workflow), a RETIRED table answering each old name with its successor (built in the first draft, removed in review as legacy-preservation bias), folding link into lint --fix (the check/fix split is deliberate, ADR-005), and deleting the modules’ entry points along with the commands (breaks vendoring and the tests for no surface gain).Active
ADR-034Fixture codes come from a registered prefix, not from the sequenceA reserved remote-style prefix, FX, is registered in luria.toml with a constant url pointing at the fixture-codes note in the directives doc — so FX-ADR-032 is resolvable by construction: the fixer links it, nothing reports it as dangling, no directive is needed, and it can never collide with the real sequence because it is not in it. Mechanizes the convention the template already used informally (ADR-777), after filing the real ADR-032 made every specimen that borrowed that number resolve at once and five unresolved-ok directives went stale together. Existing bare-code specimens that quote history (ADR-014/015/017) keep their directives — the prefix is for everything written from now on. Rejected: a dedicated local scheme (a scheme’s documents must exist), reserved number ranges (a convention with no teeth), and leaving it to unresolved-ok (per-site maintenance that detonates when the sequence arrives).Active
ADR-035Status enforcement is a dial — reported by default, failable by configurationSupersedes ADR-007’s “warnings, never able to fail a build”: the warn-first posture stays the default, but [luria.lint] fail_on promotes named warning classes — retired-citations, unresolved-codes, hand-written-urls, stale-directives, pending-documents, unlinted-files — to lint failures. The accounting is unchanged: only unacknowledged rows ever reach a class, so inactive-ok: and its siblings remain the way to state a deliberate exception even under enforcement, and the dial changes the consequence, not the bookkeeping. An unknown class name in fail_on is itself a lint error naming the vocabulary. Rejected: keeping “never” (a soft gate against the DP-5 mechanism→guarantee promotion, review’s finding on #32), per-invocation CLI flags (CI and a laptop would enforce different policy), and a single boolean strict mode (the classes fail for different reasons on different projects).Active
ADR-036 v2One scaffold for every entry kind — luria new, driven by the configluria new [kind] scaffolds an entry anywhere the record takes one — the journal by default, any scheme by prefix (adr, dp: next free number from _template.md, date stamped), any fragment directory by name (changelog: filename stamped from the filing moment, like a journal entry; --name overrides). It computes only what a machine can know — filename, number, timestamp, date: — prints the path, and leaves every other field a template placeholder for a markdown-aware editor; --title/--status/--summary/--tags exist for tools driving the CLI, never as requirements. Kinds derive from luria.toml, so a new scheme scaffolds for free. Subsumes and removes luria journal (no shim, ADR-030). Rejected: interrogating for fields on the command line (humans author fragments in editors), hardcoding the three known kinds (the config already knows them), and keeping journal beside new (two spellings of one scaffold).Active
ADR-037The agent file is a map, not a copyCLAUDE.md — this repo’s and the scaffolded template’s — is truncated to a short list of links plus the invitation to run luria --help: the doctrine lives in docs/ and the current command surface lives in the CLI, so an agent file that restates either is a hand-maintained copy that drifts (it did, twice in one week — the command block had to be chased in ADR-030 and again in ADR-036). The map keeps only what the links can’t carry: three one-line ground rules and the statement that when this file disagrees with the docs or the CLI, this file is the one that’s wrong. Rejected: restating the command list (a fourth hand copy of COMMANDS), restating the doctrine (a worse copy of project-memory.md that costs agent context), and generating CLAUDE.md from the record (machinery for a page that is now ten stable lines).Active
ADR-038The CLI is the interface; the Makefile retiresThe Makefile is deleted. Its stated purpose — “run what CI runs is always make <target>” — stopped being true when ADR-029 moved the docs jobs into composite actions that invoke luria directly: CI’s entire remaining Makefile usage was one make test line wrapping python -m pytest tests -q, and every other target restated a CLI one-liner, a fifth hand-maintained copy of the command surface that had to be chased twice in one week (ADR-030’s removals, ADR-036’s journalnew). ci.yml runs pytest directly, and luria --help is the one list of what you can run. Rejected: keeping it as a convenience alias layer (aliases drift and their drift caught nothing the CLI’s own help doesn’t catch), and a task runner swap (same restatement, new syntax).Active
ADR-042 v2The record publishes as a Quartz vault: paths preserved, sources withheld, frontmatter surfacedluria site stages the record as an Obsidian/Quartz vault and a composite action builds it onto GitHub Pages, giving the citations a graph and backlinks nobody maintains. Three rules do the work. Paths are preserved, so the relative targets luria link --fix already wrote keep resolving and no second link resolver exists to drift from the first (DP-4). A source that renders into a view is withheld — derived from link_base, not listed: a fragment, a journal entry and a document-scheme source all spell their links for the page they land in, so publishing them in place would break every one and duplicate the view besides. A link leaving the published set goes to the repository, because the record cites workflows and templates that are files, not pages. Plus one addition: each decision gets a rendered record line — status, date, issue, influenced_by — since a site renders frontmatter as nothing, and a retired decision that reads as current is worse than one nobody can find. Rejected: publishing docs/ alone (the decisions it links to live in record/), rewriting links for a flattened vault (a second resolver), Quartz v5 (its plugin installer crashes on this Node), and expanding wikilinks at build time (the fixer consumes them at source, so the vault never sees one — which is why the interpretation gap the issue worried about never arrives).Active
ADR-048The scaffold is planned from configuration, not copied from a treeluria init plans its scaffold from a config — one named with --config, the project’s own luria.toml if it already has one, or the shipped template’s — and writes the shape that config declares: a directory, template and view stub per scheme, a template per journal and fragment directory, and a docs index generated to list exactly the views this record renders. Config-first adoption follows: write the luria.toml you want, run luria init --config, and the record you declared is the record you get — RFCs and an incident log, with no decision directory you never asked for. ADR and a document-rendered DP keep the shipped rich templates (the decision doctrine and the seed principles are the content this package has to offer); any other scheme gets a neutral template using the {PREFIX}-NNN placeholder luria new already substitutes (ADR-036), and a stub titled after itself. The never-overwrite rule stands, with one hard refusal: --config against a project that already has a luria.toml errors instead of skipping, because scaffolding one config’s shape while another governs the record builds directories the project’s machinery doesn’t know about. Rejected: keeping the fixed-tree copy (it scaffolded decision directories for records whose configs declared none — the wart that prompted the question); a --scheme flag vocabulary (re-states in flags what the config already says); and templating the whole tree per scheme (Luria has no doctrine about what an RFC should say, and a neutral template is honest about that).Active
ADR-049Temporary codes at filing; concretized and aliased at the serialization pointA scheme can allocate its numbers at the merge point instead of at filing. With allocate = "merge", luria new adr issues a temporary code — ADR-tmp47fje, visibly provisional and never readable as a number — and the document is first-class on its branch: indexed, linted, citable as [[ADR-tmp47fje]], linked by the fixer like any neighbour. luria concretize, run wherever merges serialize (a merge queue, the merge-to-main job), assigns the next free sequential numbers in merge order, renames the files, rewrites every reference in the tree, and records each temporary code in the document’s formerly: frontmatter — which the resolver honours forever, so a temp code cited in a PR thread, a commit message, or another repository’s LU- reference is never a dead link, only an old name. luria concretize --check is the guard for the trunk: a temporary code on main is always wrong and mechanically fixable, so it fails (ADR-035’s admission rule). The motivating failure: filing-time numbering is a distributed claim on a global counter, so two branches both mint ADR-123 and collide at merge — rarely for one human, structurally for N concurrent agent branches. Rejected: filing-time numbering (the status quo, which lies about order under concurrency); timestamp codes forever (no collision, but trades away the small citable numbers the record’s culture runs on); reserving numbers against main at filing (a coordination round-trip per document, and racy without the serialization it was trying to avoid); and coupling this to an intermediate branch (concretization needs a serialization point, not a branch — a queue or the merge job serializes fine, and the batching question is separable).Active
ADR-052The draft signal: a contribution that asks for a verdict, not a reviewA draft pull request carrying a Proposed decision means the contribution itself is the question: the choice could only be weighed from the finished diff, the writeup argues the trade in both directions, and rejection is a live, cheap outcome — merge flips the decision Active, close files it Rejected, and either way the record keeps the reasoning. Use the signal when the work exists to settle its own worth (a worth-it experiment, the first live run of new machinery); skip it for agreed work, where a draft only slows the loop. Rejected alternatives: no signal at all (every PR presumes merge, making “no” socially expensive), and deciding before building (some trades are only visible in the diff).Active
ADR-053The published version is derived from the release tagRung 1 of the fix #93 started. pyproject.toml declares dynamic = ["version"] and hatch-vcs reads git describe, so there is one source for the version rather than two that can disagree. #93’s assertion stays as rung 2 — deriving prevents the drift, the guard makes a recurrence loud. Needs fetch-depth: 0 on the publish checkout, without which the version silently becomes a dev fallback. Rejected: keeping the field with a release checklist, and a bot commit that writes it before tagging.Active
ADR-058Luria is a truth maintenance system, and should say soNobody could name the category, so every description reached for a new metaphor and none of them stuck. The category exists and is from 1979: a truth maintenance system maintains beliefs plus the justifications linking them, and retracting one propagates to everything that rested on it. That is the engine, exactly. What is ours is narrow and worth stating narrowly — the nodes are human prose, propagation halts at a finding instead of resolving itself, and acknowledgement is a first-class move. Rejected: claiming a new category, which costs us the prior art.Rejected — the README opens on what the record does, not on what to call it; the prior art belongs in the concepts page instead
ADR-062A suppressed build is branch protection’s problem, not the lint’sA commit message describing the CI skip marker contains it, and so stops its own build. A checker was written for this and measured backwards: it cannot fire in the case that does harm, and does fire on commits that caused none. Required status checks answer the question that matters — does the commit being merged have a green check of its own — and no mechanism here can. Rejected: shipping the checker, and a commit-msg hook.Active
ADR-065A principle is written as a value, unless it is actually a ruleA principle drafted as “a record must not constrain the project it records” could only be satisfied or violated, which loses the state a value spends most of its life in — partly met, and moving. Rewritten as “meet the project where it is” it kept the same content and gained that reading. Principles are written in the aspirational voice by default; the constraining voice is reserved for the ones that genuinely are rules, which this record has two of. Rejected: requiring the positive voice everywhere, and adding a lint.Active
ADR-068 v2Source repairs are committed on the branch that authored them; generated views on the default branch onlyThe generation job ran on pull requests too, committing regenerated views onto every branch — so any two concurrent branches that added a decision or a devlog entry diverged on the decision index, its tag pages and the devlog book, and the second to merge conflicted on files nobody wrote. Six times across one stack, resolved the same mechanical way each time. The two kinds of write the job makes now land in two places: a source repair (luria repair — a bare code linked, a missing created: filled) touches only the files a branch authored and is committed onto the branch, where the review reads it; a generated view is a shared file every branch would rewrite and is committed on the default branch only. A pull request pushes its repairs, regenerates the views in the working tree, lints the result in the same job, and commits no view. Amends ADR-029, whose choice stands — a view is committed by something — while the somewhere moves. Rejected: a merge driver (the merge button does not run one), a documented routine of regenerate-on-conflict, and the first draft of this decision, which discarded the repairs on a pull request along with the views.Active
ADR-070A decision is stated for what it chooses; a prohibition is reserved for a constraint that has been verifiedThree decision drafts in one day carried a “never” the mechanism did not enforce — a title saying edges are never inferred from prose, a rule that a workflow file never cites a temporary code — and each was sent back in review. A decision is stated for what it chooses: the title and the decision paragraph say what is preferred and what is done, and a rejected alternative is named as such. A prohibition (“never”, “must not”) appears only where the constraint has been verified against the thing that enforces it, and the text names that enforcer. The “X, not Y” title names a rejected alternative and is not a prohibition. Rejected: a lint for the words (a rejected-alternative list legitimately says “never”), and leaving it to review, which is where it kept being caught. alternatives that lost. Written to be read in a table row.Active