ADR-0019: Publication marker + reconciliation protocol (database-free)¶
| Status | Accepted (D-030 — Phase-3 freeze review) |
| Date | 2026-07-24 |
| Deciders | Konrad Heimel |
| Context links | ADR-0011 (UpsertComment/SyncThreads) · ADR-0012 (finding-key / marker comments) · ADR-0015 §6 (serve dedup) · ADR-0016 §1 (renderer-owned marker region) · ADR-0017 §7 (Reconcile) · D-007 · D-017 (B6) · frozen contract marker grammar |
Context¶
D-017 (B6) commits to freezing the marker + reconciliation protocol that the Reconcile port (ADR-0017 §7) and the finding-lifecycle state machine (ADR-0011, ADR-0012) already assume, while preserving D-007 (no database — the forge itself, via hidden-HTML markers on bot-authored comments, is the durable reconciliation surface). Without a named ADR stating the marker grammar, the numbered reconciliation contract, and the one-publisher-per-MR topology as independently supersedable decisions, "rerun idempotence" (P4-E1) has nothing precise to implement against, and duplicate-comment incidents have no deterministic repair rule.
The content of this ADR is lifted from the Phase-3 frozen contracts under
docs/contracts/p3-e5-publication-protocol/ (P3-E5-S01..S03). It does not invent new
protocol content; it records the authorship criteria for Phase-3 freeze review acceptance.
Options¶
| Option | Pros | Cons |
|---|---|---|
| A. Keep protocol only in contracts/ — no ADR until engine impl | Thin Phase-3 surface | Freeze review has no ADR to accept; walkthrough/doctor drift; supersession path unclear |
| B. Single ADR with three numbered decisions (this ADR) | Matches D-017 (B6); each concern supersedable; adopters find one place | Slight restatement of contracts/ |
| C. Three separate ADRs (marker / reconcile / topology) | Maximal independence | Index churn; cross-links heavier than the coupling warrants |
Decision¶
1. Four-concept marker split¶
Every bot-authored forge comment/thread carries a hidden-HTML marker whose payload has
exactly four top-level concepts — slot, occurrence, decision, artifact — as frozen
in marker-grammar.md and
marker-grammar.schema.json:
slot: stable identity from project/MR, rule ID, obligation, EntryRef, effect, anchor.occurrence: hash of the safety-relevant judged content (changed content cannot inherit a prior resolution).decision: DecisionRecord hash that requested this state.artifact: kind (finding-thread|summary-comment) + marker schema version.
Markers are correlation metadata only — never decision input or authorization evidence. Only bot-authored comments are parsed (contributor spoofing is ignored by author-identity filter). Markers carry no secrets, fact values, user-controlled Markdown, or raw policy expressions. This decision preserves D-007: the forge comment list + markers are the sole durable reconciliation surface.
2. Nine-step reconciliation contract¶
Every reconciliation run — fresh, plain rerun, or crash-then-rerun — executes the nine
numbered steps frozen in
reconciliation-state-table.md,
in order:
- Recompute
DesiredReviewStatefrom trusted inputs (target-ref trust). - List paginated bot-authored artifacts (no-database/D-007).
- Update the one summary slot in place (determinism).
- Leave the same unresolved occurrence untouched (determinism).
- Preserve resolution of the same occurrence across reruns (no-database/D-007).
- Supersede stale occurrences with a fresh challenge (determinism).
- Resolve no-longer-desired findings (target-ref trust).
- Deterministically repair pre-existing duplicates — lowest forge ID canonical; repairs
recorded in
PublicationReceipt.repairs(determinism). - Rescan after publication before reporting success (no-database/D-007).
The (existing-artifact-state × desired-state) → action table and the fixtures
(rerun-idempotence, crash-then-rerun, duplicate-repair) are normative for P4-E1's
rerun-idempotence exit gate. No row triggers more than one action.
3. One-publisher-per-MR topology¶
Strict duplicate prevention (as opposed to step 8's post-hoc repair) requires exactly one publisher per MR at a time:
| Mode | Serialization mechanism |
|---|---|
One-shot CI (assent run) |
GitLab CI resource_group keyed per MR IID (e.g. assent-mr-$CI_MERGE_REQUEST_IID) |
Long-lived serve |
Keyed per-MR lock held for the duration of one Reconcile call |
Multi-replica HA is unsupported. Concurrent unserialized publishers converge only on the
next reconciliation (step 8 repair), never immediately. assent doctor MUST emit
duplicate_prevention: single-writer-serialized | unserialized-best-effort and must never
claim single-writer-serialized when it cannot verify the serialization mechanism — the safe
default on ambiguity is unserialized-best-effort. The setup walkthrough's CI step and the
doctor checklist must state this requirement explicitly (P3-E5-S04).
Consequences¶
- Phase-3 freeze review accepted this ADR (D-030); Status is Accepted and matches the ADR index row.
- Engine impl (Reconcile port,
doctor,servekeyed lock) implements against the frozen contracts + this ADR; superseding any one of the three numbered decisions does not require rewriting the other two. - Adopters copying the walkthrough without reading this ADR still get
resource_group(or serve keyed-lock) wiring; doctor warns rather than silently claiming serialization. - GitHub marker parity remains Locked (D-012); this ADR is GitLab-first.
Counterpoints considered¶
"Contracts alone are enough — an ADR restates." Rejected: D-017 (B6) and the named-consumer compat note require New ADRs authored inside their owning epics and accepted at the Phase-3 freeze review. Without ADR-0019, freeze review has no decision record to accept, and the three concerns cannot be superseded independently.
"Fold marker + reconcile + topology into one unnumbered Decision paragraph." Rejected: the spec (REQ-P3-E5-S04-01) requires three separately numbered decisions so each can be superseded independently later — e.g. a future multi-replica lock protocol must not force rewriting the marker grammar.