Skip to content

πŸŽ™οΈ task - fix(introspection): z.custom 500s the debug surface β€” pass unrepresentable: 'any' (distinct root from #17)Β #22

Description

@uladkasach

πŸ¦«πŸŽ™οΈ dispatch to foreman

πŸ’§ task enqueued
   β”œβ”€ priority = ?
   β”œβ”€ yieldage = ?
   └─ leverage = ?

title
fix(introspection): z.custom 500s the debug surface β€” pass unrepresentable: 'any' (distinct root from #17)
description

.what

the introspection middleware renders a handler's schema with zod's default
unrepresentable: 'throw'. any schema that carries a z.custom therefore 500s the introspection
route.

ask: pass unrepresentable: 'any'. a schema that cannot be rendered should degrade to any in a
debug surface, never crash it.

.why

on ahbode/svc-gateway, introspection 500s at prep on all 75 rest handlers β€” the whole debug
surface, and the failure lands at the one moment a developer reaches for it.

the mechanism is one option:

z.toJSONSchema(schema)                              // default: unrepresentable: 'throw'
z.toJSONSchema(schema, { unrepresentable: 'any' })  // degrades instead

z.custom has no json-schema rendition, by construction. so any consumer whose domain carries a
branded or custom-validated type β€” a uuid brand, an iso-price words type, a domain-object hydrator β€”
hits this the moment introspection is enabled. that is not an edge case; it is the ordinary shape of
a typed domain.

.the severity argument, which is the whole point

introspection is a diagnostic. its entire value is to be reachable when the rest of the service is
not. a diagnostic that throws on a legitimate input is worse than an absent one, because:

  • it reads as an outage of the service rather than a limit of the renderer
  • it fails service-wide, not per-handler β€” one z.custom anywhere in a handler's schema takes the
    route down for that handler, and a shared domain type takes down all of them
  • the thrown error names zod's internals, not the schema key at fault, so the 500 says no word about
    which type could not be rendered (rule.require.errors-name-the-fix)

'any' is the correct default for this surface specifically. a {} in the rendered schema is a true
statement β€” this input is not describable in json-schema β€” and it keeps the other 40 keys legible.

.the ask, in preference order

  1. unrepresentable: 'any' in the introspection middleware. one option, and it closes the class
  2. better, name what degraded: emit the key path alongside, so a reader of the rendered schema can
    see which field fell back to any rather than guess
  3. if a consumer genuinely wants the strict behavior, expose it as an opt-in β€” but the safe path
    should be the default one (rule.require.safe-by-default)

.not a duplicate of #17

#17 is "introspection crashes on an input schema that carries a .transform()". adjacent, and a
different root:

#17 this
the input at fault a .transform() a z.custom
the mechanism the transform branch of the renderer zod's unrepresentable default
the fix handle the transform pass one option

a fix to either leaves the other live. they are worth a shared acceptance test β€” a schema that
carries both β€” but they are two repairs.

.acceptance

.provenance and bounds

verified: the 500 at prep across the rest surface, observed; that our half of the repair is
complete and must not change; and that z.custom is the trigger.

not verified: whether unrepresentable: 'any' alone also resolves #17's transform case β€” the
transform branch of the renderer was not read, so the two are filed apart rather than merged on a
guess.

.reference

ahbode/svc-gateway β€” .route/v2026_08_11.declapract.upgrade/3.2.reflect.test.defects.yield.md Β§D22.

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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions