Let business rules join entities and conclude relations - #861
Conversation
|
I wrote 0047, so I checked this against what the record actually decided rather than against what it sounds like. Decisions 1 through 3 are implemented faithfully, including the hard one. Decision 3 is the one I expected to be fudged, and it is not. Inside the round loop, Decision 2 is exact. Also noticed and appreciated: Three things. 1. Blocked rule-concluded edges have no row in the Review queue. This is the one I would fix before merging.
That invariant — every blocked derivation has a row in Review — is what 0017 rests on, and after this change a relation conclusion that loses to an assertion is dropped with nothing to show a person. The graph will have a hole the queue cannot explain. 2. 3. Decision 4 is not satisfied. The record says the three caps are set by a measurement published in the PR that changes them, and gives the reason: a number chosen in a record is a guess wearing a decision's clothes. CI has not reported yet beyond DCO; I have approved the run. |
WaylandYang
left a comment
There was a problem hiding this comment.
The migrations job failure reproduces locally against Postgres 16, and it is a
regression this branch introduces rather than a flake. Control run on dev with
the same database: cargo test -p utopia-store → 315 passed, 0 failed (116 test binaries), including
test a_contradiction_points_upstream ... ok. On this
branch, same command, same database:
panicked at crates/utopia-store/tests/a_contradiction_points_upstream.rs:189:9:
assertion `left == right` failed
left: 0
right: 1
Line 189 is assert_eq!(m.derived, 1);.
The cause looks like an unintended change to what DeriveReport.derived counts.
On dev:
derived: derivation.facts.len(), // reasoning.rs:1722On this branch it starts at 0 and accumulates merge_axiom_derivation's first
return value, which is wanted.len() - before — the number of new distinct rows
queued for write. That loop skips blocked derivations, derivations with no valid
span, unruled ones, and anything whose or_insert key already exists.
So derived has moved from "derivations the engine produced" to "distinct new
derived rows this pass wants to write". In this test the one derivation is blocked
on purpose — the next assertion is assert_eq!(m.blocked, 1, "the derivation that hits an assertion stays out") — so the old count was 1 and the new one is 0.
Two things suggest this wasn't intended:
- the field's doc comment is unchanged and still describes the old meaning
(/// 这一轮算出来的派生总数) derivedis part of an HTTP response body
(api/review_routes.rs:1196and:1207), so the change is externally visible
If the new meaning is what you want, it probably deserves its own commit plus a
doc-comment and test update, rather than riding along inside the join feature.
The migration itself is fine — 0071 applies cleanly on a fresh database, the
number is free on dev, and the composite FK resolves against
relation_types_kb_id_key UNIQUE (kb_id, id) added by 0070.
One caveat: cargo stops after the first failing test binary, so there may be more
failures behind this one. --no-fail-fast would show the full picture.
Signed-off-by: wangzifei <wangzifei@cit.group.hk>
… count, and bucket the join Review fixes on deeplethe#861, applied as maintainer edits. run() and materialize() now share one resolve() step: asserted edges plus the surviving rule-concluded relation edges form the pool, derive() runs over it once per round, and contradictions() sees every relation candidate. A candidate that loses leaves the pool but stays in the candidate list, so the queue row exists for it and says which business rule produced it (`rule: business_rule`, `attribute_rule_id`); the conclusions that stood on it retire with it, and a refused key is not retried, so the fixed point still ends. The queue's key and foreign key fall back to the last asserted premise when the chain runs through a provisional edge. `Rule::Business` names such a candidate instead of borrowing `Transitive`, so nothing positional keeps a rule-concluded edge out of the axiom persistence loop. `derive_with_blocked` goes: the pool is rebuilt each round, so a refused edge is simply not in it. `DeriveReport.derived` is again what the engine produced (the last round's axiom derivations and relation candidates, plus the rules' distinct conclusions), which `a_contradiction_points_upstream` pins. `joined_evaluate` buckets the join edges by subject: scanning all edges per X was quadratic in pairs, 5.9 s for 100,000 pairs against 82 ms for 10,000; bucketed it is 105 ms. Decision 4's numbers are in the record and the PR; the caps stay. Migration 0071 keeps its number, CURRENT_SCHEMA_VERSION is the file count (75), and the two tests dev gained since the branch use the new condition side and join argument. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Wayland Yang <wayland0916@gmail.com>
fee89d6 to
61bcea3
Compare
|
Rebased onto current dev and applied the review points as maintainer edits in a second commit; your commit and sign-off are kept. Review queue (my point 1).
Decision 4. Measured in release on a synthetic base and written into the record. One Also: migration 0071 keeps its number, Locally on a fresh database: utopia-reason 86/86, utopia-store all suites, utopia-server 409/409, utopia-cli 13/13, workspace clippy clean, web 135/135 and build. Merging once CI agrees. Thanks for building the hard decision faithfully. |
WaylandYang
left a comment
There was a problem hiding this comment.
Review points applied in the second commit (shared resolve step so the queue sees a refused relation, Rule::Business label, derived count restored, join bucketed, decision 4 measured and recorded); full local suites and CI green. Approving to clear my earlier request-changes.
What
This adds the missing one-hop relation join to business rules and lets a rule conclude a relation rather than only an entity or attribute. Conditions can now select the relevant side (
x/y) of a declared relation, rule evaluation can traverse that relation when deriving matches, and accepted rules may emit the joined relation as a conclusion with evidence and reasoning provenance.The backend changes cover rule storage/validation, evaluation, reasoning, API, and MCP responses. The rules UI adds relation-aware condition sides and relation conclusion rendering, with English and Chinese strings. Database state is extended by migration
0071_a_rule_joins_two_entities.sql.Why
Closes #818. A user could previously express that two entities have attributes, but could not make one rule join them across a meaningful relation. That prevents several ordinary business assertions from being modeled, such as "if an organization is the supplier of a contract, the contract belongs to the organization." This change keeps those derivations explicit, validated against ontology relation ends, and visible in reasoning results.
The design decision is recorded in ADR
0047-a-rule-may-conclude-a-relation.md, and the decisions index is updated.Testing
cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspacewithUTOPIA_TEST_REQUIRE_DB=1andUTOPIA_DATABASE_URL: 354 tests passed, 1 ignored, before an unrelated chat-persistence test encountered a transient PostgreSQL deadlock; that test passed when rerun, for 355 passed total in the server suite.pnpm buildpnpm test— 129 tests in 19 files passed.0071remains available after checking the latest upstream migration (0070).