feat(temporal): canonical half-open bounds for claim windows (D107 WP-T.0a) - #372
Conversation
…-T.0a) A stored D41 window is inclusive and carries a precision, and the engine compared those raw values directly: a day-precision claim was a zero-width point that an intraday claims_as_of window missed, adjacent units touched without overlapping, and D106's disjointness test inherited the same point semantics. Add one canonicalisation with two twins that must stay equal: the pure `core/temporal.py` (`canonical_bounds`, `inclusive_request`, `point_request`) and IMMUTABLE SQL functions `claim_canonical_start` / `claim_canonical_end` (migration p9_26_0047, with an expression index so the as-of scan stays indexed). Both ends align to the precision unit in UTC; an instant is a non-empty point; an open window has no end; unknown is no interval; the caller's inclusive `to` becomes an exclusive `to + 1 µs`, so `(t, t)` is a point query. `claims_as_of` filters on the canonical functions; the D106 block lateral, claim-timing reads and `_windows_disjoint` use the same half-open bounds, and the prompt renders the last instant inside a window. Claim storage and its CHECKs are unchanged. Roll the observation adjudicator and obs-flush generations and the LoCoMo protocol (Full-v21 -> Full-v22). Tests: the pure table, the SQL twin row by row, an intraday `claims_as_of` window and a point query, and a day-vs-instant adjudication proof. WP-T.0 is split into T.0a (this) and T.0b (query-space exposure) in the plan. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016p9xB9T7VYREuVmohDPKjR
…dent Codex review of #372: `timestamptz + interval` uses the SESSION time zone's calendar fields, so `claim_canonical_end` disagreed with its Python twin in a non-UTC session (a March-1 UTC month boundary read as February 28 in America/New_York, so "+1 month" gave March 28) and an IMMUTABLE function that feeds an expression index must not depend on the session. Both functions now truncate and advance on a UTC-naive timestamp (`AT TIME ZONE 'UTC'` in and out). The verdict prompt's `_date_text` likewise takes the UTC calendar day of an aware driver value instead of the session-zone day. Tests: the SQL twin equals Python under America/New_York, Asia/Kolkata and Pacific/Auckland across month/day(DST)/year/quarter-with-null-end/instant; `_date_text` prints the UTC day for a New York-rendered midnight. Benchmark banner and status page now say the library's `claims_as_of` results changed while the benchmark's read-tool set did not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016p9xB9T7VYREuVmohDPKjR
Codex review (gpt-5.6-sol, reasoning xhigh) — round 1, and triageAntigravity is quota-blocked until ~2026-09-07 ("Individual quota reached"); this PR carries the Codex review only, as #360/#369 did. Triage
Verified after the fix: SQL-twin + pure temporal suites 34 passed; adjudication + E3 fan-out 60 passed; LoCoMo benchmark suites 174 passed; operational-scale spike + temporal 20 passed (pre-fix run; the spike does not exercise the functions' session-zone path); ruff/pyright clean. Full review text
Verified
Merge verdictDo not merge until findings 1 and 2 are fixed and exercised under non-UTC PostgreSQL session timezones. The remaining canonicalization, query predicate, D106 interaction, migration lifecycle, and version-roll work otherwise looks consistent with WP-T.0a. |
…d literals CI on #372: the two new test files were absent from the CI test inventory; the migration chain and head literals still ended at p9_25_0046; and the batch-B intraday test inserted its claim against a random chunk id, so the history hydration view (claims_visible_history joins chunks) dropped it after the candidate scan had found it. The test claim now hangs off a live chunk of the live document, which is what the proof is about. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016p9xB9T7VYREuVmohDPKjR
|
CI round 1 on 47e7ec7 failed on three mechanical points, fixed in 870b0d1: the two new test files were missing from |
The Compose quickstart lane asserts the post-upgrade alembic head literal; migration p9_26_0047 (canonical bounds) moves it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016p9xB9T7VYREuVmohDPKjR
Problem
A stored D41 claim window is inclusive and carries a precision (
day/month/quarter/year/instant/open/unknown), and the engine compared the raw values directly. Audit finding 4.13 (plan/analysis/time_handling_audit.md): a day-precision claim was a zero-width point[2023-05-07 00:00, 2023-05-07 00:00], so an intradayclaims_as_ofwindow returned nothing; adjacent units touched without overlapping; D106's_windows_disjointinherited the same point semantics, so a day-precision claim and an instant inside that day missed each other.Change — D107 §5, WP-T.0a (engine-internal)
One canonicalisation, two twins pinned equal by tests:
core/temporal.py—canonical_bounds(valid_from, valid_until, precision) → [start, end): both ends aligned to the precision unit in UTC (year2022-01-01…12-31 →[2022-01-01, 2023-01-01); a day whose stored start is noon → the whole calendar day);instant→[t, t+1µs)(a non-empty point);open→ unbounded;unknown→ no interval.inclusive_request(from, to)maps the caller's inclusive window to[from, to+1µs), so(t, t)is a point query, never empty.p9_26_0047— IMMUTABLE SQL twinsclaim_canonical_start(valid_from, valid_precision)/claim_canonical_end(valid_from, valid_until, valid_precision)(date_trunc(unit, ts, 'UTC')), plus a partial expression index onclaimsso the as-of scan stays indexed. Claim storage and its CHECK constraints are unchanged.claims_as_offilters the candidate scan on the canonical functions withto_exclusive._CLAIMS_ASSERTED,_SELECT_EVIDENCE_FOR_OBSand the_BLOCK_ENTITYlateral select canonical bounds;_windows_disjointis half-open; the verdict prompt renders the last instant inside a window. Adjudicator and obs-flush generations roll (…:canonical-bounds-1).memory_v1.canonical_bounds, theclaims_canonicalview with catalog fixtures and deletion-matrix cells, the shippedclaims_as_ofexample whoseunknowncount is structurally zero today). Recorded inplan/plans/temporal_clocks.mdand D107 temporal clocks — implementation program (WP-T.0…T.5) and the four gating spikes #364.Tests
src/tests/core/test_temporal.py(pure table: bounded precisions align both ends incl. year rollover; instant overlaps itself; open unbounded; unknown no interval; adjacent days disjoint; intraday and point requests find the day; a day and an instant inside it overlap; naive inputs read as UTC),src/tests/spine/test_canonical_bounds_sql.py(the SQL twins equal the Python function row by row), batch-Btest_claims_as_of_finds_a_day_precision_claim_from_an_intraday_window(intraday window, point query, next-day miss),test_d107_a_day_and_an_instant_inside_it_overlap(adjudication honoursevidencefor an instant inside the day; the same instant next day is a new occurrence). Existing D43/D88/D90/D106 proofs, E3 fan-out, protocol and runner suites: 148 passed; batch-B and the SQL twin suites green.ruff/pyrightclean repo-wide. The operational-scale spike run is in progress at PR-open time and will be reported here.Contributor agreement
Review
Codex (
gpt-5.6-sol, xhigh) review to be posted with a triage. Part of #364.🤖 Generated with Claude Code
https://claude.ai/code/session_016p9xB9T7VYREuVmohDPKjR