Skip to content

Commit 5f73b05

Browse files
committed
feat(spec): publish the dimensionless marker on the reference page
The `dimensionless` schema marker landed with its gate reader and no docs half. Ruling B on #14478 put both halves in the mechanism: a marker "declared ON THE SCHEMA, never in a gate ledger … the gate honours and the docs generator publishes". Its sibling `externalVocabulary` has had its renderer since #15626; this one had none, so a key marked `dimensionless` would carry an in-schema declaration the published reference page says nothing about. Adds `dimensionlessNote()` beside `externalVocabularyNote()` in `scripts/lib/schema-section.ts`, appended to the same description cell, and reading the same declaration the gate reads: a non-empty string literal, so an empty or non-string marker publishes nothing. No key is marked by this change — the live population stays 0 — so no reference page moves and no published payload changes. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7f7b855 commit 5f73b05

2 files changed

Lines changed: 154 additions & 1 deletion

File tree

‎packages/spec/scripts/lib/schema-section.ts‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,42 @@ function externalVocabularyNote(prop: any): string {
259259
return ` (unit per ${standard.trim()})`;
260260
}
261261

262+
/**
263+
* The published half of the `dimensionless` exemption (#15676, ruling B on
264+
* #14478) — the sibling of {@link externalVocabularyNote}, and one grammar with
265+
* it rather than a second one.
266+
*
267+
* A key that carries `.meta({ dimensionless: '<what it counts>' })` is a count,
268+
* a multiplier or a ratio whose describe prose happens to name a time unit
269+
* belonging to something else in the sentence: `Failures seen in the last 5
270+
* minutes` is a number of failures, not a number of minutes. The marker rides
271+
* `z.toJSONSchema` verbatim, the same channel `externalVocabulary` / `xRef` /
272+
* `xExpression` / `xEnumDeprecated` use, so it arrives here as a property of
273+
* the JSON-Schema node.
274+
*
275+
* Printing it is what makes THIS exemption honest, on exactly the argument its
276+
* sibling rests on. `check:duration-unit-keys` exists because a naked number
277+
* beside prose naming a unit leaves the reader guessing; the rename is waived
278+
* here because the number HAS no unit, and that reason is invisible on the
279+
* page unless the page says it. A key exempted silently publishes the same
280+
* guess the gate was built to remove — with the marker printed, the answer is
281+
* stated: no unit, and here is what it counts instead.
282+
*
283+
* Both halves read the SAME declaration the gate reads — a non-empty string
284+
* literal — so an empty or non-string marker, which exempts no key there,
285+
* publishes nothing here. The page must never name a count the contract did
286+
* not.
287+
*
288+
* Appended to the description cell for the reason its sibling is: it qualifies
289+
* the prose already in that cell, and a marker on a handful of keys does not
290+
* earn a column on every table in the reference.
291+
*/
292+
function dimensionlessNote(prop: any): string {
293+
const counts = prop?.dimensionless;
294+
if (typeof counts !== 'string' || counts.trim() === '') return '';
295+
return ` (dimensionless — counts ${counts.trim()})`;
296+
}
297+
262298
/**
263299
* Render one schema's section, heading included.
264300
*
@@ -465,7 +501,9 @@ export function renderSchemaSection(schemaName: string, schema: any, ctx: Sectio
465501
// pipe), then pipes — an unescaped `|` (even inside a code span)
466502
// splits the cell.
467503
const desc = escapeMdxDescription(
468-
((prop.description || '') + externalVocabularyNote(prop)).replace(/\n/g, ' '),
504+
((prop.description || '')
505+
+ externalVocabularyNote(prop)
506+
+ dimensionlessNote(prop)).replace(/\n/g, ' '),
469507
)
470508
.replace(/\\/g, '\\\\')
471509
.replace(/\|/g, '\\|');

‎packages/spec/scripts/schema-section.test.ts‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,3 +657,118 @@ describe('externalVocabulary — the published half of the duration-rule exempti
657657
}
658658
});
659659
});
660+
661+
/**
662+
* [#18500] The PUBLISHED half of the `dimensionless` exemption — the sibling of
663+
* the block above, ruling B on #14478: the marker is one the gate honours AND
664+
* the docs generator publishes. `check:duration-unit-keys` shipped the reader
665+
* first; this is the other half.
666+
*
667+
* The argument is its sibling's, one step further. A bare `maxAge` leaves the
668+
* reference-page reader guessing seconds from milliseconds; a bare
669+
* `recentFailures` described as `Failures seen in the last 5 minutes` leaves
670+
* them guessing whether the number IS that span. The marker says it is not —
671+
* it counts failed attempts — and that reason reaches the page only if the
672+
* page prints it.
673+
*
674+
* The marker reaches this renderer as a property of the JSON-Schema node,
675+
* riding `z.toJSONSchema` verbatim (measured on zod 4.4.3 against
676+
* `z.number().meta({ dimensionless: 'failed attempts' })`: the key arrives on
677+
* the emitted node unchanged) — the same channel `externalVocabulary` / `xRef`
678+
* / `xExpression` / `xEnumDeprecated` use.
679+
*
680+
* MEASURED (reverse verification): deleting the `dimensionlessNote(prop)` term
681+
* from the description cell turns the first four cases below red and leaves the
682+
* last two green — the last two assert the note's ABSENCE, which is what keeps
683+
* it from decorating every row in the reference.
684+
*/
685+
describe('dimensionless — the published half of the duration-rule exemption', () => {
686+
const withMarker = (marker: unknown) => ({
687+
type: 'object',
688+
properties: {
689+
recentFailures: {
690+
type: 'number',
691+
description: 'Failures seen in the last 5 minutes',
692+
...(marker === undefined ? {} : { dimensionless: marker }),
693+
},
694+
},
695+
});
696+
697+
it('prints what the number counts, beside the prose whose unit belongs to something else', () => {
698+
const md = renderSchemaSection('HealthSignal', withMarker('failed attempts'));
699+
700+
expect(md).toContain(
701+
'Failures seen in the last 5 minutes (dimensionless — counts failed attempts)',
702+
);
703+
});
704+
705+
it('keeps the describe prose — the note QUALIFIES the number, it does not replace it', () => {
706+
const md = renderSchemaSection('BackoffPolicy', withMarker('retry attempts'));
707+
708+
expect(md).toContain('Failures seen in the last 5 minutes');
709+
expect(md).toContain('(dimensionless — counts retry attempts)');
710+
});
711+
712+
it('renders inside a nested shape table too — one grammar, not two', () => {
713+
const md = renderSchemaSection('RetryConfig', {
714+
type: 'object',
715+
properties: {
716+
backoff: {
717+
type: 'object',
718+
description: 'Backoff options',
719+
properties: {
720+
multiplier: {
721+
type: 'number',
722+
description: 'Growth applied to the previous 30 second delay',
723+
dimensionless: 'the factor each delay is multiplied by',
724+
},
725+
},
726+
},
727+
},
728+
});
729+
730+
expect(md).toContain(
731+
'Growth applied to the previous 30 second delay (dimensionless — counts the factor each delay is multiplied by)',
732+
);
733+
});
734+
735+
it('composes with the sibling marker — a key declaring both publishes both reasons', () => {
736+
// Nothing in the gate makes the two exemptions exclusive, so the cell is
737+
// built by appending both notes rather than choosing between them. Pinned
738+
// because a renderer that picked one would look identical on every key that
739+
// carries only one.
740+
const md = renderSchemaSection('CacheStats', {
741+
type: 'object',
742+
properties: {
743+
maxAge: {
744+
type: 'number',
745+
description: 'Entries seen in the last 60 seconds',
746+
externalVocabulary: 'HTTP Cache-Control `max-age`',
747+
dimensionless: 'cached entries',
748+
},
749+
},
750+
});
751+
752+
expect(md).toContain(
753+
'Entries seen in the last 60 seconds (unit per HTTP Cache-Control `max-age`) (dimensionless — counts cached entries)',
754+
);
755+
});
756+
757+
it('prints nothing for a key that declares no marker — the note is not decoration', () => {
758+
const md = renderSchemaSection('HealthSignal', withMarker(undefined));
759+
760+
expect(md).toContain('Failures seen in the last 5 minutes');
761+
expect(md).not.toContain('dimensionless');
762+
});
763+
764+
it('prints nothing for an empty or non-string marker — an unverifiable claim publishes nothing', () => {
765+
// The gate refuses these too (they exempt no key), so the page must not
766+
// name a count the contract never declared. Held on the SAME inputs from
767+
// both sides so the two halves cannot drift into disagreeing about what
768+
// counts as a declaration.
769+
for (const marker of ['', ' ', 42, null, { counts: 'failed attempts' }]) {
770+
const md = renderSchemaSection('HealthSignal', withMarker(marker));
771+
expect(md, `marker ${JSON.stringify(marker)}`).not.toContain('dimensionless');
772+
}
773+
});
774+
});

0 commit comments

Comments
 (0)