Repository navigation
driver-sql + driver-memory: an epoch-millisecond NUMBER against a date field is read by neither driver's storage rule — where: { placed_on: { $gt: 1769940000000 } } returns 6 of 6 rows on SqlDriver and 0 on InMemoryDriver over REST #20203
Description
Filing gate: ① a defect with a named landing site, where a numeric temporal comparand meets a date column:
packages/core/src/utils/temporal-storage-form.ts(PR fix(objectql,core): a per-aggregation filter and having read a temporal comparand by the column's storage rule — one rule in core, shared with both drivers' where #20202, pending): thedaterule returns a number unchanged;packages/drivers/driver-sql/src/sql-driver.ts:coerceFilterValue/temporalFilterValueon thedatekind;packages/drivers/driver-memory/src/memory-temporal.ts:coerceTemporalValueon thedatekind.
Finding class (a). reach: was measured at the public REST door (below).
The domain:engine execution seat 1 (session_01Bvd69VPa6puiNzzPUroDBx) filed this from its #20176 dev's report (os-dev-report 5854303341 on #20176, PR #20202), out-of-scope finding 2. ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.
What happens
Measured by the #20176 dev on a real InMemoryDriver and a real SqlDriver (better-sqlite3 :memory:), schema-synced, over a 6-row object whose placed_on is a date field. The readings are identical at base cfe2387a3b and at PR #20202's head:
| Door | where: { placed_on: { $gt: 1769940000000 } } (a JS number, epoch ms) |
|---|---|
REST POST /api/v1/data/:object/query, SqlDriver |
6 rows: SQLite orders every INTEGER below every TEXT, so every stored date string is "greater" |
| REST, InMemoryDriver | 0 rows: mingo never matches a number against text |
engine.find, both drivers |
the same split |
- The two drivers disagree, and neither reads the number as a date. SqlDriver returns every row for a
$gtbound, which is a silent wrong answer at a public door. - The same comparand on a
datetimefield IS read by the storage rule: epoch ms converts to the instant, and both drivers count 3. - The temporal-comparand door (
assertTemporalComparandsInterpretable) judges only strings, by design, so a number reaches the drivers unjudged.
Suggested shape (⛔ not a ruling)
One of the two, decided once for every temporal face:
- read an epoch-ms number on a
datefield as its UTC calendar day, as thedatetimerule already reads it as an instant; or - refuse it (
INVALID_FILTER/ 400) at the temporal-comparand door, as an unparseable date string is refused (An unparseable date comparand on a datetime filter is passed through and compares false — HTTP 200, zero rows, no diagnostic — while an unknown{placeholder}is correctly rejected 400 (17.0.0 GA) #8690).
The rule lives in ONE place: temporalStorageForm in @objectstack/core once PR #20202 lands. Pin it on both drivers, through the engine and REST, with $gt / $lt / $eq / $in / $between, and with a datetime control.
Filing-gate answers
- Class: ① (defect, named landing site); finding class (a), measured by the objectql + REST: a per-aggregation
filter(andhaving) compares a temporal comparand type-blind, not by the column's storage rule — an ISO instant on adatefield counts 1 where thewheretwin counts 3 #20176 dev. - Acting reader: this lane's seat (
domain:engine, the owner ofpackages/coreand the SQL / memory drivers). It is sequenced after PR fix(objectql,core): a per-aggregation filter and having read a temporal comparand by the column's storage rule — one rule in core, shared with both drivers' where #20202 (objectql + REST: a per-aggregationfilter(andhaving) compares a temporal comparand type-blind, not by the column's storage rule — an ISO instant on adatefield counts 1 where thewheretwin counts 3 #20176), which lifts the rule this card changes. That is a region order, not aBlocked-by:. - Dedupe, MCP semantic issue search in this repo,
closedincluded:epoch milliseconds number comparand date field where drivers disagree sqlite integer text ordering→ 19 hits, top 10 read:- SQLite datetime ORDER BY is wrong on mixed-storage columns: INTEGER-epoch rows always sort before ISO-TEXT rows #3928 / [17.0.0-rc.0] SQLite datetime window filters return empty: filter comparands coerced to epoch-ms while writes store ISO TEXT #3912: SQLite datetime storage and ordering on mixed columns, fixed; not a number comparand on a
datefield; - aggregate()/distinct() 不过 formatOutput —— SQLite 上
Field.datetime的裸 epoch 从聚合出口漏出去(#3773 同根不同出口) #3797, service-analytics:AVG()over aField.datetimemeasure returns SQLite's text→numeric coercion (an average YEAR) with no error, andderived: { op: 'difference' }renders the difference of two of them as a clean plausible number #16737,os generate migration's audit-stamp columns diverge from driver-sql — the generators emitNOT NULLwhere the driver emits nullable, and the SQL format emitsTIMESTAMPwhere both knex paths yieldtimestamptz#15521, driver-sql: on SQLite the builtin audit columns DEFAULT to zone-naiveCURRENT_TIMESTAMPwhile a declaredField.datetimeNOW() default in the SAME table gets the canonical ISO-8601 form #11321, driver-sql: every Field.date read from PostgreSQL is one day early when the process TZ is east of UTC — toDateOnly() reads UTC components off a local-midnight Date #11389, driver-sql: on MySQL the UPDATE door'supdated_atstamp truncates to whole seconds against aDATETIME(3)column — a row updated in its first second readsupdated_atEARLIER thancreated_at#11224: other temporal columns and doors; - driver-sqlite-wasm: a text value does not round-trip the way driver-sql's does — an embedded NUL truncates the stored value, and a leading BOM is stripped on read #19978: text round-trip.
- SQLite datetime ORDER BY is wrong on mixed-storage columns: INTEGER-epoch rows always sort before ISO-TEXT rows #3928 / [17.0.0-rc.0] SQLite datetime window filters return empty: filter comparands coerced to epoch-ms while writes store ISO TEXT #3912: SQLite datetime storage and ordering on mixed columns, fixed; not a number comparand on a
numeric comparand on date column temporal comparand door does not judge numbers→ 4 hits, all read: objectql + REST: a per-aggregationfilter(andhaving) compares a temporal comparand type-blind, not by the column's storage rule — an ISO instant on adatefield counts 1 where thewheretwin counts 3 #20176 (the parent), objectqlhaving: a{ $field }reference withaddDaysagainst a non-temporal aggregated column answers by epoch-ms coercion, where SQL push-down refuses the same pair onwhere— the aggregated row declares no temporal class to judge it by #20127, service-analytics:AVG()over aField.datetimemeasure returns SQLite's text→numeric coercion (an average YEAR) with no error, andderived: { op: 'difference' }renders the difference of two of them as a clean plausible number #16737 and An unparseable date comparand on a datetime filter is passed through and compares false — HTTP 200, zero rows, no diagnostic — while an unknown{placeholder}is correctly rejected 400 (17.0.0 GA) #8690 (an unparseable date STRING).- None covers a numeric comparand on a
datefield.
Dedupe words: epoch ms number date field where drivers disagree · date field numeric comparand sqlite integer text ordering · temporal comparand number date column
Activity
objectstack-fleet commentedon Sep 27, 2026
Pointer: a third public-door answer, on live PostgreSQL.
domain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-27T09:14Z. ⛔ Not a claim.PR #20202's at-tier contract review (record 5854540769) measured this card's row on a private live PostgreSQL 16.13 as well. Over REST,
where: { placed_on: { $gt: 1769940000000 } }on adatefield answersDATABASE_ERROR/ 500 ("Internal server error"). So one comparand now has three answers across three drivers: InMemoryDriver 0 rows, SQLite 6 rows, PostgreSQL 500. The readings are identical at PR #20202's base and head.objectstack-fleet commentedon Sep 27, 2026
Path: an API a customer can call | api-backend.query-contract-matrix | P2
Triage: first grade —
bug·priority:p2·domain:engine·area:api·pm:queueTriage: lands in
packages/core/src/utils/temporal-storage-form.ts, the one storage-form rule that PR #20202 lifted into core (merged 2026-09-27T09:34Z), which both drivers now read (driver-sqltemporalFilterValue,driver-memorycoerceTemporalValue) ⇒domain:engine. Rationale: an epoch-ms number against adatefield is read by no storage rule. One REST request gets three answers: InMemoryDriver 0 rows, SQLite 6 of 6, PostgreSQLDATABASE_ERROR/ 500 (pointer5854552106). The same number on adatetimefield is read as an instant on both drivers (3 = 3). It is a silent wrong answer, plus a 500, at a public door. It is the same family as #20176 (p2, just closed) ⇒ p2.reach:is measured over REST on three drivers.Triage seat (objectstack-wide, seat post #6015) ·
session_01W89enF2dYV7K4N2Fbfj33f· 2026-09-27T10:11Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its pointer, #20176 and PR #20202 (both closed and merged), and #8690.The serial constraint is gone. PR #20202 merged, so the rule this card changes now lives in one place,
temporalStorageForm. It is not folded into #20176: that card is closed, and its rows were the per-aggregation positions, notwhere.Direction. Read or refuse must be decided once for every temporal face, as the card says. On the texts already in force the answer is read:
datetimeface already reads an epoch-ms number as its instant;{placeholder}is correctly rejected 400 (17.0.0 GA) #8690 refuses an unparseable date string, which is a different input.⇒ Read an epoch-ms number on a
datefield as its UTC calendar day, intemporalStorageForm, the same rule for every caller. Stop valve: if ADR-0053 (the date / datetime semantics record) states that adatecomparand must be a calendar-day string, refuse at the temporal-comparand door instead, cite the sentence, and report the choice here. ⛔ Not a third copy in a driver.Execution notes.
$gt/$lt/$eq/$in/$betweenon adatefield with an epoch-ms number, on both drivers, through the engine and REST, with thedatetimecontrol.## Acceptance notesthe PostgreSQL reading after the change (the 500 must be gone). Use the live cell PR fix(objectql,core): a per-aggregation filter and having read a temporal comparand by the column's storage rule — one rule in core, shared with both drivers' where #20202's review used, if it is reachable.objectstack-fleet commentedon Sep 27, 2026
Claim: PM loop round 22
Session:
session_01Bvd69VPa6puiNzzPUroDBxAccount:
os-sales(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-20203-epoch-ms-date-comparandWorktree:
objectstack-issue-20203Domain:
domain:engineSeat:
domain:engine#1File surface:
packages/core/src/utils/temporal-storage-form.ts: thedaterule. Following triage 5854940483, an epoch-ms NUMBER is read as its UTC calendar day, the same rule for every caller. The stop valve: if ADR-0053 states that adatecomparand must be a calendar-day string, refuse it at the temporal-comparand door instead, cite the sentence, and stop and report;packages/core, the two drivers and REST (test side only);.changeset/20203-*.md.Stop on breach and explain in the report. ⛔ No third copy in a driver:
driver-sqlanddriver-memoryreadtemporalStorageFormsince PR #20202. ⛔ Notpackages/spec. ⛔ Not thedatetime/timerules, which must stay byte-identical.Container & model:
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: no
Thread-read: 5854940483
Serial constraints cleared: at 2026-09-27T10:45Z, the region predecessor PR #20202 (#20176) has landed as
e5cf27d65fand is verified onmain(landing record 5854697554). A census of the 12 open PRs finds none ontemporal-storage-form.ts,sql-driver.tsormemory-temporal.ts. The seat runsbatch:3(the maintainer's ruling); this is the only card in flight.objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{
"issue": 20203,
"status": "done",
"branch": "claude/issue-20203-epoch-ms-date-comparand",
"pr": "#20224",
"session": "session_01Bvd69VPa6puiNzzPUroDBx — mode:subagent; the id the claim 5855157485 names, and the one this container derives (CLAUDE_CODE_REMOTE_SESSION_ID cse_01Bvd69VPa6puiNzzPUroDBx → session_…)",
"premise_still_valid": true,
"summary": "temporalStorageForm (@objectstack/core) now reads a finite number on a
datefield as the UTC calendar day of new Date(value): the same conversion a Date takes, so a number and its Date always agree. NaN, ±Infinity and numbers past the Date range come back unchanged. driver-sql (toDateOnly / temporalFilterValue), driver-memory (coerceTemporalValue) and the engine's per-aggregation filter / having already call this rule, so there is no driver source change. One REST request that answered memory 0, SQLite 6 of 6 and PostgreSQL DATABASE_ERROR/500 now answers the date's own count on all three (e.g. $gt 1769940000000 → 1 row), and where = per-aggregation filter = having on every cell. Every datetime, time and non-number date cell is byte-identical base → head. Stop valve: ADR-0053 states no requirement that a date comparand be a calendar-day string, so the read direction was taken. Draft PR #20224; the card had assignee os-sales on arrival, and PR #20224 now carries the same assignee.","tests": "Commits: the fix ba5c0de; tests 6cb41f4; changesets 4c8b92e; merge of main 16aedca; final 1c8c5ce (types the three new pins' query options, test files only). Pins: core temporal-storage-form.test.ts 72 passed (file unchanged since 4c8b92e); at 1c8c5ce driver-sql sql-driver-20203-epoch-ms-date-comparand.test.ts on SQLite + live PostgreSQL 16.13 (TZ=America/New_York, server Asia/Shanghai) 46 passed | 1 skipped (MySQL declared unprovisioned); driver-memory memory-20203-epoch-ms-date-comparand.test.ts 23 passed; rest data-query-epoch-ms-date-comparand.test.ts 18 passed. Whole suites at 16aedca, after merging main 4df101c and rebuilding the closure (vitest run, no --project, so both local and repo projects): core 57 files / 1488 passed; driver-memory 55 / 1351 passed; driver-sql with live PostgreSQL 197 passed, 3 skipped files / 3852 passed, 87 skipped (reporter: sqlite RAN, live postgres RAN, live mysql NOT RUN); objectql 319 / 5767 passed; rest (includes every query-route test) 201 / 3582 passed, 1 skipped. Typecheck: core at 16aedca, driver-memory / driver-sql / rest at 1c8c5ce, exit 0 each; every new/changed test file is in a compiled program by --listFiles (tsconfig.json for the drivers, tsconfig.test.json for core and rest); test-typecheck ledgers unchanged (core 4 held, rest 0). Ablation at 4c8b92e (fix committed first; ablation-replace wrap mode with EXIT/INT/TERM restore on the absolute path): anchor "const instant = typeof value === 'number' && Number.isFinite(value) ? new Date(value) : value;" x1 → x0, blob 61a6c0861a → 2a9bd5d997; core rebuilt; ablation-dist-preflight --absent exit 0 (marker gone from dist). Mutated: core 13 failed / 59 passed; memory 12 failed / 11 passed (the empty-object rows); driver-sql 35 failed / 11 passed / 1 skipped (SQLite empties green, PG empties red: the 500 came back); rest 17 failed / 1 passed (the min(datetime) control). Restored by the tool: blob == HEAD 61a6c0861a, git diff HEAD empty, whole-tree git status --porcelain empty; core rebuilt; preflight marker present exit 0; pins 72 / 23 / 46+1 skipped / 18 passed. Reverse-verification direction observed: turned red (the ordinary direction).",
"mcp_calls": "0 — no MCP GitHub tool was called (reads were REST GETs; writes went through scripts/pm and the fleet-write relay)",
"api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot], each carried by one POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft) = #20224, relay run 36317832633; (2) assign via scripts/pm/label-write.mjs → POST /repos//issues/20224/assignees [os-sales], relay run 36317881511, read back MATCHES; (3) this os-dev-report → POST /repos//issues/20203/comments via scripts/pm/post-stamped.mjs. Plus git push of the branch (not a REST write). No label was written by this run (the PR's documentation / size/l / tests / tooling labels were applied by the repo's labeler).",
"open_questions": [
{
"question": "The date rule's Date conversion spells a year outside 1000..9999 unpadded (999-06-15, 10000-01-01, -1-01-01), so its text stops ordering as a YYYY-MM-DD day. This PR routes a number through that same conversion (H3: a number and its Date agree), so a REST number now reaches the spelling too. os-dev rule 3 says a published defect this round touches must be fixed; the dispatch ruling says every non-number date comparand stays byte-identical, and fixing the spelling moves Date cells. Which governs?",
"options": [
"A — leave this PR as is (number ≡ Date on the whole Date range); file the spelling as its own class (a) card, whose ruling may move Date cells deliberately (4-digit pad for years 0..999; a decided reading for years past 9999 and below 0, which have no YYYY-MM-DD spelling at all).",
"B — pad the year to 4 digits for numbers only in this PR: the number path becomes right for years 0..999, but a number and its Date disagree there (breaks H3), and years past 9999 / below 0 stay unresolved.",
"C — fix the shared conversion here (pad 0..999) and move the Date cells, overriding the dispatch ruling; still leaves years past 9999 / below 0 without a pinned form."
],
"recommendation": "A. Real business need: epoch-ms numbers before year 1000 or after 9999 on a date field have no measured writer; the measured pull is the card's 2026 numbers, which this PR serves. Long-term soundness: one conversion for Date and number (no third dialect); the unpadded spelling is a Date-arm defect, and its right fix (including the out-of-range years with no YYYY-MM-DD form) is a decision, not a mechanical edit, so the bounded-fix exemption's "shape already pinned" condition fails. Keeping AI from writing wrong code: B would teach two spellings of one day depending on input type, the exact dialect the single rule removed. Startup focus: no new surface; the edge gets its own card with evidence."
}
],
"out_of_scope_findings": [
"class: a · driver-mongodb src/mongodb-temporal.ts storageDateValue (its own copy of the date rule, outside this claim) returns a finite number unchanged: probe storageDateValue(1769940000000) → 1769940000000 while core temporalStorageForm(1769940000000, "date") → "2026-02-01" (a Date and an ISO string agree on both). On MongoDB a number against the YYYY-MM-DD text meets BSON type bracketing, the mechanism behind driver-memory's 0 at base; the live Mongo answer is NOT MEASURED (no server here). Fix shape: re-point driver-mongodb's date/time arms at core's rule, as PR #20202's acceptance note already suggested. · dedupe words: driver-mongodb epoch ms number date field · mongodb storageDateValue number unchanged · mongodb-temporal date rule copy",
"class: a · the date rule's Date conversion (
${y}-${m}-${d}from getUTCFullYear, in core temporal-storage-form.ts, formerly each driver's copy) spells a year outside 1000..9999 unpadded, so the text no longer orders or parses as a YYYY-MM-DD day. Measured at head, same six-row fixture, REST number and in-process Date agree: year 999 → memory/SQLite $gt 0 / $lt 6 (PG 6 / 0, correct); year 10000 → memory/SQLite 6 / 0 (PG 0 / 6, correct); year -1 → PG DATABASE_ERROR / 500. Reachable in-process by a Date before this PR, and by a REST number after it. See open_questions[0]. · dedupe words: date rule year below 1000 unpadded · calendar day year outside 4 digits text order · toDateOnly getUTCFullYear padding","carrier: 承接者:无 · SqlDriver.toDateOnly's docstring still says "Anything else (and null/undefined) passes through unchanged", and a number no longer does. driver-sql source is outside the claim (test side only). Noted in PR #20224 Acceptance notes, not filed."
],
"gates": {
"derived_at": "1c8c5ce756",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths); stderr line 1 names objectstack-ai/objectstack at 1c8c5ce; 62 commands (the dispatch clue's 47 at 3875ae6, plus 15 the real diff adds: check-adr-0087-registration x2, check-empty-changeset x2, release-rehearsal-clone --self-test, check:dispatcher-error-vocabulary, check:driver-conformance, check:engine-double-contract, check:objectql-double-limit, check:objectui-changeset, check:pm-changeset-deadline-census, check:query-options-erasure, check:type-check-coverage, check:type-check-debt, check:where-matcher)",
"run_at": "1c8c5ce756",
"exit_0": 59,
"exit_1": [
"node scripts/check-empty-changeset.mjs --base origin/main — the deliberate one-clause correction to .changeset/20176-aggregation-temporal-storage-rule.md; red on purpose, needs a person's confirmation on the PR, the file must not be restored from base"
],
"not_measured": [
"pnpm check:dual-build-cjs-loads — exit 3 PREREQUISITE NOT MET: reads every package's built dist, which needs the whole ./packages/* build; CI builds it first",
"pnpm check:type-check-debt — exit 3 PREREQUISITE NOT MET: --re-measure needs the whole ./packages/* build closure; CI builds it first"
],
"ran_reconcile": "dispatch-gates --ran ran.list: 62 derived, 60 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3), 0 UNRUN",
"fixed_during_run": "check:query-options-erasure was exit 1 at 16aedca (test surface 236 → 241: five
as anycasts on query options in the new pins); typed as FilterCondition / EngineAggregateOptions in 1c8c5ce, then exit 0 (236, at ceiling)","also_run": [
"pnpm check:authz-resolver exit 0",
"pnpm check:error-code-casing exit 0",
"pnpm check:filter-alias-parity exit 0 (the three roster gates whose roster sits under packages/)",
"node scripts/check-issue-citations.mjs --base 4df101c exit 0 (every added citation resolves)",
"control-byte scan grep -naP over the 7 changed files: none"
],
"lint": "declared narrowing, three pieces: (1) population read from eslint's own config: --print-config resolves a config for each of the 5 changed .ts files; (2) counts from --format json at 1c8c5ce: 5 files, 0 errors, 0 warnings, 0 fatal; (3) invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so no untouched file's verdict can move. Repo-wide pnpm lint is CI's.",
"type_check_lanes": "per-package typecheck run for core, driver-memory, driver-sql, rest (exit 0 each); the workspace-wide lint.yml Type Check lanes are CI's"
},
"line_budget": "n/a — no skills/** in the diff",
"deviations": [
".changeset/20176-aggregation-temporal-storage-rule.md: one-clause DELIBERATE CORRECTION. "…, so no
where, write or read answer of either driver changes." → "…, so the lift itself changes nowhere, write or read answer of either driver (#20203, in the same release, then reads an epoch-millisecond number on adatefield as its UTC calendar day)." Nothing else in the note moved; check-empty-changeset red for it by design. The 20148-* notes read true and were not touched.","Pin placement: packages/rest has no driver-memory dependency and driver-memory cannot reach the engine, so memory's engine and REST cells are measured by a scratch harness but not committed; memory's where is pinned at its own driver door. PostgreSQL is pinned at the driver door in driver-sql's live matrix (CI Temporal Conformance runs it on PG and MySQL); its engine and REST cells are measured by the harness. Same precedent as PR #20202.",
"Merged origin/main (4df101c, one commit touching objectql and spec) into the branch before the whole suites (AGENTS Multi-agent §10); clean merge, closure rebuilt.",
"The live PostgreSQL used is a private PostgreSQL 16.13 this run started (server zone Asia/Shanghai), not the cell PR #20202's review used, which was not reachable; stopped and removed at the end."
],
"files_changed": [
"packages/core/src/utils/temporal-storage-form.ts",
"packages/core/src/utils/temporal-storage-form.test.ts",
"packages/drivers/driver-sql/src/sql-driver-20203-epoch-ms-date-comparand.test.ts",
"packages/drivers/driver-memory/src/memory-20203-epoch-ms-date-comparand.test.ts",
"packages/rest/src/data-query-epoch-ms-date-comparand.test.ts",
".changeset/20203-epoch-ms-date-comparand.md",
".changeset/20176-aggregation-temporal-storage-rule.md"
],
"rows_legend": "Fixture: six rows, placed_on (date) 2026-01-10, 01-02, 03-01, 02-01, 01-15, 02-01; opened_at (datetime) 01-01T10Z, 01-02T10Z, 02-01T10Z, 02-05T10Z, 02-06T10Z, 03-01T10Z. N = 1769940000000 = 2026-02-01T10:00:00.000Z; $in = [N, 1768057200000 (01-10T15Z)]; $between = [1767394800000 (01-02T23Z), N]; "$eq midnight" = 1769904000000. Counts are rows (per_aggregation_filter: the filtered count m). base = 3875ae6; head = ba5c0de (the fix; the pins re-confirm at 1c8c5ce). engine = engine.find / engine.aggregate; rest = POST /api/v1/data/:object/query with a JSON round-tripped body. date_twin_head = the same comparand as new Date(N), engine door (a Date is not JSON); the driver-door pins assert it on all 11 operators. pg = live PostgreSQL 16.13; DATABASE_ERROR is SQLSTATE 22008 DateTimeParseError from the server's date input parser.",
"rows": {
"$gt": {
"memory": {
"engine": {
"base": 0,
"head": 1
},
"rest": {
"base": 0,
"head": 1
},
"per_aggregation_filter": {
"base": 0,
"head": 1,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 3,
"head": 3
},
"date_twin_head": 1
},
"sqlite": {
"engine": {
"base": 6,
"head": 1
},
"rest": {
"base": 6,
"head": 1
},
"per_aggregation_filter": {
"base": 0,
"head": 1,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 3,
"head": 3
},
"date_twin_head": 1
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 1
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 1
},
"per_aggregation_filter": {
"base": 0,
"head": 1,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 3,
"head": 3
},
"date_twin_head": 1
}
},
"$lt": {
"memory": {
"engine": {
"base": 0,
"head": 3
},
"rest": {
"base": 0,
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 2,
"head": 2
},
"date_twin_head": 3
},
"sqlite": {
"engine": {
"base": 0,
"head": 3
},
"rest": {
"base": 0,
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 2,
"head": 2
},
"date_twin_head": 3
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 3
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 2,
"head": 2
},
"date_twin_head": 3
}
},
"$eq": {
"memory": {
"engine": {
"base": 0,
"head": 2
},
"rest": {
"base": 0,
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 2
},
"sqlite": {
"engine": {
"base": 0,
"head": 2
},
"rest": {
"base": 0,
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 2
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 2
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 2
}
},
"$in": {
"memory": {
"engine": {
"base": 0,
"head": 3
},
"rest": {
"base": 0,
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 3
},
"sqlite": {
"engine": {
"base": 0,
"head": 3
},
"rest": {
"base": 0,
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 3
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 3
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 3
}
},
"$between": {
"memory": {
"engine": {
"base": 0,
"head": 5
},
"rest": {
"base": 0,
"head": 5
},
"per_aggregation_filter": {
"base": 6,
"head": 5,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 5
},
"sqlite": {
"engine": {
"base": 0,
"head": 5
},
"rest": {
"base": 0,
"head": 5
},
"per_aggregation_filter": {
"base": 6,
"head": 5,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 5
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 5
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 5
},
"per_aggregation_filter": {
"base": 6,
"head": 5,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
},
"date_twin_head": 5
}
},
"$gte": {
"memory": {
"engine": {
"base": 0,
"head": 3
},
"rest": {
"base": 0,
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 4,
"head": 4
}
},
"sqlite": {
"engine": {
"base": 6,
"head": 3
},
"rest": {
"base": 6,
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 4,
"head": 4
}
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 3
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 3
},
"per_aggregation_filter": {
"base": 0,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 4,
"head": 4
}
}
},
"$lte": {
"memory": {
"engine": {
"base": 0,
"head": 5
},
"rest": {
"base": 0,
"head": 5
},
"per_aggregation_filter": {
"base": 0,
"head": 5,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 3,
"head": 3
}
},
"sqlite": {
"engine": {
"base": 0,
"head": 5
},
"rest": {
"base": 0,
"head": 5
},
"per_aggregation_filter": {
"base": 0,
"head": 5,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 3,
"head": 3
}
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 5
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 5
},
"per_aggregation_filter": {
"base": 0,
"head": 5,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 3,
"head": 3
}
}
},
"$ne": {
"memory": {
"engine": {
"base": 6,
"head": 4
},
"rest": {
"base": 6,
"head": 4
},
"per_aggregation_filter": {
"base": 6,
"head": 4,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 5,
"head": 5
}
},
"sqlite": {
"engine": {
"base": 6,
"head": 4
},
"rest": {
"base": 6,
"head": 4
},
"per_aggregation_filter": {
"base": 6,
"head": 4,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 5,
"head": 5
}
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 4
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 4
},
"per_aggregation_filter": {
"base": 6,
"head": 4,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 5,
"head": 5
}
}
},
"$nin": {
"memory": {
"engine": {
"base": 6,
"head": 3
},
"rest": {
"base": 6,
"head": 3
},
"per_aggregation_filter": {
"base": 6,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 5,
"head": 5
}
},
"sqlite": {
"engine": {
"base": 6,
"head": 3
},
"rest": {
"base": 6,
"head": 3
},
"per_aggregation_filter": {
"base": 6,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 5,
"head": 5
}
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 3
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 3
},
"per_aggregation_filter": {
"base": 6,
"head": 3,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 5,
"head": 5
}
}
},
"implicit": {
"memory": {
"engine": {
"base": 0,
"head": 2
},
"rest": {
"base": 0,
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
}
},
"sqlite": {
"engine": {
"base": 0,
"head": 2
},
"rest": {
"base": 0,
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
}
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 2
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 1,
"head": 1
}
}
},
"$eq midnight": {
"memory": {
"engine": {
"base": 0,
"head": 2
},
"rest": {
"base": 0,
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 0,
"head": 0
}
},
"sqlite": {
"engine": {
"base": 0,
"head": 2
},
"rest": {
"base": 0,
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": 0,
"head": 0
},
"datetime_control": {
"base": 0,
"head": 0
}
},
"pg": {
"engine": {
"base": "DATABASE_ERROR (engine)",
"head": 2
},
"rest": {
"base": "500 DATABASE_ERROR",
"head": 2
},
"per_aggregation_filter": {
"base": 0,
"head": 2,
"rest_equals_engine": true
},
"empty_table": {
"base": "DATABASE_ERROR (engine)",
"head": 0
},
"datetime_control": {
"base": 0,
"head": 0
}
}
}
},
"having": {
"legend": "groupBy customer_id, max(placed_on) as last_placed, min(opened_at) as first_opened; c1 last 01-10, c2 last 03-01, c3 last 02-01. native = driver aggregates; rows = engine aggregates in memory. Empty object: no group at base and head, every cell.",
"memory/max(date) $gt N/native": {
"base": "(no group)",
"head": "c2",
"rest_equals_engine": true
},
"memory/max(date) $gt N/rows": {
"base": "(no group)",
"head": "c2",
"rest_equals_engine": true
},
"memory/max(date) $eq N/native": {
"base": "(no group)",
"head": "c3",
"rest_equals_engine": true
},
"memory/max(date) $eq N/rows": {
"base": "(no group)",
"head": "c3",
"rest_equals_engine": true
},
"memory/max(date) $in/native": {
"base": "(no group)",
"head": "c1,c3",
"rest_equals_engine": true
},
"memory/max(date) $in/rows": {
"base": "(no group)",
"head": "c1,c3",
"rest_equals_engine": true
},
"memory/min(datetime) $gte N/native": {
"base": "c2,c3",
"head": "c2,c3",
"rest_equals_engine": true
},
"memory/min(datetime) $gte N/rows": {
"base": "c2,c3",
"head": "c2,c3",
"rest_equals_engine": true
},
"sqlite/max(date) $gt N/native": {
"base": "(no group)",
"head": "c2",
"rest_equals_engine": true
},
"sqlite/max(date) $gt N/rows": {
"base": "(no group)",
"head": "c2",
"rest_equals_engine": true
},
"sqlite/max(date) $eq N/native": {
"base": "(no group)",
"head": "c3",
"rest_equals_engine": true
},
"sqlite/max(date) $eq N/rows": {
"base": "(no group)",
"head": "c3",
"rest_equals_engine": true
},
"sqlite/max(date) $in/native": {
"base": "(no group)",
"head": "c1,c3",
"rest_equals_engine": true
},
"sqlite/max(date) $in/rows": {
"base": "(no group)",
"head": "c1,c3",
"rest_equals_engine": true
},
"sqlite/min(datetime) $gte N/native": {
"base": "c2,c3",
"head": "c2,c3",
"rest_equals_engine": true
},
"sqlite/min(datetime) $gte N/rows": {
"base": "c2,c3",
"head": "c2,c3",
"rest_equals_engine": true
},
"pg/max(date) $gt N/native": {
"base": "(no group)",
"head": "c2",
"rest_equals_engine": true
},
"pg/max(date) $gt N/rows": {
"base": "(no group)",
"head": "c2",
"rest_equals_engine": true
},
"pg/max(date) $eq N/native": {
"base": "(no group)",
"head": "c3",
"rest_equals_engine": true
},
"pg/max(date) $eq N/rows": {
"base": "(no group)",
"head": "c3",
"rest_equals_engine": true
},
"pg/max(date) $in/native": {
"base": "(no group)",
"head": "c1,c3",
"rest_equals_engine": true
},
"pg/max(date) $in/rows": {
"base": "(no group)",
"head": "c1,c3",
"rest_equals_engine": true
},
"pg/min(datetime) $gte N/native": {
"base": "c2,c3",
"head": "c2,c3",
"rest_equals_engine": true
},
"pg/min(datetime) $gte N/rows": {
"base": "c2,c3",
"head": "c2,c3",
"rest_equals_engine": true
}
},
"stop_valve": {
"reading": "none found — no sentence in ADR-0053 or packages/spec states that a date comparand must be a calendar-day string; read direction taken",
"quotes": [
"ADR-0053 TL;DR: "
date= a timezone-naive calendar day, represented as aYYYY-MM-DDstring end-to-end (storage, query, CEL). It is never converted to an instant and never timezone-shifted." — representation, not an input restriction","ADR-0053 Phase 1, decision 1: "A
Date, a full ISO string, or aYYYY-MM-DDstring all collapse to the calendar day (UTC calendar day for aDate, matching the existing filter coercion)." — non-day inputs are collapsed, not refused","ADR-0053 D-C1 (the sibling time rule): "A
Date/ epoch-ms / full-timestamp value folds to its UTC time-of-day"","packages/spec/src/data/filter.zod.ts ORDERING_COMPARAND_DESCRIPTION: "Comparand is a number, a Date, a string, or a { $field } reference"",
"packages/core/src/utils/temporal-comparand.ts: "A number is epoch milliseconds, a
Dateis an instant,nullis a null test. The refusal scopes to strings by ruling.""],
"adjacent_not_adr": "The engine/REST WRITE doors refuse a number on a date field (VALIDATION_FAILED, "placed_on must be a valid date (ISO-8601)"): the stored-value contract (CalendarDateValueSchema = YYYY-MM-DD), not a comparand sentence; unchanged base → head."
},
"collateral": {
"rule_sweep": "34 shapes × 3 kinds = 102 cells of temporalStorageForm, base vs head dist: 12 moved, all date × a finite number inside the Date range (epoch ms, a UTC midnight, -86400000, -1, a fraction, a negative fraction, 0, -0, ±8.64e15, year 10000, year 0); 90 byte-identical: every datetime and time cell, and on date every Date, Invalid Date, NaN, ±Infinity, 8.64e15+1, MAX_SAFE_INTEGER, bigint, boxed Number, epoch-ms string, bare day, zone-naive, ISO, offset, wall clock, junk, empty, null, undefined, boolean, object, array",
"end_to_end": "466 measured cells (where, per-aggregation filter, having, write, read, Date twin, rule sweep) across memory / SQLite / PostgreSQL: 113 moved, 0 outside "a number on a date field"",
"where_equals_per_aggregation_filter_head": "132 of 132 keys (3 drivers × populated/empty × date/datetime × 11 operators), engine and REST: where count = per-aggregation filter count on every key",
"per_aggregation_filter_base": "date field, all three drivers, both doors: 0 on every operator except $between, $ne, $nin (6); datetime control equal to where at base and head",
"having": "only max(date) moved (both doors, native and rows, all three drivers): $gt N no group → c2; $eq N no group → c3; $in no group → c1, c3; min(datetime) $gte N control c2, c3 at base and head",
"write_paths": "engine.insert and REST create of a number on a date field: VALIDATION_FAILED / 400 at base and head (unchanged). Direct driver create/update of a number on a date field: memory and SQLite stored 1769940000000, PostgreSQL refused 22008 → all three store "2026-02-01". The same number on datetime / time writes "2026-02-01T10:00:00.000Z" / "10:00:00" at base and head.",
"read_paths": "SQLite, a raw INTEGER already in a date column: find, findOne, a groupBy key and distinct presented 1769940000000 → "2026-02-01" (ADR-0053 Phase 1 read repair); aggregate max over a mixed group unchanged ("2026-01-05"); a where day comparand against that raw row unchanged ([]: the date column side has no legacy repair)",
"other_callers": "driver-turso inherits temporalFilterValue from SqlDriver and service-analytics binds through driver.temporalFilterValue, so both follow the rule; not separately measured",
"pending_changesets": "20176-*: one sentence read false in the same release → corrected (deviations[0]); 20148-aggregation-filter-keys-rest and 20148-aggregation-filter-where-doors read true and were not touched"
},
"cleanup": "Private PostgreSQL (initdb /var/lib/postgresql/issue-20203-pg, port 45203, recorded PID 4218) stopped with pg_ctl -m fast; its data dir removed; no process left for it. Worktree /home/user/objectstack-issue-20203: branch pushed (local = remote 1c8c5ce), tree clean, node_modules removed, git worktree remove without --force exit 0, directory gone, git worktree list has no issue-20203 entry. Scratch harnesses were never in the repo (scratchpad issue-20203/). This comment was posted from a scratchpad copy of scripts/ at 1c8c5ce. No background process or monitor was started."
}
objectstack-fleet commentedon Sep 27, 2026
Seat ruling on the
os-dev-report5855694754 (PR #20224)domain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-27T12:14Z.datefield as its UTC calendar day, so driver-memory, SQLite and PostgreSQL give one answer #20224 stays as it is: a number and itsDatetake one conversion across the wholeDaterange. The spelling of a year outside 1000..9999 is a defect of theDatearm that predates this card. Years past 9999 and below 0 have noYYYY-MM-DDspelling at all, so fixing it is a decision, not a bounded edit. The seat files it through the filing gate after landing, with its reach re-measured onmainat the REST door.Clause-②: no. No export or payload key moves, and no request that answered now gets refused. The at-tier review judges it.20176-*one-clause DELIBERATE CORRECTION: confirm as written. The same-head review record confirms it, andCheck Changesetis red on that one name by design.driver-mongodb'sstorageDateValuereturns a number unchanged). There is no measured public-door answer: nomongodis reachable here. It is recorded with carrier none, beside PR fix(objectql,core): a per-aggregation filter and having read a temporal comparand by the column's storage rule — one rule in core, shared with both drivers' where #20202's acceptance note on the same copy. Not filed.SqlDriver.toDateOnly's docstring still says a number passes through). It is an acceptance note, carrier none. The claim keeps driver-sql to the test side.objectstack-fleet commentedon Sep 27, 2026
ACCEPT: PR #20224 at
1c8c5ce756(#20203)domain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-27T12:38Z. Reviewed on GitHub againstreferences/review-checklist.md, not from the dev'sos-dev-report.Fixes #20203, and the body declaresClause-②: no. The changeset grades@objectstack/core,driver-sql,driver-memoryandobjectqlpatch.datearm oftemporalStorageFormin@objectstack/core: a finite number reads as the UTC calendar day ofnew Date(value). No driver source moves, and there is no third copy.20203-*changeset, and one DELIBERATE CORRECTION (20176-*).1c8c5ce756(5855881108).Date, and the per-aggregationfilter=where(88/88 keys per driver). Everydatetimecontrol is byte-identical.date". Of 445 / 452 end-to-end cells, 0 moved outside that class.datecomparand to be a day string.Temporal Conformance (live PG + MySQL)job at this head (success).success, 3skipped, 1 failure. All seven required contexts aresuccess.Check Changeset, red by design on exactly.changeset/20176-aggregation-temporal-storage-rule.md: the DELIBERATE CORRECTION class. The same-head PASS confirms it, and the gate runs onpull_requestonly.merge-treeonto currentmainis clean.Date, and theDatecells are byte-identical.Datearm's unpadded year spelling, which predates this card. The seat files it after landing, with its reach measured onmain.SqlDriver.toDateOnly's docstring ("Anything else … passes through unchanged") is now false for a finite number. Carrier none; it rides on the next driver-sql source PR.wherewith a NUMBER comparand against a raw INTEGER already stored in a SQLitedatecolumn matched at base and does not at head. Only a raw write reaches it; it is inside the class and not in the changeset.driver-mongodb'sstorageDateValuestill returns a number unchanged. Nomongodwas reachable to measure it. Carrier none, beside PR fix(objectql,core): a per-aggregation filter and having read a temporal comparand by the column's storage rule — one rule in core, shared with both drivers' where #20202's note on the same copy.Landing:
readyplus auto-merge through the queue now.objectstack-fleet commentedon Sep 27, 2026
Landed: PR #20224, verified on
maindomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-27T13:01Z.datefield as its UTC calendar day, so driver-memory, SQLite and PostgreSQL give one answer #20224 merged through the merge queue as615c468746. It has one parent (93cfc3f593) and is an ancestor oforigin/mainafter a fresh fetch.4df101c383..1c8c5ce756(PASS 5855881108): 7 files, +499/−9.20203-epoch-ms-date-comparand.mdis present at that commit and absent at its parent.temporal-storage-form.tslanded at the reviewed blob61a6c0861a.20176-*landed at the reviewed blob6b9d78d447, as ACCEPT 5855886361 ruled. The redCheck Changesetdoes not run onmerge_group.completedviaFixes #20203.pm:dispatchedis removed in the same act.datefield now reads as its UTC calendar day in the one storage rule. InMemoryDriver, SQLite and PostgreSQL give one answer through the engine and REST, and PostgreSQL'sDATABASE_ERROR/ 500 is gone.Clause-②: no;patchlevels.Datearm's unpadded year spelling (years outside 1000..9999) through the filing gate, with its reach measured onmain.fix(core): read an epoch-ms number on a `date` field as its UTC calen…
fix(core,objectql)!: a date field's number or Date comparand spells a…
fix(core,objectql)!: a date or datetime names a year from 0001 to 999…