AGPL-3.0-or-later · forever.

ADR-086

builtin Rams sweep · nika:csv_to_json → nika:convert universal multi-format · less but better

accepted · 2026-05-27 · L0 · cites 7

0

ADR-086 · builtin Rams sweep · nika:csv_to_json → nika:convert #

Context #

Operator-led audit 2026-05-27 of the post-D-2026-05-22-N6 26-builtin set surfaced 4 high-confidence Rams « less but better » consolidation candidates · the operator named nika:csv_to_json specifically as the canonical anti-pattern (single-direction · single-format · vs the fetch+extract super-powerful-with-mode-args canonical exemplar).

Operator verbatim · « faut vraiment réfléchir de manière plus atomique · nika:csv_to_json faudrait plutôt un builtin super puissant qui gère à convertir n'importe quoi · le fetch il est avancé avec extract · les autres ça devrait être pareil · faut pas hésiter à améliorer · réunir · Rams · et chercher les meilleurs crates correspondant ».

The D-N6 stdlib-collapse (42 → 27) + 2026-05-27 json_merge cut (27 → 26) established the precedent · the canonical pattern is « one super-powerful builtin · multi-mode parameters » (jq subsumes ~13 data builtins · fetch subsumes 8 extract modes). This ADR continues the sweep at the format-conversion layer.

Decision #

nika:csv_to_json removed · nika:convert adopted as universal multi-format converter.

`nika:convert` surface #

yaml
invoke:  tool: "nika:convert"  args:    input: "${{ tasks.A.output }}"   # data to convert    from: csv                         # REQUIRED · enum · json | yaml | toml | csv    to: json                          # REQUIRED · enum · json | yaml | toml | csv    has_header: true                  # OPTIONAL · CSV only · default true

Formats v0.1 baseline #

4 formats · json · yaml · toml · csv · 12 directions in scope (4×3 minus identity). Forward-compat · xml · tsv · messagepack etc. ADD on consumer signal per #[non_exhaustive] ratchet (additive MINOR per ADR-002 · amended D-2026-06-20-N1 · real semver toward 1.0).

Engine catalog mutation #

nika-catalog::ALL_BUILTINS (line ~21 in data/builtins.rs) ·

  • DELETE entry · Builtin { name: "csv_to_json", category: Data }
  • INSERT entry (alphabetical · between assert and cost) · Builtin { name: "convert", category: Data }

Sort invariant preserved · convert < cost alphabetically (c-o-n < c-o-s). The 6-existing tests (builtin_count · sorted_order · find_known_builtins · is_known_builtin_works · case_sensitive · all_builtins_have_non_empty_names · category_counts) stay GREEN · total 26 unchanged · 8 Data category unchanged.

Engine trust.rs mutation (per ADR-084 §C precedent) #

TRUST_PROPAGATING_BUILTINS list ·

  • DELETE "nika:csv_to_json"
  • INSERT "nika:convert" (alphabetical · first slot)

Trust class unchanged (csv_to_json was PROPAGATING · convert stays PROPAGATING · pure-data transform · output trust = input trust).

Engine codegen.rs mutation (ADR-085 no-drift gate) #

enum_excludes_legacy_post_d_n6 regression test extends the legacy exclusion list with nika:csv_to_json · 5 → 6 legacy names guarded (the cut-set regression).

Spec mutation (companion · nika/spec submodule) #

Spec ratchet shipped same-arc per the operator-led Rams sweep ·

  • nika/spec/stdlib/builtins-v0.1.md · §1 Data row + §3 Data section rename csv_to_json → convert with full new semantics (formats list · pattern rationale · reference implementation crates · alphabetical position preserved in §3).
  • nika/spec/schemas/workflow.schema.json · invoke.tool oneOf nika branch enum · alphabetical re-sort (convert moves before cost) · count stays 26 · LSP-perfect autocomplete preserved.

Reference implementation (L2 nika-builtin admission · future) #

Primary-source verified candidate crates (crates.io API · 2026-05-27) ·

  • Crate · Version · Updated · Notes
  • `serde-transcode` · 1.1.1 · 2021-07-10 · Canonical orchestrator · sfackler · 15M+ downloads · API-stable (COMPLETE not abandoned)
  • `serde_json` · 1.0 · (already nika dep) · JSON · zero-add
  • `serde_yaml_bw` · 2.5.6 · 2026-05-02 · YAML · modern + maintained · 133k downloads · 2026-current
  • `toml` · 1.1.2 · 2026-04-01 · TOML · spec 1.1.0 compliant · active
  • `csv` · 1.4.0 · 2025-10-17 · CSV · quoting-aware · active

Cohérent internal NUKE-LEGACY Class 2 (git is the archive · zero deprecation alias) · zero-add prefer when an existing dep covers the case · serde_json already in workspace. New deps gated on the L2 nika-builtin admission ceremony per Diamond Rule 2 12-gate.

Consequences #

Positive #

  • Symmetry signal · per « less but better » Rams principle · the fetch+extract pattern · jq subsumption · convert · all the same « one super-powerful builtin · multi-mode args » canonical shape applied at 3 distinct layers (HTTP · data transform · format conversion). The pattern propagates the more it's applied · easier cognitive surface for cookbook authors + LSP consumers.
  • Bidirectional coverage · nika:convert from:csv to:json AND nika:convert from:json to:csv (the latter previously covered only via jq @csv) · single canonical builtin · no « which direction goes through which tool » mental load.
  • Forward extensibility · xml · tsv · messagepack add via enum extension (forward-compat MINOR ratchet per ADR-002 · amended D-2026-06-20-N1 · zero new builtin slot consumed).
  • Trust class preserved · csv_to_json was PROPAGATING · convert stays PROPAGATING · zero security regression.
  • No-drift gate enforced · ADR-085 codegen.rs test enum_matches_catalog_one_to_one AUTO-detects the spec ↔ engine parity · the rename propagates automatically once ALL_BUILTINS mutates · nika_builtin_tool_enum_schema() returns the new 26-set derived at call time.

Negative #

  • Public API breaking · existing workflows using tool: nika:csv_to_json will fail to parse against the spec-26 closed enum + against is_known_builtin("csv_to_json") in engine. Acceptable on forever- v0.x per ADR-002 (NUKE-LEGACY · git is the archive · zero deprecation alias per internal no-legacy doctrine).- Mitigation · the existing nika spec is pre-v1.0 GA · cookbook authors haven't started migration · breakage cost ≈ 0 today. - Mitigation · the trust.rs test `legacy_builtins_unknown_post_d_n6` explicitly catches any code path that still references `"nika:csv_to_json"` · fails closed (uncategorized → Untrusted output).

Neutral #

  • Tests · +1 net new legacy guard in codegen.rs · 8 conformance tests stay GREEN (6 from baseline + 2 spec-26 gates from ADR-084) · legacy exclusion list 5 → 6.
  • L0 layer constraint preserved · the rename is pure data + pure test mutation · zero I/O · zero async · zero L0 violation.
  • Backward graph projection (TTL) · internal monorepo nika- language graph carries 4 csv_to_json refs (instance · Builtin family member-list · raises arc · invokedBy arc) · these update SAME-ARC in the monorepo cascade (separate from this engine ADR).

Alternatives considered #

A · Keep `nika:csv_to_json` · add `nika:yaml_to_json` · `nika:toml_to_json` · etc. #

REJECTED · N² builtin slot explosion · 4 formats × 3 target = 12 builtins for what should be 1 · violates « less but better » Rams principle + the D-N6 anti-anti-pattern. Per existing spec comment « CSV parsing is not jq's job · the reverse direction is jq @csv » · the asymmetric treatment was exactly the friction the operator audit named.

B · Keep `csv_to_json` only · skip the Rams sweep #

REJECTED · operator-led directive (« FAIS · do it ») + the symmetry signal (fetch+extract · jq · now convert · all the same « one super-powerful builtin · multi-mode args » pattern) · skipping leaves the canonical anti-pattern in the spec.

C · Adopt `dasel` library or similar all-in-one Go-origin tool #

REJECTED · dasel is a Go binary · embedding requires unsavory subprocess shenanigans · violates L0 « zero I/O · zero async » + sovereignty Rule 1 + ADR-001 pure-Rust mandate. serde-transcode is the canonical serde-ecosystem Rust solution · primary-source verified.

Gate verification #

Per ADR-084 precedent (data refactor not crate admission · 12-gate reduces to applicable gates) ·

  • ✅ Gate 2 TDD · trust.rs legacy_builtins_unknown_post_d_n6 extended · codegen.rs enum_excludes_legacy_post_d_n6 extended · existing builtins.rs category_counts (Data still 8) preserved · all GREEN.
  • ✅ Gate 3 IMPL · cargo check --workspace GREEN.
  • ✅ Gate 4 CLIPPY · cargo clippy --workspace --all-targets -- -D warnings 0 warnings.
  • ✅ Gate 8 DOCS · cargo doc --no-deps -p nika-catalog -p nika-schema 0 warnings.
  • ✅ Gate 12 ATOMIC · single engine commit + single monorepo bump.
  • cargo fmt --check GREEN · cargo deny check ok.
  • Gates 1 / 5 / 6 / 7 / 9 / 10 / 11 · N/A for data refactor.

Future Rams ratchets (trigger-gated · ADR-087+ reserved) #

3 remaining consolidation candidates from the operator audit · trigger-gated per LOCK-031 spirit · don't pre-build infra ·

  1. `nika:wait` · collapse sleep + wait_until into one temporal primitive with duration: OR until: mode. Net 26 → 25. Trigger · D-lock + operator OUI/NON Bucket-C user-explicit-lock.
  2. `nika:inspect` · collapse 4 introspection builtins (cost · records · dag_info · threads) into one with view: enum. Net 26 →23. Trigger · D-lock + workflow.schema.json oneOf-vs-untyped design decision (return-shape heterogeneity).
  3. `nika:json_diff` jq-subsume feasibility · jq CAN express diff (recipe in audit doc §5) but loses the RFC-6902-canonical-Patch output format. Trigger · cookbook adoption of the jq-diff recipe + operator preference signal.

When all 3 land · 26 → ~21 collapse · same nika: v1 envelope · real-semver-toward-1.0 discipline (ADR-002 · amended D-2026-06-20-N1).

Companion artefacts #

  • nika-catalog/src/data/builtins.rs · ALL_BUILTINS rename (delete csv_to_json · insert convert in alphabetical position).
  • nika-schema/src/trust.rs · TRUST_PROPAGATING_BUILTINS rename + legacy_builtins_unknown_post_d_n6 test extended (+csv_to_json).
  • nika-schema/src/codegen.rs · enum_excludes_legacy_post_d_n6 test extended (+csv_to_json · 5 → 6 legacy guarded).
  • nika/spec/stdlib/builtins-v0.1.md (separate submodule commit 2a14540) · csv_to_json section → convert section with universal multi-format semantics.
  • nika/spec/schemas/workflow.schema.json (same submodule commit) · invoke.tool oneOf nika branch enum alphabetical re-sort.
  • Internal · Rams audit doc (private monorepo) · the 26-builtin builtin-by-builtin table + 4-collapse proposal + crate research + shipping plan.

References #

  • ADR-085 · spec workflow.schema.json oneOf bridge (prerequisite)
  • ADR-084 · nika-catalog reconciliation to spec 26 (prerequisite)
  • ADR-082 · envelope nika: v1 single-version-marker
  • D-2026-05-22-N6 · stdlib-collapse 42 → 27 (precedent · « one obvious way » + jq subsumption)
  • 2026-05-27 · nika:json_merge cut (precedent · razor-edge Rams decision + jaq source-verified)
  • Internal · no-legacy-no-back-compat.md Class 2 (git is the archive)
  • Internal · LOCK-031 trigger-gated discipline (no infra behind locked gate · future ratchet 1/2/3 deferred)
  • Internal · cross-source-validation.md §2.6 Signal 7 (verify-the-auditor · crate claims primary-source-verified via crates.io API)

🦋

read at v0.107.0 · the decision record ships with the engine