Skip to content

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

Description

@claude

Observation filed from stack card 4/6 of #14478 (PR #15938). Not fixed there — it is outside that card's population and touches the gate's own reading surface, which card 6/6 owns.

The shape

check:duration-unit-keys reads a key's unit prose from its .describe() (and from .meta({ description })), and its own self-test pins that behaviour: "a describe declared through .meta({ description }) is READ — no exemption by blindness". It does not read the JSDoc block immediately above the key.

So a duration-shaped z.number() whose name carries no unit, and whose unit is stated only in its JSDoc, is invisible to the population. It is not exempt by either declared structural exemption (EpochMs, externalVocabulary) — it simply never enters the count. The rule's own premise is that the unit must not live in prose alone; here it lives in a less discoverable prose channel than the one the rule already rejects.

Three verified instances, read rather than pattern-matched

Measured on claude/issue-15679-system-duration-unit-in-key-name @ 45a35896c:

site JSDoc says .describe() says
packages/spec/src/kernel/plugin-security-advanced.zod.ts:294 timeout "Execution timeout in milliseconds" 'Maximum execution time' — no unit
packages/spec/src/system/metrics.zod.ts:552 window "Window size in seconds" 'Window size' — no unit
packages/spec/src/system/metrics.zod.ts:626 interval "Export interval in seconds" no .describe() at all

Why the first one is the sharp end

RuntimeConfig.resourceLimits.timeout is not a hypothetical. Card 3/6 (#15678) reasoned about it explicitly and pinned it as out of population, recording in its changeset that the key "names no unit anywhere in its prose". That reading is measurably imprecise — the JSDoc two lines above it names milliseconds. The conclusion (leave it alone, it is outside the gate's population) is still correct as a statement about the gate, but the reason recorded for a future author is wrong, and a pin test now holds it in place.

That is the cost of the blindness: it does not just miss keys, it produces confident wrong prose about why they were missed.

Not a duplicate of #15642

#15642 reports that the gate scans packages/spec/src/** only, so the same offender shape survives in other workspace packages. That is about the scanned subtree. This is about which prose channel is read inside a file the gate already scans. Both could be true and fixed independently.

Sizing — deliberately not asserted

A crude heuristic scan of packages/spec/src/**/*.zod.ts (duration-shaped numeric key, no unit token in the name, no unit in its describe, unit present in the JSDoc above) returns 60 candidate sites. That number is not a population count: reading a sample shows it includes clear false positives — byte and percentage measurements, counts, and keys that already carry a unit token. The three rows above are the ones actually read and confirmed. The real count needs the gate's own predicate applied to the JSDoc channel, which is the fix, not the triage.

Options, for whoever picks this up

  1. Read the JSDoc as a prose channel too. Most faithful to the rule's premise; expands the population, so it wants its own card and its own before/after count.
  2. Refuse a duration-shaped key whose JSDoc names a unit its describe does not. Narrower: it treats the divergence itself as the defect, and pushes authors to move the unit into the describe, where the gate and the reference pages both see it.
  3. Leave it and correct the prose. Fix card 3/6's pin note so the recorded reason is true, and accept the blindness as declared behaviour.

I have no recommendation between these — the choice is a rule change and belongs with the maintainer ruling that shaped #14478.


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