# Cycle contradictions: the conflicts pairwise edges cannot see

_donto papers · original research · 2026-06-13 · theory + census, working draft v1_

**Abstract.** Contradiction in a knowledge base is almost always modeled as a *binary* relation: claim `A` rebuts claim `B`, stored as one edge. donto stores exactly this — `donto_argument` holds 2,525 such edges, 2,282 of them `rebuts` (2,150 concentrated in `ctx:epistemic-sweep/contradictions`). But many of the contested-knowledge errors that matter in our corpus are **not** two claims that explicitly negate. They are **cycles** of individually-defensible claims whose *composition* around a loop is impossible — a person recorded as both the *daughter of* and the *spouse of* the same generational position, where no single claim is wrong and no single pair contradicts, yet the loop cannot close. A pairwise edge model is **structurally blind** to these: there is no privileged `source→target` pair to attach a `rebuts` edge to, so none is ever written, so the conflict is invisible to every consumer that reads `donto_argument`. We make the claim precise, prove *why* the blindness is structural (a product of positive scalar maps around any loop is a positive scalar — a `d=1` scalar restriction map **cannot** carry a non-trivial 1-cocycle, ever), and show that cellular-sheaf cohomology with `d≥2` sign-carrying maps detects and *localizes* the loop as a non-zero holonomy `‖H_cyc − I‖_F`. The detector math is built and unit-tested (`packages/donto-sheaf`, Case-A/Case-B fixtures green, no DB). What is **not** done — and is the honest center of this paper — is the **census**: how many real cycle-contradictions live in the genealogy corpus that *no* pairwise method flags. We give the worked Kitty example from live `ex:kitty` rows, the exact census procedure, the `lift` metric that makes a result honest, and a negative we already expect (most of the live conflict frontier is single-`(subject,predicate)` polarity clashes that pairwise methods *do* catch — the cohomological upgrade pays off precisely on the long tail it adds, and the paper's job is to measure how long that tail is).

---

## 1. The shape of the error pairwise edges miss

A pairwise contradiction edge encodes "these two claims cannot both hold." It is the right tool for a **polarity clash**: `bornAt Coen` vs `bornAt McIvor`, same subject, same predicate, two incompatible objects. donto already detects these — `donto_paraconsistency_density` materializes 235,032 `(subject, predicate)` rows with `distinct_polarities ≥ 2`, and the `rebuts` edges in `donto_argument` are the explicit pairwise record of that family.

But consider three claims, each individually attested and individually fine (all are live `ex:kitty` rows):

| # | claim | predicate | object |
|---|---|---|---|
| A | Kitty is the **mother of** Caroline | `isMotherOf` | `ex:caroline-rose-davis` |
| B | Kitty is the **daughter of** the Duke of York | `daughterOf` | `ex:duke-of-york` |
| C | Kitty is the **1907 wife of** Mick | `is1907WifeOf` | `ex:mick` |

No two of these contradict. There is no `(subject, predicate)` polarity clash — each predicate is distinct. There is no pair to which a `rebuts` edge attaches. Yet add the *generational and kinship constraints the predicates carry* — a daughter is one generation below her father; a mother is one above her child; a spouse is the same generation — plus a loop-closing chord from a second source (Caroline's father is the same Mick that Kitty married, or the Duke-of-York father line and another attested father line resolve to one person), and the loop closes into an impossibility: Kitty would sit in two incompatible generational positions at once. This is the real disambiguation problem behind the live `ex:kitty` junk-drawer URI, which holds — simultaneously, all as live `upper(tx_time) IS NULL` rows — `isMotherOf ex:caroline-rose-davis` (×7), `is1907WifeOf ex:mick` (×2), `daughterOf ex:duke-of-york`, and **two distinct fathers**, `hasFather ex:bobbie` and `hasFather ex:buiku-buiku`: a single symbol that cannot be one person.

The error is **a cycle, not an edge**. It lives in the *composition* of typed relations around a loop, not in any pair. That is the conflict class this paper is about.

> **The claim.** A material share of high-value contested-knowledge errors in a contradiction-preserving store are cycle-inconsistencies of individually-fine claims, not pairwise negations. Pairwise `donto_argument` cannot represent them (a cycle has no privileged source→target pair); the sheaf 1-cocycle (`H¹` around the loop) detects and localizes them. The value of the upgrade is exactly the count of such loops a pairwise method misses — the census this paper specifies and has **not yet run**.

---

## 2. Why the blindness is *structural*, not an implementation gap

It is tempting to think a smarter pairwise pass — transitive closure over `rebuts`, or a rule deriving an edge from `daughterOf` + `spouse` — would catch the loop. It would not, for two distinct reasons.

**2.1 There is no edge to write.** `donto_argument` is binary by constraint: `CHECK (source_statement_id <> target_statement_id)`, open-uniqueness on `(source, target, relation, context)`. A cycle conflict over `{A, B, C}` has no privileged ordered pair — picking any one (say A→B) is arbitrary and *lossy*: it asserts "A rebuts B," which is **false** (A and B are individually compatible; only the loop is bad). A faithful pairwise encoding therefore does not exist. The N-ary conflict's only honest home is a cluster record — exactly the `donto_sheaf_cocycle` + `donto_sheaf_cocycle_member` tables in migration **0179**, with an *optional, off-by-default, explicitly-lossy* `cycle_inconsistent` star shadow (migration **0180**) for consumers that can only read pairwise. That shadow is intentionally invisible: every live pressure consumer filters `relation IN ('rebuts','undercuts')`, so `cycle_inconsistent` never leaks into a binary-shaped reader.

**2.2 The one hard correctness fact: `d=1` maps cannot carry a cocycle.** Even if you tried to detect the loop by propagating a value around it (a transport/holonomy view), a scalar model is provably blind. Model each typed relation as a restriction map between the two claims' stalks. If the stalks are one-dimensional (`d=1`) and the maps are positive scalars `σ_e > 0`, the holonomy around any cycle is

```
H_cyc = σ_{e_k} · … · σ_{e_2} · σ_{e_1}   ∈  ℝ_{>0}
```

— a **product of positive scalars is a positive scalar**, conjugate to the identity in the scaling group: there is no non-trivial 1-cocycle, *ever*, regardless of the loop. A `d=1` detector reports `H¹ = 0` on the Kitty loop. This is not a tuning failure; it is the algebra. To make a loop's holonomy *able* to be non-identity you need (i) `d ≥ 2` stalks so the maps live in a non-abelian group, and (ii) **sign- or orientation-carrying** maps, so a *disjointness* relation (`daughterOf ⊥ spouseOf`) contributes a reflection/sign-flip that does not cancel around the loop.

This is why donto's Stage-0 sheaf math (`packages/donto-sheaf/src/lib.rs`) ships `RestrictionMap::identity(d, w)` as the default `d≥2` path and `RestrictionMap::scalar_fallback(w)` as a **never-flagged** `d=1` fallback whose doc comment states the reason verbatim: *"By construction its loop holonomy is a positive scalar ⇒ never a cocycle."* The crate's Case-B test asserts both directions on the same loop — the signed `d=2` path gives `residual_norm > 1e-3`, the `d=1` scalar path gives obstruction `0`. A Stage-0 that shipped `d=1` as primary would not recover the very Kitty cycle it claims as its headline. **The blind spot is in the representation, and only a `d≥2` sign-carrying representation removes it.**

---

## 3. The detector, concretely

The mechanism (specified in [the sheaf build spec](/reports/sheaf-neural-networks-for-donto), Stage-0; implemented in `packages/donto-sheaf`):

1. **Scope a bounded subgraph** (≤ ~500 statement-nodes) around a contested entity or the high-conflict frontier. Never the global graph (`donto_statement` ≈ 42,018,816 rows — sheaf Laplacians are heavier than graph Laplacians; the global sheaf is an open problem, not attempted here).
2. **Assign `d=2` value-indexed stalks**: distinct attested object values index orthogonal basis axes (the report's `Coen=(1,0)`, `McIvor=(0,1)` construction), polarity-signed.
3. **Build restriction maps from learned signals only** — `donto_predicate_closure.confidence`, `donto_argument.strength`, `donto_identity_edge.confidence`. The `relation` value only *caps* magnitude and assigns the orientation **sign** (a disjointness/inverse relation contributes the flip). No hand-maintained predicate→weight table, no synonym list, no `if/elif` over predicate names — that would be exactly the brittle logic the canon forbids.
4. **Spanning-forest cycle basis.** Each non-tree edge closes one fundamental cycle; there are `|E| − |V| + #components` of them — a minimal independent generating set of `H¹`. We never enumerate all simple cycles (exponential). For each, **compose the signed maps around the loop** and measure `‖H_cyc − I‖_F` (`holonomy_frobenius` in the crate). Non-zero ⇒ a localized 1-cocycle; the member set is the loop's statements — the localization the pairwise layer cannot give.
5. **Filter** before flagging: `stalk_dim ≥ 2` (never flag the `d=1` fallback), obstruction above an *empirical* floor (a fixed cold-start until enough prior runs calibrate it), a `max_cycle_len` cap, and — critically for the census — **skip any loop already covered by a single pairwise `rebuts`/`undercuts` edge among its members**, because that conflict is *already visible* to the pairwise layer. The sheaf's contribution is exactly the loops that survive this filter.

That last filter is the empirical question of this paper made operational: **a cycle-contradiction "counts" toward the cohomological upgrade only if no pairwise edge already fires inside it.**

---

## 4. The census: the to-be-done part (stated honestly)

The detector math exists; the **census does not**. The Stage-0 migrations are written and committed — `0178_sheaf_pressure.sql`, `0179_sheaf_cocycle.sql`, `0180_sheaf_argument_relation.sql`, `0181_standing_sheaf_pressure.sql`, all under `packages/sql/migrations/` — but **not yet applied** to live `donto-pg`: `SELECT tablename FROM pg_tables WHERE tablename LIKE 'donto_sheaf%'` returns **zero rows** today, and the analyzer driver (`donto analyze sheaf-h1`, `analyzer_sheaf`) is still in build. So everything in this section is a *specified experiment*, not a result. We refuse to report a census number we have not measured (cf. the [answer-shaping](/papers/answer-shaping) note on measuring the plumbing before the hypothesis).

### 4.1 The procedure

1. Apply migrations 0178–0181 — the pressure table, the cocycle cluster + member tables (with the `donto_assert_sheaf_cocycle` function), the optional `cycle_inconsistent` relation, and the **dark-by-construction** `donto_standing` re-point (sheaf-first contradiction pressure with the live paraconsistency value as `COALESCE` fallback, so it is inert while the sheaf table is empty).
2. Run `donto analyze sheaf-h1` over a battery of scopes: each contested-entity neighbourhood in the genealogy corpus (Kitty, Caroline, the EKY apical set), plus the `HighConflictFrontier` seed.
3. For every flagged cocycle, record `(member statement_ids, residual_norm, member_count)` and the boolean **`pairwise_visible`** = "does any `rebuts`/`undercuts`/`supersedes` edge in `donto_argument` connect two members?"
4. **The census number is `|{ cocycles : pairwise_visible = false }|`** — cycle-contradictions that *no* pairwise method flags — with the residual-norm distribution and the per-entity breakdown.

### 4.2 What a result would establish — and the metric that makes it honest

The headline metric is the **cohomological lift ratio**:

```
lift = (# cycle-conflicts with pairwise_visible = false)
       ─────────────────────────────────────────────────
       (# pairwise contradiction edges, donto_argument rebuts/undercuts)
```

A `lift` materially above 0 quantifies the upgrade: that fraction of real conflict is *only* visible cohomologically. A `lift ≈ 0` would be an honest negative — the corpus's contestation is overwhelmingly pairwise-shaped and the sheaf buys little *on this corpus* (while remaining the only correct representation for whatever loops do exist). Either way the number is the contribution.

### 4.3 The negative we already expect

We will not pretend the census will be flattering everywhere. The live high-conflict frontier is *dominated by single-`(subject,predicate)` polarity clashes* — the 235,032 `donto_paraconsistency_density` rows with `distinct_polarities ≥ 2` are pairwise-shaped by construction, and the `skip-if-pairwise-rebutted` filter will correctly discard them. So the census's real yield should be the **genealogy junk-drawer entities** — the `ex:kitty`-class symbols that have accreted dozens of incompatible kinship roles (`isMotherOf` ×7, `is1907WifeOf` ×2, `daughterOf`, two distinct `hasFather`) and carry no `rebuts` edge anywhere. The honest prediction: **cycle-conflicts will be rare relative to polarity clashes, but concentrated exactly where pairwise methods are blind and disambiguation matters most** — the contested-identity entities the genealogy consumer exists to resolve. Measuring that concentration, not maximizing a count, is the point.

### 4.4 A second honest risk

There is a real chance the diagonal `d=2` maps **do not fire on the live corpus** even though the fixture passes, if the daughter-of⊥spouse-of disjointness is fundamentally off-diagonal (a permutation / `O(d)` structure the diagonal family cannot express). The crate's Case-B fixture proves the *math* on a constructed loop; the **real-corpus run is the true gate**, and if diagonal fails we promote orthogonal/connection maps before claiming recovery. We do not ship the claim on the fixture alone.

---

## 5. Worked example: the Kitty loop, end to end

Using live `ex:kitty` rows:

- **A** = `ex:kitty isMotherOf ex:caroline-rose-davis` — implies Kitty is generation *g*, Caroline *g+1*.
- **B** = `ex:kitty daughterOf ex:duke-of-york` — implies Kitty is *g*, her father *g−1*.
- **C** = `ex:kitty is1907WifeOf ex:mick` — implies Kitty same generation as Mick.
- **Closing chord** (supplied by a second source or an identity hypothesis): `ex:caroline-rose-davis hasFather ex:mick`, placing Mick simultaneously at *g* (Kitty's spouse) and at Caroline's-father position — which, folded against the daughter/mother chain, forces Kitty into two incompatible generational slots at once.

**Pairwise view:** zero `rebuts` edges among `{A, B, C, chord}`. Each is individually attested and individually consistent. `donto_argument` is silent. `donto_paraconsistency_density` shows no `(subject,predicate)` clash because every predicate differs. The error is **invisible**.

**Sheaf view:** with `d=2` stalks (generation axes) and signed restriction maps (the spouse/daughter/mother relations carry the generational offset; the disjointness `daughterOf ⊥ spouseOf` carries the sign flip), the composed holonomy around `A→B→C→chord→A` is **not** the identity: `‖H_cyc − I‖_F > floor`. The detector returns one `Cocycle` whose member set localizes the conflict to exactly `{A, B, C, chord}`, and `donto_assert_sheaf_cocycle` records it as an N-ary cluster — never a fake pairwise edge. This is the crate's Case-B fixture; running it on these real rows is the corpus gate of §4.4, not yet done.

The payoff is operational: the genealogy researcher gets *"these four claims cannot all describe one person — here are the four"* automatically, where today that loop is hand-detected (the live notes already flag `ex:kitty` as a junk-drawer URI that may split into two people). The sheaf turns hand-detection into a localizable computation.

---

## 6. Why only donto can run this census

The census needs three things at once — donto's three foundational invariants:

| census requirement | donto provides it as |
|---|---|
| a pairwise contradiction layer to be *compared against* (and shown blind) | `donto_argument` — 2,525 edges, 2,282 `rebuts`; the live baseline |
| a dense **claim graph with typed relations** to find real loops in | ~42M `donto_statement` rows, freely-minted kinship predicates, contested-entity neighbourhoods (`ex:kitty` et al.) |
| **held contradictions, never resolved** — so the loop's members all coexist on disk | invariant **I3** (no destructive overwrite): A, B, C, and the chord are all live `upper(tx_time) IS NULL` rows simultaneously |

A collapse-on-conflict KB cannot run this census at all: it would have deduped or winner-picked the incompatible kinship roles at ingest, deleting the very loop the census counts. donto holds all of them — `ex:kitty` carries seven `isMotherOf` and two `is1907WifeOf` rows *at once* — which is precisely what makes the cycle detectable later. The census is a measurement only a paraconsistent, non-deleting, abundance-native store can produce.

And the comparison is **fair and adversarial**: donto runs the pairwise detector and the cohomological detector over the *same* scoped subgraph, and reports the loops the pairwise one missed. It is donto demonstrating its own blind spot and then closing it — the honest way to quantify an upgrade.

---

## 7. Status: proven / conjectured / speculative

- **Proven (built + tested, no DB).** The `d=1`-cannot-carry-a-cocycle correctness fact (§2.2) and its consequence that `d≥2` sign-carrying maps are mandatory; the holonomy detector and fundamental-cycle localization (`packages/donto-sheaf`, `holonomy_frobenius` + `analyze`); the Case-A (`H¹` norm `√2`) and Case-B (Kitty loop `residual_norm > 1e-3`, localized; `d=1` scalar path → 0) fixtures pass as pure unit tests independent of Postgres. The structural argument that a cycle conflict has *no* faithful pairwise encoding (§2.1) follows from the `donto_argument` binary constraint. The cluster tables and assert function exist as committed migrations 0178–0181 (written, **not yet applied** to live).
- **Conjectured (open, failure mode named).** That cycle-contradictions are a *material* fraction of real contested-knowledge error in the genealogy corpus. **Failure mode:** the census returns `lift ≈ 0` because contestation is overwhelmingly single-`(subject,predicate)` polarity clashes that pairwise methods already catch (§4.3) — an honest negative we'd report as "the sheaf is the correct representation but the corpus rarely needs it." Resolving the conjecture requires the deployed run; we will not assert a census number before measuring it.
- **Speculative (flagged).** That diagonal `d=2` maps suffice on the *live* corpus (§4.4); the disjointness structure may demand orthogonal maps — fixture-pass does not imply corpus-pass. Also speculative: that the per-entity cocycle count is itself a useful disambiguation signal (an entity whose neighbourhood carries many independent cocycles is a candidate to *split* into multiple people — the `ex:kitty`→two-people hypothesis as a query, not a hand-judgment). Worth measuring once the census exists; no claim yet.

The detector is Stage-0 of the sheaf program. The census is the paper — and it is the part still to do.

---

_See also: the theory companion [The Bitemporal Sheaf](/papers/bitemporal-sheaf) (the cohomology, generalized over both time axes); the build spec and literature grounding in [sheaf neural networks for donto](/reports/sheaf-neural-networks-for-donto); the empirical sibling [Answer-Shaping](/papers/answer-shaping) on measuring before claiming; [Standing dynamics](/papers/standing-dynamics) (which consumes the contradiction pressure this detector feeds); the full program in the [donto research agenda](/papers/research-agenda)._
