Skip to content

A MISSPELLED format name in a json_schema validation rule still enforces nothing — format: 'emial' is logged-and-dropped, at runtime AND at publish #5178

Description

@os-zhuang

Found while implementing #5029 (PR pending) — filed unassigned, deliberately not fixed there. #5029's ruling was "register ajv-formats so format is enforced"; this is the residual half that registration does not close, and #5029 pins the current behaviour by test rather than changing it.

What is still unenforced

After #5029 the runtime's shared ajv is new Ajv({ allErrors: true, strict: false }) + addFormats(ajv), so format: 'email' now really rejects not-an-email. But strict: false is also what makes an unrecognised format name a non-event: ajv logs one line at compile time and drops the keyword.

Reproduced with the repo's own ajv + ajv-formats and the runtime's own options:

$ node -e "const Ajv=require('ajv'); const addFormats=require('ajv-formats');
  const ajv=new Ajv({allErrors:true,strict:false}); addFormats(ajv);
  const v=ajv.compile({type:'object',properties:{e:{type:'string',format:'emial'}}});
  console.log('validates {e: zzz} =', v({e:'zzz'}));"
unknown format "emial" ignored in schema at path "#/properties/e"
validates {e: zzz} = true

So an author who types emial, e-mail, uri-reference (real) vs urireference (not), datetime vs date-time, ipv_4, … gets a rule that is declared, appears in the metadata, runs on every write, enforces type / required — and enforces nothing for the keyword they actually wrote. Same declared-but-not-enforced shape as #5029 itself, with the trigger moved from "we forgot the plugin" to "the author made a typo".

Why the #4762 publish gate does not catch it either

packages/lint/src/validate-rule-compilability.ts compiles each json_schema rule with the same ajv environment the runtime uses — deliberately, and #5029 extended that parity to the ajv-formats registration. A schema whose format name the runtime silently drops is therefore a schema the gate also compiles happily. That is correct for that gate (a gate inventing a verdict the runtime does not share is the strict: true mistake in a different hat), and it is why this needs its own decision rather than a tweak there.

Why this is worth a decision rather than a shrug

A misspelling is the single most likely authoring mistake in a hand-written or AI-generated JSON Schema, and it is invisible: nothing in the metadata, the UI, or a test run says the constraint is inert. The failure is also silent in the direction that matters — the record is accepted. And it is decidable statically: the set of registered format names is knowable from the metadata alone, with no record in hand, which is exactly the property #4762 used to argue for an authoring-time gate.

Options

  1. New publish-gate rule (validation-rule-json-schema-unknown-format): the gate walks the schema, collects every format value, and errors on any name not in the registered ajv-formats set — naming the rule, the object, the JSON pointer, and the nearest match. Keeps the runtime untouched, keeps runtime/gate compile parity intact (this is a separate judgement layered on top, not a different compile), and rejects before deployment. Cost: the gate must enumerate the registered format set, which couples it to the plugin version.
  2. strict: 'log' → throw on unknown format in the runtime. ajv's strictSchema/validateFormats knobs can make an unknown format a compile error. Rejects the same class, but at runtime and fail-open (the rule is skipped and logged, per checkJsonSchema), i.e. it converts one silent gap into another — and strict: false is load-bearing for vendor keywords, so this must not be done by loosening that dial globally.
  3. Do nothing but document it (the status quo A json_schema validation rule's format keyword is silently IGNORED — ajv runs without ajv-formats, so format: 'email' enforces nothing #5029 pinned).

Acceptance sketch

Blocked-by: #5029 (the registered format set only becomes well-defined once the runtime registers ajv-formats; the pin this issue proposes to flip lives in that PR's tests).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions