Skip to content

[Decision] what counts as a duration for the unit-in-key census — replace the 25-token name list with a declared shape? (class A 23→6 and class D ≥7 of census #18078; tranche 2 precondition of ruling C on #18080) #18115

Description

@os-elon-musk

维护者速读

上一张卡(#18080)您裁了 C:体检「时长键的单位要写进键名」这条规矩,判断依据要从一张 25 个词根的名字表换成**「这个数到底是不是一段时间」**。做之前必须先回答一个问题,而它没有机械答案:什么算一个时长?

普查(#18078)给了两个读数说明为什么这题非答不可:

  • 名字表今天认出 23 个「像时长」的键,只有 6 个真是时长;另外 17 个是「每 N 条消息」「重试倍数」「桶数」这种没有单位的数 —— 对它们,「把单位写进键名」这句话没有意义。
  • 至少 7 个真正的时长/时刻任何地方都没写单位,而名字表认不出它们 —— 它们是最危险的一批(60000 是毫秒还是秒,读者无从知道),但今天没有任何检查能碰到。

三个答案:

  • A 让作者声明:引入和 EpochMs 同款的闭合时长类型(如 DurationMs / DurationSeconds),体检只认「用了时长类型,或键名带单位」的键;17 个没单位的数自然出列,≥7 个漏网的要么改用类型、要么改名。名字表退休。
  • B 保留名字表,加一本人工维护的例外账:17 个假阳性逐条登记「不是时长」,≥7 个漏网逐条登记进普查。
  • C 让体检读注释猜语义(JSDoc/describe 里有没有「时间」字样)—— 这就是批 Add comprehensive test coverage for ObjectStack spec protocols - 100% coverage achieved #65 已经否掉的那个方向换了说法。

推荐 A。请回一个字母:A / B / C。


Background

Decision sub-card required by ruling C on #18080 (batch #133 item 4, maintainer 「决裁批 #133 同意」, 2026-09-14T03:31Z). Tranche 1 of that ruling (the 10 instants → EpochMs) is #18114 and does not wait on this card. Tranche 2 — the durations — cannot start until the census's admission predicate is defined, because today's predicate (isDurationShaped(), a 25-token name list in packages/spec/scripts/check-duration-unit-keys.ts) both over-admits (17 dimensionless rows) and under-admits (≥7 unit-nowhere rows).

Governing text — quoted, ⛔ not paraphrased

Protocol declaration

Option A adds closed types to @objectstack/spec (Clause-②: yes when it lands — new exported symbols); it changes no key's runtime value. Options B and C change only the gate. ⛔ Nothing is adopted by filing.

Premises, each with its re-check

premise re-check
A-class: 23 mechanical / 6 genuine (contract.zod.ts:311 duration, field-value.zod.ts:424 duration, plugin-lifecycle-advanced.zod.ts:413 shutdownTimeout, plugin-security-advanced.zod.ts:623 maxAge, auth-config.zod.ts:563 updateAge, metrics.zod.ts:412 slideInterval) cd packages/spec && tsx scripts/check-duration-unit-keys.ts --list | grep -c '\[name: -\] \[prose: -\]' → 23 (22 after #18114 moves tokenExpiry)
17 dimensionless: 11 counts (reflectionInterval, slidingWindowSize, contextWindow, ageBuckets…), 5 backoffMultiplier, 1 instant the same --list slice, hand-read; #18078 §A lists them
D-class durations ≥2 with no unit anywhere and outside the census: metadata-persistence.zod.ts:259 loadTime, :287 saveTime git grep -n -E 'loadTime|saveTime' origin/main -- packages/spec/src/system/metadata-persistence.zod.ts
B-class 2 (unit only in JSDoc): plugin-versioning.zod.ts:191 estimatedMigrationTime, metrics.zod.ts:847 resolution git grep -n -E 'estimatedMigrationTime|resolution' origin/main -- packages/spec/src/kernel/plugin-versioning.zod.ts packages/spec/src/system/metrics.zod.ts
Closed-type precedent exists and is int-only git grep -n 'export const EpochMs' origin/main -- packages/spec/src/shared/epoch.zod.tsz.number().int()

The question

By what rule does a numeric key enter the unit-in-key census, so that 「listed, not judged」 stops being the resting place of the rows that most need judging?

Options × real cost

what happens cost an author feels
A declared shape New closed types beside EpochMs (DurationMs, DurationSeconds; the set is decided by the six genuine rows' units). Admission = 「uses a duration type OR key name carries a unit token」. The 17 dimensionless rows fall out by construction; the 6 + 2 + ≥2 genuine durations each pick a type or a unit-suffixed name (one PR per file family, ADR-0087 entries only where a key is renamed). The 25-token list retires to a hint the gate prints when a bare numeric's name looks time-shaped but declares nothing. An author writing a duration must say which unit — by type or by name. Declared = enforced: a bare timeout: z.number() is refused at authoring, loudly.
B curated ledger Keep the name list; add a hand-maintained ledger: 17 dimensionless rows, ≥7 admit rows. The gate consults it. Zero authoring change; every new key is a ledger question the next author will not know to ask; D-class stays as large as the ledger's neglect.
C prose heuristics The gate reads JSDoc/describe for time words to decide admission. Batch #65's declined shape; downloadsLastMonth ("last 30 days") becomes a false positive on day one; drifts with prose.

业务含义直译

  • A = 「时长必须自报单位,不报就不让写」—— 和已经在用的 EpochMs 一个做法。
  • B = 「保留旧名单,再养一本例外账」—— 每个新键都是一次没人记得要问的登记。
  • C = 「让机器读注释猜」—— 上次已经否过。

四维分析(业务角度)

  • ① 项目长远合理性:两年后的样子应当是「每个时长/时刻键要么类型闭合、要么单位在名里」,门禁读声明而不是名字 —— 那正是 A;EpochMs 已经证明这条路在本仓走得通(6 处在用,int-only,无 transform)。B 让一张名单外加一本账成为永久维护面;C 把注释变成第三个不进发布产物的契约通道。
  • ② 实际业务拉动:零事故记录;拉动来自结构 —— ≥7 个键上作者今天读不出单位,静默差 1000 倍;A 是唯一让这批键必须表态的方案。
  • ③ 防 AI 犯错(出错时谁看到什么):A 出错 = 作者写裸 timeout: z.number() 被门禁响亮拒绝;B 出错 = 新键漏登记,静默进入「listed, not judged」;C 出错 = 一个下载计数被当成时长误拒(假阳性教作者不信任门)。闭合枚举优于自由结构 —— A 是那个形。
  • ④ 创业阶段不扩散:A 新增 1–2 个闭合类型(与 EpochMs 同族)并退休一张名字表 —— 净减一个会漂移的特例;B 新增一本永久账;C 零新增但重开已否方向。

os-decision-facets

  • ① 项目长远合理性:A 缩小特例(名字表退休、声明即强制);B 扩大(名单 + 例外账两处维护);C 扩大(注释成契约通道)。
  • ② 实际业务拉动:零事故;≥7 个无单位键是结构性拉动,只有 A 强制它们表态。
  • ③ 防 AI 犯错:A 响亮拒绝裸时长;B 静默漏登记;C 假阳性误拒。
  • ④ 创业阶段不扩散:A +1–2 闭合类型 −1 名字表;B +1 永久账;C 重开已否方向。

Prior rulings read: duration-unit|unit-in-key|isDurationShaped|EpochMsAGENTS.md docs/adr 0 hits; batch #65 (5564447683) · #18080 ruling C (batch #133 item 4) · #15676 ruling B (instants)

推荐 A(只看①选 A;②③④ 是否翻转:否)。回退 B(若您不想在创业阶段引入新类型)。置信缺口:D 类 ≥7 是下界 —— A 落地时的全量转换清单要靠新谓词跑出来才知道;DurationMs/DurationSeconds 的输入类型故事按 EpochMs 先例(无 transform)推定可行,未实测 zod 推断对现有 z.input 消费者的影响;未数 packages/spec 之外(#15642 子树)同形键。

裁后执行

Refs

#18080(ruling C)· #18114(tranche 1,不等本卡)· #18078(census)· #14519(unit-nowhere 形状)· #15676(instants ruling B)· #15642(scanned subtree,独立)· packages/spec/scripts/check-duration-unit-keys.ts · packages/spec/src/shared/epoch.zod.ts

Filed by the director seat, session_01WCEaPsmKY4UyoivKkkaUHt, 2026-09-14T03:31Z. Dedup terms: what counts as a duration, isDurationShaped, DurationMs, census admission, unit-nowhere, check-duration-unit-keys admission


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