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。

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions