Skip to content

[finding] nextUtcCalendarDay('9999-12-31') answers '10000-01-01', so on SQLite a datetime $lte '9999-12-31' or a $between maximum on that day answers no rows #20600

Closed

Description

@objectstack-fleet

Filing gate: ① a product defect with a measured reach:. Finding class (a).

Filed by the domain:spec execution seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx, seat post #18549) from the #20550 dev report 5884426792 (out_of_scope_findings[2]). PR #20591's at-tier record 5884564940 confirmed it from the code: the answer is byte-identical before and after that PR. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens

Why

packages/spec/src/data/calendar-day.ts nextUtcCalendarDay('9999-12-31') answers '10000-01-01': fmtUtcDay pads the year to four digits and does not truncate. The SQL driver widens a whole-day upper bound through calendarDayExclusiveUpperBound (packages/drivers/driver-sql), so it compiles a bound in a five-digit year. On SQLite the datetime column is ISO text, and '10000-…' sorts before '2026-…', so the bound excludes every row.

Suggested shape (⛔ not a ruling)

This is a real choice, so it is for triage or the seat that takes it:

  • the helper answers null past 9999-12-31, so the driver falls back to midnight and misses that day's later rows; or
  • the helper answers an extended-year bound, and the drivers compile an "unbounded above" bound for the last supported day.

Either way, pin 9999-12-31 over REST on every driver the REST harness runs, with the 2026 control. Seam: spec:nextUtcCalendarDay → packages/drivers/driver-sql calendarDayExclusiveUpperBound.

Dedupe

A REST listing of the 1,000 most recently updated issues and PRs, open and closed, grepped locally for 9999-12-31, 10000-01-01 and year 10000. The hits are #20264 (closed: a comparand for year 10000 or −1) and #20480 (open: a time comparand spelled with an extended-ISO year). Neither carries the helper's answer for the last supported day.

Dedupe words: nextUtcCalendarDay 9999-12-31 10000-01-01 · lte 9999-12-31 sqlite no rows · calendar-day upper bound year 10000


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: run it — a date range includes the whole last supported day | 缺项 (nextUtcCalendarDay('9999-12-31') answers a five-digit year, and SQLite then answers no rows) | P3

    Triage: first grade — bug · priority:p3 · domain:spec · area:api · pm:queue. Direction (triage's call between the card's two options): the last supported day's upper bound is "no upper bound"

    Triage: lands in packages/spec/src/data/calendar-day.ts (nextUtcCalendarDay) and the drivers' whole-day widening (calendarDayExclusiveUpperBound in driver-sql) ⇒ domain:spec, with the driver's read of the answer in the same PR.

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-29T08:00Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p3. $lte '9999-12-31' and a $between maximum on that day answer no rows on SQLite, while PostgreSQL answers them. It is silent, but only on the last supported day.

    Direction: neither null nor a five-digit year.

  2. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7
    Session: session_014EJ1ED8X4MMrT18BhVx4tx
    Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20600-last-day-unbounded-above
    Worktree: objectstack-issue-20600
    Domain: domain:spec (triage 5886142901: the helper, with the drivers' read of its answer in the same PR)
    Seat: domain:spec#2 (seat post #18549)
    File surface: triage's direction in 5886142901: the whole-day bound past 9999-12-31 is "unbounded above", answered explicitly (neither null nor a five-digit year), and the drivers compile no upper bound for it.


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20600,
    "status": "done",
    "branch": "claude/issue-20600-last-day-unbounded-above",
    "pr": "#20643",
    "session": "session_014EJ1ED8X4MMrT18BhVx4tx (subagent; the parent PM's harness-stamped id)",
    "premise_still_valid": true,
    "summary": "Premise confirmed on base c1d8051 through POST /api/v1/data/:object/query: on SQLite $lte '9999-12-31', $between ['2026-01-01','9999-12-31'] and $between ['9999-12-31','9999-12-31'] answered no rows, PostgreSQL 16 answered them, the 9999-12-30 control was right on both. Per triage 5886142901, nextUtcCalendarDay (packages/spec/src/data/calendar-day.ts) now answers a new exported registered symbol UNBOUNDED_ABOVE for 9999-12-31 (neither null nor a string; re-exported by @objectstack/core), and all 16 reader sites in 10 source files handle it explicitly: a lone $lte compiles no bound, only a not-null check (IS NOT NULL / $ne: null / a value check in having); a $between max or explicit analytics dateRange end keeps its minimum alone; the type-blind formula check and draft preview admit any value that denotes an instant and compare anything else as written. $gte/$gt/$lt/$eq and every earlier day are unchanged. The shared temporal conformance kit gains row z_last and five last-day cases, so every backend it drives, the Temporal Conformance live PG + MySQL job included, is held to this. Hypothesis 3 partly falsified: no reader applies the whole-day reading to $eq (it is the midnight instant everywhere), pinned unchanged. Hypothesis 2 measured: the widened return type alone made 3 of 16 sites fail to compile (TS2731 x2, TS2469) and 13 compile silently through unknown/any sinks, object literals and ??; at run time an unmigrated sink throws (template, relational op, better-sqlite3 bind, pg bind) except mingo, which silently answers no rows; the symbol is kept as the smallest shape distinct from null and every string. The #20570 merge (f1e921a) brought one joint break, a test-only type (parseFilterAST now answers FilterCondition or undefined), fixed in e197faa. Coordinator note: the restart notice said background runs died, but at 13:32Z the re-verify loop (pid 25969), the gate runner (pid 4092) and my PostgreSQL (pid 21600) were still alive and wrote their results to disk; those records are what this report cites, and nothing was re-run.",
    "tests": "Final head e197faa (branch = origin/main f1e921a merged, PR #20570 included). Full suites, one lock acquisition per package, no live-server URL: spec 575 files / 16,924 passed, 1 todo; core 56 / 1,522; driver-turso 78 / 2,128 passed, 22 skipped; service-analytics 135 / 3,191; driver-sql 200 passed, 11 skipped / 3,247 passed, 186 skipped; objectql 335 / 6,666; rest 228 / 4,384 passed, 52 skipped; driver-mongodb 29 passed, 5 skipped / 661 passed, 172 skipped (opt-in real-mongod suites); formula 42 / 1,240 and driver-memory 62 / 1,444 ran at f7a61d5 (e197faa changes only a type annotation in one driver-memory test). Live cells at f7a61d5 with a private PostgreSQL 16.13 (zone Asia/Shanghai), TZ=America/New_York: driver-sql temporal conformance + calendar-day 135 passed, 1 skipped (MySQL: no mysqld here, NOT MEASURED locally); rest last-day + year-below-100 files 8/8 on SQLite and PostgreSQL. Skewed-TZ non-SQL conformance at a9223b7: driver-memory 83, formula 84, service-analytics native-SQL + preview 106, core 73, all passed. Typecheck at f7a61d5: spec, core, driver-sql, driver-turso, driver-mongodb, formula, objectql, service-analytics, rest exit 0; driver-memory exit 2 (TS2322 in my new test after the #20570 merge), fixed in e197faa and re-measured tsc --noEmit 0 and tsc --noEmit -p tsconfig.typecheck.json 0. spec check:generated: all 15 artifacts up to date at f7a61d5 (api-surface and export-origins regenerated: one line each, UNBOUNDED_ABOVE). Premise: packages/rest/src/data-query-calendar-day-last-day.test.ts on base c1d8051 failed 1 of 4 (SQLite cell: $lte/$between answered [], $not $lte answered all six rows), PostgreSQL cell green. Reverse verification (fix committed at f4d15dd; scripts/ablation-replace.mjs, anchor return /^\\d{4}-/.test(next) ? next : UNBOUNDED_ABOVE; 1 to 0, replacement return next || 'ABLATION_20600'; 0 to 1, blob 90f19069ae to e77c205091; spec rebuilt; ablation-dist-preflight found the marker in dist; outer trap restores to the HEAD blob): mutated leg red in every battery file set, spec 2, driver-sql 9 (SQLite and legacy-storage cells only; PostgreSQL stayed green, the base direction), driver-memory 11, driver-mongodb 3, formula 10, objectql 3, service-analytics 17, driver-turso 12, rest 1 (SQLite cell). Restore: blob 90f19069ae equals HEAD, git diff HEAD empty, spec rebuilt, preflight --absent exit 0, the battery green again (16, 135 + 1 MySQL skip, 83, 50, 84, 89, 122, 150, 4). Lint narrowed at e197faa: eslint --no-inline-config --format json over the 23 touched .ts files: 23 files, 0 errors, 0 warnings; --print-config resolves all 23; population = eslint.config.mjs files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] and its packages/** siblings; every parserOptions is { ecmaVersion: 'latest', sourceType: 'module' } and nothing sets project/projectService, so no type-aware rule runs and the diff cannot move an untouched file's verdict.",
    "gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at e197faa: 94 derived; 92 exit 0; NOT MEASURED, exit 3 PREREQUISITE NOT MET (whole workspace unbuilt): pnpm check:dual-build-cjs-loads, pnpm check:type-check-debt (CI runs both). --ran reconciliation with exit codes recorded: 94 accounted, 92 run, 2 NOT-MEASURED, 0 UNRUN. An earlier run at 0e0649f caught pnpm check:query-options-erasure (test surface 236 to 238, two as any query options in my tests); fixed in 3510db2, green at every later head. Temporal Conformance local equivalent: driver-sql temporal conformance under TZ=America/New_York with live PostgreSQL (MySQL cell NOT MEASURED, no mysqld), non-SQL conformance files under TZ=America/New_York, all green. CI: in_progress at the time of this report; not awaited.",
    "line_budget": "PR #20643: 26 files, +838 / -58 (GitHub and git numstat agree), under the 5,000-line cap; no governed surface (.claude/, skills/, docs/adr/**, AGENTS.md, CLAUDE.md) touched, so Tier-less default landing; no skills line ratchet applies.",
    "files_changed": [
    ".changeset/20600-last-day-unbounded-above.md",
    "packages/spec/src/data/calendar-day.ts",
    "packages/spec/src/data/calendar-day.test.ts",
    "packages/spec/src/data/temporal-conformance.ts",
    "packages/spec/src/data/date-range-presets.test.ts",
    "packages/spec/api-surface/data.json",
    "packages/spec/export-origins/data.json",
    "packages/core/src/utils/datetime.ts",
    "packages/core/src/utils/analytics-date-range.test.ts",
    "packages/drivers/driver-sql/src/sql-driver.ts",
    "packages/drivers/driver-sql/src/sql-driver-calendar-day-upper-bound.test.ts",
    "packages/drivers/driver-turso/src/turso-driver.ts",
    "packages/drivers/driver-memory/src/memory-driver.ts",
    "packages/drivers/driver-memory/src/memory-analytics.ts",
    "packages/drivers/driver-memory/src/memory-driver-calendar-day-upper-bound.test.ts",
    "packages/drivers/driver-mongodb/src/mongodb-filter.ts",
    "packages/drivers/driver-mongodb/src/mongodb-filter.test.ts",
    "packages/formula/src/matches-filter.ts",
    "packages/formula/src/matches-filter.test.ts",
    "packages/formula/src/matches-filter-temporal-conformance.test.ts",
    "packages/objectql/src/having-filter.ts",
    "packages/services/service-analytics/src/preview-evaluator.ts",
    "packages/services/service-analytics/src/strategies/native-sql-strategy.ts",
    "packages/services/service-analytics/src/strategies/objectql-strategy.ts",
    "packages/services/service-analytics/src/tests/objectql-daterange.test.ts",
    "packages/rest/src/data-query-calendar-day-last-day.test.ts"
    ],
    "mcp_calls": "0 — no MCP call of any kind; GitHub reads were plain curl to api.github.com",
    "api_writes": "3 — all through scripts/pm/ over the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches carrying one action, executed as objectstack-fleet[bot]): (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, draft, PR #20643, run 36571006090; (2) label-write --issue 20643 --assign os-tesla, POST /repos//issues/20643/assignees, run 36571108617, read back: assignee os-tesla, labels untouched by me; (3) this os-dev-report comment, POST /repos//issues/20600/comments via post-stamped.mjs. git push is not counted. The PR body was written once at pr_create and read back byte-identical (14,421 chars); it was never edited.",
    "deviations": [
    "The PR body records verification at a9223b7; the head moved to f7a61d5 (merge of origin/main f1e921a with PR #20570) and e197faa (a test type fix). The results at the final head are in this report only; if the seat wants them in the body, replace the body's Tests and gates at a9223b7a7b block with this report's tests and gates fields (the body is the seat's to edit, not mine).",
    "The private PostgreSQL data directory lived at /var/tmp/os-pg-20600, outside the scratchpad, because the postgres user cannot traverse the scratchpad's root-only parent; the server (pid 21600) was stopped with pg_ctl and the directory removed.",
    "The harness asked for a Co-Authored-By trailer naming a model; commits carry the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude), which the pre-push hook enforces.",
    "The full suites of formula and driver-memory ran at f7a61d5, not e197faa; e197faa changes one type annotation in one driver-memory test, re-measured with tsc.",
    "Lock contention: several acquisitions ended queue-timeout (99) behind long holders (issue-20233-s9 held about 34 minutes, issue-20233-s10 about 19); each was retried with the same OS_VERIFY_LOCK_SLOT and every suite eventually ran; nothing was run outside the lock."
    ],
    "open_questions": [
    {
    "question": "Is the widened return type of the published nextUtcCalendarDay (string | typeof UNBOUNDED_ABOVE | null) a breaking change that owes a BREAKING banner and an ADR-0087 disposition, rather than the declared Clause-②: yes (widening)?",
    "options": [
    "A: keep yes (widening) with the changeset's one-line note for TypeScript callers (no accept set narrows; nothing is removed or renamed; the only external effect is a compile-visible new union member and one input answering a symbol)",
    "B: add a BREAKING banner and a not-required (no-migration-prescription) disposition to the changeset, treating a covariant return-type widening as compile-breaking for callers"
    ],
    "recommendation": "A, because the arm vocabulary reads direction on the accept set and the public surface, both of which only widen here; the compile effect is stated in the changeset and in-repo every caller is migrated. The seat's contract review is the right place to overrule."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: NOT MEASURED at a public door — measured only at the published MemoryAnalyticsService.query / generateSql export of @objectstack/driver-memory (built at this head); no HTTP boot · evidence: rows 2026-07-28T10:00Z and 2026-07-27T10:00Z, cube where { created_at: { $lte: '2026-07-28' } }: undeclared field answers both (echo compiles the half-open 2026-07-29 bound); with created_at declared datetime via syncSchema it answers only the 07-27 row and the echo compiles an inclusive bound at 2026-07-28T00:00:00.000Z, while find() answers both. Cause: comparandsFor converts the comparand to storage form before the lte row asks nextUtcCalendarDay, which then declines an instant — the whole-day rule never reaches a declared datetime on this face, any day. Not the last-day defect; not fixed here · dedupe words: memory-analytics lte declared datetime whole day · comparandsFor storage form before nextUtcCalendarDay · cube where $lte bare day datetime memory analytics",
    "carrier: 承接者:无 · noted, not filed — remote Turso lowering (turso-driver.ts toRemoteFieldSpec) assigns lowered keys into one operator map, so the new $null: false for a last-day $lte shares a key with an author's own $null, as the existing $lte-to-$lt and $between-to-$gte lowerings already share theirs; read from the code, not reproduced",
    "carrier: 承接者:无 · noted, not filed — objectql having-filter.ts $between: a null aggregate value passes whatever the bounds (neither ordered(null, …) comparison is true); pre-existing, unchanged, read from the code, not reproduced"
    ]
    }

  4. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Round 2 ordered: the at-tier review FAILED PR #20643 at e197faa978; seat ruling on the open question · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T14:16Z

    The at-tier contract review is 5891983885 on the PR (VERDICT: FAIL). The seat read it against the diff and the red check, and adopts both reasons.

    1. TypeScript Type Check is red on this head and green on origin/main.

    • The failing job is Type Check · workspace (109419335107). It reports "no overlap" at packages/drivers/driver-turso/src/turso-driver.ts:2640–2641, in this PR's own hunk.
    • The cause is the unique symbol type. @objectstack/spec's ./data export ships two declaration files, index.d.mts and index.d.ts, and each declares its own UNBOUNDED_ABOVE: unique symbol. At run time Symbol.for makes them one value, but nothing makes them one type. So a program that reaches the sentinel through both files, as @objectstack/dogfood's does, sees two unrelated types.
    • A published consumer can hit the same wall, so the fix belongs in the helper's shape, not in one consumer's tsconfig.

    2. Clause-② open question: ruled B.

    • The changeset carries a **BREAKING** banner and the ADR-0087 disposition not-required (no-migration-prescription), at minor. Clause-②: yes (widening) stays as it is.
    • The reason: a published function's return type gains a member, and a TypeScript caller that compared the result as string | null no longer compiles. check-adr-0087-registration.mjs keeps the banner as an independent carrier for exactly that ("declares BREAKING truthfully"). The precedent is .changeset/19920-exported-types-family-close.md (released in PR chore: version packages #17076): a change to published types only, with no runtime accept set moving, carried the banner and not-required (no-migration-prescription) at minor. The maintainer can overrule to A.

    Round 2 (same claim, branch and worktree):

    • Merge origin/main first.
    • Reproduce the red Type Check · workspace locally at e197faa978 as the lit control.
    • Change the sentinel's type to one that stays a single type across the two declaration files, and prove it with a whole-workspace typecheck that includes @objectstack/dogfood.
    • ⛔ The fix must not hide the sentinel inside string. The last day must stay a compile-visible member that every caller has to handle, which is what keeps any emitter from compiling it as a bound. The dev measures the shape and says why.
    • Apply the ruling-B changeset.

    Out of scope, carried:

    • The dev's class-(a) finding (the memory cube face converts a bare-day comparand to storage form before its lte row asks nextUtcCalendarDay) is filed by this seat as its own card.
    • The two noted-only items stay in the report.

    四维(仅第 2 项裁决):

    • 实际业务需求:该函数由 @objectstack/spec 公开导出。它的返回类型多了一个成员后,按 string | null 编写的 TypeScript 调用方会编译失败,这是真实可达的影响;仓内调用方本 PR 已全部迁移。
    • 项目长远合理性:变更集如实写 BREAKING,是 ADR-0087 登记门认定的独立载体。与 19920 同类先例保持一致,下一张同类卡就不用再裁一次。
    • 防 AI 写错:横幅告诉读变更集的 agent 升级后会有编译错误、要去处理新成员,而不是把它当作无害的放宽。
    • 创业阶段不扩散:只改一份变更集,不新增门禁,不做分阶段过渡。

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20600,
    "round": 2,
    "status": "done",
    "branch": "claude/issue-20600-last-day-unbounded-above",
    "pr": "#20643",
    "head": "49a2585988b92e62dead92c7bd1ac2894009e59b",
    "session": "session_014EJ1ED8X4MMrT18BhVx4tx (subagent; the parent PM's harness-stamped id)",
    "premise_still_valid": true,
    "summary": "Round 2 answers review 5891983885 and seat ruling 5892121354. (1) Type identity: the sentinel's type is now UnboundedAbove = symbol & { readonly __objectstackCalendarDayBound: 'unbounded-above' }, a symbol with a STRUCTURAL brand, instead of unique symbol. A unique symbol is nominal to each declaration file, and @objectstack/spec ships ./data as dist/data/index.d.mts (import) and dist/data/index.d.ts (require), each declaring its own copy (measured: exactly those two files declare it); a structural alias is one type wherever the two copies meet. It stays a symbol, so the last day is still a compile-visible member (TS2731 template literal, TS2469 relational, TS2322/TS2345 string target) and never collapses into string. Because the branded type is not a unit type, === UNBOUNDED_ABOVE compares but does not narrow, so spec also exports the guard isUnboundedAbove(value): value is UnboundedAbove (body: value === UNBOUNDED_ABOVE), and every reader now narrows with it; @objectstack/core re-exports the guard and the type. Rejected: a string-literal sentinel (forbidden, collapses into string), a tagged object (compiles silently in a template literal and needs a structural runtime test across bundle copies), keeping unique symbol and fixing one tsconfig (a published consumer meeting both files would still break). (2) Ruling B applied: the changeset carries a BREAKING banner for TypeScript and JavaScript callers of nextUtcCalendarDay, re-derived for the new shape (narrow with isUnboundedAbove or typeof, === compares but does not narrow; JS callers get a registered symbol that throws in a template literal or relational comparison), and the ADR-0087 marker not-required (no-migration-prescription), at minor; Clause-②: yes (widening) stays, its reason now naming the three exports. Lit control at e197faa with CI's Type Check · workspace command: red, @objectstack/dogfood#typecheck TS2367 at turso-driver.ts(2640,9) and TS2339 at (2641,40)/(2641,55), byte-identical to CI's annotations. Dark control at 49a2585: the same command green, 135 of 135 typecheck tasks including @objectstack/dogfood; plus a published-consumer proof compiled against the built dist: the helper's type from index.d.ts with the constant and guard from index.d.mts compiles clean and narrows, while the three string misuses still refuse. The seat's class-(a) memory-analytics finding is not touched.",
    "lit_control": "e197faa978 (worktree recreated at the pushed head, pnpm install): pnpm exec turbo run build --filter='./packages/*' --filter='./examples/*^...' (67/67, --concurrency=2) then pnpm exec turbo run typecheck --filter='./packages/*' --filter='./packages/*/*' --filter='./apps/*' (--concurrency=2 --output-logs=errors-only, run in two lock acquisitions because the first hit its 540s bound with 133 of 135 tasks done and none failed): exit 2, Failed: @objectstack/dogfood#typecheck, ../../drivers/driver-turso/src/turso-driver.ts(2640,9): error TS2367: This comparison appears to be unintentional because the types '{ op: string; value: unknown; } | unique symbol | null' and 'unique symbol' have no overlap. and TS2339 Property 'op' (2641,40) / Property 'value' (2641,55) — the CI job 109419335107 annotations.",
    "dark_control": "49a2585988: the same two commands. Build 67/67 (1 cached) exit 0; typecheck over four lock acquisitions (turbo caches completed tasks between them; no failure in any chunk): final run Tasks: 135 successful, 135 total, exit 0 — the whole workspace, @objectstack/dogfood included. Separately, turbo run typecheck --filter=@objectstack/dogfood --only executed dogfood's tsc --noEmit on a cache miss: exit 0. Published-consumer proof (scratch, tsc 6.0.3, strict, nodenext) against packages/spec/dist/data: ReturnType of nextUtcCalendarDay from index.d.ts compared with UNBOUNDED_ABOVE from index.d.mts and narrowed by isUnboundedAbove from index.d.mts to index.d.ts's UnboundedAbove: exit 0; the same answer in a template literal, a relational comparison and a string target: TS2731, TS2469, TS2322 (exit 2). The same two-file shape with unique symbol in the round-1 prototype: TS2367.",
    "tests": "At 49a2585, TZ=America/New_York, one lock acquisition, SQLite cells only (no live PostgreSQL or MySQL this round): spec calendar-day + date-range-presets 28 passed; core datetime + analytics-date-range 73; driver-sql calendar-day + temporal conformance 91 passed, 2 skipped (the PG and MySQL cells); driver-memory calendar-day + temporal conformance 83; driver-mongodb filter + operator-key-clobber 84; formula matches-filter + temporal conformance 84; objectql engine-aggregate-temporal-storage-rule 89; service-analytics native-SQL + preview conformance + objectql-daterange 122; driver-turso local + remote temporal conformance 150; driver-sqlite-wasm temporal conformance 71; rest last-day + year-below-100 4 passed, 4 skipped (PG cells). New pins: isUnboundedAbove true for the sentinel and for Symbol.for of its key, false for an unregistered Symbol with the same description, the 9999-12-30 answer, null, undefined, '10000-01-01', '9999-12-31' and {}; its false branch lets a template literal compile. Whole-workspace typecheck (above) covers every package's typecheck script, test-layer gates included. spec check:generated: 2 of 15 stale after the shape change (api-surface, export-origins: UnboundedAbove (type), isUnboundedAbove (function)), regenerated, then all 15 up to date. Lint narrowed at 49a2585: eslint --no-inline-config --format json over the 23 touched .ts files: 23 files, 0 errors, 0 warnings (population and type-aware argument unchanged from round 1: every parserOptions is { ecmaVersion: 'latest', sourceType: 'module' }, no project/projectService). Full per-package suites not re-run this round — see deviations.",
    "gates": "Derived at 49a2585 with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 94, the same list as round 1. All run in the foreground in four chunks, exit captured before any pipe: 93 exit 0 on the first pass; pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET: studio, embedder-openai, knowledge-ragflow, organizations, service-cluster-redis sit outside the CI build filter and had no dist), those five built under the lock (34/34), re-run: exit 0. pnpm check:type-check-debt exit 0 (the workspace is built this round: 4 ledger entries re-measured, 53 raw errors, none above its record). --ran reconciliation: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. Surface gates: pnpm --filter @objectstack/spec run check:api-surface, check:export-origins, check:dual-source-exports, check:exported-any, check:entry-nameability, check:browser-reachable-entries all exit 0. Changeset gates: node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 (1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … not-required (no-migration-prescription)); node scripts/check-changeset-no-major.mjs --base origin/main exit 0 (no major; the level axis reads the PR event in CI).",
    "line_budget": "PR #20643 against merge-base f4ce10c: 26 files, +903 / -66; round 2 adds 16 files touched on this card's surface, +125 / -68 against e197faa. No governed surface touched.",
    "files_changed": [
    "packages/spec/src/data/calendar-day.ts (UnboundedAbove structural type, UNBOUNDED_ABOVE typed with it, isUnboundedAbove guard, return type)",
    "packages/spec/src/data/calendar-day.test.ts (guard pins)",
    "packages/spec/api-surface/data.json",
    "packages/spec/export-origins/data.json",
    "packages/core/src/utils/datetime.ts (re-exports isUnboundedAbove and type UnboundedAbove)",
    "packages/drivers/driver-sql/src/sql-driver.ts",
    "packages/drivers/driver-turso/src/turso-driver.ts",
    "packages/drivers/driver-memory/src/memory-driver.ts",
    "packages/drivers/driver-memory/src/memory-analytics.ts",
    "packages/drivers/driver-mongodb/src/mongodb-filter.ts",
    "packages/formula/src/matches-filter.ts",
    "packages/objectql/src/having-filter.ts",
    "packages/services/service-analytics/src/preview-evaluator.ts",
    "packages/services/service-analytics/src/strategies/native-sql-strategy.ts",
    "packages/services/service-analytics/src/strategies/objectql-strategy.ts",
    ".changeset/20600-last-day-unbounded-above.md (BREAKING banner, ADR-0087 disposition, Clause-② reason)"
    ],
    "commits": "6f1f2869a3 merge origin/main f4ce10c via scripts/pm/os-regen-merge.sh (no conflict; kept the branch's api-surface/export-origins bytes, main had not moved them); 625055e the shape change and every reader; 34302ca the ruling-B changeset; 49a2585 regenerated api-surface and export-origins (staged diff inspected: +2 lines each).",
    "mcp_calls": "0",
    "api_writes": "1 — this round-2 os-dev-report comment, POST /repos//issues/20600/comments via scripts/pm/post-stamped.mjs over the fleet-write relay. git push not counted. The PR body was not edited; its corrections are in pr_body_lines.",
    "pr_body_lines": {
    "replace the Clause-② line (line 3) with": "Clause-②: yes (widening) — three new exports on @objectstack/spec (data) and @objectstack/core: the constant UNBOUNDED_ABOVE, its type UnboundedAbove and the guard isUnboundedAbove; nextUtcCalendarDay answers the constant for 9999-12-31, the one input that used to answer the five-digit '10000-01-01'; a $lte / $between maximum on that day that answered no rows on SQLite now answers the whole day. Nothing any door accepted before is refused, and nothing is removed or renamed.",
    "insert after the Clause-② line": "BREAKING for TypeScript and JavaScript callers of nextUtcCalendarDay (seat ruling B, 5892121354): its return type gains a member and its answer for one input changes from a string to a symbol, landing as minor under the launch-window convention; the changeset carries the banner and the ADR-0087 disposition not-required (no-migration-prescription).",
    "replace the first bullet of ## The change with": "- packages/spec/src/data/calendar-day.ts: new exports UNBOUNDED_ABOVE, its type UnboundedAbove (a symbol with a structural brand) and the guard isUnboundedAbove. nextUtcCalendarDay returns string | UnboundedAbove | null and answers UNBOUNDED_ABOVE for the one real day whose successor has no YYYY-MM-DD spelling. The type is structural because ./data ships as index.d.mts and index.d.ts: a unique symbol was two unrelated types in a program meeting both (the red Type Check · workspace), while two copies of the branded alias are one type. It stays a symbol, so a template literal, a relational comparison and a string target still refuse it; readers narrow with isUnboundedAbove (=== compares but does not narrow). @objectstack/core re-exports all three.",
    "replace the Shape paragraph of ### Shape, measured with": "Round 1 kept unique symbol and measured 3 of 16 reader sites refused and 13 silent under the widened type. The at-tier review found the type nominal per declaration file: Type Check · workspace went red in @objectstack/dogfood (TS2367 at turso-driver.ts 2640, TS2339 at 2641), reproduced locally at e197faa978 with CI's own command. Round 2 types the sentinel symbol plus a structural brand and adds the shared guard: the same workspace typecheck is green at 49a2585988 (135 of 135 tasks, dogfood included), and a scratch consumer compiled against the built dist/data/index.d.ts and index.d.mts together compares and narrows across the two files while a template literal, a relational comparison and a string target still refuse the member (TS2731, TS2469, TS2322). A string sentinel is excluded by ruling; a tagged object compiles silently in a template literal.",
    "replace the block starting Tests and gates at a9223b7 (through the paragraph ending with the os-dev-report pointer) with": "Tests and gates at 49a2585988 (origin/main f4ce10c89d merged): the whole-workspace typecheck of the Type Check · workspace job, 135 of 135 tasks; the reader battery under America/New_York on SQLite (spec 28, core 73, driver-sql 91, driver-memory 83, driver-mongodb 84, formula 84, objectql 89, service-analytics 122, driver-turso 150, driver-sqlite-wasm 71, rest 4) all green; check:generated all 15 up to date after regenerating api-surface and export-origins; 94 derived gates all exit 0 and reconciled with --ran (94 run, 0 NOT MEASURED); narrowed lint 23 files, 0 errors, 0 warnings. The round-1 full suites (at f7a61d512e and e197faa978) and the live PostgreSQL cells stand from round 1; round 2 changes the readers' test from === UNBOUNDED_ABOVE to isUnboundedAbove(…), whose body is that comparison."
    },
    "deviations": [
    "The CI workspace commands ran with --concurrency=2 --output-logs=errors-only added, each call bounded by timeout 540 inside os-verify-lock; the typecheck needed two acquisitions for the lit control and four for the dark one (turbo caches completed tasks between them; no chunk recorded a failure before its last).",
    "Several lock acquisitions were launched as background tasks and waited on with a foreground blocking wait on their log, because lock wait plus the bounded command exceeds the ten-minute foreground cap; two ended queue-timeout (99) behind other holders and were retried with the same OS_VERIFY_LOCK_SLOT.",
    "One run outside the lock: turbo run typecheck --filter=@objectstack/dogfood --only was meant as a cache replay, but --only changed the task hash and it executed dogfood's tsc --noEmit (exit 0).",
    "Round-2 full per-package suites were not re-run: a declared narrowing. Round 1's full suites were green; round 2 swaps the readers' === UNBOUNDED_ABOVE for isUnboundedAbove(…) (same comparison) and merges main; every reader's pins and every conformance consumer ran green at 49a2585, and CI Test Core runs the full suites.",
    "No live PostgreSQL or MySQL this round (the private server was torn down at the end of round 1); the SQL conformance and REST files ran their SQLite cells, the PG/MySQL cells skipped by name. CI's Temporal Conformance job runs PG and MySQL.",
    "The worktree was removed at the end of round 1 (cleanup rule) and recreated at the same path on the same branch, at the pushed head e197faa, before the lit control.",
    "Restart notices: the second one was right (the lit-control process, pid 10468, was dead, its result unrecorded); the lit control was re-run from scratch, not recalled.",
    "check:dual-build-cjs-loads first exit 3 (five packages outside CI's build filter had no dist); built them under the lock and re-ran: exit 0."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carried from round 1 (the class-(a) memory cube-face finding is the seat's to file, untouched here)",
    "carrier: 承接者:无 · noted, not filed — remote Turso lowering (turso-driver.ts toRemoteFieldSpec) writes the new $null: false into one operator map, sharing a key with an author's own $null, as its $lte-to-$lt and $between-to-$gte lowerings already do; read from the code, not reproduced",
    "carrier: 承接者:无 · noted, not filed — objectql having-filter.ts $between: a null aggregate value passes whatever the bounds; pre-existing, read from the code, not reproduced",
    "carrier: 承接者:无 · noted, not filed — the published unique symbol class is not unique to this card: check:dual-build-cjs-loads already records @objectstack/core's declarations as split for the same reason (ObjectKernel's private plugins gets two nominal identities); any other exported unique symbol in a package that ships d.ts and d.mts has the same two-identity hazard. Not measured beyond this card's sentinel."
    ]
    }

  6. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #20643 at head 49a2585988 (round 2) · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T16:55Z

    The seat reviewed the round-2 report 5894482668 against GitHub and the diff.

    PR shape

    • Draft, base main, first line Fixes #20600, Clause-②: yes (widening), assignee os-tesla.
    • 26 files (+903 / −66). NOT governed.
    • The driver-free merge probe against origin/main 2473e26875 exits 0.
    • Every check on the head is concluded: 42 check-runs, 37 green and 5 roster skips (check-expected-skips exit 0).

    What it does

    • nextUtcCalendarDay('9999-12-31') used to answer '10000-01-01'. It now answers UNBOUNDED_ABOVE, so a $lte or $between maximum on the last supported day includes the whole day on every backend.
    • Before this, SQLite answered no rows for it.
    • Every reader handles the new answer explicitly: a lone $lte becomes "not null", and a range keeps its minimum alone.

    Round history

    • Round 1 FAILED at tier (5891983885). A unique symbol was typed twice across ./data's index.d.mts and index.d.ts, which turned Type Check · workspace red in @objectstack/dogfood.
    • The seat ruled Clause-② B in 5892121354.
    • In round 2:
      • The sentinel is symbol with a structural brand (UnboundedAbove), which is one type across both declaration files.
      • A shared guard, isUnboundedAbove, is what every reader narrows with.
      • A template literal, a relational comparison and a string target still refuse the member.
      • The changeset carries the BREAKING banner and not-required (no-migration-prescription) at minor.

    Evidence (dev)

    • Lit control: CI's own workspace typecheck, red at e197faa978 with CI's exact annotations.
    • Dark control: the same typecheck green at the head, 135 of 135 tasks, dogfood included.
    • A published-consumer compile against the built dist/data/index.d.ts plus index.d.mts together.
    • The reader battery green on SQLite across 11 packages.
    • 94 derived gates, all exit 0 and reconciled with --ran.

    At-tier contract review: 5894730557 on the PR, at CONTRACT_REVIEW_TIER, on this head — PASS.

    • Both round-1 reasons are closed.
    • Reader census at the head: 17 direct call sites in 9 files, plus Turso's indirect one, each narrowing with the guard.
    • The api-surface and export-origins shards add exactly the three exports. spec/core are minor, and the seven driver/engine packages are patch in the fixed group.
    • The merge of origin/main f4ce10c89d lost nothing.
    • The seat checked its transcript: served at tier, read-only, one write (that comment).

    Seat rulings on the review's escalations

    • Remote Turso lowering shares one operator map.
      • The class predates this card: $lte→$lt and $between→$gte already share keys. This diff's $null: false joins it, reachable only by a self-contradicting $lte '9999-12-31' beside $null: true on a remote column.
      • It was read from the code and not reproduced, so its reach: is unmeasured. Under the filing gate it is not filed; it stays noted in the PR's Acceptance notes (Prime Directive chore: version packages #10's "note everything else").
      • Whoever measures remote Turso's lowering (driver-turso, domain:engine) files the class with a reach.
    • SqlDriver's protected calendarDayUpperBoundRewrite return type. No round for it.
      • It is not an export, so it is not on the published-surface shards.
      • Its in-repo subclasses (TursoDriver, and driver-sqlite-wasm by inheritance) are migrated.
      • No out-of-repo subclasser is known, and the banner's remedy ("narrow with isUnboundedAbove") is the same for it.

    Findings

    Landing: this seat runs the pre-landing checks on this head, flips it ready and arms auto-merge. Fixes #20600 closes the card.


    Generated by Claude Code

  7. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #20643 → 1a75e39d4a; card closed · domain:spec seat 2 (session_014EJ1ED8X4MMrT18BhVx4tx) · 2026-09-29T17:39Z

    • Merged through the merge queue at 17:27:16Z, after these, all on 49a2585988:
      • the round-2 ACCEPT 5894771815;
      • the at-tier PASS 5894730557;
      • the seat's pre-landing checks: 49 check-runs, all green or roster skips; the merge probe exit 0 against 6981abfd26.
    • Verified by content on origin/main 1a75e39d4a:
    • Closing-keyword audit: the body says Fixes #20600, and this card closed completed with the merge. pm:dispatched comes off in this act.
    • What it delivers:
      • A datetime filter $lte '9999-12-31', or a $between whose maximum is that day, now includes the whole last supported day on every backend. On SQLite it used to answer no rows.
      • nextUtcCalendarDay answers UNBOUNDED_ABOVE for that one day. Readers narrow with isUnboundedAbove, and the type is one type across the package's two declaration files.
    • Left, each with its carrier:

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:specpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions