Repository navigation
Provenance + authority on facts; keep and surface contested claims #13
Description
Context
From Fowler's "The Specification Is Not a Document" (https://aicoding.leaflet.pub/3mtgs36dnq22o): document contradictions rather than hiding them. Store who asserted a fact and with what authority; when claims conflict, keep both marked contested instead of silently superseding; surface contested pairs on recall.
The codebase is already most of the way there — this issue proposes closing three specific gaps, not a new subsystem.
What already exists
Fact#statusincludes"disputed", andResolve::Resolvercreates disputed fact + conflict rows on:conflictoutcomes.- A
provenancetable links facts to source content with line ranges (migrations 001, 008). confidenceis already a first-class fact attribute.
Gap 1: supersession ignores authority
Resolver#supersession_signal? returns true for any strength == "stated" assertion, so a newly stated claim silently retires the old one — even if the old one was stated by the user and the new one came from a stale doc. There is no notion of who asserted a fact or how much weight that source carries.
Proposal:
- Add
asserted_by(freeform:user,agent:<name>,doc:<path>) andauthority(small ordinal enum, e.g.user_stated > maintainer_doc > inferred) to facts — or, if churn on the facts table is unwanted, to provenance rows with the fact's effective authority = max over its provenance. - Resolver rule change: a stated claim supersedes only when
new.authority >= old.authority. When authorities are equal-and-conflicting or incomparable, fall through to the existing:conflictpath (keep both, one marked disputed, conflict row records the pair) instead of superseding.
Gap 2: contested pairs are invisible on recall
FactQueryBuilder only ever returns active facts; a disputed fact stays hidden until explicitly asked for (the resolver comments around the example-text guard describe exactly this failure). That means recall confidently returns one side of a live contradiction.
Proposal:
- When a recalled slot (subject + predicate) has disputed siblings, include them, annotated. Formatter output shape:
X is contested: "A" (user, 2026-08-12) vs "B" (doc:README, 2026-08-20). - Keep the default result ordering by authority then recency, so callers that only read the first value still get the best guess — but the contradiction is on the record.
Gap 3: conflict rows don't record authority
Conflict rows should carry both sides' asserted_by/authority/asserted_at snapshot so a contested pair is judgeable later without re-deriving provenance.
Non-goals
- No auto-resolution of contested pairs (human or explicit tool call resolves them).
- No change to the example-text/ReferenceMaterialDetector guard — it correctly discards, not disputes.
Suggested order
- Migration:
asserted_by,authority,asserted_at(+ backfillauthority = inferred). - Resolver authority gate (Gap 1) + conflict row snapshot (Gap 3).
- Recall surfacing (Gap 2).
Filed from bean task-4bed2663 (Val intake 2026-08-20). Happy to take the implementation in a follow-up branch if this direction looks right.
Activity
docs: WORLD.md restructured under the fleet's six headings (fleet-5b)