Skip to content

A wrong date can be corrected without deleting the document - #311

Merged
WaylandYang merged 1 commit into
devfrom
feat/a-wrong-time-can-be-corrected
Sep 4, 2026
Merged

WaylandYang merged 1 commit into
devfrom
feat/a-wrong-time-can-be-corrected

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

Closes #302 (the editing half; instant precision stays a separate cut).

Extraction reads "the first half of 2023" as 2023-01-01, precision day. A wrong name reaches Review; a wrong date reached nothing, so the only fix was deleting the document and re-extracting it — on a product whose whole claim is time.

The interval goes through the ledger

correct_interval is close_superseded's sibling: invalidate the old row, insert a corrected row with supersedes pointing back, copy the evidence. Both ends come from the caller rather than being inherited.

Not UPDATE facts SET valid_from = …. Editing in place erases the correction itself, and the correction is what the recording axis exists to replay (0019) — after this change, asking in March and asking in September give different intervals, which is the first real content that record has to replay.

What each axis shows

The recording axis (the entity panel's History) gains the edit for free — entity_history derives corrected from supersedes IS NOT NULL. Two things had to be fixed for it to say anything true:

  • intervalNote assumed a correction is a closure: with a valid_to it said "closed at X", otherwise nothing. An edited start has no end, so the event rendered as a bare icon. Both branches now state the interval after the correction, without claiming a cause.
  • The attribution whitelist for corrected listed three actions and not this one, so a person's edit was credited to engine. A decision ledger that records a human decision as a machine's is worse than one that records nothing.

The world axis (Timeline) keeps only the new interval. The old one is our misreading, not a state the world was in; two rows side by side read as two handovers. The ⟲ marker stays but its tooltip no longer names a source — automatic succession, a review ruling and a hand edit are indistinguishable on that row, and History has the actor.

Reconciliation runs again

Moving a start can carry it past a successor's start date, which is where the uniqueness invariant sees the collision for the first time. The edit reuses reconcile_moved_facts — a changed interval is the same kind of event as a moved subject.

Not included

No entity-level "why" enum. "We misread it" and "new evidence says June" are identical in the ledger, and forcing a choice on every edit makes the entry heavier while the criterion stays vague. An optional one-line note goes to the audit record instead; it can be promoted to a field if it turns out to be needed.

Verified

Four database-backed tests in a_wrong_time_can_be_corrected.rs: the old reading stays in the ledger with evidence copied; an end that was never there can be taken back (precision cleared with it); moving a start earlier makes that fact the predecessor and closes it at the successor's start; an already-invalidated row cannot be corrected twice.

End to end in the browser on a seeded base: pencil on hover → edit 2023-01-01 to 2023-06 → row becomes 2023-06 ~ now with ⟲ → History shows "Interval corrected · from 2023-06 · open-ended · Time Tester" above the original "Recorded · from 2023-01 · engine". The audit row carries both intervals, the precision change from day to month, and the note. Unchanged submits short-circuit; a date without a precision and an inverted interval are both refused with a readable message and leave no rows behind.

cargo fmt --check, cargo clippy -D warnings, cargo test --workspace with UTOPIA_TEST_REQUIRE_DB=1, and pnpm build all pass.

🤖 Generated with Claude Code

@WaylandYang
WaylandYang force-pushed the feat/a-wrong-time-can-be-corrected branch from 13696a6 to f210415 Compare September 4, 2026 18:05
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: WaylandYang <wayland0916@gmail.com>
@WaylandYang
WaylandYang force-pushed the feat/a-wrong-time-can-be-corrected branch from f210415 to e4ed4d8 Compare September 4, 2026 18:25
@WaylandYang
WaylandYang merged commit 24d7207 into dev Sep 4, 2026
@WaylandYang
WaylandYang deleted the feat/a-wrong-time-can-be-corrected branch September 4, 2026 18:27
@WaylandYang

Copy link
Copy Markdown
Contributor Author

Review pass on 0dcc47d. Ten fixes, one deliberate skip.

The correction could put a fact somewhere it could not be edited again. The editor accepts an empty start and an open end, and a fact with no date on either end drops out of the timeline's dated group into "Undated", which renders FactRow — the row without the pencil. Clearing a date was a one-way door back to "delete the document and re-extract", the workflow this PR exists to remove. The pencil is now a shared TimeEditButton used by both rows, skipped for eternal predicates. A fact extraction never dated can be given one, which is the other half of #302.

Two people correcting the same fact could both succeed. WHERE invalidated_at IS NULL inside the INSERT…SELECT does not stop a second transaction that read the row before the first committed, so both could insert a live successor and the graph would carry two edges for one subject/predicate/object. correct_interval now takes the row with SELECT … FOR UPDATE before inserting. The existing test only exercised the sequential case, which is why this passed.

A failed snapshot silently swallowed the ledger entry. fact_snapshot turns any database error into None, and the audit record sat inside if let Some(...). Without the fact.time_corrected row, entity_history falls back to the whitelist and credits a person's edit to engine — the failure this PR's own description calls worse than recording nothing. The audit is written unconditionally now; the snapshot only decorates it.

Also: the "Interval corrected" toast no longer fires when the server reports unchanged; emit_graph joins emit_review, since reconciliation can close intervals on facts the editor never touched; the entityHistory query is invalidated; the tests delete the organization rather than leaking one per run; timeSave/timeCancel give way to the existing editSave/editCancel; the pencil is a real <button> outside the row's button rather than a role="button" span inside it; and the derived_by_rule IS NULL filter is gone — derived facts have had their own table since 0013, so that guarantee is structural, not a WHERE clause.

Skipped: start must not be after end still lives only in check_interval, so other writers can store an inverted interval. It belongs in the table next to facts_to_precision_matches_date, but that means editing an applied migration and patching _sqlx_migrations checksums on every local database. Left for the next change that touches the schema.

cargo clippy --all-targets -- -D warnings clean, pnpm build clean, and the four store tests pass against a live database.

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.

A wrong extracted date can only be fixed by deleting the document

1 participant