ADR-041: Bugs enter the record characterized: the minimal working example protocol
Status Active · Filed 2026-08-08 · Issue #57
Context
Filing the devlog entry for ADR-040 tripped a live bug class
(#57): journal-entry links live
in the rendered book’s frame, so a link that is correct at source silently
breaks the published view — and luria lint has no relative-link resolution
check, so in an adopting project (which has no access to this repo’s pytest
guard) the breakage ships green. The initial account of that bug was one
loose sentence in a chat: “a small trap the sweep will need to respect.”
Loose sentences are how bugs get half-fixed: the fix addresses the sentence,
not the mechanism, and the class survives.
The project already has doctrine for what kind of guard a defect deserves (ADR-035: lint if always wrong and mechanically fixable, report otherwise) and for trusting a new guard (DP-6: fire it once before believing it). What it lacks is the step before both: how a bug is reported so that the guard decision has something solid to stand on.
Decision
A bug enters the record as an issue carrying a minimal working example, and no fix lands ahead of one. The protocol, in order:
- Characterize before responding. The MWE is a replayable transcript —
commands, inputs, observed output, and the expectation violated — small
enough that a reader can rerun it, complete enough that the mechanism
(not just the symptom) is visible. For #57
that meant a fresh
luria init, one entry, one link, and both frames’ resolution shown; it surfaced a second finding the loose sentence had missed (the original hand-written link was broken in both frames). Characterizing is investigation, not paperwork: the MWE is where “I think the fixer is wrong” becomes “the fixer is right, the lint is blind.” - File it as an issue, MWE included. The issue is the bug’s home — the narrative, the mechanism, the affected code cited by path. A bug that lives only in a chat or a commit message is invisible to the next person who hits it.
- Classify the response on the ADR-035 ladder. Always wrong and mechanically fixable → lint check. Needs judgement → report. The code is right and the description was wrong → doc fix. The code is right and the expectation was wrong → close, recording the corrected expectation where the reporter looked first. The MWE is what makes this classification honest — each rung is a claim the transcript either supports or refutes.
- The fix PR turns the MWE into a regression test. The issue keeps the narrative; the test keeps the executable form. This is also DP-6 discharged for free: a test distilled from the MWE has, by construction, been seen to fail.
The scope boundary: this governs defects — behavior diverging from the record’s own claims. Feature requests and design questions keep their existing route (an issue, then an ADR if a choice is being made).
Alternatives considered
- Fix first, explain in the PR — the natural reflex, and it inverts the burden of proof: the diff becomes the specification of the bug, and review can only check the fix against itself. The both-frames-broken link in #57 is the concrete counterexample — the reflex fix (reletter the path) was applied live and characterization afterwards showed the real defect was lint’s blindness, which the fix left fully intact.
- Require a root-cause analysis, not just an MWE — more rigor on paper, and heavy enough that small bugs would go unfiled; an unfiled bug is the most expensive kind. The MWE is deliberately the minimum that makes a response classifiable — root cause often falls out of building it, but is not the entry fee.
- Status quo (ad hoc reporting) — free until it isn’t: the cost is paid in re-derivation, exactly the cost the record exists to avoid (project memory), and in fixes that address the sentence rather than the mechanism.
Consequences
- Reporting a bug costs more up front — an MWE instead of a sentence — and buys a response that can be classified, reviewed, and regression-tested without re-deriving the mechanism.
- #57 is this protocol’s first
live run and carries its worked example; its fix (a relative-link
resolution check promoted into
luria lint, a frame-aware fixer rewrite) will be the first PR bound by step 4. - The migrations work inherits an obligation from the same incident: the
rung-2 sweep (#54) computes
journal-entry link targets in the
link_base()frame, never from the entry file’s location — and the #57 regression test is what holds that line. - When triage and the fix are the same small diff, the protocol still holds in miniature: the MWE can be the regression test filed with the fix, but it exists, and the issue exists, before the merge.