Repository navigation
A binding to a state property says what a single date marks, and a state row whose ends are equal is never written - #975
Conversation
…ate row whose ends are equal is never written Signed-off-by: dada-yan <BinjunYann@gmail.com>
Signed-off-by: dada-yan <BinjunYann@gmail.com>
Signed-off-by: dada-yan <BinjunYann@gmail.com>
…s 0097 Signed-off-by: dada-yan <BinjunYann@gmail.com>
… single date under a state means Signed-off-by: dada-yan <BinjunYann@gmail.com>
WaylandYang
left a comment
There was a problem hiding this comment.
Thank you. This is the decision on #966 built as decided: the invariant holds at precision level because truncation runs before under, materialization filters point statements in SQL so a refusal never fails a document, cases A, B and C are pinned in both orders, the four queue readers share one condition, and 0053 is revised in the new one-line format. I ran the chain on a fresh database (88 files) and the store and phrase tests; all pass.
Three things before it lands, the first being the one that matters.
1. Asking for marks must not reopen a settled binding, and must happen once.
An agent's binding to a state with no value rejoins the opening selection of every run. When the model leaves out the fourth cell, nothing is written, so the next run asks again: with three-cell replies, three runs in a row each spent two requests on the same binding and queued a re-ask. And because it is a full re-decision, votes that come back without a property turn a bound binding into none, which retires its typed rows. On upgrade every existing base sends all of its state bindings through this.
- The question put to a binding that is already bound is only what a single date marks. If both votes name the same property and direction as the binding and agree on the value, write the value. In every other case leave the binding exactly as it is.
- Record that it was asked, durably, on the binding. A binding that was asked and still has no value is not asked again; it is listed in the alignment queue for a person, as a person's binding already is.
- Please add the test for a model that never gives the value: one ask, the binding unchanged, its typed rows still live, the row in the queue.
- Then the description and the 0053 revision can say asked once and be right.
2. Step 2b can retire a row a person corrected. correct_interval and rewrite_end_tx carry from_statement_id, implied and the source links (#967, #911), so a computed row a person set to equal ends looks like a computed one. Retire a row only when a source statement has the same equal ends as the row; implied rows need the same test against implied_fact_sources.
3. An end compares instants, not what the date names. "works for from 2024-03-01" with "left in 2024" at year precision and marks end gives (none → 2024-01-01) and [2024-03-01, open), so the person still works there in 2025. A coarse end closes the open row that starts inside the period it names, at the end of that period, or is left unmaterialized if you judge that safer; either way it must not leave the open row running. Dated endings behaved this way before, but marks end now sends many more statements down that path. Please pin it with a test.
Smaller:
- The leftover doc line above type Vote in phrase_alignment.rs.
- An unreadable fourth cell makes the whole answer malformed even under an event property, where the value is not read. Ignore it there.
- resolve_conflict with close and no close_at on a simultaneous conflict now returns empty_state_span, and an errata revision of a dated event row to a state property is refused. Both are right under the invariant; please say so in the description and give each a test.
- The queue preselects neither, so one click writes none for every binding that arrives on upgrade. Preselect nothing and require a choice.
- 0031 owns the write rule; a dated sentence there pointing to the 0053 revision would help the next reader.
Migration 0097 and constant 88 are right as of now; #976 may take a number first, so count the files in migrations/ when you rebase. Thanks again; the core of this is sound.
|
A note on numbers: #976 is landing with migration 0097 (a partial index on audit_events), so this one becomes 0098 and CURRENT_SCHEMA_VERSION 89 when you rebase. |
…d by the question, a coarse end closes a row that starts inside its period, and only rows copied from a single date are retired Signed-off-by: dada-yan <BinjunYann@gmail.com>
…ment Signed-off-by: dada-yan <BinjunYann@gmail.com>
… intervals, and the CLI's schema version counts 89 Signed-off-by: dada-yan <BinjunYann@gmail.com>
|
Thank you. All three are in efe20c0, together with the smaller points. The description now opens with an "After the review" list, and its verification section has the control run.
The smaller points: the doc line is gone, a fourth cell under an event is ignored, both refusals are described and tested, the queue preselects nothing, and 0031 has the dated sentence. #976 took 0097, so this migration is now 0098 and the CLI constant 89. |
Maintainer edit on deeplethe#975. The retirement of state rows with equal ends lacked the guard the next step has: a row a person wrote gains a source link when a single-date statement merges into it, and was then invalidated. It now takes only rows with from_statement_id or implied. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Wayland Yang <wayland0916@gmail.com>
WaylandYang
left a comment
There was a problem hiding this comment.
Thank you. All three are done as asked and I verified each against the code and by running it: the marks-only ask writes only marks and marks_asked_at, its update is a no-op if a person decided in the meantime, a model that never gives the value is asked once, the retirement needs a source statement with the same equal ends, and a coarse end closes the open row in either order. Merged with dev: store 291, server 518, extract 97, web 211, all passing.
I pushed one line. The retirement step lacked the guard the next step has, from_statement_id or implied. A row a person wrote gains a source link when a single-date statement merges into it, and the step then invalidated it; with the guard it stays. A test for that case would be welcome in a follow-up.
One thing to think about, not blocking: closing at the end of the period stores "left in 2024" on a row from 2024-03-01 as 2024-03-01 → 2025-01-01 at year precision, which the line prints as → 2025. The reading on the world axis is what was asked for, but a reader will take it as left in 2025. An end that names a period probably wants to print the period it names. Please open an issue for it with what you think is right.
Landing it. #966 closes with it. Thanks again.
WaylandYang
left a comment
There was a problem hiding this comment.
Maintainer review: sound, landing.
Closes #966, as decided there: option 2, with option 3's guard and the invariant.
After the review
0098and the CLI constant 89.Problem
works_for, a state, it was materialized as 2023-06-01 → 2023-06-01. The world axis reads a state as holding whilevalid_from <= T < valid_to, so the row held at no moment.Change
The binding. Migration
0098adds two columns tophrase_bindings, in one plainALTER:marks:start,endornone, NULL for unknown;marks_asked_at: when the aligner asked for it, NULL if never.validate_decisionaccepts either only on a bound decision.The aligner (
phrase_align.rs,phrase_alignment.rs).[id, key, direction, marks].marks/mfield, id-keyed objects.votes, with what each said.updated_at, which the fingerprint already covers, so a property that becomes a state is asked again. No fingerprint changes shape.record_markswrites it. Any other reply leaves the binding exactly as it is: a vote without a property, the other direction, two values, or no value.The person (
decide_alignment_phrase, the alignment queue, the web).marks. The route refuses an unknown value (unknown_marks), and a value with anything but a state property (marks_needs_state), with 422. The value is recorded in the votes and the audit entry.waiting, the review badge and the review summary use one condition.Materialization (
materialize.rs,graph.rs). For a statement whose ends are equal, bound to a state:start: written as [t, open). With "works for … from t" in the same document, both statements are sources of one row, in either order.end: written as an end at t, the path a dated ending takes ininsert_fact_on. The date names a period, its bucket at its precision: "left in 2024" is stored as 2024-01-01 and means all of 2024.noneor unknown: no typed row. The statement stays in the open graph with its date. Step 1 drops a source computed from such a statement.Also:
typed_fact_sourcesfor a typed row,implied_fact_sourcesfor an implied one;The invariant and the guard (
graph.rs,temporal.rs,errata.rs).Validity::underreturnsAppResult. Under a state property it refuses equal ends after truncation to precision, withempty_state_span.resolve_conflictwithcloseand noclose_atcloses the old row at the new row's start. On asimultaneousconflict that is the old row's own start, so the call returnsempty_state_spanand the conflict stays open until a person gives a date.insert_fact_on. Under a state property, a stored row with equal ends is not taken for the same observation, so it neither absorbs a later observation nor is closed by one.Docs.
revised 2026-09-27 (#975, migration 0098).timeandontologydescribe the change.CLI.
CURRENT_SCHEMA_VERSIONis 89: the 88 files on dev, #976's 0097 among them, plus 0098.Known limits
noneand stays open. Reading such a date as holding through its bucket, as an event is read, would be a separate decision.starttoend, or back, keeps the rows already computed from its single-date statements. A change to or fromnone, and a first value, are computed again.endstatement is not reopened when that statement is retracted, as with any dated ending.map_toadoption writes a person's binding. Under a state it waits in the queue for its value like any person's binding.Verification
Run in containers on a Linux host: Rust 1.98.1 and PostgreSQL 16 with pgvector, and Node 20 for the web.
New tests
Store,
tests/store/a_moment_marks_its_state.rs(12 tests):startbinding give one row from 2023-06-01 with every statement as a source. Lin Zhao works for Meridian at 2023-06-01 12:00, 2023-07-01 and 2026-01-01.noneand no value compute no typed row. The statement stays on the canvas with its date.empty_state_span. The old row stays open, and the conflict stays in the list until a date is given.Server,
phrase_alignment_tests.rs, with a scripted model:waitingand the badge.Also:
review_routes_phrase_tests.rs: a person's decision writes the value and clears the queue;unknown_marksandmarks_needs_statewrite nothing.alignmentPhrase.test.ts(vitest): the choice appears under a state only, nothing is preselected, the request body, and the new error codes in en and zh.The review's tests on the code before the fix
The control branch is this branch with the aligner, the parser, the queue condition,
insert_fact_on, materialization and the web helper as they were at fd5a220. Only the new tests fail, each on the behavior the review describes:phrase_alignmenttests fail.none.undecided.The conflict and errata refusals pass there too: fd5a220 already refused them, and the tests pin it.
This branch
After merging dev (efe20c0):
cargo test --locked -p utopia-clipasses (13),schema_version_policy_compares_against_currentamong them.phrase_alignment,review_routesandapi::: 242.cargo fmt --all --checkandcargo clippy --locked --workspace --all-targets -- -D warningsare clean.UTOPIA_TEST_REQUIRE_PDFTOTEXT=1 cargo test --locked --workspacepasses: 1274 passed, 0 failed.pnpm build(tsc and vite) pass.