{{ message }}
Repository navigation
A rule reads attributes and concludes a type (0021 / #277) - #359
Merged
Merged
Conversation
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
marked this pull request as ready for review
September 5, 2026 12:46
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>
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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_factswidens to matchfacts: nullableobject_id, anobject_value, and the sameobject_id IS NOT NULL OR object_value IS NOT NULLcheck. Rules live inattribute_ruleswith 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 ofderive(): 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
materializerather 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), andentity_factsnow 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:
NULLS NOT DISTINCTandobject_valuein the key.derived_for_entityinner-joined entities and rules, so rule conclusions vanished from the panel — in the database, absent from the UI, no error.proof()had the same two inner joins: opening a conclusion's proof 404s.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
rule_matchesis the backend for it, the UI is not built.cappedis reported once in a toast with nothing persistent on the card.🤖 Generated with Claude Code