Translates an Ash.Expr AST fragment into a Cypher expression string + a
param map, for use in SET clauses (atomic updates) and expression filters.
Pure: holds no state, performs no I/O, reads no clock. Every dynamic value
becomes a positional $param (AGENTS.md Critical Security Rule 1 — no value
is ever interpolated into the Cypher string). Every property reference is
backtick-quoted and identifier-validated: a property whose name collides with
a Cypher keyword (count, label, …) mis-parses when bare, a vulnerability
closed by mandatory n.`attr` quoting (AGE probe D-row). Returns
{:error, UnsupportedExpression} for any node it cannot safely map — never
a silent drop.
Param allocation
Param names are positional (p0, p1, …) allocated against the caller's
taken set so they cannot collide with attribute-name params at the JSON
boundary (an atom :p0 and string "p0" in the merged param map would
produce a duplicate JSON key, silently dropping one value — plan-review N1).
Summary
Functions
Translate expr into a Cypher fragment and a map of $param => value.