Repository navigation
ADR-0053 D-A3:storage-form 轴只锁住了 driver-sql 的 datetime —— 另外五个后端和整条 Field.time 仍在裸奔 #4191
Description
D-A3 的矩阵已经立起来了(#4081 已关),但 ADR 为它点名的 storage-form 轴只落在一个格子里。
D-A3's conformance matrix should gain a storage-form axis (canonical, legacy-epoch, legacy-naive) and a server-timezone axis, since both are now known to have produced dialect-divergent row results.
——docs/adr/0053-date-and-datetime-semantics.md,D-B3 的 Consequences
现状(已逐个核对,不是推测)
sql-driver-temporal-conformance.test.ts:107 有一个 un-backfilled legacy storage 的 describe:用 LegacyStorageDriver.seedLegacyRows 绕过写入路径,把同一张表按 writer-form 标签分别注入 INTEGER epoch ms 和 zone-naive TEXT,然后跑全套 TEMPORAL_CASES。这个做法是对的,问题是它只有一份。
| 消费者 | canonical | legacy-epoch / legacy-naive |
|---|---|---|
driver-sql(datetime) |
✅ | ✅ :107 |
driver-sql(Field.time) |
✅ :150 |
❌ |
driver-sqlite-wasm |
✅ | ❌ |
driver-memory |
✅ | ❌ |
driver-mongodb |
✅ | ❌ |
formula |
✅ | ❌ |
service-analytics(native-sql / preview) |
✅ | ❌ |
其余六个消费者全部只做 driver.create(...) 播种,即
at: r.writerForm === 'native' ? new Date(r.at) : r.at,wire | native 两种形态都是今天的写入路径会产出的东西。也就是说这些夹具在构造上就不可能含有遗留形态——矩阵测的是「我写进去的我能读回来」,而四次事故里有两次的机理是读到自己没写过的形态。
为什么 mongo 是最尖的一格
其他后端的遗留数据是过渡态,mongo 的是永久态,这是 ADR 明写的设计决定:
MongoDB gets no automatic backfill — rewriting a real collection is a migration, not a boot step — so a pre-existing collection keeps whatever it holds until migrated
—— D-E3
而 mongo 读错形态时的表现是所有后端里最坏的:
a string comparand never matches a
Datevalue, in either direction, for every operator ——$gteincluded, not just the upper bound #3777 was about. SQLite's affinity rules at least left one half reachable
—— D-E1
SQLite 至少还有一半能匹配上,mongo 是整段窗口静默返回错误行集(#4047 当时的表现是空集)。所以:唯一保证长期持有遗留形态的后端,恰好是唯一没有遗留形态测试的后端,且它的失败模式最安静。
driver-memory 的情况稍好——D-E3 说它在 schema 到达时会收敛已有行(覆盖 initialData 夹具和持久化恢复,两者都发生在 schema 声明之前)——但这个收敛本身也没有被矩阵测过。
为什么 Field.time 那一格必须补
#3994 的成因逐字就是这个:
INTEGER epoch rows fail
>= '09:00'becauseINTEGER < TEXT
—— D-C 段
TEMPORAL_TIME_ROWS 里 a_midnight 的 why 也已经写着「epoch 0 on SQLite, which sorts before every TEXT row」。但 :107 的遗留扫描只跑 TEMPORAL_CASES,Field.time 的 describe(:150)没有对应的遗留版本。修复 #3994 的那条读取路径,今天没有任何遗留形态的回归锁。
建议形状
把 LegacyStorageDriver 的做法从「driver-sql 的一个 describe」提升为矩阵的一条正式轴:
- spec 侧给
TemporalRow/TemporalTimeRow加 storage-form 的判定依据(形态本身是各驱动物理层的事,spec 只需声明「这一行在遗留形态下应该长什么样」的语义,不下沉方言拼写)。 - 每个消费者加一个绕过写入路径、直接注入存储层的 describe,跑同一批
TEMPORAL_CASES/TEMPORAL_TIME_CASES。各驱动的注入手法不同(sql 走seedLegacyRows,mongo 直接collection.insertMany塞字符串/数字,memory 在 schema 声明前塞initialData),但断言的行 id 集合必须完全一致——这正是矩阵的意义。 - 优先级:mongo > Field.time(全后端)> memory 的 schema-到达收敛 > sqlite-wasm / formula / analytics。
分两步做也可以,第 1 步只做 mongo 的 datetime 就能把最尖的那格封住。
验收
遗留形态下的行 id 集合与 canonical 形态逐格相同。任何一格不同,都说明该后端的读取修复路径与共识有偏差——这是 #3773 / #3994 / #4047 三次事故共同缺的那个信号。
顺带记一笔(不在本 issue 范围)
同一句 ADR 还点名了 server-timezone 轴。SQL 侧已部分就位:Temporal Conformance job 的 Postgres 服务容器跑在 Asia/Shanghai,进程侧自 #4180 起五个非 SQL 包也在 America/New_York 下运行且有非空转守卫。但「服务器时区 ≠ 进程时区 ≠ UTC」这个三方错开的组合没有被系统化成矩阵的一条轴。若要立项建议单开。
关联
#3773 / #3777 / #3994 / #4047 / #3928;#4081(D-A3 本体,已关)、#4098、#4109、#4147、#4175、#4180;ADR-0053 D-A3 / D-B3 / D-C / D-E1 / D-E3 / D-E4。
Activity
test(temporal): D-A3 storage-form 轴补齐 SQL 系——Field.time 遗留扫描 + wasm 驱…
fix(formula,service-analytics): 类型盲后端把 `Date` 与线上文本比出错——storage-form …
test(driver-sql): run the D-A3 temporal matrix across the driver axis…
os-zhuang commentedon Aug 5, 2026
僵尸认领核查(drivers 车道 PM,会话
session_01WyvqvKMG6asi9aXjKE6xtx,座位登记见 #4604):本单 assignee 已设但无认领评论(共享身份下无法判定归属),创建至今 ~6 天,实测远端无
claude/issue-4191-*分支、无关联 PR。按 stale-claim 规程发出问询:另注(退回后供分诊参考):本单覆盖面横跨多个后端,其中 driver-memory / driver-mongodb 族受 #5499 冻结令约束,重新入队前需按冻结边界重新框定范围(driver-sql / sqlite 侧不受影响)。
Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
僵尸认领回收执行(drivers 车道 PM,会话
session_01WyvqvKMG6asi9aXjKE6xtx):claude/issue-4191-*分支、无关联 PR,创建至今 6 天零产出;维护者 2026-08-06 两次指示由本座位继续处理,故不再等满 24h 静默窗口。pm:*标签,分诊 Routine 下轮扫描)。分诊提示:范围横跨多个后端 — driver-mongodb 半边受 [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 冻结令与维护者 2026-08-06「mongodb 维持暂停」口径约束;driver-sql / sqlite 侧不受影响(且 driver-sql 的 LIKE 转义(自标 P0 的过滤器旁路)只在 SQLite 上验证过 —— live PG + MySQL 矩阵已存在、testkit 也已存在,LIKE 族一个用例都没接进去 #5589/PR test(driver-sql): LIKE-escape 守卫接入 live PG + MySQL 方言矩阵,补字面反斜杠用例 (#5589) #5655 已把 LIKE 族接入 live 方言矩阵,与本单的 storage-form 轴相邻不重叠);driver-memory 半边随 driver-memory 测试面替代:项目内测试后端迁到 sqlite:memory:(#5499 重启条件 · memory 半边,维护者 2026-08-06 立项) #5704 测试面替代程序推进后大概率消亡。建议按此边界重新框定范围后再入队。Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
存量裁决轮(维护者 2026-08-06 委托,session_01LeEfA7CFwbJb7JJmXm2KM3):关闭(completed)。storage-form 轴的可做格子经 origin/main 核实已全部落地:driver-sql datetime+time 遗留 sweep、sqlite-wasm 遗留 sweep、driver-memory D-E3 收敛、formula 类型盲面、analytics 委托——五处 conformance 测试均在案引用本单。唯一剩下的 mongo 格已拆为 #4220(本轮裁停放,落 #5499 冻结范围)。server-timezone 轴正文已声明范围外另立。维护者可否决重开。
Generated by Claude Code