A rule reads what a rule concluded - #485
Merged
Merged
Conversation
Contributor
Author
|
Cut 2 landed on the same branch: the runner.
Five database tests in
The record's status line and its Still cut 3: the rule table saying which rules feed which, and the entity panel drawing a proof more than one level deep. |
WaylandYang
force-pushed
the
feat/a-rule-may-read-a-rule
branch
from
September 8, 2026 03:15
977542a to
2a4a32c
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-say-or
branch
from
September 8, 2026 03:15
67c9f14 to
2aace5e
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-read-a-rule
branch
from
September 8, 2026 03:53
2a4a32c to
da712cd
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-say-or
branch
from
September 8, 2026 03:53
2aace5e to
2829f86
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-read-a-rule
branch
from
September 8, 2026 04:10
da712cd to
b359041
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-say-or
branch
from
September 8, 2026 04:10
2829f86 to
2613d6a
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-say-or
branch
from
September 8, 2026 04:41
2613d6a to
5127dd4
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-read-a-rule
branch
from
September 8, 2026 04:41
b359041 to
3ee5623
Compare
WaylandYang
added a commit
that referenced
this pull request
Sep 8, 2026
Three records were written as 0026-0028 on three branches on the same day. By the order the pull requests were opened, the adjudication stack (#473-#475) keeps 0026-0028, the rules stack (#484, #485) becomes 0029 and 0030, and the event record (#487) becomes 0031. Signed-off-by: WaylandYang <wayland0916@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
WaylandYang
force-pushed
the
feat/a-rule-may-say-or
branch
from
September 8, 2026 05:03
5127dd4 to
ea36c26
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-read-a-rule
branch
2 times, most recently
from
September 8, 2026 05:08
04df549 to
d1c7abf
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-say-or
branch
from
September 8, 2026 05:08
ea36c26 to
8b9509a
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-read-a-rule
branch
from
September 8, 2026 05:12
d1c7abf to
e11450f
Compare
WaylandYang
force-pushed
the
feat/a-rule-may-say-or
branch
2 times, most recently
from
September 8, 2026 05:21
3219159 to
e78ec78
Compare
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
WaylandYang
force-pushed
the
feat/a-rule-may-read-a-rule
branch
from
September 8, 2026 05:27
e11450f to
4c21f28
Compare
This was referenced Sep 8, 2026
WaylandYang
added a commit
that referenced
this pull request
Sep 19, 2026
Cuts 1 and 2 of #477 landed as record 0030 + the runner in #485; the backend's `proof()` now returns `ProofStep` with a recursive `premises: Vec<ProofStep>` field (the proof is a tree, depth bounded by the same cap as the fixed-point iteration). The frontend type was still flat and the renderer treated the result as a single ``<ol>``, which works for proofs whose premise tree is one level deep and silently underflows for anything deeper. ## What this changes - `web/src/api.ts`: `ProofStep` gains `premises: ProofStep[]`, mirroring the Rust `utopia_core::models::ProofStep`. Comment now says 'tree, not chain' (0030's wording). - `web/src/pages/Graph.tsx`: `ProofSteps` is unchanged at the API level (still takes `steps: ProofStep[]`). Internally it delegates each row to a new `ProofStepRow` component, which renders one step plus, recursively, its own `premises` as a nested `<ol>`. The recursion bottoms out at the server's depth cap; leaves have `premises.length === 0`. Both call sites (`derivedProof` on a landed derivation, `blockedProof` on a violation that blocked one) get the recursion without further change. ## Why a left border on the nested block The nested `<ol>` gets `ml-4 border-l border-edge pl-3` so a multi-level proof reads as a tree visually, not as one tall column. This is the existing convention for indented children in the same file (`ExpandCard`, the recursion in `MapPane`). ## Verification ``` pnpm typecheck # tsc --noEmit, clean pnpm test # 82 vitest tests, all pass pnpm build # vite build, clean (1.7 MB JS, pre-existing) pnpm guard # style-guard, 84 files compliant ``` Refs #477 Signed-off-by: rollroyces <rollroyces@users.noreply.github.com> Co-authored-by: rollroyces <rollroyces@users.noreply.github.com> Co-authored-by: WaylandYang <145302500+WaylandYang@users.noreply.github.com>
WaylandYang
added a commit
that referenced
this pull request
Sep 21, 2026
* Render nested proof trees in the entity panel (#477 cut 3) Cuts 1 and 2 of #477 landed as record 0030 + the runner in #485; the backend's `proof()` now returns `ProofStep` with a recursive `premises: Vec<ProofStep>` field (the proof is a tree, depth bounded by the same cap as the fixed-point iteration). The frontend type was still flat and the renderer treated the result as a single ``<ol>``, which works for proofs whose premise tree is one level deep and silently underflows for anything deeper. ## What this changes - `web/src/api.ts`: `ProofStep` gains `premises: ProofStep[]`, mirroring the Rust `utopia_core::models::ProofStep`. Comment now says 'tree, not chain' (0030's wording). - `web/src/pages/Graph.tsx`: `ProofSteps` is unchanged at the API level (still takes `steps: ProofStep[]`). Internally it delegates each row to a new `ProofStepRow` component, which renders one step plus, recursively, its own `premises` as a nested `<ol>`. The recursion bottoms out at the server's depth cap; leaves have `premises.length === 0`. Both call sites (`derivedProof` on a landed derivation, `blockedProof` on a violation that blocked one) get the recursion without further change. ## Why a left border on the nested block The nested `<ol>` gets `ml-4 border-l border-edge pl-3` so a multi-level proof reads as a tree visually, not as one tall column. This is the existing convention for indented children in the same file (`ExpandCard`, the recursion in `MapPane`). ## Verification ``` pnpm typecheck # tsc --noEmit, clean pnpm test # 82 vitest tests, all pass pnpm build # vite build, clean (1.7 MB JS, pre-existing) pnpm guard # style-guard, 84 files compliant ``` Refs #477 Signed-off-by: rollroyces <rollroyces@users.noreply.github.com> * Add walker + tests for the recursive proof renderer (#477 cut 3) Follow-up to #790 per maintainer's review: the recursive renderer's behaviour is now under test, and the deep-tree indentation question has a cap. ## Walker `walkProofSteps(steps, depth = 0): WalkedRow[]` is a pure function exported from `Graph.tsx` that depth-first flattens the `ProofStep` tree into rows tagged with `depth` and `has_premises`. The component still recurses — the walker is a shape predicate, not a replacement for the recursive render. The recursion in the component and the iteration in the walker are the same algorithm. Keeping them in the same file means a change to the recursion has to update the walker, and vice versa — the bug class maintainer named ("a two-level proof as if it were one") is the kind of thing the walker catches at the data level, not the DOM level. ## Tests (3 new in `Graph.test.ts`) 1. A leaf (empty `premises`) produces one row at depth 0 with `has_premises = false`. 2. A 3-level, 9-node tree produces exactly 9 rows in DFS preorder: A, B, D, G, E, H, C, F, I. Depths: `[0,1,2,3,2,3,1,2,3]`. The test pins every row's `fact_id` and `depth`, and asserts no depth exceeds the tree's height and no step goes from 0 to a positive depth in a single row (premises adds one at a time). 3. `has_premises` is false on leaves and true everywhere else. 4. The walker's `depth` parameter offsets every node — important if a future change calls the component at a non-zero starting depth (e.g. rendering a sub-proof inside a larger tree). ## Indentation cap `ProofSteps` and `ProofStepRow` now take a `depth` parameter (default 0). The nested `<ol>` wrapper stops adding `ml-4 pl-3 border-l` once `depth >= 6` so a deep tree in a 384 px panel doesn't push the right edge off-screen. The cap is visual; the server's reasoning depth cap is the actual bound, and this just keeps the layout sensible when that bound isn't hit. `ProofSteps` is now exported for the same reason `fmtInterval` is — a small pure renderer is the kind of thing a future change might want to render in a story, a snapshot, or a different context. ## Verification ``` pnpm typecheck # clean pnpm test # 95 vitest tests, 4 new, all pass pnpm build # clean pnpm guard # 88 files compliant (was 84, +4 walker/test) ``` Refs #790 Signed-off-by: rollroyces <rollroyces@users.noreply.github.com> * Make the proof indentation cap a tested pure function Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com> --------- Signed-off-by: rollroyces <rollroyces@users.noreply.github.com> Signed-off-by: WaylandYang <wayland0916@gmail.com> Co-authored-by: rollroyces <rollroyces@users.noreply.github.com> Co-authored-by: WaylandYang <145302500+WaylandYang@users.noreply.github.com> Co-authored-by: WaylandYang <wayland0916@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
Cut 1 of #477. Stacked on #484.
docs/decisions/0027-a-rule-may-read-what-a-rule-concluded.md, plus its index row. No code — this is the argument, so it can be disagreed with before the runner is written.A gas-bearing well with a completion date is a producer. That second criterion cannot be written today: a rule's conclusion is not something another rule can read, so the hydrocarbon condition gets copied into the producer rule and two rules end up sharing a premise that nothing in the base says they share.
The objection this has to answer is written into the
fact_derivationsDDL by 0013: premises are always assertions, or a re-run depends on what the last round left behind. The record does not waive it. The feedback happens inside onematerialize()call, in memory — a fixed point that starts from the asserted facts every time and never queriesderived_factsas input. A run stays a pure function of(facts, rules, axioms), which is what that objection was actually protecting; it does not follow from "premises are assertions", it follows from "the run does not read its own past output", and those coincided only while there was one pass.What the record settles:
MAX_DEPTHrounds, sorted frontier, same shape asderive(), because otherwise whetherA → B → Cfires depends on rule load order.entities.type_idcarries no interval, but a derived membership does — an entity that is aBfrom 2019 to 2022 must not satisfy aBrule on a 2024 reading. Putting the typing in the premise list makesvalidity()narrow the conclusion for free, and is what makes retirement work with no code written for it.fact_derivationsgains a nullablepremise_derived_idwith a CHECK that exactly one premise column is set, andproof()becomes a walk. TheProofStepcomment that says otherwise changes with it.wantedin the same statement.Two open points are named rather than hidden: a kept row can keep a stale proof (already true with asserted premises, easier to hit with chains), and how deep a proof is worth drawing.
Runner and real-database tests for
A → B → C, for a cycle, and for a leaf reading taking the chain with it are the next cut.🤖 Generated with Claude Code