Skip to content

[Decision] #18124 余下两行只剩改名一条路 —— FileValue.duration(小数秒)与 estimatedMigrationTime(小时)是否走 ADR-0087 改名,仓内已有两次同形先例 #18669

Description

@os-bill

⏱️ 本卡所有读数取自同一动作:2026-09-17T13:30Z。MEASURED / RELAYED 逐条标注 —— 标 MEASURED 的由本席在 origin/main 上第一手重测,标 RELAYED 的来自 #18124 的 dev 报告且被本席复测。

Governing text: packages/spec/scripts/check-duration-unit-keys.ts 顶部 docblock(三条采纳通道)· docs/adr/0087-metadata-protocol-upgrade-contract.md · #18115 ruling A(决裁批 #134 item 1)· packages/spec/src/migrations/registry.ts 的两条同形先例。

事情

#18115 ruling A 的步骤③(卡 #18124)把十行「真时长」里的八行交付了(PR #18657,已入队)。余下两行被 dev 正确地退回,因为两者都撞上同一堵墙:现有的闭合类型都表达不了它们的单位

实测单位 为什么闭合类型不覆盖
data/field-value.zod.ts FileValue.duration(MEASURED::484 duration: z.number().optional()) 小数秒(RELAYED:外部惯例 HTMLMediaElement.duration / ffprobe;本仓库内单位成文) DurationSeconds.int() ⇒ 拒 12.34;DurationMs 与惯例差 1000×
kernel/plugin-versioning.zod.ts CompatibilityMatrixEntry.estimatedMigrationTime(MEASURED::188-191 JSDoc 逐字「Estimated migration time in hours」+ z.number().optional()) 小时 词汇表里没有小时类型;卡面 fence「⛔ 无新类型」

关键读数(MEASURED),它把这道题从「加类型还是收窄」变成了「改不改名」:门禁的采纳通道有三条,不是两条 —— 它自己的 docblock 逐字写:「carries it in its NAME or in its TYPE, never only in its .describe() prose」,第三条是 .meta({ externalVocabulary })。⇒ 键名自带单位就被采纳,值类型不必是闭合类型。

⇒ 于是两行都有一条不加新类型、不收窄已存储数据的出路:durationdurationSeconds(值仍 z.number(),小数秒合法);estimatedMigrationTimeestimatedMigrationTimeHours

而且这条路本仓库已经走过两次(MEASURED,migrations/registry.ts):window.sizewindow.durationSeconds;ServiceLevelObjective.period.durationdurationSeconds,其条目原文逐字「Rename the key to durationSeconds; the value (seconds) …」。⇒ 不是新发明,是既有做法

选项 × 真实代价

做什么 客户可感知的后果 代价
A 改名:durationSeconds + estimatedMigrationTimeHours,各一条 ADR-0087 转换层条目 老数据照常读(转换层重放);新写的元数据里键名自己说单位 两条 ADR-0087 条目落 packages/spec/src/migrations/registry.ts —— 该文件被 #17534 持有(MEASURED:它仍 open + pm:dispatched)⇒ 必须排在它之后
B 上闭合类型:FileValue.durationDurationSeconds 一个写 12.34 秒的生产者会在写的那一刻被拒;而 FileValueSchema 是 ADR-0104 双模已存储内联 blob ⇒ 对可能已存在的行收窄 estimatedMigrationTime 仍无解,还得回到 A 或 C
C 给闭合词汇表加新类型:小数秒 + 小时 与 A 对客户等价 #18124 卡面 fence「⛔ 无新类型」⇒ 要维护者亲自解 fence;词汇表从 2 个长到 4 个
D 退役两键 两键在本仓库零生产者、零消费者(MEASURED:文件字段水合写出的形状是 { id, name, size, mimeType, url },packages/objectql/src/engine.ts 自己的注释逐字如此,不含 duration;RELAYED:objectui 侧也无消费者) 退役同样欠 ADR-0087 条目、同样落被持有的那个文件 ⇒ 省不下排期,只省下键

业务直译:A = 「把单位写进名字,像我们前两次那样」。B = 「为了把单位写进类型,接受它开始拒绝小数,并赌没有已存的小数行」。C = 「为这两个单位各造一个新词,并请维护者收回自己设的禁令」。D = 「这两个字段没人用,删掉」。

项目长远合理性:A 用的是仓库已经在用的采纳通道与已经走过两次的改名先例 ⇒ 特例不增;C 把闭合词汇表从 2 扩到 4,而扩它的理由是两个零拉动的字段;B 制造一个「声明了单位但会拒绝该单位的自然取值」的字段,这是特例。
实际业务拉动:今天没有人撞上 —— 两键在本仓库零生产者零消费者(MEASURED)。⇒ 按模板「零拉动默认 defer 或 remove」,方向定了也应排在有拉动的活后面
防 AI 犯错:一个叫 duration 的裸数字,正是 AI 照着邻居抄一个数、静默差 1000× 的形状;改名后错的那一刻在键名上就读得出来,且门禁会响亮拒绝矛盾声明(#18657 的消融腿 A/B 已证该门禁真能红)。B 的拒绝也响亮,但它拒的是正确的小数值。
创业阶段不扩散:D(退役)本是 ④ 最偏好的,但它省不下代价 —— 退役同样欠 ADR-0087 条目、同样落被持有的文件;而 FileValue 是发布契约,删一个已发布可 authoring 键的下游面比改名大。⇒ ④ 在这里不推翻 A。

Prior rulings read: duration,filevalue,estimatedmigrationtime,adr-0087 → 4 hits; ADR-0087 D8, ADR-0120 D2, ADR-0120 D7, ADR-0131 D13

⚠️ 逐条读过,没有一条已经答了本题(MEASURED):ADR-0087 D8 管的是 breaking changeset 的 no-migration-prescription 处置类别 —— 即「改名之后台账怎么记」,⛔ 不是「改不改名」;ADR-0120ADR-0131duration / FileValue / estimatedMigrationTime 三个词零命中(亮控:同一把 grep 在 ADR-0087 上读回内容,所以仪器是响的),它们是被共现词 adr-0087 带出来的。⇒ 本卡是决策,⛔ 不是已被裁决过的执行。

推荐 A。自检行:只看①选 A;②③④ 是否翻转:否 —— ② 把它推后(排在 #17534 之后,且零拉动不插队),③ 加强它,④ 本想选 D 但 D 省不下代价。⛔ 字母不改。

回退项:D(退役)。若维护者判「零拉动的已发布键不值得一条转换层条目」,退役比改名更彻底,代价同级。

置信缺口(本席看不见的):① FileValue.duration外部消费者 —— 本仓库与 objectui 内为零是测过的,但这是发布契约,仓外用它的人本席看不到;② 是否真有已存储的小数 duration 行 —— 本席没有生产库可查,B 的收窄风险因此只能定性、⛔ 不能定量。

裁后执行段

A ⇒ 本席在 #17534 释放 migrations/registry.ts 后立一张执行卡(⛔ 不重开 #18124):两条改名 + 两条 ADR-0087 转换层条目 + 两处 .zod.ts 改名 + 生成物重生成,Part of #18124;#18124 在那一笔之后才收口。
D ⇒ 同样排在 #17534 之后,执行卡改为两条退役条目(retiredKey() 墓碑,按 #17502 ⛔ 不加 title)。
BC ⇒ 本席回到 #18124 补派,并在派发令里写明所解的 fence 原话。

维护者速读

事情。 规格里有两个「时长」字段,单位只写在注释里或哪里都没写:一个是文件的时长(网上通行的写法是小数秒),一个是「预计迁移工时」(单位是小时)。我们这批把其它八个字段的单位写进了合约,这两个卡住了 —— 现有的两种「带单位的类型」是整数毫秒和整数秒,装不下小数秒,也装不下小时

发现。 规则其实还有第三条路:键名自己带单位就算数。把 duration 改叫 durationSeconds、把 estimatedMigrationTime 改叫 estimatedMigrationTimeHours,不用造新类型,也不用让字段开始拒绝小数。这条路我们已经走过两次,台账里有记录。

代价。 改名要一条「老数据怎么继续读」的台账条目,而那个台账文件现在被另一张在做的卡占着,所以这件事得排在它后面。另外:这两个字段今天没有任何代码在写、也没有任何代码在读,所以不着急。

你要做的。 选一个字母:A 改名(推荐)· B 上整数类型(会拒绝小数,且对可能已存在的数据收窄)· C 给词汇表加新类型(需要你收回「不加新类型」那条禁令)· D 退役这两个字段。


Part of #18124 · 执行 #18115 ruling A 时长出的残留问题,按巡检 H52 的处方另立新卡而 ⛔ 不在 #18124 上重挂 needs-user-decision(那会让收件箱说不清哪个问题还开着,且 #18124 收口时会把标签连同问题的唯一可见性一起带走)。


Generated by Claude Code

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions