Skip to content

GroupingConfigSchema.fields describes "(supports up to 3 levels)" but the gate is .min(1) with no upper bound — 50 levels parse green #7084

Description

@os-project-manager

Finding from the axis-① .describe() sweep (describe claims vs measured acceptance face — the #6762 class). Recorded unassigned; suggest domain:spec-surface for routing.

Anchor

packages/spec/src/ui/view.zod.ts, GroupingConfigSchema:

export const GroupingConfigSchema = lazySchema(() => strictObject({
  surface: 'this grouping configuration',
  history: VIEW_HISTORY,
}, {
  fields: z.array(GroupingFieldSchema).min(1).describe('Fields to group by (supports up to 3 levels)'),
}).describe('Record grouping configuration'));

Described claim

"Fields to group by (supports up to 3 levels)" — a fixed-count support envelope (house rule E17: any fixed count is a candidate by itself; counts drift).

Measured acceptance face

Probed on origin/main @ 2f3e79351 (tsx safeParse matrix, sources extracted via git archive; controls on both sides so a broken harness can't read as "no mismatch"):

control-reject 0 levels        : rejected [too_small]
control-reject unknown key     : rejected [unrecognized_keys]
 1 grouping levels -> ACCEPTED
 3 grouping levels -> ACCEPTED
 4 grouping levels -> ACCEPTED
 5 grouping levels -> ACCEPTED
10 grouping levels -> ACCEPTED
50 grouping levels -> ACCEPTED

The gate is .min(1) with no upper bound — the "3" exists only in the sentence.

Nothing downstream enforces it either, measured on the same checkout:

  • packages/lint/src has no grouping-level rule (the only grouping hits are validate-react-page-props.ts nested-key plumbing and org-axis prose).
  • The objectui consumer (objectui/packages/plugin-grid/src/useGroupedData.ts, buildLevel) recurses over all configured fields — if (depth >= fields.length) return [] is the only stop; no slice, no depth cap.

Why it matters for an authoring reader (ADR-0033)

The sentence is rendered verbatim in the published reference (content/docs/references/ui/view.mdx, the GroupingConfig.fields row). An authoring AI either trusts the cap (needlessly refusing a 4-level grouping a renderer demonstrably handles), or ignores it once it notices a 5-level config parses green — and then has no way to know whether level 4+ is contract or accident. Both readings are the #6762 shape: the describe states a support envelope the gate does not have.

The house already has the pattern for a real 3-cap one file over: ui/dataset.zod.ts include claims "max 3 hops" and enforces it.refine((p) => p.split('.').length <= 3, { message: 'include path exceeds the 3-hop limit (ADR-0071)' }). GroupingConfigSchema has the claim without the refine.

Suggested shape, if triage wants it fixed

Decide which side is true and make the other match:

  • if 3 is the intended contract, add .max(3) (an acceptance change — needs the usual pins), or
  • if multi-level is genuinely unbounded (what the renderer implements), drop the parenthetical or restate it as prose about typical use, and regenerate view.mdx.

Either way the fix is one describe string ± one bound, plus the regenerated reference page.

Refs

#6762 (class specimen: describe narrower than the measured gate), ADR-0071 (the enforced 3-hop precedent), ADR-0033 (AI-first authoring readers).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions