Skip to content

A merged entity comes back before the merge - #337

Merged
WaylandYang merged 1 commit into
devfrom
feat/entities-rewind-too
Sep 5, 2026
Merged

WaylandYang merged 1 commit into
devfrom
feat/entities-rewind-too

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

Closes #336. The second cut of 0019, after #317 did facts and derivations.

Entities had no clock of their own: merged_into says a merge happened, not when. So two entities merged in March were still one node at an as_of in February, and the facts that moved with the merge showed up under a name that did not hold them yet.

Where the clock actually is

entity_merges has carried created_at and reverted_at since 0005 — the same shape record_axis reads everywhere else, so the predicate joins the others rather than starting a new idea:

  • merge_in_effect_at — this merge was in force at T
  • entity_visible_at — the entity existed at T and no merge in force at T had swallowed it. This replaces merged_into IS NULL on the read paths; with a NULL parameter the two are the same answer, so nothing about "now" changes.

Which entity a fact belonged to

A merge rewrites rows in place — UPDATE facts SET subject_id = target — so the row cannot say who held it before. The ids that moved are recorded, though, so fact_owner_at (migration 0027) reads them back:

  • Undo the merges that are in force now and were made after T. A reverted merge is deliberately not in that set: revert_merge already moved those rows back, and undoing them twice would put a fact on an entity it never sat on.
  • Apply the reverted merge whose window contains T. Those rows are home again today, so the only trace that they spent May somewhere else is the array — which is exactly what makes the window worth replaying.
  • Chains resolve in one step: with A→B then B→C, the earliest not-yet-made merge that moved a fact names the entity that held it at T.

It is a SQL function rather than a CTE per read site for the reason 0019 gives about spread-out defences, and it is the same choice fact_surface_predicate already made. The mapping is only applied when a time is actually given: wrapping subject_id in a function costs the index, and "now" is the path every draw takes.

What rewinds now

Node visibility and the node total in overview, the BFS that finds neighbours, node degree, the edges themselves, and the entity panel's facts. Facts a merge invalidated on the way (self-loops, reconciliation) needed nothing — they carry invalidated_at, so #317's predicate already brings them back.

Out of scope, as the issue says: the target's own type and profile at T, which entity_merges also records.

Verified

a_merge_rewinds_with_the_second_clock.rs, database-backed, walks one base through four instants: now (B is merged away, C is back because its merge was reverted), mid-March (all three stand, each holding its own fact — the row says A, the ledger says B), mid-May (inside the reverted merge's window, so C is folded in and A holds all three facts), and January (the entities did not exist yet, so the graph is empty rather than falling back to now). Checked against the previous implementation: it fails there, on the first assertion that a merged entity reappears.

One older fixture changed with it. the_second_clock_can_be_rewound.rs recorded its facts in March but created its entities at now() — a ledger that cannot exist, and one that this change now notices, since an entity that did not exist at T is no longer drawn. Its entities are backdated to January.

Full utopia-store suite (42 binaries) green against a freshly migrated database with UTOPIA_TEST_REQUIRE_DB=1, 144 server unit tests green, cargo fmt --check and cargo clippy --all-targets clean.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
@WaylandYang
WaylandYang merged commit f3d05f3 into dev Sep 5, 2026
4 checks passed
@WaylandYang
WaylandYang deleted the feat/entities-rewind-too branch September 5, 2026 01:02
WaylandYang added a commit that referenced this pull request Sep 5, 2026
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
WaylandYang added a commit that referenced this pull request Sep 7, 2026
Entity merges had a clock since #337 but no reader: opening an entity
showed the facts a merge invalidated, never the merge. `entity_history`
now carries merged_in / merged_away / merge_reverted, with the other
entity by name and the revert attributed from the audit trail rather
than borrowed from the merge row.

Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two entities merged in March are one node in February

1 participant