A rule reads attributes and concludes a type (0021 / #277) by WaylandYang · Pull Request #359 · deeplethe/utopia · GitHub
Skip to content

A rule reads attributes and concludes a type (0021 / #277) - #359

Merged
WaylandYang merged 13 commits into
devfrom
feat/business-rules
Sep 5, 2026
Merged

WaylandYang merged 13 commits into
devfrom
feat/business-rules

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

Implements 0021 end to end, closing #277. Not ready to merge — the record's status line still has to move and a few polish items are listed at the bottom.

A mud-logging user wants a well marked gas-bearing when total hydrocarbon clears a threshold and the interpreter filed it as a gas anomaly. Both values were already dated attribute facts with their passage attached; the rule that reads them had nowhere to live.

What landed

Schema — derived_facts widens to match facts: nullable object_id, an object_value, and the same object_id IS NOT NULL OR object_value IS NOT NULL check. Rules live in attribute_rules with structured conditions. A derived row now comes from an axiom rule or a business rule, exactly one, enforced by a check.

Evaluator (utopia-reason::rules) — a second pass, not an extension of derive(): that one walks entity–entity edges under axioms, this one compares literals against thresholds. Evaluation runs over combinations — one satisfying fact per condition — because an entity can carry several readings of the same attribute, and the interval of each combination is the intersection of its premises. So a well whose 2023 reading fires and whose 2025 reading does not gets one interval, not a row that flips.

Materialisation — rule hits join the axioms' single reconciliation pass. They must: the stale check scans the whole table, so two passes each diffing separately would retire each other's rows every run.

Declaration and UI — CRUD with errors written for people, a "Run now" that reuses materialize rather than a second code path, and a Business rules page under Ontology where a rule reads as one sentence: Applies to Well (and its subclasses), when all of Total hydrocarbon is above 8 · Interpretation is one of gas anomaly, post-effect gas anomaly ⇒ concludes GasBearingWell.

Explanation — the entity panel's Derived tab shows the conclusion under the rule's own name with the readings that made it true.

MCP — two read-only tools (list_rules, rule_matches), and entity_facts now returns conclusions marked [rule: <name>]. Writing a rule is deliberately not exposed: 0002 and 0021 both hold that the reasoning engine's criteria are written by a person, and a tool call cannot tell "a person dictated this" from "the model invented it".

Five silent failures this turned up

Widening a table means re-reading every path that reads it. Each of these fails without an error:

  • The identity index treated NULL object ids as distinct, so a literal conclusion inserted a fresh row every run. Rebuilt with NULLS NOT DISTINCT and object_value in the key.
  • derived_for_entity inner-joined entities and rules, so rule conclusions vanished from the panel — in the database, absent from the UI, no error.
  • The graph's edge count included literal conclusions, which are not drawn, so the status line claimed more edges than the canvas has.
  • proof() had the same two inner joins: opening a conclusion's proof 404s.
  • RDF export inner-joined rules, so no rule conclusion reached the file at all — and 0020's promise is that an auditor reads it without us. Fixed, with a test pinning that the object is a literal, the premises are attached, and the activity carries the rule's name.

One regression was mine and an existing test caught it: I overwrote DeriveReport.derived, whose meaning is "derivations computed this round" (blocked ones included), with "rows about to land". Rule hits now add to it instead.

Verified

Unit: 8 evaluator cases — the conjunction, a missing condition, two readings giving two intervals, premises that never overlapped, a number extracted as a string, raising the threshold, an empty rule concluding nothing, the combination cap being reported.

Database-backed: 5 cases — the premise chain, a rerun inserting nothing, retirement rather than deletion, disabling a rule, and the asserted type staying untouched.

Live, on a real instance: three wells separating a hit from two near-misses; then a subclass entity, two premises that never overlapped, a string-typed number, three disjoint intervals on one well, and both conclusion kinds coexisting on one entity. 48 test targets green, clippy clean with -D warnings.

Before this merges

  • 0021's status line still says "planned · nothing built".
  • A rule's conclusion cannot be edited, only deleted and rewritten.
  • The rule card's entity count is not clickable — rule_matches is the backend for it, the UI is not built.
  • capped is reported once in a toast with nothing persistent on the card.
  • No canvas marker for a rule-classified entity, deliberately: a conclusion is a judgement about one entity rather than a connection, and asserted attributes have no marker either. Worth an open question rather than a fifth visual language on the canvas.

🤖 Generated with Claude Code

WaylandYang and others added 9 commits September 5, 2026 10:55
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
@WaylandYang
WaylandYang marked this pull request as ready for review September 5, 2026 12:46
WaylandYang and others added 4 commits September 5, 2026 21:53
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant