# Requirements-to-Code Gap Analysis — devnw/zig/json **Date:** 2026-06-11 · **Commit:** `3c238be` · Requirements derived from README, docs/, CHANGELOG, ROADMAP, and the public API in `src/json.zig`. ## Test Infrastructure Status | Action | Command | Status | |--------|---------|--------| | Build | `zig build` | PASS | | Unit Tests | `zig build test` | PASS (305/305) | | Acceptance | `zig build test-accept` | PASS (14/14) | | Edge-case | `zig build test-data` | PASS (34/34) | | Schema Suite | `zig build test-schema` | FAIL (1/38) — `testdata/JSON-Schema-Test-Suite/` empty (GAP-012) | | All | `zig build test-all` | FAIL (354/391) — solely due to missing schema-suite fixtures | ## Status Summary | Status | Count | |--------|-------| | Implemented | 15 | | Partial | 8 | | Not Implemented | 2 | | Unknown | 0 | ## Findings Table | Gap-ID | Requirement / Feature | Status | Severity | Location | |--------|----------------------|--------|----------|----------| | GAP-001 | ECMA-262 regex: alternation `\|` and grouping `()` | Resolved (implemented) | Critical | `src/regex_ecma262.zig` | | GAP-002 | Schema `$ref` depth-exceed silently returns valid (no `RefDepthLimit` error) | Partial | High | `src/schema.zig:1058` | | GAP-003 | Streaming tokenizer ignores `max_depth` / `max_string_len` options | Partial | High | `src/tokenizer.zig:65-68` | | GAP-004 | ECMA-262 `\s`/`\S` missing form-feed, vertical-tab, NBSP, other Unicode whitespace | Partial | Medium | `src/regex_ecma262.zig:146-153` | | GAP-005 | JSONPath function extensions absent (`length()`, `match()`, `search()`, `value()`, `count()`, `keys()`) | Partial | Medium | `src/jsonpath.zig` | | GAP-006 | SIMD utilities never wired into parser or tokenizer (dead code) | Resolved (implemented) | Medium | `src/simd.zig`, `src/parser.zig` | | GAP-007 | ROADMAP/docs advertise `BatchProcessor.findAllStructurals()` which does not exist | Not Implemented | Low | `src/simd.zig`, `docs/ROADMAP.md:728` | | GAP-008 | `AsyncIterator.next()` is a stub returning `null` unconditionally | Partial | High | `src/async.zig:1054-1057` | | GAP-009 | JSON-LD `@import` context keyword silently skipped; blank-node counter is global mutable state | Partial | Medium | `src/jsonld.zig:619-680,970` | | GAP-010 | JSON-LD remote context loading is a no-op placeholder | Partial | Medium | `src/jsonld.zig:282-288` | | GAP-011 | `parallel.zig` tail-element drop: floor-division chunk math leaves up to 63 elements unprocessed; uninitialized reads | Partial | High | `src/parallel.zig:89,102-113,193,262` | | GAP-012 | JSON Schema official Test Suite fixtures missing; 37/38 `test-schema` fail with `FileNotFound` (the fixtures must be fetchable, and a run without them must not pass) | Not Implemented | High | `testdata/JSON-Schema-Test-Suite/` (empty); `tests/schema_suite.zig` | ## Notes - GAP-001/004 overlap security ASR-020 and quality QUAL-026 (regex correctness). GAP-002 overlaps ASR-021. GAP-003 overlaps ASR-005/ASR-014/QUAL-006. GAP-006/007 overlap QUAL-001/PERF-001. GAP-011 overlaps ASR-013/PERF-009. These are tracked once and closed together by module. - GAP-012 fix: fetch the suite with `scripts/fetch-json-schema-test-suite.sh`, the pinned-SHA clone CI also runs. Without it, `zig build test-schema` and `test-all` fail and name that command. SONNY-2426 replaced the original fix, which skipped the suite gracefully and let `test-all` pass vacuously. --- ## Resolution Status (updated 2026-06-11, commit 458bf24) All gaps were addressed across two review/fix passes. Verification: `zig build test-all` = 14/14 steps, 475/512 pass (at the time, 37 schema-suite tests skipped when the external suite was absent). Since SONNY-2426, `test-all` fails without the suite and names `scripts/fetch-json-schema-test-suite.sh`. With the suite, any failing case or unexpected skip fails the step. | Gap-ID | Status | Resolution | |--------|--------|-----------| | GAP-001 | Resolved (implemented) | Regex alternation `\|` and grouping `()` are now fully implemented as a Pike NFA (replacing the fail-closed subset), with bounded repetition, a total-instruction cap, a parser depth bound, and a global step budget. JSON Schema `pattern`/`patternProperties` alternation now validates correctly. Groups are non-capturing; capture-group *extraction* remains deferred (not required by `pattern` validation). | | GAP-002 | Resolved | `$ref` depth-exceed records an error and reports invalid. | | GAP-003 | Resolved | Tokenizer enforces `max_depth`/`max_string_len`. | | GAP-004 | Resolved | `\s`/`\S` include form-feed and vertical-tab. | | GAP-005 | Resolved (fail-closed) | Unknown JSONPath filter predicates now fail closed. RFC 9535 function extensions deferred (disclosed in ROADMAP/API). | | GAP-006 | Resolved (implemented) | SIMD whitespace/string scanners (`skipWhitespaceSIMD`/`scanStringSIMD`) are now wired into the strict parser's hot path — a no-escape string fast-path (single `dupe`) plus SIMD whitespace skipping, alongside small-object linear key dedup (~44–52% higher string/object parse throughput). | | GAP-007 | Resolved | Removed the nonexistent `findAllStructurals` doc example. | | GAP-008 | Resolved | `AsyncIterator.next` implemented. | | GAP-009 | Resolved | `@import` resolved/merged; blank-node counter per-instance. | | GAP-010 | Resolved (fail-closed) | URL contexts with no loader return an error; built-in network loader remains out of scope by design. | | GAP-011 | Resolved | Parallel chunking covers the full input. | | GAP-012 | Resolved (reworked by SONNY-2426) | `scripts/fetch-json-schema-test-suite.sh` fetches the suite at the pinned SHA, and CI runs the same script. Without the suite, `test-schema` and `test-all` fail and name that command. With it, any failing case or unexpected skip fails the step; only 5 pinned Unicode-property-escape cases may skip. | ## Deferred / Accepted by Design The following were evaluated and **intentionally not implemented**. They are tracked, justified trade-offs — **not open defects**. None is an untrusted-input memory-safety or DoS exposure (those are closed); each is a feature addition, an unneeded capability, a deliberate safety/speed choice, or a security-motivated no-op. | Item | Disposition | Rationale | Cross-reference | |------|-------------|-----------|-----------------| | **CBOR datetime/bignum tag round-trip** | Deferred — feature | Tag *safety* (depth limit, bounded allocation, valid free) is closed. Preserving tag *semantics* (datetime tag 0/1, bignum tag 2/3) is a feature; decode currently unwraps and discards the tag number. | ROADMAP CBOR section; `src/cbor.zig` | | **Regex capturing-group *extraction*** | Deferred — not needed | Alternation `\|` and grouping `()` are now implemented (Pike NFA); groups are **non-capturing**, which is exactly what JSON Schema `pattern`/`patternProperties` require. No public feature consumes captures. | GAP-001; `src/regex_ecma262.zig` | | **JSONPath RFC 9535 function extensions** | Deferred — feature | `length()`/`match()`/`search()`/`value()`/`count()`/`keys()` are not implemented; unknown filter predicates fail closed. | GAP-005; `src/jsonpath.zig` | | **Several micro-performance items** | Deferred — low value / safety-first | Small allocation/scan overheads, some of which are the deliberate cost of memory-safety fixes (owning number lexemes), or would change the public `Value` layout, or carry correctness risk for negligible gain. The dominant parser/serializer hot paths are optimized. | `PERF_GAP_ANALYSIS.md` | | **Inert, no-network JSON-LD remote loader** | Accepted — by design (security) | Implicit network fetch of attacker-controlled `@context`/`$ref` URLs would be an SSRF primitive. The remote-context loader is intentionally inert; callers opt in via their own allowlisting `document_loader`, or preload contexts into the cache. | GAP-010; `src/jsonld.zig` | | **JSON Patch in-place / copy-on-write** | Deferred — feature | `apply` returns a new value (does not mutate the original); an in-place / COW variant is future work. | `src/patch.zig` | ### Detail - **CBOR datetime/bignum tag round-trip (feature, deferred).** The CBOR decoder handles tags safely (depth limit, bounded definite-length allocation, heap-owned float/NaN/Infinity lexemes), but does not preserve tag *meaning*: a datetime (tag 0/1) or bignum (tag 2/3) is unwrapped to its bare underlying value and the tag number is dropped, so semantics do not round-trip. Tag-aware encode/decode is a future release. - **Regex capturing groups (unneeded, deferred).** The ECMA-262 engine is now a Pike NFA with working alternation and grouping; groups are non-capturing because `pattern`/`patternProperties` only need a boolean match decision. No other public feature exposes captures. Capture extraction is a future enhancement, not a correctness gap for the advertised surface. - **Micro-performance items (deferred, safety-first).** The dominant parser hot paths are optimized (SIMD whitespace/string scan, no-escape string fast-path, small-object linear dedup, removed O(n²) leading-zero strip). The remaining items in `PERF_GAP_ANALYSIS.md` are deliberately deferred: extra small allocations per CBOR float / streamed number are the *cost* of memory-safety fixes (reducing them re-introduces lexeme/digits aliasing — a use-after-free hazard); a `Value`/`Number` layout change would break the public API; and a whitespace line/col popcount carries error-position correctness risk for negligible gain. - **Inert no-network JSON-LD remote loader (accepted by design).** The remote-context loader performs no network I/O. This is a security decision: automatically fetching attacker-controlled `@context` / external `$ref` URLs would be a server-side request forgery (SSRF) primitive. Callers that need remote contexts supply their own allowlisting `document_loader`, or preload contexts into the cache. A URL context with no caller-supplied loader returns an error (fail-closed) rather than silently succeeding.