- Status: In progress · R0, R1 and R2 built · open: R3, incremental maintenance
- Written: 2026-08-28 · condensed into English 2026-09-03
- Related: 0001 P5 (this record replaces its schedule); 0010; 0015 reuses decision 3's criterion; 0016; 0017
A reasoner amplifies defects. On Industry Corpus (28 press releases) 39% of edges were related_to,
and 185 part_of facts expanded to 828 under transitive closure at depth cap 10, with per-depth counts
oscillating from depth 5 instead of decaying: cycles, real ones, from extraction errors such as
Microsoft → FarmBeats for Students → Microsoft. Closure on day one would derive "Microsoft part_of
Microsoft" with an evidence chain attached. The same rule evaluator run backwards is a consistency
check with no truth maintenance and no explosion, so the engine is built as a checker first, the
corpus is cleaned, then materialization is switched on.
-
R0 checks and writes no facts. Pure logic in
utopia-reason; kindsself_loop,asymmetry,cycle(depth-first, with the fullpath),functional(including inverse-functional) andsignature(#190 / #196: subject outside the domain or object outside the range, computed in SQL bystore::reasoning::signature_breaks; untyped entities excluded). Rows go toaxiom_violations, which asks "wrong data or wrong definition":fact_retracted/axiom_relaxed/accepted. -
The ontology checks itself first.
ontology_defects, eight kinds (symmetric and asymmetric, transitive and functional, subclass cycle, disjoint with an ancestor, inherited disjoint, self-inverse, inverse not mutual, sub-property cycle), shown before violations because a self-contradicting ontology makes every fact-level conclusion suspect. A KB without an ontology pack reports zero, since there is no criterion; importing a pack runs a check. -
Derived facts live in their own table.
derived_facts, withfact_derivations (derived_fact_id, premise_fact_id, seq)and the rule onderived_facts.rule_id(migration0013_reasoning.sql).fact_evidence.chunk_idis NOT NULL and a derivation's evidence is other facts; and of forty-odd queries overfactsone knew a flag, so a new query would read derivations as assertions by default. Separate tables fail the safe way: a forgotten UNION hides derivations.facts.derived_by_ruleis never written; its only reference is a Review filter. -
Asserted beats derived, hard.
reconcile_new_factauto-closes older facts of a functional predicate, so a derivation on that path would let one wrong rule close human assertions. An asserted triple is never derived (derive.rs); derived vs asserted does not land and becomes anaxiom_violationsrow of kindderived_contradiction(cap 50 per predicate); derived vs derived aggregates per rule pair intoontology_defectskindrules_disagreeand neither lands (0017). -
Rules come only from ontology axioms. Transitive, Symmetric (#132), inverseOf, subPropertyOf (#177, #179; projection in migration
0016); no user DSL. Rule identity is(kb, predicate, kind)and rows are kept when an axiom is withdrawn, sorule_idand history survive. Inverse normalization at axiom load fills gaps only; a mismatched pair goes toinverse_not_mutual. Cold-start bootstrap declares no axiom (Axioms::default()): the reasoner's criteria are written by people. -
Materialization is a KB switch.
Revised 2026-09-12 (0050). The default is now on. Off was chosen because a declaration can be wrong and this step changes the graph; what that reasoning missed is that derived facts never enter the part of the ledger people write. They carry a
derivedmark, sit in their own section, and come off in one piece the moment the switch goes off. The cost of off was paid on every new base: transitive chains and symmetric pairs were simply absent until someone found the switch.The text below is the original and stays as written.
The job records its run time before deriving so a failure cannot loop; endpoints return an explicit error while off.
MAX_DERIVED_PER_PREDICATE= 20,000, truncation reported ascapped; anunruledcounter that should stay zero records a real bug (rule lookup by predicate instead ofviadropped cross-predicate rules silently). Derived edges are gold on the graph behind a toggle; the entity panel has a "derived" section. -
Explanation is a chain (
reasoning::proof).fact_derivationsrecords asserted premises only, so the proof runs derivation → assertions → evidence → chunk → document byseq. Retracted premises stay listed and flagged, so a proof stays readable after invalidation. -
Validity intervals intersect: half-open, empty means nothing derived, touching endpoints do not overlap, precision the coarsest premise, confidence the minimum, literal objects excluded (
object_id IS NOT NULL). Cycles go to Review with their path, since both edges came from the model and neither deserves prior trust; R1 never derives a self-loop.
- R0 output into
fact_conflicts, "zero new UI". That table asks "which one is right"; violations ask "data or definition". New table, two new Review tabs. - Fact-level
disjointWithviolations as an R0 kind: not built; disjointness feeds the ontology self-check only. - Derivations shown by
entity_historyfor free. Its UNION has facts, merges and retypes; visibility went to the graph and the panel instead.
- 2026-09-02: assumed derivations enter
factsunder thederived_by_rulecolumn reserved in0004_graph.sql; the code hasderived_factsand the column is never written. - 2026-09-02: the "11 existing contradictions" rested on the seed ontology's
functionaldeclarations; with the seeds gone, a KB without a pack reports zero. - 2026-09-03: the contradiction signals promised in decision 4 exist (0017); the
related_toobstacle (39% empty edges) vanished with 0010, since null-predicate edges never enter reasoning. - 2026-09-03: "Data is wrong" retracts the fact (#202). Until now
decideonly marked the violation resolved; the fact stayed in the graph, and a rerun hit the resolved row and stayed silent. The decision now names the fact (single-fact kinds pick it themselves; asymmetry, functional and cycle cards offer a button per fact), retracts it throughreject_fact, and reruns the check. A resolved row whose violation is computed again is reopened when its resolution promised a change (fact_retracted,fact_closed,axiom_relaxed);acceptedstays quiet.
- Materialize or evaluate at query time: 4.5× expansion on a small corpus, unknown at scale. For now a per-predicate cap and a periodic full recompute.
- R3 incremental maintenance: correctness is the hardest to verify, and full recompute holds.
The status line as it stood on 2026-09-27, before status lines were cut to one line:
R0 built: six fact-level violation kinds in
axiom_violations(five checks plusderived_contradiction, 0017), eight ontology defect kinds inontology_defects, two Review tabs. R1 built behind the KB switchmaterialize_inferences(default on since 0050; off when this was written), rules from four axiom kinds (#132, #177, #179). R2 built as a proof chain (GET /kbs/{id}/derived/{id}/proof, 0016 B1). Contradiction signals for derivations built per 0017. R3 not built: every run recomputes the whole KB, oninference_interval_minutes(default 60).