From 9af1f7cf6f130b66e7aa8eef6bb27113e5de71d2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 08:44:29 +0000 Subject: [PATCH] test(spec): alias-integrity fails an alias row a guidanceSet already consumes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `strictUnknownKeyError` consults `guidance`, then `guidanceSets`, and only then the `aliases` rename fallback — a guidanceSets match `continue`s past the alias lookup entirely. An alias row whose written key also matches a guidanceSet on the same strict-options table is therefore dead on arrival, and nothing checked that until now. Extends alias-integrity.test.ts with `unreachableAliasRows`, which reads only `options.aliases` / `options.guidanceSets` (never `.shape`) and flags any alias key a guidanceSet already matches. Two tests: the live-table verdict (also pinning that the one existing view-family alias, `disabled -> readonly` on FormFieldSchema, stays reachable outside VISIBILITY_KEY_PATTERN), and a self-test proving the check can go red on a planted synthetic dead row without touching any live schema. Fixes #7889 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WocN37om5bw81JDoEEMA2e --- .../spec/src/shared/alias-integrity.test.ts | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/packages/spec/src/shared/alias-integrity.test.ts b/packages/spec/src/shared/alias-integrity.test.ts index 20065700d5..f90b8c47b5 100644 --- a/packages/spec/src/shared/alias-integrity.test.ts +++ b/packages/spec/src/shared/alias-integrity.test.ts @@ -657,6 +657,56 @@ const PROSE_TARGET_SURFACE = /^this `[a-z]+` navigation item$/; const isProseTarget = (surface: string, target: string): boolean => PROSE_TARGET_SURFACE.test(surface) && PROSE_ALIAS_TARGETS.has(target); +/** + * Alias rows made UNREACHABLE by a `guidanceSet` in the same strict options + * table (#7889). + * + * `strictUnknownKeyError` (`suggestions.zod.ts`) consults three channels per + * unrecognized key, in order: exact `guidance`, then `guidanceSets`, and only + * THEN the `aliases` rename fallback. A `guidanceSets` match `continue`s past + * the alias lookup entirely — it never runs for that key. So an alias row + * whose WRITTEN key also matches a guidanceSet declared on the same table is + * dead on arrival: the set answers first, every time, and the alias entry + * reads as coverage for a spelling nobody is actually helped with. + * + * Not hypothetical: PR #7884 had to place its one view-family alias + * (`disabled → readonly`, `ui/view.zod.ts`) OUTSIDE `VISIBILITY_KEY_PATTERN`'s + * reach by hand, because nothing checked it — a `visible: 'visibleWhen'` or + * `showWhen: 'visibleWhen'` row on the same table would have shipped exactly + * as dead, with every existing gate green (#7889). + * + * The pattern-vs-list asymmetry the checks above already draw — an enumerated + * `keys: string[]` can be judged against the DECLARED shape, a `keys: RegExp` + * cannot (`VISIBILITY_KEY_PATTERN` deliberately also matches the canonical + * `visibleWhen`, which the shape declares and which therefore never reaches + * either channel) — does not apply here. An alias's written key is a concrete + * authored string, not a member of the shape, so it can be tested against a + * pattern set exactly as a real rejected key would be: `keySetMatches` is the + * same function `strictUnknownKeyError` itself calls. + * + * Reads only `options.aliases` and `options.guidanceSets` — never `.shape` — + * so it composes with (and does not duplicate) the declared-key checks above. + */ +function unreachableAliasRows(surfaces: readonly StrictObjectDeclaration[]): string[] { + const dead: string[] = []; + for (const s of surfaces) { + const sets = s.options.guidanceSets ?? []; + if (sets.length === 0) continue; + for (const [written, target] of Object.entries(s.options.aliases ?? {})) { + const set = sets.find((candidate) => keySetMatches(candidate, written)); + if (set) { + dead.push( + `"${s.options.surface}": alias \`${written}\` -> \`${target}\` is unreachable — ` + + `guidanceSet \`${set.name}\` already matches \`${written}\` and consumes it before ` + + 'the alias table is ever consulted (drop the alias row, or fold ' + + `\`${written}\` into \`${set.name}\`'s prescription instead)`, + ); + } + } + } + return dead; +} + describe('alias integrity — every table is a true claim about its schema', () => { it('no alias key is itself a declared key (a dead entry that can never fire)', () => { // An alias is consulted only from the `unrecognized_keys` path. A key the @@ -827,6 +877,89 @@ describe('alias integrity — every table is a true claim about its schema', () expect(broken.sort()).toEqual([]); }); + it('no alias row is dead on arrival because a guidanceSet in the same table already consumes it (#7889)', () => { + // The live-table verdict. If this ever turns red on a real schema, the fix + // is at the authoring site (drop the row, or fold the key into the set's + // prescription) — never here, and never a change to the predicate that + // makes the row stop being found. + expect(unreachableAliasRows(SURFACES).sort()).toEqual([]); + + // The acceptance shape's second half: the one existing view-family alias + // row (`disabled -> readonly` on `FormFieldSchema`, `ui/view.zod.ts:1775`) + // is confirmed to exist and to sit on `VISIBILITY_STRICT_OPTIONS`'s + // `VISIBILITY_KEY_PATTERN` table — so the assertion above is judging the + // real, live, non-trivial case (a table that DOES carry a pattern-matching + // guidanceSet), not an empty one that would pass vacuously. + const visibilityTablesWithThisAlias = SURFACES.filter( + (s) => + s.options.aliases?.disabled === 'readonly' + && (s.options.guidanceSets ?? []).some((set) => set.name === 'VISIBILITY_KEY_PATTERN'), + ); + expect( + visibilityTablesWithThisAlias.length, + 'the FormFieldSchema `disabled -> readonly` row should exist and carry VISIBILITY_KEY_PATTERN', + ).toBeGreaterThan(0); + }); + + it('the guidanceSet-reachability check can actually go red — a planted dead row, no live schema touched (#7889)', () => { + // Self-test, per the triage ruling: prove the gate can fail before trusting + // that it passing on the live table means anything. Entirely synthetic — + // `unreachableAliasRows` only reads `options.aliases` / `options.guidanceSets`, + // so `shape` is never inspected and can be an empty stand-in. + const emptyShape = {} as StrictObjectDeclaration['shape']; + + const planted: StrictObjectDeclaration[] = [{ + options: { + surface: 'synthetic reachability probe', + history: 'n/a — planted for #7889 self-test', + aliases: { visibleIf: 'visibleWhen' }, + guidanceSets: [{ + name: 'SYNTHETIC_VIS_PATTERN', + keys: /vis/i, + examples: ['visibleIf'], + prescription: 'n/a', + }], + }, + shape: emptyShape, + }]; + const dead = unreachableAliasRows(planted); + expect(dead).toHaveLength(1); + expect(dead[0]).toContain('visibleIf'); + expect(dead[0]).toContain('SYNTHETIC_VIS_PATTERN'); + + // Control: the identical alias row with NO guidanceSet on the table is + // reachable — proves the planted row above went red because of the + // pattern match, not because of anything else about the shape. + const reachable: StrictObjectDeclaration[] = [{ + options: { + surface: 'synthetic reachability probe (no set)', + history: 'n/a — planted for #7889 self-test', + aliases: { visibleIf: 'visibleWhen' }, + }, + shape: emptyShape, + }]; + expect(unreachableAliasRows(reachable)).toEqual([]); + + // Second control: a guidanceSet present but NOT matching the alias key — + // proves the row is judged by an actual pattern match, not merely by the + // presence of a guidanceSets array on the table. + const nonMatching: StrictObjectDeclaration[] = [{ + options: { + surface: 'synthetic reachability probe (non-matching set)', + history: 'n/a — planted for #7889 self-test', + aliases: { disabled: 'readonly' }, + guidanceSets: [{ + name: 'SYNTHETIC_VIS_PATTERN', + keys: /vis/i, + examples: ['visibleIf'], + prescription: 'n/a', + }], + }, + shape: emptyShape, + }]; + expect(unreachableAliasRows(nonMatching)).toEqual([]); + }); + it('the three #6416 hand-written maps are FOLDED and judged here — the blind spot stays closed (#6619)', () => { // The reason #6619 existed: `strictVisibilityError`, // `strictWidgetAnalyticsError` and `strictTenancyError` were hand-rolled