Two values clash only while both hold - #635
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
This was referenced Sep 13, 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.
Fixes #624. Fixes #634.
What was wrong
Three defects in how the consistency check judges
asymmetric,functionalandinverse_functional:left_fact/right_factwere the first two facts the checker happened to meet.axiom_violationsis unique on(kb_id, kind, left_fact, right_fact), so the same clash read in another order became another row, and a resolved row was bypassed instead of reopened. Same disease as the cycle rotation fixed in The ledger keeps what its comments promise #620.reasoning::runread every fact with its span and dropped the span before callingcheck. A raise (salary28000 until 2024-02-20, 32000 from 2024-02-20) and a change of project lead were both reported as contradictions.derive::contradictionsalready required overlapping spans; the assertion side did not.functional(Functional and asymmetry checks ignore validity time, and inverse functional is stored as functional #634), pointing the reviewer at the wrong end of the relation.What changed
utopia_reason::checktakesTimedEdge. The three exclusive kinds flag two facts only when their half-open spans overlap, read exactly as derivation reads them (read_span: eternal predicates unbounded, undated events empty). Self-loops and cycles stay time-blind. That is unchanged here; see the note below.pathis the whole group sorted by id,left/rightare its first and last. With every order of the input the result is identical, and a long run of successive values is swept inO(n log n).Kind::InverseFunctional(inverse_functional), also used in derived-contradiction details.acceptedgroup covers the facts it listed. A later group that is a subset stays silent even when its key changed, because a fact was retracted. A group that gained a fact the reviewer never saw reopens.0053: adds the kind to the check constraint; deletes open asymmetry/functional rows (recomputed on the next run); relabels resolved object-side rows asinverse_functional; canonicalises resolved rows to(least, greatest)withpathset, keeping the earliest row when two orders were each decided.Verification
utopia-reason: 77 tests, including every permutation for all three kinds, successions, unbounded and empty spans, and connected versus separate groups.a_clash_needs_both_at_once(DB-backed):functionaland oneinverse_functionalrow;axiom_relaxed, flip heap order with a no-opUPDATE, re-run) givesinserted 0, reopened 1;utopia-storesuite passes locally.fmt,clippy -D warningsandpnpm buildare clean.functional, and an open row. The result matched the expectations above.POST /kbs/{id}/consistency/checknow reportsfound: 0. After inserting two genuinely overlapping facts it reports onefunctionaland oneinverse_functionalclash. Accepting one through the review endpoint and re-running givesinserted: 0, and the row stays resolved. The Review card renders the new title.Not in this change
Cycles still ignore time: a transitive cycle whose edges never hold together is reported. Fixing that means intersecting spans along the whole path rather than pairwise overlap, so it is left for its own issue.
🤖 Generated with Claude Code