维护者速读
上一张卡(#18080)您裁了 C:体检「时长键的单位要写进键名」这条规矩,判断依据要从一张 25 个词根的名字表换成**「这个数到底是不是一段时间」**。做之前必须先回答一个问题,而它没有机械答案:什么算一个时长?
普查(#18078)给了两个读数说明为什么这题非答不可:
- 名字表今天认出 23 个「像时长」的键,只有 6 个真是时长;另外 17 个是「每 N 条消息」「重试倍数」「桶数」这种没有单位的数 —— 对它们,「把单位写进键名」这句话没有意义。
- 至少 7 个真正的时长/时刻任何地方都没写单位,而名字表认不出它们 —— 它们是最危险的一批(60000 是毫秒还是秒,读者无从知道),但今天没有任何检查能碰到。
三个答案:
推荐 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.ts → z.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|EpochMs → AGENTS.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
维护者速读
上一张卡(#18080)您裁了 C:体检「时长键的单位要写进键名」这条规矩,判断依据要从一张 25 个词根的名字表换成**「这个数到底是不是一段时间」**。做之前必须先回答一个问题,而它没有机械答案:什么算一个时长?
普查(#18078)给了两个读数说明为什么这题非答不可:
三个答案:
EpochMs同款的闭合时长类型(如DurationMs/DurationSeconds),体检只认「用了时长类型,或键名带单位」的键;17 个没单位的数自然出列,≥7 个漏网的要么改用类型、要么改名。名字表退休。推荐 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 inpackages/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
5564447683): 「the gate does not start reading JSDoc as a unit channel (option 1 not adopted; its population is unmeasured)」 · 「Keys with no unit in either channel stay "listed, not judged" (the spec:tenant.zod.tsidleTimeout / sessionTimeout publish a describe with no unit, while the JSDoc one line above says seconds #14519 shape) — unchanged.」packages/spec/src/shared/epoch.zod.tsdocblock: 「Name the key*At. That is this package's measured convention for an instant, and it is what keeps an instant out of the*Msduration family.」 — the one closed-type precedent already in the tree.git grep -n -iE 'duration-unit|unit-in-key|isDurationShaped' origin/main -- AGENTS.md docs/adr@57343f76: 0 hits (controlADR-0087overdocs/adr: 49) — no ADR rules on the predicate.Protocol declaration
Option A adds closed types to
@objectstack/spec(Clause-②: yeswhen 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
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 movestokenExpiry)reflectionInterval,slidingWindowSize,contextWindow,ageBuckets…), 5backoffMultiplier, 1 instant--listslice, hand-read; #18078 §A lists themmetadata-persistence.zod.ts:259 loadTime,:287 saveTimegit grep -n -E 'loadTime|saveTime' origin/main -- packages/spec/src/system/metadata-persistence.zod.tsplugin-versioning.zod.ts:191 estimatedMigrationTime,metrics.zod.ts:847 resolutiongit grep -n -E 'estimatedMigrationTime|resolution' origin/main -- packages/spec/src/kernel/plugin-versioning.zod.ts packages/spec/src/system/metrics.zod.tsgit grep -n 'export const EpochMs' origin/main -- packages/spec/src/shared/epoch.zod.ts→z.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
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.timeout: z.number()is refused at authoring, loudly.dimensionlessrows, ≥7admitrows. The gate consults it.downloadsLastMonth("last 30 days") becomes a false positive on day one; drifts with prose.业务含义直译
EpochMs一个做法。四维分析(业务角度)
EpochMs已经证明这条路在本仓走得通(6 处在用,int-only,无 transform)。B 让一张名单外加一本账成为永久维护面;C 把注释变成第三个不进发布产物的契约通道。timeout: z.number()被门禁响亮拒绝;B 出错 = 新键漏登记,静默进入「listed, not judged」;C 出错 = 一个下载计数被当成时长误拒(假阳性教作者不信任门)。闭合枚举优于自由结构 —— A 是那个形。EpochMs同族)并退休一张名字表 —— 净减一个会漂移的特例;B 新增一本永久账;C 零新增但重开已否方向。os-decision-facets
Prior rulings read:
duration-unit|unit-in-key|isDurationShaped|EpochMs→AGENTS.md docs/adr0 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 子树)同形键。裁后执行
shared/epoch.zod.ts旁定义闭合时长类型(单位集合 = 六个真时长行的单位),Clause-②: yes,changesetminor;② spec 卡:check-duration-unit-keys.ts的 admission 改为「类型或名字带单位」,名字表降为提示,--self-test与--list基线随之更新;③ 转换卡(可按文件族折叠):6 + 2 + ≥2 个时长行各取类型或改名,改名者带 ADR-0087 条目;②③Blocked-by:①。tenant.zod.tsidleTimeout / sessionTimeout publish a describe with no unit, while the JSDoc one line above says seconds #14519 加注「第三次因同一方向被否」。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.tsFiled 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 admissionGenerated by Claude Code