{{ message }}
Repository navigation
The graph reads rewind the recording axis - #317
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
This was referenced Sep 4, 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.

Part of #307 — the read paths and the API. The graph control is 0019's open question and stays a separate cut, so this does not close the issue.
edges_amongcarried an unconditionalinvalidated_at IS NULL, so a fact corrected in March was absent at every slider position. The rows have been on disk since the graph migration; nothing shipped could read them.One predicate, in one place
crates/utopia-store/src/record_axis.rsrendersheld_at(T)—recorded_at <= T AND (invalidated_at IS NULL OR invalidated_at > T)— and no read writes that filter by hand any more. The parameter bindsOption<DateTime<Utc>>, where NULL means now and the predicate degenerates into the oldinvalidated_at IS NULL; each read keeps one statement rather than one per case, and a second statement is the next place a WHERE goes missing.Five tables answer the same question under different column names, and each gets exactly one renderer:
facts(recorded_at),derived_facts(derived_at),axiom_violations(detected_at+decided_at),fact_conflicts(created_at+resolved_at),chunks(created_at+superseded_at).Write paths keep the hard-coded filter, as 0019 specifies:
confirm_fact,reject_fact, adoption's undo and the dedup lookups guard the current row, and a correction is never made as of March. Review's queues are "now" by definition and are untouched.What rewinds
overview,neighborhood,edges_amongandentity_detailtakeas_ofbeside the existingat, and the two travel to the API as separate query parameters. Folded into one control they would answer "the world in March, as we understand it now" with "the world in March as we understood it then", and both look plausible on screen.Everything drawn alongside the edges follows the same clock, or the picture contradicts itself: node degree, the edge total behind "150 / 325", the BFS that finds the neighbours, the panel's facts, the contested marks, and
stale— a chunk superseded in August was live evidence in March. Ghost edges (0017 §3) rewind through their violation's own two columns rather than being pinned to today's open set.Node totals do not rewind. Entities have no recording axis —
merged_intorecords that a merge happened, not when — so two entities merged in March are still one node at anas_ofin February. Unwinding that means readingentity_merges, which 0019 already names as a second cut.Verified
the_second_clock_can_be_rewound.rsis database-backed and asserts both directions, since they fail differently: a retracted fact absent atas_of = now(a predicate written backwards revives every invalidated row), present at anas_ofbefore itsinvalidated_at(a predicate left unconnected reads "now" forever and replays nothing), and a correction recorded on 03-20 absent at 03-10 (writing only the lower bound lets March see April's rewrite). A derived fact with its own two dates is in the fixture, so thederived_factsbranch of the UNION is covered rather than assumed.The whole
utopia-storesuite passes against a freshly migrated database withUTOPIA_TEST_REQUIRE_DB=1, pluscargo fmt --checkandcargo clippy --all-targets, clean.No browser check: this cut has no UI. The route layer is parse-and-pass, and what it passes is covered above.
🤖 Generated with Claude Code