73 lines
5.6 KiB
Text
73 lines
5.6 KiB
Text
let d = import "adr-defaults.ncl" in
|
|
|
|
d.make_adr {
|
|
id = "adr-023",
|
|
title = "ncl-eval wrapper: nu_plugin_nickel as the single ^nickel export abstraction in Nu",
|
|
status = 'Accepted,
|
|
date = "2026-04-16",
|
|
|
|
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.",
|
|
|
|
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.",
|
|
|
|
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.",
|
|
},
|
|
],
|
|
|
|
consequences = {
|
|
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",
|
|
],
|
|
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",
|
|
],
|
|
},
|
|
|
|
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.",
|
|
},
|
|
],
|
|
|
|
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,
|
|
},
|
|
|
|
related_adrs = ["adr-022-ncl-sync-daemon"],
|
|
|
|
constraints = [
|
|
{
|
|
id = "c1-no-direct-nickel-export",
|
|
claim = "Hot-path files (C1) must not contain direct ^nickel export calls after migration",
|
|
scope = "dispatcher.nu, components.nu, workflow.nu, extensions.nu",
|
|
severity = 'Hard,
|
|
check = { tag = 'Grep, pattern = "^nickel export", 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"], must_be_empty = true },
|
|
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.",
|
|
},
|
|
],
|
|
}
|