{ "alternatives_considered": [ { "option": "Wrap each call site individually with do { } | complete (existing pattern)", "why_rejected": "Works only for external commands, not for Nu plugin commands. Plugin commands raise LabeledError — not catchable via complete. Keeping ^nickel export at call sites means all cache benefits are lost." }, { "option": "Single ncl-export.nu wrapper delegating to ^nickel export with inline cache check", "why_rejected": "Duplicates the cache logic already inside nu_plugin_nickel. Two cache implementations with different key strategies would diverge. The plugin is the correct cache owner — the wrapper should delegate to it." }, { "option": "Migrate all 124 call sites at once", "why_rejected": "Risk surface too large. Priority-ordered migration (C1 hot-path first) allows validating cache correctness on the most-exercised paths before touching validation, bootstrap, and diagnostic paths that are harder to test." } ], "consequences": { "negative": [ "nu_plugin_nickel must be registered in the Nu session for performance benefits; unregistered sessions fall back to the `^nickel export` path in nickel-eval-soft (via the catch branch)", "Block C2/C3 (remaining 120 call sites) are not yet migrated — those paths still use ^nickel export directly" ], "positive": [ "Hot-path call sites (4 files, C1) are now cache-backed via nu_plugin_nickel", "Single try/catch location for soft-failure pattern — easy to audit", "| from json eliminated from migrated call sites" ] }, "constraints": [ { "check": { "must_be_empty": true, "paths": [ "provisioning/core/nulib/main_provisioning/dispatcher.nu", "provisioning/core/nulib/main_provisioning/components.nu", "provisioning/core/nulib/main_provisioning/workflow.nu", "provisioning/core/nulib/main_provisioning/extensions.nu" ], "pattern": "^nickel export", "tag": "Grep" }, "claim": "Hot-path files (C1) must not contain direct ^nickel export calls after migration", "id": "c1-no-direct-nickel-export", "rationale": "Direct ^nickel export in C1 files bypasses the plugin cache, negating the performance benefit of ADR-022. All C1 exports must go through ncl-eval or ncl-eval-soft.", "scope": "dispatcher.nu, components.nu, workflow.nu, extensions.nu", "severity": "Hard" } ], "context": "After ADR-022 established the ncl-sync daemon and shared cache, Nu call sites needed to be migrated from `^nickel export --format json ... | from json` to the plugin. Two call patterns exist: hard-failure (export failure should propagate as an error — uses `error make`) and soft-failure (export failure should return a fallback value — uses `if $result.exit_code != 0`). Distributing try/catch across 124 call sites would violate the guideline against widespread use of try/catch for Nu plugin commands.", "date": "2026-04-16", "decision": "Two wrapper functions in `lib_provisioning/utils/nickel_processor.nu` serve as the single abstraction layer: `ncl-eval [path import_paths]` for hard-failure call sites (error propagates from the plugin directly — no try/catch needed), and `ncl-eval-soft [path import_paths fallback]` for soft-failure call sites (a single try/catch returns `fallback` on any plugin error). Block C1 migrates the four hot-path call sites: `dispatcher.nu` (commands-registry), `components.nu` (comp-ncl-export helper + servers.ncl), `workflow.nu` (wf-ncl-export helper + settings.ncl + state.ncl), `extensions.nu` (metadata.ncl per taskserv). Block C2/C3 cover the remaining operation-path and validation call sites.", "id": "adr-023", "ontology_check": { "decision_string": "ncl-eval + ncl-eval-soft wrappers in nickel_processor.nu replace ^nickel export at hot-path call sites; single try/catch in ncl-eval-soft", "invariants_at_risk": [ "config-driven-always" ], "verdict": "Safe" }, "rationale": [ { "claim": "ncl-eval-soft isolates the single try/catch to one location", "detail": "In Nu 0.111.0, try/catch is valid for Nu internal commands (including plugins). However, dispersing try/catch across dozens of call sites increases cognitive load and creates inconsistency. Centralizing it in ncl-eval-soft means the pattern is reviewed once and applied uniformly. Callers declare intent via the `fallback` parameter (`{}`, `[]`, or `null`) rather than embedding error-handling logic inline." }, { "claim": "ncl-eval (hard-failure) requires no try/catch — plugin LabeledError propagates naturally", "detail": "When `nickel-eval` fails, it raises a `LabeledError` that Nu surfaces as a structured error. This is identical in behavior to `error make { msg: ... }` in the existing code. The call site is simply `ncl-eval $path [$ws $prov]` — one line instead of four. No error handling is needed because the error propagation is the correct behavior." }, { "claim": "nickel-eval returns Nu values natively, eliminating | from json", "detail": "The plugin converts `serde_json::Value` to `nu_protocol::Value` via `json_value_to_nu_value`. Call sites receive a Nu record or list directly and can use cell path access (`$data.components`, `$data.dimensions`) without an intermediate string parse step. This removes a class of parse errors where `from json` would fail on empty stdout from a cached result." } ], "related_adrs": [ "adr-022-ncl-sync-daemon" ], "status": "Accepted", "title": "ncl-eval wrapper: nu_plugin_nickel as the single ^nickel export abstraction in Nu" }