ADR-089
nika:json_diff jq-subsume feasibility · REJECTED · keep RFC-6902 Patch as canonical output format
rejected · 2026-05-27 · cites 5
0ADR-089 · `nika:json_diff` jq-subsume feasibility · REJECTED #
Context #
Operator-led Rams audit 2026-05-27 of the canonical 22-builtin set
(post-ADR-086/087/088 collapses) identified nika:json_diff as one of
the residual data builtins · question to answer · « can jq subsume
json_diff like it subsumed map/filter/group_by/json_merge? »
This is the same Rams test that drove the 42 → 22 cascade · the canonical razor « a nika:builtin is justified SSI NOT expressible in jq+CEL+Schema · IS the logic behind the D-N6 collapse 42→26 + ADR-086 csv_to_json cut + ADR-088 4-introspection collapse ».
Per Internal · cross-source-validation §2.6 Signal 3 · single-source jq research insufficient · this ADR documents the primary-source verification (jq capability matrix) and the disposition.
Decision #
REJECTED · keep `nika:json_diff` as canonical builtin emitting RFC-6902 JSON Patch.
Rationale · what jq CAN do #
jq is Turing-equivalent for JSON transformation. A recursive walk over two documents to find differences IS expressible · the rough shape ·
# Compare two documents at $base and $patch · recursive · produces# {path, base, patch} tuples for each difference (one possible shape)def diff($base; $patch; path): if ($base | type) == "object" and ($patch | type) == "object" then (($base | keys) + ($patch | keys) | unique)[] as $k | diff($base[$k]; $patch[$k]; path + [$k]) elif $base != $patch then { path: path, before: $base, after: $patch } else empty end ;diff(.[0]; .[1]; [])Capability-wise · jq CAN walk + compare + emit difference tuples. The test « is NOT expressible in jq+CEL+Schema » FAILS at the capability layer for ARBITRARY difference reporting.
Rationale · what jq CANNOT do (the actual cut) #
The justification for nika:json_diff is NOT « jq can't diff » · it's
« jq cannot emit RFC-6902 JSON Patch (the canonical interop format) ».
RFC 6902 is the canonical IETF standard for JSON document diffs · shape ·
[ { "op": "replace", "path": "/foo/bar", "value": 42 }, { "op": "add", "path": "/baz", "value": "new" }, { "op": "remove", "path": "/qux" }]The 6 operations (add · remove · replace · move · copy · test)
have semantic rules · particularly ·
replacevsadddiscrimination · based on key existence in the base · jq doesn't track « did this key exist in the original document » in a way that survives the recursive walkremoverequires the absence in patch (jq can express absence-of-key viahasbut threading it through nested recursion is verbose · ~30+ lines of jq for a correctness-equivalent emitter)move+copyrequire structural pattern detection (was this add+remove pair semantically a move?) · jq has no notion of « detect this pair as one logical operation »testis a runtime assertion semantic · belongs to the consumer
A 30+ line jq diff emitter that gets RFC 6902 SEMANTICS right is ·
- Not « less but better » · longer than the 22-builtin emit shape it would replace · violates Rams 10 spirit
- Cookbook anti-pattern · Internal · the « one obvious way » validator canon (D-N9/N10) requires authors to NOT memorize 30-line jq programs · prefer the canonical builtin
- Interop loss · the RFC 6902 output format is the contract
downstream tools (JSON Patch apply libraries · OpenAPI diff tooling ·
GraphQL change-set encoders) expect. Emitting a custom
{path, before, after}shape via jq breaks interop · forces every consumer to write a translator
The other 4 introspection razor sister tests #
Same razor applied to ADR-086/087/088 · those COLLAPSED because the collapsed forms WERE strictly better (Rams 10) ·
- ADR-086
csv_to_json → convert· was 1 narrow direction · convert exposes 4 formats × 3 directions = 12 capabilities · strictly more power · same surface - ADR-087
sleep + wait_until → wait· the two former builtins were artificially split by mode (relative vs absolute) · unified mode arg IS the « one obvious way » - ADR-088
4 introspection → inspect view:· the 4 were artificially split by query-shape · unified view-discriminator IS the « one obvious way »
The json_diff case is different · the alternative (« express via
jq ») would be LESS obvious (30+ lines vs 1 builtin call) AND
LESS interoperable (custom diff shape vs RFC 6902 standard). Rams
test FAILS in both directions.
Disposition #
KEEP `nika:json_diff` as canonical builtin ·
diff: invoke: tool: "nika:json_diff" args: base: ${{ vars.original }} patch: ${{ vars.modified }} # output · RFC 6902 JSON Patch arrayOutput shape · canonical RFC 6902 Patch array per IETF spec ·
downstream-interop-clean.
Canonical 22-builtin count UNCHANGED post this ADR · the 5-layer Rams
symmetry (fetch+extract · jq · convert · wait · inspect) holds ·
json_diff stands as the « jq can't diff to a canonical format »
exception · same shape as json_merge_patch (RFC 7396 null-delete · jq
can't) · same shape as validate (JSON Schema · jq is not a
validator).
Future revisit triggers (LOCK-031 strict) #
Per Internal · round-1-ship-pattern §6 LOCK-031 spirit · documented triggers that COULD reopen this disposition ·
- Trigger · What it would mean · Likelihood
- Cookbook adopts a `jq '... diff ...'` recipe with ≥3 successful invocations · Authors prefer jq-form over builtin · razor shifts · LOW · 30+ line jq programs are off-doctrine
- RFC 6902 deprecated by IETF · Output-format interop argument weakens · NEAR-ZERO · RFC is stable since 2013 · widely deployed
- jaq ships an `--rfc6902` flag or canonical `diff` filter · jq becomes the canonical RFC 6902 emitter · LOW · would require upstream jaq change
- Operator-explicit `« cut json_diff »` signal · The ultimate trigger · same shape as ADR-086 · OPEN
Per Internal · cross-source-validation §2.6 Signal 3 · this ADR is the canonical anti-promotion gate · any future « should we cut json_diff? » discussion routes here.
Cross-source verification (per §2.6 Signal 3 + §2.7.1 cascade-completeness) #
Primary-source verified 2026-05-27 ·
- jaq source ·
jaq/jaq-json/src/lib.rs· NO nativedifforrfc6902filter. The capability is expressible (Turing-equivalent) BUT no canonical output-format emitter shipped. - jq manual ·
jq-1.7.1manual at https://jqlang.org/manual/ · nodiffbuiltin · no RFC 6902 support in stdlib. - RFC 6902 · IETF spec stable since 2013 · 6 ops canonical ·
widely supported by JSON Patch libraries (e.g.
serde_json_patchin Rust ecosystem). - Cookbook signal · NIKA_COOKBOOK-v0.1.md · zero recipes that express diff-via-jq · the « one obvious way » authoring contract would reject 30+ line jq programs.
Bucket A (already canon) · nika:json_diff was a D-N5 KEEP · this ADR
formalizes the KEEP rationale.
Cohérent #
- ADR-082
nika: v1envelope · 22 builtins canonical · ADR-089 holds count - ADR-084 catalog-spec reconciliation ·
json_diffstays in catalog - ADR-085 schemars codegen ·
json_diffenum variant stays - ADR-086/087/088 · 5-layer Rams symmetry ·
json_diffis the canonical exception that proves the « jq can't do everything » rule - Per Internal · cross-source-validation §2.6 Signal 3 verification
- Per Internal · cognitive-design-canon P4 cognitive load · 1-line builtin call beats 30-line jq program
Update log #
2026-05-27 v1.0 — Initial ADR · disposition REJECTED (keep nika:json_diff) · Trigger · operator Rams audit 2026-05-27 evening · post ADR-086/087/088 5-layer symmetry close · « can jq subsume json_diff like jq subsumed json_merge? » · Verification · primary-source jaq + jq + RFC 6902 IETF · capability YES (Turing-equivalent walk) · output-format interop NO (no canonical RFC 6902 emitter) · Decision · keep `nika:json_diff` · 30+ line jq programs violate Rams 10 less-but-better · violate cookbook one- obvious-way contract · break RFC 6902 interop · Canonical 22-builtin count UNCHANGED · 5-layer Rams symmetry (fetch+extract · jq · convert · wait · inspect) holds · json_diff stands as the « jq can't diff to a canonical format » exception · Future revisit triggers documented per LOCK-031 spirit · cookbook signal · RFC deprecation · jaq native diff filter · operator explicit cut · Cohérent ADR-082 envelope + ADR-084/085/086/087/088 + Internal · cross-source-validation §2.6 + cognitive-design P4 + one-obvious-way validator canon D-N9/N10 · 5 raisons coherence ✅✅✅✅✅read at v0.107.0 · the decision record ships with the engine