ForbiddenScan
in package
Recursive forbidden meta-key scan (SPEC §6) — the runtime half of the global rejection that the meta-schema's `propertyNames` enforces statically.
Byte-for-byte port of validator-ts/src/forbidden-scan.ts.
R1: the types/parser PRESERVE every key (round-trip), so blocking forbidden meta keys is the VALIDATION layer's job, not the model's. The direct audit found that the Go/Rust typed models only rejected forbidden keys at the top level and one level under open buckets — a deeply nested meta key (validate.required.if, options.x.display_switch, …) leaked 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).
Table of Contents
Constants
- FORBIDDEN_SET : array<string, true> = ['display_switch' => true, 'display_target' =>...
- The enumerated forbidden meta keys (SPEC §6), identical to the JS reference FORBIDDEN_META_KEYS: condition-only meta keys, the retired seqtokey/__13hex__, the magic '_' default symbol, the $-composition-overlay keys, and the xclass/xstyle comment literals (the broader x{key} family is matched by pattern, not enumerated). Stored as a value->true map for O(1) membership.
Methods
- scan() : void
- Recursively scan a composed single spec for any forbidden meta key at any depth. Throws ComposeLoadError('FORBIDDEN_META_KEY') on the first hit, with the dotted path to the offending key in the message and trace.
- isForbiddenKey() : bool
- Whether $key is globally forbidden (enumerated literal OR x{key}).
- isXCommentKey() : bool
- Whether $key is an x{key} comment key: a lowercase 'x' followed by at least one more character (xclass, xstyle, xnote, …). The bare key 'x' is not treated as a comment. The authoritative strip belongs to the meta-schema; this is the runtime backstop that rejects an x{key} that survived.
- walk() : void
Constants
FORBIDDEN_SET
The enumerated forbidden meta keys (SPEC §6), identical to the JS reference FORBIDDEN_META_KEYS: condition-only meta keys, the retired seqtokey/__13hex__, the magic '_' default symbol, the $-composition-overlay keys, and the xclass/xstyle comment literals (the broader x{key} family is matched by pattern, not enumerated). Stored as a value->true map for O(1) membership.
private
array<string, true>
FORBIDDEN_SET
= ['display_switch' => true, 'display_target' => true, 'if' => true, 'when' => true, 'show_if' => true, '_' => true, 'seqtokey' => true, '__13hex__' => true, '$after' => true, '$before' => true, '$merge' => true, '$remove' => true, 'xclass' => true, 'xstyle' => true]
Methods
scan()
Recursively scan a composed single spec for any forbidden meta key at any depth. Throws ComposeLoadError('FORBIDDEN_META_KEY') on the first hit, with the dotted path to the offending key in the message and trace.
public
static scan(mixed $spec[, array<int, string> $rootPath = [] ]) : void
The scan descends into every object value AND every array element (a forbidden key nested inside an array of sub-specs is caught too). Map keys are checked before descending into their values, so the reported path points at the shallowest offending key.
Parameters
- $spec : mixed
-
the composed (composition-free, x-stripped) single spec to scan
- $rootPath : array<int, string> = []
-
path prefix for the error trace (default [])
Tags
isForbiddenKey()
Whether $key is globally forbidden (enumerated literal OR x{key}).
private
static isForbiddenKey(string $key) : bool
Parameters
- $key : string
Return values
boolisXCommentKey()
Whether $key is an x{key} comment key: a lowercase 'x' followed by at least one more character (xclass, xstyle, xnote, …). The bare key 'x' is not treated as a comment. The authoritative strip belongs to the meta-schema; this is the runtime backstop that rejects an x{key} that survived.
private
static isXCommentKey(string $key) : bool
Parameters
- $key : string
Return values
boolwalk()
private
static walk(mixed $node, array<int, string> $path) : void
Parameters
- $node : mixed
- $path : array<int, string>