Skip to content

feat(lint-meta): schema-enum-field-consistency — keep the generated API client precise#306

Merged
agjs merged 2 commits into
mainfrom
feat/schema-enum-field-consistency
Jul 17, 2026
Merged

feat(lint-meta): schema-enum-field-consistency — keep the generated API client precise#306
agjs merged 2 commits into
mainfrom
feat/schema-enum-field-consistency

Conversation

@agjs

@agjs agjs commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What

A new lint:meta rule (schema-enum-field-consistency, source-text category): a fixed-value TypeBox field — one typed t.Union([t.Literal(...)]) in any schema in a *.schemas.ts file — must be typed the same way in every schema in that file. Widening it to t.String() (typically in the response schema) fails the gate.

Why (real failure it prevents)

The response schema becomes the OpenAPI spec, which generate:api turns into the UI's typed client — the single source of truth for the UI's API types. If status is t.Union([t.Literal("todo"),…]) on create/update but t.String() on the response, the generated client types the response status as string. The UI (which expects the enum) can't reconcile string with "todo"|"doing"|"done".

Observed on a live autonomous build: a TaskFlow feature had exactly this drift (status/priority enums on input, t.String() on TaskResponse). The type clash sent the builder into near-green oscillation — it reached 1 error, misread the clash as "spec not regenerated", reverted its UI to stubs, and thrashed for 82 cycles before parking. This rule turns that silent drift into a clear, actionable gate error so the fix is "tighten the schema" (define the enum once, reuse it everywhere), never "delete the UI".

How

  • Text-based (whitespace-collapsed regex), matching the existing source-text rules — no new AST tooling.
  • t.Union([t.String(), t.Null()]) (a nullable string) is deliberately not treated as an enum field.
  • Scoped to src/**/*.schemas.ts so fixtures/tests aren't linted.
  • Pure analyzer inconsistentEnumFields(src) unit-tested (drift, clean-via-shared-enum, nullable-string-not-flagged, path-filter).
  • RULES.md regenerated. No violations on the current API source.

bun run check green; 116/116 lint-meta tests pass.

agjs added 2 commits July 17, 2026 12:56
…PI client precise

A fixed-value TypeBox field (typed t.Union([t.Literal(...)]) in any schema in a
*.schemas.ts file) must be typed the SAME way in every schema in that file — never
widened to t.String(), typically in the response schema.

Why it's a gate error: the response schema becomes the OpenAPI spec, which generate:api
turns into the UI's typed client — the single source of truth for the UI's API types. A
status field that's an enum on input but t.String() on the response makes the generated
client type it `string`; the UI (expecting the enum) then can't reconcile the two, and a
weak model "fixes" the clash by reverting/stubbing the feature instead of tightening the
schema. Observed live: a TaskFlow build oscillated near-green for 82 cycles and parked,
because task status/priority were t.Union([t.Literal…]) on create/update but t.String()
on TaskResponse. This rule fails the build on that drift so the model must fix the schema
(define the enum once, reuse it everywhere), not delete its UI.

Text-based (whitespace-collapsed), matching the other source-text rules; scoped to
src/**/*.schemas.ts so fixtures/tests aren't linted. Pure analyzer (inconsistentEnumFields)
unit-tested; RULES.md regenerated; no violations on the current API source.
@agjs
agjs merged commit 4ccd16a into main Jul 17, 2026
29 checks passed
@agjs
agjs deleted the feat/schema-enum-field-consistency branch July 17, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant