Status: design placeholder. Do not implement until a real user asks. Filed so the next person who hits the guard finds the reasoning instead of re-deriving it — or worse, relaxing the guard.
The gap
Since #3810, a CRUD node refuses to run when interpolation erased any authored filter condition. That is correct as a default: an absent condition does not narrow a query, it widens it (see the decision note on #3810 for why get_record keeps the hard failure).
But it closes off a shape that is sometimes genuinely intended:
Filter by campaign if the run received one; otherwise return all rows.
Today that is inexpressible. { campaign: '{campaignId}' } on a run without campaignId is indistinguishable — to the guard and to a reader — from { campaign: '{campiagnId}' }, a typo. Both erase a condition; the guard refuses both, which is the right call precisely because it cannot tell them apart.
The constraint any design must respect
Fail-closed stays the default. The author must say "this condition is optional" explicitly, at authoring time. An implicit or inferred optionality re-opens the fail-open direction #3810 closed, and the typo case is far more common than the optional case.
Options sketched (not chosen)
A. An $optional marker on the condition
filter: { campaign: { $optional: '{campaignId}' } }
B. Branch to two get_record nodes
- Zero new syntax; a
decision node routes on whether the variable is set, each branch carrying its own fully-literal filter.
- Both queries are explicit and greppable — arguably the clearest thing to read.
- Verbose, and it duplicates the rest of the filter across both branches. Gets ugly with two or more optional conditions (2^N branches).
C. Author-side default at the variable, not the filter
- Give the flow variable a declared default (
campaignId ?? '*') so the token always resolves.
- Requires a filter semantic for "match anything," which is a bigger and riskier change than either of the above.
Recommendation if this ever gets picked up
Start from A, and require it to be inert unless explicitly written. B already works today with no platform change — point people at it in the meantime; it is the honest answer for the one-or-two-condition case.
Do not
Relax the #3810 guard or downgrade it to a warning to serve this use case. That trades a deterministic build/run failure for a silent data-widening bug, and the optional case is the rare one. See the decision note on #3810.
Related
Status: design placeholder. Do not implement until a real user asks. Filed so the next person who hits the guard finds the reasoning instead of re-deriving it — or worse, relaxing the guard.
The gap
Since #3810, a CRUD node refuses to run when interpolation erased any authored filter condition. That is correct as a default: an absent condition does not narrow a query, it widens it (see the decision note on #3810 for why
get_recordkeeps the hard failure).But it closes off a shape that is sometimes genuinely intended:
Today that is inexpressible.
{ campaign: '{campaignId}' }on a run withoutcampaignIdis indistinguishable — to the guard and to a reader — from{ campaign: '{campiagnId}' }, a typo. Both erase a condition; the guard refuses both, which is the right call precisely because it cannot tell them apart.The constraint any design must respect
Fail-closed stays the default. The author must say "this condition is optional" explicitly, at authoring time. An implicit or inferred optionality re-opens the fail-open direction #3810 closed, and the typo case is far more common than the optional case.
Options sketched (not chosen)
A. An
$optionalmarker on the conditionresolveNodeFilter(drop this key when it does not resolve, keep refusing on every other erasure).$optionaldrop the condition, or is there a fallback value slot?B. Branch to two
get_recordnodesdecisionnode routes on whether the variable is set, each branch carrying its own fully-literal filter.C. Author-side default at the variable, not the filter
campaignId ?? '*') so the token always resolves.Recommendation if this ever gets picked up
Start from A, and require it to be inert unless explicitly written. B already works today with no platform change — point people at it in the meantime; it is the honest answer for the one-or-two-condition case.
Do not
Relax the #3810 guard or downgrade it to a warning to serve this use case. That trades a deterministic build/run failure for a silent data-widening bug, and the optional case is the rare one. See the decision note on #3810.
Related
{…}before the query engine sees it #3810 — the guard, and why it fails closederrorfor filter positions