A timeline is placed whole, whatever order its values arrive in - #679
Merged
Merged
Conversation
This was referenced Sep 13, 2026
WaylandYang
marked this pull request as draft
September 13, 2026 14:59
Contributor
Author
|
Back to draft after review; this needs a redesign before it can merge. The review found six problems, with reproductions:
The redesign will:
|
WaylandYang
force-pushed
the
fix/a-value-that-arrives-late-takes-its-place
branch
from
September 13, 2026 16:18
a1bee23 to
eb15919
Compare
WaylandYang
marked this pull request as ready for review
September 13, 2026 16:20
WaylandYang
force-pushed
the
fix/a-value-that-arrives-late-takes-its-place
branch
from
September 13, 2026 17:46
eb15919 to
98a3d20
Compare
WaylandYang
force-pushed
the
fix/a-value-that-arrives-late-takes-its-place
branch
from
September 13, 2026 18:50
98a3d20 to
b2facea
Compare
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
…lues arrive Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
… one before it Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
WaylandYang
force-pushed
the
fix/a-value-that-arrives-late-takes-its-place
branch
from
September 13, 2026 19:26
b2facea to
af3c4e3
Compare
… what people and texts said Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
…te, and a read start follows its anchor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
WaylandYang
deleted the
fix/a-value-that-arrives-late-takes-its-place
branch
September 13, 2026 21:32
This was referenced Sep 14, 2026
Merged
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.
The temporal engine now keeps a single-valued state's timeline whole, whatever order its values arrive in. This PR also carries the two #681 decisions that live in the engine: §1, start-less values, and §4, deadlines stated relative to an event. It revises 0022, whose status line and index row are updated, and adds migration 0057.
Why
Amendments are extracted in parallel, and historical filings are uploaded in any order, so a new value often lands in the middle of a history. The engine compared a new value only with the open rows, which is at most the latest value. On the Blackbaud HQ lease chain on EDGAR, three extended deadlines all closed at 2020-06-08, so on 2020-05-01 the lease had three deadlines.
Two review rounds shaped the design.
This version removes the test instead of patching around it.
What changes
The row says who drew its end (migration 0057,
facts.end_derived). The flag is true only on rows the temporal engine closed. It stays false on:A timeline is recomputed in one pass (
temporal.rs,plan_timeline). All live rows of one (base, holder, predicate, side) are ordered by start, or, for a start-less row, by its earliest dated evidence.Rewriting an end never changes a row's start, value or confidence. So one pass is final, there are no rounds, and the result depends only on which rows exist.
plan_timelineis a pure function with unit tests, one of which shuffles the input order.Every change to a timeline recomputes it.
reject_factand the conflict decisionreject_newgo throughtemporal::retract.Lock order. Reconciliation takes the timeline's advisory lock, then locks rows. A transaction that touches several timelines takes all their advisory locks first, in a fixed order (
lock_timelines), and only then changes rows. The transactions that do this are merge revert, document deletion, document restore and retraction. Neither side ever holds a row lock while waiting for an advisory lock.Merge revert moves back what the merge moved, with every row rewritten from it (
resolution.rs), followingsupersedes. That includes a row the engine closed after the merge, a person's interval correction, and a rejected row, which stays rejected.temporal_correctionsis still recorded on the merge, for audit.§1: a start-less row has a place (revision note in 0022). Its ordering date is the earliest date among its evidence documents whose own date is known (
doc_time_sourcecontentorsource); upload time and file mtime don't count.valid_toNULL, precision'unknown', andattested_toat that date. The reads therefore abut the two rows.valid_fromorvalid_to.§4: a deadline relative to an event is kept. The model marks such a value
relative: true; the server matches no wording.utopia_extract::attr_object_valuestores it as written with"relative": true, and only when the value does not parse as a date. That function is now the tested glue the extraction loop calls.xsd:stringliteral, plusutopia:relativeValue trueon the statement. It is never an invalidxsd:date.Third review (8ff7362)
Row::key).temporal::rehome_tx), and invalidated ones stay on the target.DATED_ATignores deleted documents.temporal::state_derived_end, from the three reuse paths ininsert_fact_inner).graph::add_evidence) locks the fact rowFOR SHAREand followssupersedesto the live row, so evidence never lands on a row that was just rewritten.end_derivedon rows the old engine closed. All of these must hold:closedconflict decision, and nofact.closeorfact.time_correctedaudit on the parent.merge_entitiesnow takes the timeline locks before it moves any row, the same way revert does.Checks
a_timeline_is_recomputed_from_the_rows_it_has(15 tests, the second review's reproductions):correct_intervalrow;a_timeline_holds_whatever_the_order(8, from the first review): one assertion changed. A reverted value comes back as a rewritten row rather than the old row un-invalidated, so the test compares values, not ids.a_late_value_takes_its_place_in_history(5).plan_timeline(3),attr_object_value, and RDFa_relative_deadline_is_a_string_that_says_it_is_relative.the_engine_redraws_only_what_it_drew(11, the third review's reproductions): a relation unique on both sides stays put under re-extraction; an ended start-less value does not close the current one (two shapes); replay of the merge window is unchanged by revert; a deletion that waited for the lock still takes the rewritten row; evidence under 5-way concurrency (30 trials); a kept pair isn't asked again; an open conflict survives a rewrite; a stated end on an engine-closed row survives rejecting its successor; a deleted document no longer dates a row; a no-time value meets a dated one the same way in either order; the 0057 backfill marks only the engine's closure.an_undated_upload_is_not_a_document_datenow expects the no_time pair (both open) instead of a closure.cargo test -p utopia-store259 passed, 0 failed;-p utopia-server277 passed.cargo clippy --all-targetsandfmt --checkare clean.Lease bench: run on the previous head, 98a3d20, before this rework: 7 and 8 of 18. Every live timeline traced there matched its inputs. The loss is extraction:
attr_datatype, because the model wrote "March 17, 2020" instead of ISO (A date the text spells out is read as a date #688);malformed_item;On b2facea (the second rework) the lease bench scored 9 and 8 of 18, and every timeline matched its inputs; the remaining losses were extraction (written dates, fixed in #689).
Migration number: #684 took 0056 and is merged; this PR is 0057 and applies after it.
Fourth review, fixed in 7d79616
A fourth review round confirmed every fix from the third round. It ran the backfill on copies of three real bases: none of the flagged rows had a human trace, and
reconcile_predicateafterwards only corrected overlaps the old engine had left. It also found problems in code the third round added. This branch's owning session had ended, so the fixes were made on top:merged_intofrom the target and moves rows held by any entity on that chain. Test:undoing_the_first_merge_of_a_chain_brings_its_facts_home.out of shared memory. Locks are now two-level. A normal lock takes a shared predicate lock, then the exclusive timeline lock. Past 256 timelines, the transaction takes exclusive predicate locks instead of per-timeline locks. Predicate locks always come before timeline locks, each group sorted. The 20,000-timeline delete and restore now complete. Test:deleting_a_document_that_dates_many_timelines_still_works(REDRAW_BULK_N=20000reproduces the review's scale).rehome_txnow carries open conflicts to the new row before invalidating the old one. Test:a_revert_keeps_the_conflict_its_rows_carried.attested_from(the read start of a start-less row) did not. Delete and restore now recomputeattested_fromfrom the remaining dated evidence. Test:deleting_the_first_document_moves_the_read_start_with_the_anchor.Not changed, noted:
no_timeconflict for a person.Checks on 7d79616 (fresh database, migrated 0001→0057):
clippy --workspace --all-targets -D warningsis clean.🤖 Generated with Claude Code