feat(spec,drivers): the temporal matrix gains its Field.time axis, and time gets a storage form off SQL (ADR-0053 D-A3.2) - #4147
Merged
Conversation
…d time gets a storage form off SQL (ADR-0053 D-A3.2) The wall-clock half of the shared conformance table, plus the gap it measured on its first run. `TEMPORAL_TIME_ROWS` / `TEMPORAL_TIME_CASES` get their OWN table rather than a third `kind` on the existing one: a time shares no comparand vocabulary with the other two — no relative token resolves to a wall clock — and the bare-day whole-day rule (#3777) must not reach it, which the table now ASSERTS rather than assumes. "The rule leaked into the wrong field type" is precisely the class of defect a conformance matrix exists to catch. The fixture is a business day carrying the boundaries #3994 measured: both window edges, the pair straddling the millisecond-suffix width change, midnight, 23:59:59.999. What it found: ADR-0053 D-C gave Field.time a canonical form on every SQL dialect, but driver-memory and driver-mongodb were never extended. Both declared TemporalFieldKind = 'datetime' | 'date', so indexTemporalFields never classified a time column and coerceTemporalValue never touched one — the column kept whatever each writer produced, and mingo and BSON both compare across types by bracket, so a text bound matched no Date-written row in either direction, for every operator. Measured on driver-memory: 8 of the 9 shared cases returned only the text-written half, a business-hours window answering [d_mid, f_close] instead of [c_open, d_mid, e_mid_ms, f_close]. That is #4047's failure one field type over, and it survived #4047 because that work extended datetime and date without revisiting time. On mongo it was also a documentation failure: that module's canon table has listed time as HH:MM:SS[.fff] text since #3994, and nothing implemented it — declared but never enforced. Both drivers now carry storageTimeValue, mirroring canonicalTimeOfDay: HH:MM:SS with .fff only when non-zero, a Date/epoch/full-timestamp folding to its UTC time-of-day (never the host's), and totality — '25:00' passes through rather than being rewritten. Text on both, mongo included: a wall clock is not an instant, so a BSON Date would invent a calendar day and a zone the author never wrote. The variable width is what makes text storage correct rather than merely convenient — `.` sorts below every digit, so lexicographic order stays chronological across both widths. Six consumers sweep the new table. The mongo end-to-end sweep needs a real server and skips where no binary is available, so the conversion itself is pinned separately by a pure mongodb-time-storage.test.ts that runs everywhere. spec 7087 / driver-sql 574 / driver-memory 230 / sqlite-wasm 166 / service-analytics 422 / formula 298, all green; 8 of the memory time cases fail without this change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqqZmPS5a4gJGBoCTwipFr
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…kind, not a copy of its members `translateFieldOperators` spelled its `kind` parameter out as a literal `'datetime' | 'date'` instead of importing `TemporalFieldKind`. Widening the canon to include `time` therefore left the two definitions out of step and the call site stopped compiling — caught by CI's workspace build, which is the gate the package-level test run does not exercise. Fixed by pointing the signature at the one definition, so the next temporal type is added once rather than everywhere it was transcribed. No behaviour change: the parameter already received exactly what the resolver returns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqqZmPS5a4gJGBoCTwipFr
The Field.time suite I appended to this file never ran: the datetime suite's `afterAll` stopped the shared `MongoMemoryServer`, so the time suite's `beforeAll` called `getUri()` on a stopped instance and failed the whole file with `Incorrect State … gotState: 'new'` — its 9 cases reported as skipped, which reads like the "no binary available" skip and hides that nothing was verified. A server shared by two suites cannot be torn down by whichever finishes first, so the stop moves to a file-level `afterAll`; each suite still disconnects its own client. Also pins the FILTER half of the time canon (`translateFilter` with a time kind resolver) alongside the existing write-half tests. Both are pure, so the comparand path is verified in environments where the end-to-end sweep cannot run — this sandbox's proxy returns 403 for the MongoDB binary download, so the server-backed cases only execute in CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqqZmPS5a4gJGBoCTwipFr
os-zhuang
marked this pull request as ready for review
July 30, 2026 12:42
Both sides appended to the end of matches-filter-temporal-conformance.ts:
main added the note explaining why the token axis cannot apply to an RLS
`check` (it is a CEL expression, so a `{token}` string never reaches this
evaluator), and this branch added the Field.time sweep. They are
complementary, so both are kept — with one sentence added to the note
recording that the same reasoning covers the wall-clock cases, which carry no
token spelling at all because no date macro resolves to a time of day.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqqZmPS5a4gJGBoCTwipFr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
把 D-A3 矩阵扩到第三个时间字段类型
Field.time,以及这条轴首跑当场量出来的缺口。#4109 的后续。一、为什么 time 要自己一张表
TEMPORAL_TIME_ROWS/TEMPORAL_TIME_CASES与现有表并列,而不是在原表上加第三个kind——因为墙上时钟与另外两者没有共享的比较值词汇:没有任何相对 token 会解析成一个时刻({today}是日历日),而且裸日整日规则(#3777)绝不能波及它。最后这点现在是断言而非假设:
nextUtcCalendarDay只加宽YYYY-MM-DD字符串,而「规则渗漏到错误的字段类型」恰恰是一致性矩阵存在的意义。所以表里专门有一格钉死「time 的闭区间上界是精确的、不被加宽」。夹具是一个营业日,边界取的正是 #3994 量过的那些:两个窗口端点、跨毫秒后缀宽度变化的一对(
14:30:00与14:30:00.500)、午夜、23:59:59.999。取舍与父表同一把尺子:只分钟的比较值(
'14:30')会被有类型的后端规范化、被无类型后端按原文比较,因此那一格是 schema-aware-only,留在各驱动自己的套件里——与上面$gt-on-datetime那个极限完全同构。二、这条轴找到了什么:D-C 从未抵达非 SQL 驱动
ADR-0053 D-C 给了
Field.time在每个 SQL 方言上的规范形态,但driver-memory与driver-mongodb从未被扩展:两者的TemporalFieldKind都只有'datetime' | 'date',于是indexTemporalFields从不识别 time 列、coerceTemporalValue从不碰它。列里就留着各个写入方各自的形态——而 mingo 与 BSON 都按类型桶比较,文本边界匹配不到任何Date写入的行,双向皆然,且对每个算子成立。在 driver-memory 上实测:9 个共享用例红了 8 个。 营业时段窗口返回
[d_mid, f_close]而不是[c_open, d_mid, e_mid_ms, f_close]。这就是 #4047 的故障换了个字段类型——它能在 #4047 之后存活,正是因为那次工作扩展了datetime和date却没有回头看time。mongo 侧还是一次文档失败:
mongodb-temporal.ts自己的 canon 表从 #3994 起就写着time存为HH:MM:SS[.fff]文本——文档是对的,代码从未实现。典型的「声明了却没强制」(Prime Directive #10),在有东西真正执行它之前完全隐形。三、修法
两个驱动各加
storageTimeValue,对齐 SQL 侧的canonicalTimeOfDay:HH:MM:SS,毫秒非零时才带.fff;Date/ epoch / 完整时间戳折叠为其 UTC 时刻(绝不用宿主时区);保持全域性——'25:00'这类越界墙钟原样透传而非被悄悄改写。两边都用文本,mongo 也是:墙上时钟不是瞬间,存成 BSON
Date会凭空发明一个日历日和一个时区——与date在那里保持文本是同一条理由。可变宽度是文本存储正确(而不只是方便)的关键:
.排在所有数字之前,所以字典序——正是 mingo 执行的比较、也正是 mongo 文本范围使用的顺序——在两种宽度间仍然等于时间顺序。四、验证
mongodb-time-storage.test.ts钉死(11 例,含 BSONDate分支),它在任何环境都会运行。api-surface.json已随新导出重新生成;改动文件 ESLint 干净。ADR 新增 D-A3.2 附录记录本轴与它量出的缺口。Refs #4081。
🤖 Generated with Claude Code
https://claude.ai/code/session_01TqqZmPS5a4gJGBoCTwipFr
Generated by Claude Code