Skip to content

ADR-0053 D-A3:storage-form 轴只锁住了 driver-sql 的 datetime —— 另外五个后端和整条 Field.time 仍在裸奔 #4191

Description

@os-zhuang

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-sqlField.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 Date value, in either direction, for every operator —— $gte included, 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' because INTEGER < TEXT
—— D-C 段

TEMPORAL_TIME_ROWSa_midnightwhy 也已经写着「epoch 0 on SQLite, which sorts before every TEXT row」。但 :107 的遗留扫描只跑 TEMPORAL_CASESField.time 的 describe(:150)没有对应的遗留版本。修复 #3994 的那条读取路径,今天没有任何遗留形态的回归锁。

建议形状

LegacyStorageDriver 的做法从「driver-sql 的一个 describe」提升为矩阵的一条正式轴:

  1. spec 侧TemporalRow / TemporalTimeRow 加 storage-form 的判定依据(形态本身是各驱动物理层的事,spec 只需声明「这一行在遗留形态下应该长什么样」的语义,不下沉方言拼写)。
  2. 每个消费者加一个绕过写入路径、直接注入存储层的 describe,跑同一批 TEMPORAL_CASES / TEMPORAL_TIME_CASES。各驱动的注入手法不同(sql 走 seedLegacyRows,mongo 直接 collection.insertMany 塞字符串/数字,memory 在 schema 声明前塞 initialData),但断言的行 id 集合必须完全一致——这正是矩阵的意义。
  3. 优先级: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。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions