Skip to content

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

Description

@claude

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:

const durationSites = sites.filter((s) => s.proseUnits.length > 0 || s.durationShaped || s.keyUnits.length > 0);

For resolution, all three disjuncts are false:

disjunct value why
proseUnits.length > 0 false 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):

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.

Not a duplicate

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

  1. Rename resolutionresolutionSeconds (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.
  2. Widen DURATION_SHAPED_TOKENS first, 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.

⚠️ 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.

Refs: #15939 · #17783 (PR #18007) · PR #17635 · #15679 · #14478 · #15642


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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions