ADR-0067: Review Comments — Anchored, Immutable, Resolvable¶
- Status: accepted (decisions taken under the night-shift rule — Nicolas asked for the implementation and is away; every point below is vetoable and cheap to adjust)
- Date: 2026-07-13
- Deciders: Nicolas Burri ("comments only for reviewers sounds good to me too… Can you implement this as well, please?"), design detail by Claude per the documented recommendations.
- Relates to: ADR-0066 (records the comments-only reviewer principle this realizes), ADR-0064 (the workflow groups that define who participates), ADR-0057 (frozen tree = stable anchors during review), ADR-0025/0016 (disposal must sever comment content — comments quote content).
Decision¶
- A comment belongs to a document VERSION and optionally carries an anchor: a block reference into the version's (frozen) content tree plus the quoted excerpt at creation time and its character offset within the block (amended 2026-07-14, after a user-found bug: with repeating text the quote alone is ambiguous — resolution is block-scoped, exact-at-offset first, never a global text search). While DRAFT, editor markers follow edits positionally and the moved anchors are re-persisted on save (position only — quote and body stay immutable). While IN_REVIEW the content is frozen, so anchors are stable for the whole round; if the version later changes (withdraw → edit), stale anchors degrade to quote-only display — honest, never dangling.
- Participants: holders of the document's AUTHOR / REVIEWER / RELEASER workflow roles may comment while the version is DRAFT or IN_REVIEW. Reading follows normal version visibility (RLS scope; base readers of released content see no draft-round discussions by construction).
- Comments are records: create, reply (one level), resolve/unresolve (resolver + timestamp recorded). No edit. Delete exists in exactly one narrow form (amended 2026-07-13, Nicolas: accidental/mispositioned comments happen in practice; redundant ones are closed by resolve-with-a-note): the comment's OWN author may delete it while the version is still DRAFT and the comment has no replies (a reply is someone else's record). The deletion is audited (COMMENT_DELETED: actor + comment id; the body is gone — that is the point — the act is not). Anything that entered a review round, or was answered, stays. Body is plain text (rendered as text; no HTML surface).
- Resolution gates RELEASE, not approval (amended 2026-07-14, Nicolas — from his source-code
review practice): a review-policy flag
requireCommentsResolved(per (scope,type), default ON, the four-eyes configurability pattern) blocks RELEASE while any unresolved root comment exists on the version — closing the gap where reviewer A's open question survives reviewer B's approval. Surfaced via the action-surface machinery (Release disabled: "N unresolved review comments"). The gate guards EVERY path into RELEASED: under an AUTOMATIC release policy the auto-release is deferred while unresolved roots exist — the version stays IN_REVIEW and a releaser releases explicitly after resolution (resolving a comment never releases as a side effect; releaser accountability stays an explicit act). Resolve rights stay participant-wide (G-2): a bulldozed comment is visible, audited misconduct rather than a prevented action — accountability over friction; restricting resolve to comment-author/reviewers remains possible later without schema change. - Visibility is state-bound (amended 2026-07-14): while DRAFT/IN_REVIEW the comments layer (markers + panel) is the working surface, visible as before — except resolved comments lose their inline markers everywhere (V-3a; they live in the panel's collapsed section — resolving visibly cleans the text). Once the version leaves the working states (RELEASED, superseded, revoked), the layer is hidden for everyone by default — the official record reads clean; a discreet "Review comments (N)" toggle appears ONLY for authorized users (V-2a: the document's workflow-group holders + holders of CONFIGURE_REVIEW_POLICY effective in the scope — the latter covers the internal-auditor case). ENFORCED SERVER-SIDE: the comments GET returns nothing to unauthorized callers on post-working states — hiding is not a UI courtesy. Underneath, nothing changes: comments stay immutable records; disposal still severs.
- Lifecycle consequences: disposal severs comment bodies + quotes with the version's content (the quoted excerpt IS content); derive/copy does NOT carry review discussions; revise keeps prior-version comments on the prior version (history intact).
- Notification, not task: AUTHOR-group holders are notified when another participant comments on their IN_REVIEW version (existing notification fan-out machinery; no new task type — comments demand attention, not workflow state).
- UI: reading view text selection → "Add comment" (anchored) + a plain "Add comment" (version-level); a Comments panel in the detail rail (participant avatars/names, quotes, one-level replies, resolve toggle); inline highlight markers where anchors resolve.
Consequences¶
- New RLS-covered table (version-anchored, scope-carrying like its siblings); additive migration; no reseed dependency beyond the usual.
- The comments panel becomes the natural home for the future "suggested changes" evolution (a comment carrying a concrete proposed edit) — deliberately out of scope here.
- REQ rows (participation, immutability, disposal) join the v1.7 baseline batch.
Amendment 1 (2026-08-13) — the participant set widens to EDIT_DRAFT holders (register #83)¶
The clean rebirth surfaced the asymmetry §2's participant rule creates once ADR-0115 made co-writing ordinary: an EDIT_DRAFT holder may rewrite every character of a working draft yet could not annotate one — the selection affordance never armed for them, in read or edit mode. Decided by Nicolas, with the rationale that carries the change: "I prefer too much conversation in the comments over people using off-platform tools to talk about a review and losing the trail in the system."
The change: a participant is now a holder of any of the document's three workflow-role bindings — or a holder of EDIT_DRAFT effective in the scope. One participant definition, so all participant acts widen together (create, reply, resolve): a co-writer who may open a thread may close one.
What does NOT change: the comment WINDOW (DRAFT/IN_REVIEW only — comments remain instruments of the working conversation, never post-release graffiti) and §5's post-working visibility rule (who reads the record afterwards is untouched). The widening is who may speak while the document is being worked on, nothing else.