Add a weak, directional Answered by field to the ADR process #123

Merged
rob merged 5 commits from feat/adr-answered-by-field into main 2026-08-11 15:42:58 +00:00
Owner

Task 131. ADR-0107 supersedes ADR-0001 wholesale (the first full, non-partial supersession in the project) rather than editing it — a record defining which edits are permitted can't exempt itself from its own rule. Restates every rule ADR-0001 stated, unchanged, and adds Answered by: written only on a record that explicitly deferred a named question, pointing at whatever later record answered it; never changes the deferring record's own Status; needs no reciprocal field on the answering record.

Applied retroactively in the same change: ADR-0003 and ADR-0011 both gain Answered by: ADR-0019 (metadata-only edits, no prose touched) — this is what actually retires the cost ADR-0019's own Consequences named and left open.

template.md gains the field. AdrIndex/AdrIndexTests gain a matching check — every Answered by field must name an ADR that actually exists (mutation-tested: a bogus number reddens the check, naming the file and the number). No bidirectional check, deliberately — the field is one-directional by design. docs/adr/README.md's generated index table and its hand-written "Open questions" section are both updated; the Status column is unaffected by Answered by, the same way it's never shown Supersedes — the field is orthogonal to whether a record is still in force.

Noted in ADR-0107's own Consequences, not fixed here: a second, independently-discovered instance of the identical gap exists between ADR-0002 and ADR-0032 (ADR-0032 says so itself). Out of this ticket's scope, which names ADR-0003/ADR-0011 specifically; left for a follow-up.

Fixed since first review (changes needed): the wholesale restatement had silently dropped two of ADR-0001's own clauses — the sentence stating the rule binds from the moment a record merges onto main, and the one valuing a wrong decision's record over its absence. Found by diffing the two documents clause for clause, not by any mechanical check; both restored verbatim. Also recorded, in the Alternatives considered section: on reflection, a partial supersession touching only the permitted-edits clause would have carried the same answer to "don't let a record exempt itself from its own rule" with far less transcription risk than restating the whole record — that alternative was never separately weighed the first time, and future single-clause amendments should default to it, reserving full restatement for records where most of the content has genuinely changed. Merged main (#120, #122 and others); the generated index was regenerated, not hand-merged.

Architecture-test suite (119 tests), dotnet format --verify-no-changes clean on the pinned SDK.

Task 131. ADR-0107 supersedes ADR-0001 wholesale (the first full, non-partial supersession in the project) rather than editing it — a record defining which edits are permitted can't exempt itself from its own rule. Restates every rule ADR-0001 stated, unchanged, and adds `Answered by`: written only on a record that explicitly deferred a named question, pointing at whatever later record answered it; never changes the deferring record's own `Status`; needs no reciprocal field on the answering record. Applied retroactively in the same change: ADR-0003 and ADR-0011 both gain `Answered by: ADR-0019` (metadata-only edits, no prose touched) — this is what actually retires the cost ADR-0019's own Consequences named and left open. `template.md` gains the field. `AdrIndex`/`AdrIndexTests` gain a matching check — every `Answered by` field must name an ADR that actually exists (mutation-tested: a bogus number reddens the check, naming the file and the number). No bidirectional check, deliberately — the field is one-directional by design. `docs/adr/README.md`'s generated index table and its hand-written "Open questions" section are both updated; the Status column is unaffected by `Answered by`, the same way it's never shown `Supersedes` — the field is orthogonal to whether a record is still in force. Noted in ADR-0107's own Consequences, not fixed here: a second, independently-discovered instance of the identical gap exists between ADR-0002 and ADR-0032 (ADR-0032 says so itself). Out of this ticket's scope, which names ADR-0003/ADR-0011 specifically; left for a follow-up. **Fixed since first review** (`changes needed`): the wholesale restatement had silently dropped two of ADR-0001's own clauses — the sentence stating the rule binds from the moment a record merges onto `main`, and the one valuing a wrong decision's record over its absence. Found by diffing the two documents clause for clause, not by any mechanical check; both restored verbatim. Also recorded, in the Alternatives considered section: on reflection, a partial supersession touching only the permitted-edits clause would have carried the same answer to "don't let a record exempt itself from its own rule" with far less transcription risk than restating the whole record — that alternative was never separately weighed the first time, and future single-clause amendments should default to it, reserving full restatement for records where most of the content has genuinely changed. Merged `main` (#120, #122 and others); the generated index was regenerated, not hand-merged. Architecture-test suite (119 tests), `dotnet format --verify-no-changes` clean on the pinned SDK.
Add a weak, directional Answered by field to the ADR process
All checks were successful
CI / build (pull_request) Successful in 3m8s
CI / e2e (pull_request) Successful in 1m45s
cb728662a0
rob left a comment

Verdict: changes needed

ADR-0107's restatement of ADR-0001's Decision section drops two clauses. Diffed the pre-PR ADR-0001 against ADR-0107's "Every rule ADR-0001 stated stands, restated here in full" section line by line. Missing, not reworded anywhere else in the document:

  1. "This rule binds from the moment a record is merged onto main. ADRs 0001 to 0011 were edited and one was renamed while this structure was still under review, which is exactly what the rule forbids afterwards." — this is substantive, not decoration: it's the only place the original states when immutability starts. Without it, ADR-0107's "An accepted ADR's body is immutable. Once a record is accepted, the only permitted edits are..." reads as binding from the moment Status: Accepted is typed, even in an open, unmerged PR — which was never the rule.
  2. "The record of a decision that turned out wrong is more useful than its absence, because it stops the same option being re-proposed by someone who does not know it was already tried." — softer, arguably rationale rather than directive, but it sat in ADR-0001's Decision section and the restatement's own promise is completeness.

This is exactly the risk you named going in — a wholesale restatement is where a clause quietly goes missing, and nothing mechanical catches it. Add both before merge.

Everything else checks out:

  • Field definition holds the line. The three conditions (explicit deferral only; never changes the deferring record's own Status; one-directional, no reciprocal field) are precise, and each is backed by a rejected alternative that named the specific way it could have been loosened. No escape-hatch reading available as written.
  • Retroactive application is sound. Read ADR-0003, ADR-0011 and ADR-0019 in full. Both ADR-0003's and ADR-0011's own Consequences explicitly name the account-deletion-versus-sole-Owner question and say it "needs its own ADR" — and ADR-0019's own Source field names both back. A genuine fit, not a stretch.
  • Metadata-only, confirmed by diff. ADR-0003 and ADR-0011 each gain exactly one Answered by field in the header block; no other line touched.
  • AdrIndexTests genuinely passes — ran it myself rather than trusting the PR description, 12/12 green. Mutation-tested AnsweredByGaps beyond the placeholder-only case already in the suite: pointed ADR-0003's own field at a well-formed but nonexistent ADR-9999 and confirmed it reddens, naming the file and the bogus number; restored.
  • Index and "Open questions" section are consistent — the generated table shows Superseded by 0107 for ADR-0001 verbatim, and the hand-written prose correctly describes both records now carrying the field.
  • No further undiscovered instances of the deferred-question gap in what I read (ADR-0001/0003/0011/0019/0107 and template.md) beyond the ADR-0002/ADR-0032 one already flagged as task 188.

Minor: mergeable is currently false against main's current tip (main has moved since this PR's base) — mechanical, needs a rebase before merge, not a content problem.

Verdict: changes needed **ADR-0107's restatement of ADR-0001's Decision section drops two clauses.** Diffed the pre-PR ADR-0001 against ADR-0107's "Every rule ADR-0001 stated stands, restated here in full" section line by line. Missing, not reworded anywhere else in the document: 1. *"This rule binds from the moment a record is merged onto `main`. ADRs 0001 to 0011 were edited and one was renamed while this structure was still under review, which is exactly what the rule forbids afterwards."* — this is substantive, not decoration: it's the only place the original states *when* immutability starts. Without it, ADR-0107's "An accepted ADR's body is immutable. Once a record is accepted, the only permitted edits are..." reads as binding from the moment `Status: Accepted` is typed, even in an open, unmerged PR — which was never the rule. 2. *"The record of a decision that turned out wrong is more useful than its absence, because it stops the same option being re-proposed by someone who does not know it was already tried."* — softer, arguably rationale rather than directive, but it sat in ADR-0001's Decision section and the restatement's own promise is completeness. This is exactly the risk you named going in — a wholesale restatement is where a clause quietly goes missing, and nothing mechanical catches it. Add both before merge. Everything else checks out: - **Field definition holds the line.** The three conditions (explicit deferral only; never changes the deferring record's own Status; one-directional, no reciprocal field) are precise, and each is backed by a rejected alternative that named the specific way it could have been loosened. No escape-hatch reading available as written. - **Retroactive application is sound.** Read ADR-0003, ADR-0011 and ADR-0019 in full. Both ADR-0003's and ADR-0011's own Consequences explicitly name the account-deletion-versus-sole-Owner question and say it "needs its own ADR" — and ADR-0019's own Source field names both back. A genuine fit, not a stretch. - **Metadata-only, confirmed by diff.** ADR-0003 and ADR-0011 each gain exactly one `Answered by` field in the header block; no other line touched. - **`AdrIndexTests` genuinely passes** — ran it myself rather than trusting the PR description, 12/12 green. Mutation-tested `AnsweredByGaps` beyond the placeholder-only case already in the suite: pointed ADR-0003's own field at a well-formed but nonexistent `ADR-9999` and confirmed it reddens, naming the file and the bogus number; restored. - **Index and "Open questions" section are consistent** — the generated table shows `Superseded by 0107` for ADR-0001 verbatim, and the hand-written prose correctly describes both records now carrying the field. - No further undiscovered instances of the deferred-question gap in what I read (ADR-0001/0003/0011/0019/0107 and template.md) beyond the ADR-0002/ADR-0032 one already flagged as task 188. Minor: `mergeable` is currently `false` against `main`'s current tip (main has moved since this PR's base) — mechanical, needs a rebase before merge, not a content problem.
# Conflicts:
#	docs/adr/README.md
Restore two dropped clauses in ADR-0107, and merge main
Some checks failed
CI / build (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
c95a24dfe3
# Conflicts:
#	docs/adr/README.md
Merge main again after #124 landed
All checks were successful
CI / build (pull_request) Successful in 3m18s
CI / e2e (pull_request) Successful in 1m38s
55f4867938
rob left a comment

Verdict: mergeable

CI green (#548, success, 9m55s — mostly queueing behind other PRs on one runner) on 55f4867, head unchanged from the SHA given.

Independently diffed the two Decision sections, programmatically rather than by eye — extracted both, joined wrapped lines into paragraphs, and checked each of ADR-0001's 10 original paragraphs for a verbatim match in ADR-0107's restatement. Result: 8 of 10 match exactly, including both previously-missing clauses ("binds from the moment a record is merged onto main" and "a record of a wrong decision is more useful than its absence") — now present word-for-word. The two non-verbatim matches are both expected and correct: the numbering sentence gained "once merged" (consistent with, not contradicting, the restored standalone sentence), and the permitted-edits field list was extended to include Answered by (the entire point of the record). Nothing else is missing. I did not accept the PR body's own claim of completeness — this is my own diff.

Placement is coherent, not just present: both restored clauses sit in the same relative position as the original (immutable-body clause → binds-from-merge → wrong-decision-value → supersession-may-be-partial), with the new Answered by section appended after, in a sensible spot rather than interleaved or buried in Alternatives/Consequences.

The new Alternatives entry, judged on its own merits, not just accepted: I agree with it. Partial supersession is a materially different, lower-risk mechanism here, not just a smaller version of the same risk — it would have required retyping nothing (point at ADR-0001's own frozen text for everything but the one changed clause), where wholesale restatement required retyping nine paragraphs to change one. The entry is honest about the process failure (states plainly the alternative wasn't weighed the first time, and that the omission was found by manual diff, not any mechanical check) without overclaiming — it doesn't argue ADR-0107 must be restructured now, and gives a defensible reason not to (redoing a reviewed record's own shape is itself a risk). The forward-looking default it sets — partial supersession for a single-clause change, full restatement only where most content has genuinely changed — is sound, generalisable guidance, not a caveat added to look responsible.

Retroactive ADR-0003/ADR-0011 application, the field's definition, and the metadata-only diffs are unchanged from cb72866 (confirmed by diffing both files against the pre-PR base — still exactly the one Answered by line each, nothing else touched) — not re-litigated per your steer.

AdrIndexTests run genuinely against this worktree: 119/119 pass. dotnet format --verify-no-changes clean. Pinned SDK confirmed (10.0.100).

Nothing else to flag.

Verdict: mergeable CI green (#548, success, 9m55s — mostly queueing behind other PRs on one runner) on `55f4867`, head unchanged from the SHA given. **Independently diffed the two Decision sections, programmatically rather than by eye** — extracted both, joined wrapped lines into paragraphs, and checked each of ADR-0001's 10 original paragraphs for a verbatim match in ADR-0107's restatement. Result: 8 of 10 match exactly, including **both previously-missing clauses** ("binds from the moment a record is merged onto `main`" and "a record of a wrong decision is more useful than its absence") — now present word-for-word. The two non-verbatim matches are both expected and correct: the numbering sentence gained "once merged" (consistent with, not contradicting, the restored standalone sentence), and the permitted-edits field list was extended to include `Answered by` (the entire point of the record). Nothing else is missing. I did not accept the PR body's own claim of completeness — this is my own diff. **Placement is coherent**, not just present: both restored clauses sit in the same relative position as the original (immutable-body clause → binds-from-merge → wrong-decision-value → supersession-may-be-partial), with the new `Answered by` section appended after, in a sensible spot rather than interleaved or buried in Alternatives/Consequences. **The new Alternatives entry, judged on its own merits, not just accepted:** I agree with it. Partial supersession is a materially different, lower-risk mechanism here, not just a smaller version of the same risk — it would have required retyping nothing (point at ADR-0001's own frozen text for everything but the one changed clause), where wholesale restatement required retyping nine paragraphs to change one. The entry is honest about the process failure (states plainly the alternative wasn't weighed the first time, and that the omission was found by manual diff, not any mechanical check) without overclaiming — it doesn't argue ADR-0107 must be restructured now, and gives a defensible reason not to (redoing a reviewed record's own shape is itself a risk). The forward-looking default it sets — partial supersession for a single-clause change, full restatement only where most content has genuinely changed — is sound, generalisable guidance, not a caveat added to look responsible. Retroactive ADR-0003/ADR-0011 application, the field's definition, and the metadata-only diffs are unchanged from `cb72866` (confirmed by diffing both files against the pre-PR base — still exactly the one `Answered by` line each, nothing else touched) — not re-litigated per your steer. `AdrIndexTests` run genuinely against this worktree: 119/119 pass. `dotnet format --verify-no-changes` clean. Pinned SDK confirmed (`10.0.100`). Nothing else to flag.
rob merged commit 8080da78d5 into main 2026-08-11 15:42:58 +00:00
rob deleted branch feat/adr-answered-by-field 2026-08-11 15:42:58 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/PlaceMark!123
No description provided.