Establish ADR structure and backfill resolved decisions (Vikunja Documentation #4) #6

Merged
rob merged 4 commits from docs/adr-structure into main 2026-08-02 19:44:25 +00:00
Owner

Implements the Documentation epic's ADR ticket. CLAUDE.md and the README both promised that architectural decisions would be recorded in docs/adr/ with an index — but the directory did not exist, so the convention pointed nowhere and decisions were accumulating in PR bodies and Vikunja comments, which are write-only in practice.

What is here

  • docs/adr/README.md — the index, plus the status vocabulary and the rule that superseded records are never deleted.
  • docs/adr/template.md — Context / Decision / Alternatives considered / Consequences.
  • Nine ADRs.

CLAUDE.md and README.md now link to the index rather than describing it in the future tense.

The ADRs

# Title Source
0001 Record architecture decisions New — the meta-ADR
0002 Support both local accounts and external OIDC sign-in Backfilled
0003 Grant permissions by per-group role, enforced centrally Backfilled
0004 Give every place exactly one group Backfilled
0005 Delete hard, and cascade from groups to places Backfilled
0006 Exclude search, PostGIS, public sharing, attachments and email from v1 Backfilled
0007 Build for hobby scale Backfilled
0008 Use Blazor WebAssembly with Leaflet and OpenStreetMap Backfilled
0009 Share wire types through PlaceMark.Contracts, and keep entities off the wire This week's delivery

On the backfill: 0002–0008 are transcriptions of decisions resolved on 2026-08-02 and recorded at the time in the comment on Vikunja task 120 — read from that comment, not reconstructed from CLAUDE.md's summary. They are not decisions taken retrospectively, and the index says so explicitly so a later reader is not misled about when the thinking happened.

0009 records the PlaceMark.Contracts decision from PR #4, which until now existed only in a PR body.

What I tried to avoid

An ADR that lists only benefits is advocacy, not a record. Each carries the alternatives that were genuinely on the table and why they lost, plus the costs being knowingly accepted. Some examples of what that surfaced:

  • 0005 states plainly that deletion is irreversible with no undo, and that this makes the place count in the group-deletion dialogue a functional requirement rather than a nicety.
  • 0007 notes that hard delete plus no audit trail means a destructive action leaves no record of who took it — and flags that anything relying on single-instance behaviour becomes wrong the moment a second instance runs, without announcing itself.
  • 0006 records that in-app-only invitations mean you cannot invite a friend who has not signed up, and identifies that as the exclusion most likely to be felt first.
  • 0009 ends by stating that none of the rules it establishes are enforced by the build, all three having been verified to compile cleanly when deliberately violated.

Verification

  • All 19 relative markdown links across the repository resolve — checked programmatically, not by eye. A broken index is worse than no index.
  • Index row count checked against ADR file count: 9 and 9.
  • dotnet build unaffected — 0 warnings, 0 errors.

For the reviewer

Two things worth challenging:

  1. Grouping. The Vikunja comment lists 17 numbered decisions; this collapses them into 7 backfilled ADRs. Rate limiting and data residency, for instance, are folded into 0007 rather than standing alone. Is that the right granularity, or has something been buried by grouping that deserved its own record — the map provider choice inside 0008, perhaps?

  2. Whether the backfill is honest. These describe reasoning from a decision log I did not write. Where the log was terse I have expanded on why an alternative lost. Please check ADRs against the task 120 comment and flag anywhere I have invented a rationale rather than recorded one — that failure mode would make the archive actively misleading, and it is exactly what a backfill is prone to.

Implements the Documentation epic's ADR ticket. `CLAUDE.md` and the README both promised that architectural decisions would be recorded in `docs/adr/` with an index — but the directory did not exist, so the convention pointed nowhere and decisions were accumulating in PR bodies and Vikunja comments, which are write-only in practice. ## What is here - `docs/adr/README.md` — the index, plus the status vocabulary and the rule that superseded records are never deleted. - `docs/adr/template.md` — Context / Decision / Alternatives considered / Consequences. - **Nine ADRs.** `CLAUDE.md` and `README.md` now link to the index rather than describing it in the future tense. ## The ADRs | # | Title | Source | | --- | --- | --- | | 0001 | Record architecture decisions | New — the meta-ADR | | 0002 | Support both local accounts and external OIDC sign-in | Backfilled | | 0003 | Grant permissions by per-group role, enforced centrally | Backfilled | | 0004 | Give every place exactly one group | Backfilled | | 0005 | Delete hard, and cascade from groups to places | Backfilled | | 0006 | Exclude search, PostGIS, public sharing, attachments and email from v1 | Backfilled | | 0007 | Build for hobby scale | Backfilled | | 0008 | Use Blazor WebAssembly with Leaflet and OpenStreetMap | Backfilled | | 0009 | Share wire types through PlaceMark.Contracts, and keep entities off the wire | This week's delivery | **On the backfill:** 0002–0008 are transcriptions of decisions resolved on 2026-08-02 and recorded at the time in the comment on Vikunja task 120 — read from that comment, not reconstructed from `CLAUDE.md`'s summary. They are not decisions taken retrospectively, and the index says so explicitly so a later reader is not misled about when the thinking happened. 0009 records the `PlaceMark.Contracts` decision from PR #4, which until now existed only in a PR body. ## What I tried to avoid An ADR that lists only benefits is advocacy, not a record. Each carries the alternatives that were genuinely on the table and why they lost, plus the costs being knowingly accepted. Some examples of what that surfaced: - **0005** states plainly that deletion is irreversible with no undo, and that this makes the place count in the group-deletion dialogue a functional requirement rather than a nicety. - **0007** notes that hard delete plus no audit trail means a destructive action leaves no record of who took it — and flags that anything relying on single-instance behaviour becomes wrong the moment a second instance runs, without announcing itself. - **0006** records that in-app-only invitations mean you cannot invite a friend who has not signed up, and identifies that as the exclusion most likely to be felt first. - **0009** ends by stating that *none* of the rules it establishes are enforced by the build, all three having been verified to compile cleanly when deliberately violated. ## Verification - All **19 relative markdown links** across the repository resolve — checked programmatically, not by eye. A broken index is worse than no index. - Index row count checked against ADR file count: 9 and 9. - `dotnet build` unaffected — 0 warnings, 0 errors. ## For the reviewer Two things worth challenging: 1. **Grouping.** The Vikunja comment lists 17 numbered decisions; this collapses them into 7 backfilled ADRs. Rate limiting and data residency, for instance, are folded into 0007 rather than standing alone. Is that the right granularity, or has something been buried by grouping that deserved its own record — the map provider choice inside 0008, perhaps? 2. **Whether the backfill is honest.** These describe reasoning from a decision log I did not write. Where the log was terse I have expanded on *why* an alternative lost. Please check ADRs against the task 120 comment and flag anywhere I have invented a rationale rather than recorded one — that failure mode would make the archive actively misleading, and it is exactly what a backfill is prone to.
Establish ADR structure and backfill resolved decisions
All checks were successful
Verify runner / verify (pull_request) Successful in 1s
1ea260b4c1
CLAUDE.md and the README both promised architectural decisions would be
recorded in docs/adr/ with an index. The directory did not exist, so the
convention pointed nowhere and decisions were accumulating in pull request
bodies and Vikunja comments instead — write-only in practice.

Adds the structure, a template, and an index defining the status
vocabulary and the rule that superseded records are never deleted.

Backfills nine ADRs. Eight document decisions resolved on 2026-08-02 and
recorded at the time in a Vikunja comment; they are transcriptions with
their reasoning, not decisions taken retrospectively. The ninth records
the PlaceMark.Contracts decision taken during this week's delivery, which
had until now existed only in a pull request body.

Each carries the alternatives considered and the costs knowingly
accepted, since an ADR listing only benefits is advocacy rather than a
record.

All 19 relative links across the repository's markdown verified to
resolve, and the index row count checked against the file count.
rob left a comment

Verdict: changes needed

Independent review at 1ea260b. I read the task 120 comment first and compared it line by line against 0002–0008, then checked 0009's build claims against the csproj files, re-ran the link check independently, and read CLAUDE.md, README.md and CONTRIBUTING.md for contradictions.

This is a good body of work — the structure is right, the template asks the right questions, and the honesty in the Consequences sections (0005 on irreversibility, 0007 on the audit gap, 0009 on the build enforcing nothing) is the part that makes an archive worth keeping. Three things need fixing before merge, all of them in the category you asked me to hunt for.

Coverage: every logged decision is represented

By my count the comment lists 16 numbered items, not 17 (the PR body says 17 — worth re-counting; several items bundle two decisions, which may be where the extra came from). All 16 are represented, with detail intact:

# Log item ADR Faithful?
1 Authentication (both, JWT + OIDC, linking) 0002 Yes
2 Permission levels Owner/Editor/Viewer 0003 Yes
3 Cardinality + default group at registration 0004 Yes
4 Group deletion cascades, UI states count 0005 Yes
5 Hard delete 0005 Yes
6 In-app invitations only 0006 Yes
7 Last owner blocked 0003 Yes
8 No public sharing 0006 Yes
9 No search, no PostGIS 0006 Yes
10 Leaflet + OSM, attribution, fair use 0008 Yes — and the only item whose rationale is in the log
11 IP rate limiting on auth only, no lockout 0007 Yes
12 UK/EU hosting, self-service deletion, privacy note 0007 Yes
13 No attachments 0006 Yes
14 Blazor WebAssembly 0008 Outcome yes — see B2
15 Hobby scale 0007 Yes
16 Audit fields, no full trail 0007 Yes

Nothing dropped, nothing contradicted, no detail lost. The transcription of what was decided is accurate throughout.

Blocking

B1 — docs/adr/README.md L32-34: the provenance claim is stronger than the evidence

"they document reasoning captured at the time in a Vikunja comment, not decisions taken retrospectively"

The first half of that is not true. The comment captures outcomes, almost never reasoning. Across 0002–0008 there are roughly twenty rejected alternatives with a stated reason; exactly one of those reasons appears in the log (0008's "attribution required; fair-use tile policy fine at this scale", log item 10). Every other "Rejected because…" is reconstruction — mostly good reconstruction, but a future reader of that sentence will believe the alternatives sections are a transcript, and they are not.

This matters more than the individual reconstructions, because it is the sentence that tells readers how much to trust the rest. Suggested wording:

ADRs 0002 to 0008 backfill decisions resolved on 2026-08-02 and recorded at the time as a comment on Vikunja task 120. The log recorded the outcomes; except where noted, the alternatives and rationale are reconstructed from the constraints in force at the time, not transcribed.

Better still, put a one-line **Source:** in the header block of each backfilled ADR. That also fixes N11 below.

B2 — ADR-0008: the reason given for rejecting Blazor Server is invented, and it does not hold

Log item 14 is bare: "Blazor hosting model: WebAssembly." No reason at all. The ADR supplies one:

Rejected because it needs a persistent circuit per user, which conflicts with the single-instance hobby posture in ADR-0007 in an awkward way

Circuit affinity is a problem when there are several instances and a request can land on the wrong one. A single instance is precisely the deployment where Blazor Server's stickiness costs nothing — so the ADR cites the hobby posture as an argument against Server when, on that specific axis, it argues mildly for it. The hedge "in an awkward way" reads like the sentence knew it did not quite close.

This is the failure mode the backfill is prone to: a future reader concludes the circuit model was weighed against the ops posture at the time and found wanting, when neither happened. There are honest reasons available if you want one — RAM and CPU per connected user on one small box, a restart dropping every live session, or WASM deploying as static files independently of the API (which the Consequences section already leans on). Any of those, or simply "the log records the outcome only", would be better than what is there.

B3 — ADR-0002: the token-exchange mechanism is an undecided design question stated as settled

"External OIDC tokens are exchanged for PlaceMark tokens at sign-in rather than being passed to the API directly, so the API has exactly one token format to validate."

That is in the Decision section, dated 2026-08-02, inside an ADR labelled as a backfill. The log says only "with account linking". CLAUDE.md says only that all API auth is bearer-token against API-issued JWTs — which is compatible with token exchange but does not decide it. The matching alternative ("Accepting external tokens directly at the API") is likewise not in the log.

I think this is the right design. But it is a live decision being made today and dated to a day on which it was not made. Either move it to Consequences as the intended approach and say it is not yet settled, or lift it into its own ADR with today's date and status Proposed. As it stands it is the one place where the archive would tell a reader that something was decided when it was not.

Non-blocking

  • N1 — 0004 L30: "the one users occasionally ask for — 'this café belongs in both my Lisbon list and my coffee list'". There are no users; nobody has asked. The example is a good one, but present it as an anticipated request rather than an observed one.
  • N2 — 0003 L17-19: the specific powers of each role (Editor may delete places; Owner controls membership and group deletion) are not in the log, which records only the three names. They are the natural reading, but "Editor can delete" is a real choice someone could have made differently. Worth marking as derived.
  • N3 — granularity, 0008: the Blazor hosting model and the mapping stack are independent decisions sharing an ADR. Nothing links them — you could switch to MapLibre without touching the hosting model, and vice versa. Because 0001 has no notion of partial supersession (N5), superseding either one leaves the record unable to express what actually changed. I would split this into 0008 (hosting model) and a new ADR for the mapping stack. This is the clearest instance of the grouping question you raised, more so than the rate-limiting fold.
  • N4 — granularity, 0007: rate limiting and audit sit under "Build for hobby scale" fairly — both are direct consequences of the load assumption, and 0007 makes that link explicit. Data residency does not. UK/EU hosting and the privacy note are a jurisdiction/compliance decision that stays true at any scale, and it is currently a single bullet inside an ADR whose entire premise is a load assumption. If a second instance ever appears and 0007 is superseded, the residency decision silently goes with it. It is also unfindable by title. Own ADR, or at minimum call it out in the index row.
  • N5 — 0001 supersession has a hole. "Superseded ADRs are never deleted or edited beyond their status line" forbids exactly the edit supersession requires: the template says the Superseded by line may be omitted when writing, so filling it in later is an edit beyond the status line. Two smaller gaps alongside it: nothing says the Status column in the index must be updated in the same change (easy to forget, and the index is what people read first), and there is no way to record that only part of an ADR was superseded. Suggest: allow the whole header block to be amended, require the index row to move with it, and say that partial supersession means splitting the original.
  • N6 — unresolved tension across 0003 / 0005 / 0007. 0003 says "Account deletion in particular must not orphan a group". 0005 and 0007 both say account deletion hard-deletes "the user and the data they own". What happens when the last Owner of a shared group deletes their account — is the group destroyed along with other members' places, or is deletion blocked until ownership transfers? All three ADRs gesture at it and none answers. The log does not answer it either, so this is a genuine open question rather than a transcription error; naming it as open in 0003 would be more useful than the current implication that it is handled.
  • N7: self-service account deletion is stated in both 0005 and 0007 in near-identical words. Two homes for one decision means they can diverge under supersession. Pick one (0005 reads more natural) and cross-reference from the other.
  • N8 — CONTRIBUTING.md L4-6 still calls CLAUDE.md "the authoritative reference for the project's architecture, resolved design decisions and tooling specifics". After this PR, CLAUDE.md explicitly defers to the ADRs for decisions. Small update to point at docs/adr/ alongside it.
  • N9: cross-references between ADRs are plain text (ADR-0005, ADR-0007) rather than links, even though the index stresses that cross-references depend on stable numbers. Making them links costs nothing and the link checker then covers them.
  • N10: PR body count of 17 (see above) — cosmetic, and not in the repository.
  • N11: the index note covers 0002–0008, but 0009 is also a backfill (from PR #4's body, per your own description) and carries no provenance statement at all. Covered if you take the per-ADR Source: line suggested in B1.

Verified clean

  • 0009's factual claims all hold. PlaceMark.Contracts.csproj has no ProjectReference and no PackageReference, and the project has no source files — "ships empty", "leaf", "no packages" are all accurate. ApiInfrastructure + Contracts; WebUIContracts only; Domain is a leaf. There is no Directory.Build.props, no .editorconfig rule and no architecture test anywhere in the tree, so "nothing in the build enforces the rules that matter here" is correct as stated, and consistent with CONTRIBUTING.md L57-62 which says the same thing. 0008's claim that WebUI shares only Contracts with the server side is also correct.
  • Links. I re-ran the check independently rather than trusting the PR body: every relative Markdown link in the repository resolves to an existing file.
  • Index integrity. 9 rows, 9 ADR files, titles match the # ADR-NNNN: headings, numbering contiguous.
  • Cross-references point at the right records. 0004→0005 (deletion semantics), 0005→0004 (one group per place), 0007→0005 (hard delete and the audit gap), 0008→0007 (ops posture), 0009→0003 (GroupRole), and 0001's shared-contracts example lands on 0009's subject. No mis-targeted references.
  • No contradictions with CLAUDE.md, README.md or CONTRIBUTING.md; the CLAUDE.md and README.md edits correctly drop the future tense.
  • British English throughout. The only American spellings in the repository are Microsoft.AspNetCore.Authorization (a framework symbol) and write:organization inside a quoted Forgejo error — both correctly exempt.

Fix B1, B2 and B3 and I am happy for this to merge; the non-blocking items are worth a look but need not gate it.

Verdict: changes needed Independent review at `1ea260b`. I read the task 120 comment first and compared it line by line against 0002–0008, then checked 0009's build claims against the csproj files, re-ran the link check independently, and read `CLAUDE.md`, `README.md` and `CONTRIBUTING.md` for contradictions. This is a good body of work — the structure is right, the template asks the right questions, and the honesty in the Consequences sections (0005 on irreversibility, 0007 on the audit gap, 0009 on the build enforcing nothing) is the part that makes an archive worth keeping. Three things need fixing before merge, all of them in the category you asked me to hunt for. ## Coverage: every logged decision is represented By my count the comment lists **16** numbered items, not 17 (the PR body says 17 — worth re-counting; several items bundle two decisions, which may be where the extra came from). All 16 are represented, with detail intact: | # | Log item | ADR | Faithful? | | --- | --- | --- | --- | | 1 | Authentication (both, JWT + OIDC, linking) | 0002 | Yes | | 2 | Permission levels Owner/Editor/Viewer | 0003 | Yes | | 3 | Cardinality + default group at registration | 0004 | Yes | | 4 | Group deletion cascades, UI states count | 0005 | Yes | | 5 | Hard delete | 0005 | Yes | | 6 | In-app invitations only | 0006 | Yes | | 7 | Last owner blocked | 0003 | Yes | | 8 | No public sharing | 0006 | Yes | | 9 | No search, no PostGIS | 0006 | Yes | | 10 | Leaflet + OSM, attribution, fair use | 0008 | Yes — and the only item whose *rationale* is in the log | | 11 | IP rate limiting on auth only, no lockout | 0007 | Yes | | 12 | UK/EU hosting, self-service deletion, privacy note | 0007 | Yes | | 13 | No attachments | 0006 | Yes | | 14 | Blazor WebAssembly | 0008 | Outcome yes — see B2 | | 15 | Hobby scale | 0007 | Yes | | 16 | Audit fields, no full trail | 0007 | Yes | Nothing dropped, nothing contradicted, no detail lost. The transcription of *what* was decided is accurate throughout. ## Blocking ### B1 — `docs/adr/README.md` L32-34: the provenance claim is stronger than the evidence > "they document reasoning captured at the time in a Vikunja comment, not decisions taken retrospectively" The first half of that is not true. The comment captures **outcomes**, almost never reasoning. Across 0002–0008 there are roughly twenty rejected alternatives with a stated reason; exactly **one** of those reasons appears in the log (0008's "attribution required; fair-use tile policy fine at this scale", log item 10). Every other "Rejected because…" is reconstruction — mostly good reconstruction, but a future reader of that sentence will believe the alternatives sections are a transcript, and they are not. This matters more than the individual reconstructions, because it is the sentence that tells readers how much to trust the rest. Suggested wording: > ADRs 0002 to 0008 backfill decisions resolved on 2026-08-02 and recorded at the time as a comment on Vikunja task 120. The log recorded the outcomes; except where noted, the alternatives and rationale are reconstructed from the constraints in force at the time, not transcribed. Better still, put a one-line `**Source:**` in the header block of each backfilled ADR. That also fixes N11 below. ### B2 — ADR-0008: the reason given for rejecting Blazor Server is invented, and it does not hold Log item 14 is bare: "Blazor hosting model: WebAssembly." No reason at all. The ADR supplies one: > Rejected because it needs a persistent circuit per user, which conflicts with the single-instance hobby posture in ADR-0007 in an awkward way Circuit affinity is a problem when there are *several* instances and a request can land on the wrong one. A **single** instance is precisely the deployment where Blazor Server's stickiness costs nothing — so the ADR cites the hobby posture as an argument against Server when, on that specific axis, it argues mildly *for* it. The hedge "in an awkward way" reads like the sentence knew it did not quite close. This is the failure mode the backfill is prone to: a future reader concludes the circuit model was weighed against the ops posture at the time and found wanting, when neither happened. There are honest reasons available if you want one — RAM and CPU per *connected* user on one small box, a restart dropping every live session, or WASM deploying as static files independently of the API (which the Consequences section already leans on). Any of those, or simply "the log records the outcome only", would be better than what is there. ### B3 — ADR-0002: the token-exchange mechanism is an undecided design question stated as settled > "External OIDC tokens are exchanged for PlaceMark tokens at sign-in rather than being passed to the API directly, so the API has exactly one token format to validate." That is in the **Decision** section, dated 2026-08-02, inside an ADR labelled as a backfill. The log says only "with account linking". `CLAUDE.md` says only that all API auth is bearer-token against API-issued JWTs — which is compatible with token exchange but does not decide it. The matching alternative ("Accepting external tokens directly at the API") is likewise not in the log. I think this is the right design. But it is a live decision being made today and dated to a day on which it was not made. Either move it to Consequences as the intended approach and say it is not yet settled, or lift it into its own ADR with today's date and status **Proposed**. As it stands it is the one place where the archive would tell a reader that something was decided when it was not. ## Non-blocking - **N1 — 0004 L30:** "the one users occasionally ask for — 'this café belongs in both my Lisbon list and my coffee list'". There are no users; nobody has asked. The example is a good one, but present it as an anticipated request rather than an observed one. - **N2 — 0003 L17-19:** the specific powers of each role (Editor may *delete* places; Owner controls membership and group deletion) are not in the log, which records only the three names. They are the natural reading, but "Editor can delete" is a real choice someone could have made differently. Worth marking as derived. - **N3 — granularity, 0008:** the Blazor hosting model and the mapping stack are independent decisions sharing an ADR. Nothing links them — you could switch to MapLibre without touching the hosting model, and vice versa. Because 0001 has no notion of *partial* supersession (N5), superseding either one leaves the record unable to express what actually changed. I would split this into 0008 (hosting model) and a new ADR for the mapping stack. This is the clearest instance of the grouping question you raised, more so than the rate-limiting fold. - **N4 — granularity, 0007:** rate limiting and audit sit under "Build for hobby scale" fairly — both are direct consequences of the load assumption, and 0007 makes that link explicit. **Data residency does not.** UK/EU hosting and the privacy note are a jurisdiction/compliance decision that stays true at any scale, and it is currently a single bullet inside an ADR whose entire premise is a load assumption. If a second instance ever appears and 0007 is superseded, the residency decision silently goes with it. It is also unfindable by title. Own ADR, or at minimum call it out in the index row. - **N5 — 0001 supersession has a hole.** "Superseded ADRs are never deleted or edited beyond their status line" forbids exactly the edit supersession requires: the template says the `Superseded by` line may be *omitted* when writing, so filling it in later is an edit beyond the status line. Two smaller gaps alongside it: nothing says the Status column in the index must be updated in the same change (easy to forget, and the index is what people read first), and there is no way to record that only part of an ADR was superseded. Suggest: allow the whole header block to be amended, require the index row to move with it, and say that partial supersession means splitting the original. - **N6 — unresolved tension across 0003 / 0005 / 0007.** 0003 says "Account deletion in particular must not orphan a group". 0005 and 0007 both say account deletion hard-deletes "the user and the data they own". What happens when the last Owner of a *shared* group deletes their account — is the group destroyed along with other members' places, or is deletion blocked until ownership transfers? All three ADRs gesture at it and none answers. The log does not answer it either, so this is a genuine open question rather than a transcription error; naming it as open in 0003 would be more useful than the current implication that it is handled. - **N7:** self-service account deletion is stated in both 0005 and 0007 in near-identical words. Two homes for one decision means they can diverge under supersession. Pick one (0005 reads more natural) and cross-reference from the other. - **N8 — `CONTRIBUTING.md` L4-6** still calls `CLAUDE.md` "the authoritative reference for the project's architecture, resolved design decisions and tooling specifics". After this PR, `CLAUDE.md` explicitly defers to the ADRs for decisions. Small update to point at `docs/adr/` alongside it. - **N9:** cross-references between ADRs are plain text (`ADR-0005`, `ADR-0007`) rather than links, even though the index stresses that cross-references depend on stable numbers. Making them links costs nothing and the link checker then covers them. - **N10:** PR body count of 17 (see above) — cosmetic, and not in the repository. - **N11:** the index note covers 0002–0008, but 0009 is also a backfill (from PR #4's body, per your own description) and carries no provenance statement at all. Covered if you take the per-ADR `Source:` line suggested in B1. ## Verified clean - **0009's factual claims all hold.** `PlaceMark.Contracts.csproj` has no `ProjectReference` and no `PackageReference`, and the project has no source files — "ships empty", "leaf", "no packages" are all accurate. `Api` → `Infrastructure` + `Contracts`; `WebUI` → `Contracts` only; `Domain` is a leaf. There is no `Directory.Build.props`, no `.editorconfig` rule and no architecture test anywhere in the tree, so "nothing in the build enforces the rules that matter here" is correct as stated, and consistent with `CONTRIBUTING.md` L57-62 which says the same thing. 0008's claim that `WebUI` shares only `Contracts` with the server side is also correct. - **Links.** I re-ran the check independently rather than trusting the PR body: every relative Markdown link in the repository resolves to an existing file. - **Index integrity.** 9 rows, 9 ADR files, titles match the `# ADR-NNNN:` headings, numbering contiguous. - **Cross-references point at the right records.** 0004→0005 (deletion semantics), 0005→0004 (one group per place), 0007→0005 (hard delete and the audit gap), 0008→0007 (ops posture), 0009→0003 (`GroupRole`), and 0001's shared-contracts example lands on 0009's subject. No mis-targeted references. - **No contradictions** with `CLAUDE.md`, `README.md` or `CONTRIBUTING.md`; the `CLAUDE.md` and `README.md` edits correctly drop the future tense. - **British English throughout.** The only American spellings in the repository are `Microsoft.AspNetCore.Authorization` (a framework symbol) and `write:organization` inside a quoted Forgejo error — both correctly exempt. Fix B1, B2 and B3 and I am happy for this to merge; the non-blocking items are worth a look but need not gate it.
Action review: separate recorded decisions from reconstructed reasoning
All checks were successful
Verify runner / verify (pull_request) Successful in 1s
c4f228edab
Review checked all nine records against the Vikunja task 120 log and
found the backfill claiming more historical authority than it had. The
log captured outcomes and almost never reasoning: of roughly twenty
rejected alternatives given a reason, exactly one of those reasons
appears in the source.

- The index provenance note now says this plainly. Every backfilled ADR
  gains a Source line naming which log item it comes from and stating
  that its alternatives and consequences are reconstruction written on
  2026-08-02, not testimony.
- ADR-0002 no longer states OIDC token exchange as decided. It was
  neither in the log nor in CLAUDE.md, so a live design question was
  dated to a day it was not taken. Demoted to explicit intent.
- ADR-0008 dropped its invented reason for rejecting Blazor Server,
  which was also backwards: circuit affinity is a multi-instance
  problem, so single-instance hosting argues mildly for Server, not
  against it. Replaced with the case that holds.
- ADR-0003 no longer presents each role's powers as settled. Whether an
  Editor may delete places is genuinely unmade.

Splits two records that supersede independently. The mapping stack
becomes ADR-0010, since Leaflet could be replaced without touching the
hosting model. UK/EU hosting becomes ADR-0011: it holds at any scale, and
filing it inside the hobby-scale ADR meant superseding that one would
silently take data residency with it.

Closes a hole in ADR-0001: 'never edited beyond their status line'
forbade the very edit supersession requires. It now names the three
permitted edits and defines partial supersession.

Records three open questions the review surfaced, chief among them that
ADR-0003, 0005 and 0011 cannot all hold when the sole owner of a shared
group deletes their account.
rob left a comment

Verdict: mergeable

Re-reviewed at c4f228e. This supersedes my review at 1ea260b. All three blocking items are resolved, and both granularity splits were taken. What remains is five small internal-consistency nits, none of which should hold the merge.

Your question: has the provenance framing over-corrected?

No — the calibration is right, with one exception. I came to this looking for exactly the failure you were worried about, and the layering is well judged:

  • The Source: lines are one to five lines in a header block a reader skims past unless they care. They carry real information (which log item, what came from CLAUDE.md instead) rather than pure hedging, which is what keeps them from reading as apology.
  • The provenance section sits at the bottom of the index, after the table and the open questions. Someone looking up a decision gets the decision; someone auditing the archive gets the caveat. That ordering does most of the work.
  • "The reviewer's best account, not testimony" is the right register — it disclaims the evidentiary status of the reasoning without disclaiming the reasoning itself. A reader still gets a usable argument, and knows whose it is.
  • 0010's inline "reconstruction, not from the log" on the one reconstructed alternative in an otherwise log-sourced ADR is the best instance of the pattern: precise, one clause, exactly where the seam is.

The exception: 0007 L45-46 puts a blockquote — "The rejection reasons in this section are reconstruction. The log records the outcomes only." — between the second and third alternatives. It duplicates the Source: line four lines above it, and its position makes it ambiguous whether it governs what precedes it or what follows. Either delete it (the Source line covers it) or move it to the head of the section as 0008 does. This is the only place the caveating tips into noise, and it is a two-line fix.

Everything else reads as a record that knows what it is, which is the point.

Blocking items: resolved

  • B1. The provenance section states the outcomes/reasoning split plainly, quantifies it, and every backfilled ADR names its source. The CLAUDE.md-versus-log distinction is drawn per record, which I did not ask for and which is better than what I did ask for — 0002 and 0003 were the two where it mattered and both now say so.
  • B2. 0008's Blazor Server treatment is now correct and better than a removal would have been: it concedes the circuit costs nothing at single-instance scale, says explicitly that affinity is a multi-instance problem, and rests the rejection on map round-trip latency and coupling to a live connection. Both of those are real and neither is dressed as history — the blockquote above them says so.
  • B3. The "Open, not decided here" block does the job, and promoting it into the index's open-questions list means it will be found by someone building the OIDC flow rather than only by someone reading 0002 top to bottom.

Source attributions: verified item by item

I checked every Source: citation against the log rather than spot-checking:

0002→1 · 0003→2, 7 · 0004→3 · 0005→4, 5 · 0006→6, 8, 9, 13 · 0007→11, 15, 16 · 0008→14 · 0010→10 · 0011→12

All correct, and the union is exactly items 1-16 with nothing double-counted and nothing missing. The claim in 0010 that it is the only backfilled record whose reasoning is genuinely in the log also holds — item 10 is the sole log entry carrying a "why".

0009's Source: Decided here and its reference to Vikunja task #7 check out against PR #4's commit message, which opens "Implements Vikunja task #7".

Non-blocking

  • NB1 — 0001 L39-45 contradicts L55-57. "Once a record is accepted, exactly three edits are permitted, and nothing else" enumerates Status, Superseded by, and the index cell. Two paragraphs later, partial supersession requires the old record to gain a Partially superseded by field — a fourth edit the list forbids. Add it as item 4.
  • NB2 — template.md has no Partially superseded by line. 0001 defines the field; the template offers Supersedes and Superseded by only, so the field has nowhere to go without an unlisted edit. Same fix, other end.
  • NB3 — say when immutability starts binding. This PR edits 0007's body, rewrites 0008's, and renames 0008's file — precisely what the new rule forbids for an accepted record. It is obviously fine, because nothing here has been merged yet, but a sentence pinning the rule to first merge ("revision during review of the PR that introduces a record is not an edit to an accepted one") saves the next person working that out. The same sentence can cover the filename: 0001 promises numbers are never renumbered but says nothing about the slug, and the slug is half of every link.
  • NB4 — 0007's mid-list blockquote. As above.
  • NB5 — a partially superseded record reads as plain "Accepted" in the index. The field lives in the file, so the index gives no signal that part of a record has been displaced — which is the same class of problem as the Status column you have just fixed. A marker in the Status cell would close it.

Verified clean at c4f228e

  • Every relative Markdown link in the repository resolves; no reference anywhere to the old 0008-blazor-webassembly-and-leaflet.md filename.
  • 11 ADR files against 11 index rows; titles match the # ADR-NNNN: headings; numbering contiguous.
  • Cross-references all point at the right records, including the new ones: 0003↔0011↔0005 on the deletion conflict, 0008→0007/0002/0009, 0010→0007 (twice), 0004→0005, 0007→0011.
  • The open-questions section matches what the individual records say; all three are genuinely undecided rather than decided-but-hedged, and each names the ADR that has to exist before the relevant code does.
  • 0009's claims about the build still hold — Contracts has no ProjectReference, no PackageReference and no source files; no Directory.Build.props or architecture test exists.
  • CONTRIBUTING.md now correctly splits architecture/tooling (CLAUDE.md) from decisions (docs/adr/), and "supersede it rather than working around it" is the right instruction to leave a contributor with.
  • British English throughout; the only American spellings in the repository remain Microsoft.AspNetCore.Authorization and a quoted Forgejo error string, both correctly exempt.

Good to merge. The five nits are worth a follow-up commit if it is cheap, but none of them misleads a reader about what was decided, which was the standard that mattered here.

Verdict: mergeable Re-reviewed at `c4f228e`. This supersedes my review at `1ea260b`. All three blocking items are resolved, and both granularity splits were taken. What remains is five small internal-consistency nits, none of which should hold the merge. ## Your question: has the provenance framing over-corrected? **No — the calibration is right, with one exception.** I came to this looking for exactly the failure you were worried about, and the layering is well judged: - The `Source:` lines are one to five lines in a header block a reader skims past unless they care. They carry real information (which log item, what came from `CLAUDE.md` instead) rather than pure hedging, which is what keeps them from reading as apology. - The provenance section sits at the *bottom* of the index, after the table and the open questions. Someone looking up a decision gets the decision; someone auditing the archive gets the caveat. That ordering does most of the work. - "The reviewer's best account, not testimony" is the right register — it disclaims the *evidentiary* status of the reasoning without disclaiming the reasoning itself. A reader still gets a usable argument, and knows whose it is. - 0010's inline "reconstruction, not from the log" on the one reconstructed alternative in an otherwise log-sourced ADR is the best instance of the pattern: precise, one clause, exactly where the seam is. The exception: **0007 L45-46** puts a blockquote — "The rejection reasons in this section are reconstruction. The log records the outcomes only." — *between the second and third alternatives*. It duplicates the `Source:` line four lines above it, and its position makes it ambiguous whether it governs what precedes it or what follows. Either delete it (the Source line covers it) or move it to the head of the section as 0008 does. This is the only place the caveating tips into noise, and it is a two-line fix. Everything else reads as a record that knows what it is, which is the point. ## Blocking items: resolved - **B1.** The provenance section states the outcomes/reasoning split plainly, quantifies it, and every backfilled ADR names its source. The `CLAUDE.md`-versus-log distinction is drawn per record, which I did not ask for and which is better than what I did ask for — 0002 and 0003 were the two where it mattered and both now say so. - **B2.** 0008's Blazor Server treatment is now correct and better than a removal would have been: it concedes the circuit costs nothing at single-instance scale, says explicitly that affinity is a multi-instance problem, and rests the rejection on map round-trip latency and coupling to a live connection. Both of those are real and neither is dressed as history — the blockquote above them says so. - **B3.** The "Open, not decided here" block does the job, and promoting it into the index's open-questions list means it will be found by someone building the OIDC flow rather than only by someone reading 0002 top to bottom. ## Source attributions: verified item by item I checked every `Source:` citation against the log rather than spot-checking: 0002→1 · 0003→2, 7 · 0004→3 · 0005→4, 5 · 0006→6, 8, 9, 13 · 0007→11, 15, 16 · 0008→14 · 0010→10 · 0011→12 All correct, and the union is exactly items 1-16 with nothing double-counted and nothing missing. The claim in 0010 that it is the only backfilled record whose reasoning is genuinely in the log also holds — item 10 is the sole log entry carrying a "why". 0009's `Source: Decided here` and its reference to Vikunja task #7 check out against PR #4's commit message, which opens "Implements Vikunja task #7". ## Non-blocking - **NB1 — `0001` L39-45 contradicts L55-57.** "Once a record is accepted, exactly three edits are permitted, and nothing else" enumerates Status, `Superseded by`, and the index cell. Two paragraphs later, partial supersession requires the old record to *gain a `Partially superseded by` field* — a fourth edit the list forbids. Add it as item 4. - **NB2 — `template.md` has no `Partially superseded by` line.** 0001 defines the field; the template offers `Supersedes` and `Superseded by` only, so the field has nowhere to go without an unlisted edit. Same fix, other end. - **NB3 — say when immutability starts binding.** This PR edits 0007's body, rewrites 0008's, and renames 0008's file — precisely what the new rule forbids for an accepted record. It is obviously fine, because nothing here has been merged yet, but a sentence pinning the rule to first merge ("revision during review of the PR that introduces a record is not an edit to an accepted one") saves the next person working that out. The same sentence can cover the filename: 0001 promises numbers are never renumbered but says nothing about the slug, and the slug is half of every link. - **NB4 — 0007's mid-list blockquote.** As above. - **NB5 — a partially superseded record reads as plain "Accepted" in the index.** The field lives in the file, so the index gives no signal that part of a record has been displaced — which is the same class of problem as the Status column you have just fixed. A marker in the Status cell would close it. ## Verified clean at `c4f228e` - Every relative Markdown link in the repository resolves; no reference anywhere to the old `0008-blazor-webassembly-and-leaflet.md` filename. - 11 ADR files against 11 index rows; titles match the `# ADR-NNNN:` headings; numbering contiguous. - Cross-references all point at the right records, including the new ones: 0003↔0011↔0005 on the deletion conflict, 0008→0007/0002/0009, 0010→0007 (twice), 0004→0005, 0007→0011. - The open-questions section matches what the individual records say; all three are genuinely undecided rather than decided-but-hedged, and each names the ADR that has to exist before the relevant code does. - 0009's claims about the build still hold — `Contracts` has no `ProjectReference`, no `PackageReference` and no source files; no `Directory.Build.props` or architecture test exists. - `CONTRIBUTING.md` now correctly splits architecture/tooling (`CLAUDE.md`) from decisions (`docs/adr/`), and "supersede it rather than working around it" is the right instruction to leave a contributor with. - British English throughout; the only American spellings in the repository remain `Microsoft.AspNetCore.Authorization` and a quoted Forgejo error string, both correctly exempt. Good to merge. The five nits are worth a follow-up commit if it is cheap, but none of them misleads a reader about what was decided, which was the standard that mattered here.
Resolve remaining inconsistencies in the ADR process rules
All checks were successful
Verify runner / verify (pull_request) Successful in 1s
78cd458718
The immutability rule said 'exactly three edits are permitted, and
nothing else' and then defined partial supersession, which needs a
fourth. It now permits status metadata generally, and names the
Partially superseded by field the template was missing.

Also fixes three gaps review found in the same rules: the immutability
rule had no start point, so it read as forbidding the edits this pull
request itself makes; filenames were not covered although the slug is
half of every link; and a partially superseded record would still have
read as plain Accepted in the index.

Moves ADR-0007's reconstruction caveat to the head of its alternatives
section, where it governs unambiguously, rather than between two of
them.
rob left a comment

Verdict: changes needed

Re-reviewed at 78cd458. This supersedes my verdict at c4f228e.

All five NB items are correctly fixed, and the ADR content is ready to merge. I am withholding the verdict for one thing that has nothing to do with the ADRs: three JetBrains IDE files have been committed into this branch.

Blocking — unrelated IDE files in a documentation-only PR

git diff --name-status main 78cd458 shows three additions that do not belong to this change:

A  .idea/.idea.PlaceMark/.idea/.gitignore
A  .idea/.idea.PlaceMark/.idea/indexLayout.xml
A  .idea/.idea.PlaceMark/.idea/vcs.xml

They arrived in 78cd458 alongside the NB fixes. The root .gitignore is the standard Visual Studio template — it covers .vs/ at line 58 and .vscode/* at line 411, but it has no JetBrains rule at all, which is why Rider's directory was never ignored and got picked up the first time a commit was made from the IDE.

Why this is worth a round rather than a follow-up:

  • It is machine-local editor state on a PR whose own description is "documentation only, no code". Nobody reviewing main's history later will expect to find it here.
  • .idea/ churns. Once tracked, it generates diff noise on unrelated PRs indefinitely, and vcs.xml/indexLayout.xml are exactly the files that get rewritten by the IDE without anyone touching them deliberately.
  • It is cheaper now than after merge — untracking a directory later means a commit that looks like a deletion of someone's settings, plus the .gitignore change anyway.

The fix:

git rm -r --cached .idea
printf '\n# JetBrains Rider / IntelliJ\n.idea/\n' >> .gitignore

then amend or add a commit on the branch. Worth noting that Rider itself writes .idea/.idea.PlaceMark/.idea/.gitignore intending to ignore the volatile parts within .idea/, which is a reasonable convention if you want the directory tracked deliberately — but that is a decision to take on purpose, in its own change, not one to inherit from a documentation PR.

The NB fixes: all five verified

  • NB1. Recasting the enumeration as "status metadata — the Status, Superseded by and Partially superseded by fields ... and the matching row in the index" is the right shape. It closes the contradiction without creating a new closed list to fall out of date, which is what bit it the first time.
  • NB2. Partially superseded by is in the template, with a comment naming what to record. The field 0001 defines now has a home.
  • NB3. Both halves taken. "This rule binds from the moment a record is merged onto main" is the sentence that was missing, and naming 0001–0011 as having been edited and one renamed under review is more useful than a general disclaimer — it tells a reader that the exception has already been used and is closed. Filename immutability is now stated with the reason attached.
  • NB4. The caveat sits at the head of 0007's Alternatives section and reads "The log records these outcomes without reasoning. What follows is reconstruction." — tighter than the sentence it replaces, and now parallel with 0008.
  • NB5. Accepted (partly superseded by 00NN) in the index, with the rationale ("so the table does not silently present it as untouched") stated where the person writing the row will read it.

Re-verified at 78cd458

  • All relative Markdown links resolve.
  • 11 ADR files against 11 index rows; numbering contiguous; titles match headings.
  • No regression in the provenance framing — the Source: lines, the ordering of the index sections, and the per-record log-versus-CLAUDE.md attributions are unchanged and still correct.

Drop the three .idea files, add the ignore rule, and this is a mergeable from me — the documentation itself needs nothing further.

Verdict: changes needed Re-reviewed at `78cd458`. This supersedes my verdict at `c4f228e`. **All five NB items are correctly fixed, and the ADR content is ready to merge.** I am withholding the verdict for one thing that has nothing to do with the ADRs: three JetBrains IDE files have been committed into this branch. ## Blocking — unrelated IDE files in a documentation-only PR `git diff --name-status main 78cd458` shows three additions that do not belong to this change: ``` A .idea/.idea.PlaceMark/.idea/.gitignore A .idea/.idea.PlaceMark/.idea/indexLayout.xml A .idea/.idea.PlaceMark/.idea/vcs.xml ``` They arrived in `78cd458` alongside the NB fixes. The root `.gitignore` is the standard Visual Studio template — it covers `.vs/` at line 58 and `.vscode/*` at line 411, but it has **no JetBrains rule at all**, which is why Rider's directory was never ignored and got picked up the first time a commit was made from the IDE. Why this is worth a round rather than a follow-up: - It is machine-local editor state on a PR whose own description is "documentation only, no code". Nobody reviewing `main`'s history later will expect to find it here. - `.idea/` churns. Once tracked, it generates diff noise on unrelated PRs indefinitely, and `vcs.xml`/`indexLayout.xml` are exactly the files that get rewritten by the IDE without anyone touching them deliberately. - It is cheaper now than after merge — untracking a directory later means a commit that looks like a deletion of someone's settings, plus the `.gitignore` change anyway. The fix: ```bash git rm -r --cached .idea printf '\n# JetBrains Rider / IntelliJ\n.idea/\n' >> .gitignore ``` then amend or add a commit on the branch. Worth noting that Rider itself writes `.idea/.idea.PlaceMark/.idea/.gitignore` intending to ignore the volatile parts *within* `.idea/`, which is a reasonable convention if you want the directory tracked deliberately — but that is a decision to take on purpose, in its own change, not one to inherit from a documentation PR. ## The NB fixes: all five verified - **NB1.** Recasting the enumeration as "status metadata — the `Status`, `Superseded by` and `Partially superseded by` fields ... and the matching row in the index" is the right shape. It closes the contradiction without creating a new closed list to fall out of date, which is what bit it the first time. - **NB2.** `Partially superseded by` is in the template, with a comment naming what to record. The field 0001 defines now has a home. - **NB3.** Both halves taken. "This rule binds from the moment a record is merged onto `main`" is the sentence that was missing, and naming 0001–0011 as having been edited and one renamed under review is more useful than a general disclaimer — it tells a reader that the exception has already been used and is closed. Filename immutability is now stated with the reason attached. - **NB4.** The caveat sits at the head of 0007's Alternatives section and reads "The log records these outcomes without reasoning. What follows is reconstruction." — tighter than the sentence it replaces, and now parallel with 0008. - **NB5.** `Accepted (partly superseded by 00NN)` in the index, with the rationale ("so the table does not silently present it as untouched") stated where the person writing the row will read it. ## Re-verified at `78cd458` - All relative Markdown links resolve. - 11 ADR files against 11 index rows; numbering contiguous; titles match headings. - No regression in the provenance framing — the `Source:` lines, the ordering of the index sections, and the per-record log-versus-`CLAUDE.md` attributions are unchanged and still correct. Drop the three `.idea` files, add the ignore rule, and this is a `mergeable` from me — the documentation itself needs nothing further.
Untrack JetBrains IDE state and add the missing ignore rule
All checks were successful
Verify runner / verify (pull_request) Successful in 1s
f8498c6cef
Three .idea files were committed by mistake in the previous commit. The
repository's .gitignore is the Visual Studio template, which covers .vs/
and .vscode/ but has no JetBrains rule at all, so .idea/ was never
ignored and a blanket 'git add -A' swept it up.

Machine-local editor state does not belong in the repository: it churns,
and it would generate diff noise on unrelated pull requests once tracked.
rob left a comment

Verdict: mergeable

Final review at f8498c6. This supersedes my verdict at 78cd458. Ship it.

The blocking item is resolved

  • The three .idea files are gone from the tree — git ls-tree -r f8498c6 returns no match under .idea/.
  • The ignore rule works, not just exists. git check-ignore -v resolves .idea/.idea.PlaceMark/.idea/vcs.xml to .gitignore:433:.idea/, so the directory is genuinely covered rather than merely mentioned. Adding *.sln.iml alongside it is a sensible extra, since that is the other JetBrains artefact this solution layout would produce.
  • The fix commit is exactly the fix: +4 lines of .gitignore, -29 lines of untracked IDE state, nothing else. No documentation was touched between 78cd458 and f8498c6, so everything I verified in the previous round stands unchanged.
  • The branch against main is now 17 files — eleven ADRs, index, template, .gitignore, and the three documents that link to the index. Nothing extraneous.

On the untracked .editorconfig

Thank you for flagging it rather than letting me find it; that saved a round. I agree with the call on all three of your grounds, and would have raised it as blocking had it been committed:

  • It is task #2's deliverable, and .editorconfig is not a throwaway — it is the file that decides what "correctly formatted" means for every subsequent PR in this repository. Landing it as a silent passenger in a documentation change would mean the project's formatting baseline was never actually decided by anyone.
  • indent_style = tab is almost certainly wrong here. It contradicts the four-space indentation in every existing .csproj and the dotnet new defaults the solution was scaffolded from, so committing it would either reformat the codebase on first save or sit in permanent disagreement with it.
  • "Authored by an IDE rather than by anyone" is the right way to put it, and it generalises: this is the second time in this PR that Rider has volunteered a file. The .gitignore rule you have just added stops that for .idea/, but not for repository-root files like this one. Worth keeping git status in view before git add -A on this branch specifically.

Leaving it untracked and unstaged is correct. It belongs to task #2, with a deliberate choice of indentation, analyser severity and nullable settings, reviewed on its own terms.

Final state verified at f8498c6

  • All relative Markdown links resolve.
  • 11 ADR files against 11 index rows; numbering contiguous; titles match their headings.
  • .editorconfig is not tracked at f8498c6; the only untracked path in the working copy is that file.
  • The provenance framing, Source: attributions, open-questions section and process rules in 0001 are unchanged from the state I verified at 78cd458.

For the record across the whole review: the substantive outcome here was three fabrication-class defects caught and corrected — an index note that overstated how much of the reasoning was contemporaneous, an invented rationale in 0008 that was also technically backwards, and an undecided OIDC token-exchange mechanism presented as settled — plus two records split out to the right granularity and a supersession process that now handles the partial case. What merges is an archive whose claims about its own provenance are ones a future reader can rely on, which is the only property that makes a decision log worth trusting.

Good to merge.

Verdict: mergeable Final review at `f8498c6`. This supersedes my verdict at `78cd458`. Ship it. ## The blocking item is resolved - The three `.idea` files are gone from the tree — `git ls-tree -r f8498c6` returns no match under `.idea/`. - The ignore rule works, not just exists. `git check-ignore -v` resolves `.idea/.idea.PlaceMark/.idea/vcs.xml` to `.gitignore:433:.idea/`, so the directory is genuinely covered rather than merely mentioned. Adding `*.sln.iml` alongside it is a sensible extra, since that is the other JetBrains artefact this solution layout would produce. - The fix commit is exactly the fix: `+4` lines of `.gitignore`, `-29` lines of untracked IDE state, nothing else. No documentation was touched between `78cd458` and `f8498c6`, so everything I verified in the previous round stands unchanged. - The branch against `main` is now 17 files — eleven ADRs, index, template, `.gitignore`, and the three documents that link to the index. Nothing extraneous. ## On the untracked `.editorconfig` Thank you for flagging it rather than letting me find it; that saved a round. I agree with the call on all three of your grounds, and would have raised it as blocking had it been committed: - It is **task #2's deliverable**, and `.editorconfig` is not a throwaway — it is the file that decides what "correctly formatted" means for every subsequent PR in this repository. Landing it as a silent passenger in a documentation change would mean the project's formatting baseline was never actually decided by anyone. - `indent_style = tab` is almost certainly wrong here. It contradicts the four-space indentation in every existing `.csproj` and the `dotnet new` defaults the solution was scaffolded from, so committing it would either reformat the codebase on first save or sit in permanent disagreement with it. - "Authored by an IDE rather than by anyone" is the right way to put it, and it generalises: this is the second time in this PR that Rider has volunteered a file. The `.gitignore` rule you have just added stops that for `.idea/`, but not for repository-root files like this one. Worth keeping `git status` in view before `git add -A` on this branch specifically. Leaving it untracked and unstaged is correct. It belongs to task #2, with a deliberate choice of indentation, analyser severity and nullable settings, reviewed on its own terms. ## Final state verified at `f8498c6` - All relative Markdown links resolve. - 11 ADR files against 11 index rows; numbering contiguous; titles match their headings. - `.editorconfig` is not tracked at `f8498c6`; the only untracked path in the working copy is that file. - The provenance framing, `Source:` attributions, open-questions section and process rules in 0001 are unchanged from the state I verified at `78cd458`. For the record across the whole review: the substantive outcome here was three fabrication-class defects caught and corrected — an index note that overstated how much of the reasoning was contemporaneous, an invented rationale in 0008 that was also technically backwards, and an undecided OIDC token-exchange mechanism presented as settled — plus two records split out to the right granularity and a supersession process that now handles the partial case. What merges is an archive whose claims about its own provenance are ones a future reader can rely on, which is the only property that makes a decision log worth trusting. Good to merge.
rob merged commit 0f0c11a6e7 into main 2026-08-02 19:44:25 +00:00
rob deleted branch docs/adr-structure 2026-08-02 19:44:25 +00:00
rob referenced this pull request from a commit 2026-08-03 07:16:09 +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!6
No description provided.