You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
spec: MetricsConfig.retention.downsampling[].resolution names its unit only in JSDoc — and is outside check:duration-unit-keys on the NAME axis, so #17635 will not reach it #18030
Found while executing #17783 (ruling A on #15939) on packages/spec/src/system/metrics.zod.ts. Filed rather than fixed: it is not one of that card's five rows, it is not in PR #17635's 21-row delta, and remediating it is a published-key rename that owes its own clause-② round. ⛔ No domain:*, no priority:* and no assignee asserted.
The site
packages/spec/src/system/metrics.zod.ts, MetricsConfig.retention.downsampling[].resolution — re-locate by symbol path, the line rots:
downsampling: z.array(z.object({/** * After this duration, downsample to this resolution */afterSeconds: z.number().int().positive().describe('Downsample after seconds'),/** * Resolution in seconds */resolution: z.number().int().positive().describe('Downsampled resolution'),})).optional(),
The JSDoc says "Resolution in seconds". The .describe() — the text content/docs/references/system/metrics.mdx renders and the one an AI author reads — says "Downsampled resolution" and names no unit. Its own sibling one key up, afterSeconds, already carries the unit in the name. So on one object, two seconds-valued numbers, one self-describing and one not.
This is the #15939 defect exactly: the unit lives only in a channel the reader of the published page never sees.
Why no gate will ever say so — the NAME axis, not the prose axis
#15939 is about the prose channel (JSDoc not read). This site survives a second, independent blindness: it never enters the population at all, so widening the prose channel does not reach it.
Read from the checker source rather than inferred — packages/spec/scripts/check-duration-unit-keys.ts, the population filter:
the .describe() is "Downsampled resolution" — no unit token
durationShaped
false
resolution is not in DURATION_SHAPED_TOKENS (timeout, ttl, interval, delay, duration, maxage, expireafter, retention, cooldown, debounce, throttle, window, grace, lifetime, expiry, expiration, heartbeat, backoff, idle, stale, age, period, every, wait, timeouts)
keyUnits.length > 0
false
the key name carries no unit token
⇒ PR #17635 does not close this one. Its changeset states its population as "an unchanged 211 duration-shaped numeric keys across 2433 source files", and its new unit-in-jsdoc-not-in-describe rule judges sites already in that census. A key that never enters the census is not judged by a new verdict applied to it.
Measurement
Measured on origin/main @ fc28c1d38, 2026-09-13T10:45Z, occurrences via grep -o | wc -l (⛔ not grep -c, which counts lines):
lit control on the same file: z.number = 38. dark control: a fabricated token = 0.
Sizing — deliberately not asserted
This card names one site, read rather than pattern-matched. The name-axis question it raises is general — how many other seconds/ms-valued keys across packages/spec/src carry a name outside DURATION_SHAPED_TOKENS and a describe with no unit — and that number is not asserted here. A census is its own card, and the same crude-heuristic caution #15939 recorded about its own 60-candidate scan applies.
Searched before filing: two targeted semantic searches over this repo (open and closed), 2026-09-13T12:0xZ. Both returned the #14478/#15939 family — the channel was alive — and neither returned a card about resolution or about the duration-shaped-name vocabulary being incomplete. ⚠️ Declared limit: /search/* is 403 for this session's REST channel, so this was the MCP semantic matcher, not an exhaustive enumeration.
Two options, no recommendation asserted
Rename resolution → resolutionSeconds (published key ⇒ retiredKey() tombstone + ADR-0087 D3 + RETIRED_KEYS_BY_MAJOR[18] row + pins), matching its own sibling afterSeconds. Nested, so no authorable-surface row moves.
⚠️ Whichever is chosen, ⛔ it should not be folded into #17783 / PR #18007: the #15939 batch was certified by triage as exactly 21 rows across seven cards with "⛔ 无重叠、⛔ 无遗漏、⛔ 无第八张", and a sixth row on that PR breaks that accounting.
Found while executing #17783 (ruling A on #15939) on
packages/spec/src/system/metrics.zod.ts. Filed rather than fixed: it is not one of that card's five rows, it is not in PR #17635's 21-row delta, and remediating it is a published-key rename that owes its own clause-② round. ⛔ Nodomain:*, nopriority:*and no assignee asserted.The site
packages/spec/src/system/metrics.zod.ts,MetricsConfig.retention.downsampling[].resolution— re-locate by symbol path, the line rots:The JSDoc says "Resolution in seconds". The
.describe()— the textcontent/docs/references/system/metrics.mdxrenders and the one an AI author reads — says "Downsampled resolution" and names no unit. Its own sibling one key up,afterSeconds, already carries the unit in the name. So on one object, two seconds-valued numbers, one self-describing and one not.This is the #15939 defect exactly: the unit lives only in a channel the reader of the published page never sees.
Why no gate will ever say so — the NAME axis, not the prose axis
#15939 is about the prose channel (JSDoc not read). This site survives a second, independent blindness: it never enters the population at all, so widening the prose channel does not reach it.
Read from the checker source rather than inferred —
packages/spec/scripts/check-duration-unit-keys.ts, the population filter:For
resolution, all three disjuncts are false:proseUnits.length > 0.describe()is "Downsampled resolution" — no unit tokendurationShapedresolutionis not inDURATION_SHAPED_TOKENS(timeout,ttl,interval,delay,duration,maxage,expireafter,retention,cooldown,debounce,throttle,window,grace,lifetime,expiry,expiration,heartbeat,backoff,idle,stale,age,period,every,wait,timeouts)keyUnits.length > 0⇒ PR #17635 does not close this one. Its changeset states its population as "an unchanged 211 duration-shaped numeric keys across 2433 source files", and its new
unit-in-jsdoc-not-in-describerule judges sites already in that census. A key that never enters the census is not judged by a new verdict applied to it.Measurement
Measured on
origin/main@fc28c1d38, 2026-09-13T10:45Z, occurrences viagrep -o | wc -l(⛔ notgrep -c, which counts lines):in seconds/in millisecondsJSDoc onsystem/metrics.zod.ts, each with the declaration below it: 10 sites — the five of spec: 5 duration key(s) insystem/metrics.zod.tsname their unit only in JSDoc — #15939 Ruling A remediation (5 of the 21-row delta) #17783, the three [#14478 stack 4/6]system/: the 15 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers;metrics.zod.tssizeneeds an honest name, not the mechanical one #15679 already renamed,afterSecondswhich already carries its unit, and this one.system/metrics.zod.tsname their unit only in JSDoc — #15939 Ruling A remediation (5 of the 21-row delta) #17783 lands,resolutionis the only remaining key whose unit is in the JSDoc and nowhere a reader reaches.z.number= 38. dark control: a fabricated token = 0.Sizing — deliberately not asserted
This card names one site, read rather than pattern-matched. The name-axis question it raises is general — how many other seconds/ms-valued keys across
packages/spec/srccarry a name outsideDURATION_SHAPED_TOKENSand a describe with no unit — and that number is not asserted here. A census is its own card, and the same crude-heuristic caution #15939 recorded about its own 60-candidate scan applies.Not a duplicate
packages/spec/srconly). A third axis.RETIRED_KEYS_BY_MAJORrows are not verified the way top-level ones are. Unrelated.Searched before filing: two targeted semantic searches over this repo (open and closed), 2026-09-13T12:0xZ. Both returned the #14478/#15939 family — the channel was alive — and neither returned a card about⚠️ Declared limit:
resolutionor about the duration-shaped-name vocabulary being incomplete./search/*is 403 for this session's REST channel, so this was the MCP semantic matcher, not an exhaustive enumeration.Two options, no recommendation asserted
resolution→resolutionSeconds(published key ⇒retiredKey()tombstone + ADR-0087 D3 +RETIRED_KEYS_BY_MAJOR[18]row + pins), matching its own siblingafterSeconds. Nested, so no authorable-surface row moves.DURATION_SHAPED_TOKENSfirst, so the gate produces the population rather than a human doing it again. That would change feat(spec): refuse a duration key whose JSDoc names a unit its describe does not #17635's measured delta and should not be decided while feat(spec): refuse a duration key whose JSDoc names a unit its describe does not #17635 is still the last card of finding: check:duration-unit-keys reads .describe() but not JSDoc — a duration key documenting its unit only in JSDoc never enters the population, and one card already recorded a wrong reason because of it #15939's sequence.Refs: #15939 · #17783 (PR #18007) · PR #17635 · #15679 · #14478 · #15642
Generated by Claude Code