Expand description
Recursive forbidden meta-key scan (SPEC §6) — the runtime half of the
global rejection that the meta-schema’s propertyNames enforces statically.
R1: the types/parser PRESERVE every key of the open buckets (round-trip), so
blocking forbidden meta keys is the VALIDATION layer’s job, not the model’s.
The typed models (FieldSpec + ExtraMap) only reject forbidden keys at the
top level, as unknown keys inside the closed buckets, and one level under open
buckets — a deeply nested meta key (validate.required.if,
options.x.display_switch, …) leaks through. This scan closes that leak: it
walks the COMPOSED single spec (after $ref/$patch expansion and x-strip)
to ARBITRARY depth and rejects a forbidden key found at ANY depth — including
one level under a slot/bucket body.
Placement (SPEC §2 pipeline): this runs in the spec LOAD path, immediately
after compose expansion and before validation entry. A hit is therefore a LOAD
failure (ComposeLoadError, code FORBIDDEN_META_KEY) — the spec never comes
into existence — never a valid:false validation result.
Forbidden set (SPEC §6): the enumerated FORBIDDEN_META_KEYS
(condition-only / retired / magic-symbol meta keys) PLUS the x{key} comment
family (any x-prefixed key). $ref/$patch are NOT forbidden — compose
already consumed them, so they do not survive to here; x{key} IS strip-
eligible, so any x{key} that survives to this scan is rejected (the strip
belongs to the meta-schema; survival means it was not stripped).
Byte-for-byte with the JS reference (packages/validator/ts/src/ forbidden-scan.ts); the shared 4-language fixture
tests/fixtures/spec-validity/cases.json is the single source of truth.
Functions§
- scan_
forbidden_ keys - Recursively scan a composed single spec for any forbidden meta key at any
depth. Returns
Err(ComposeLoadError { code: FORBIDDEN_META_KEY })on the first hit, with the dotted path to the offending key inmessageandtrace.