You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(automation,objectql): a filter that loses a condition must not run (#3810) (#3831)
Three exits from one failure mode: the query matched rows the author excluded.
1. A FLOW FILTER COULD SILENTLY WIDEN TO MATCH EVERYTHING. The flow template
interpolator expresses "this token did not resolve" as `undefined`. In a message
that renders as empty text; in a FILTER it removes the condition, and a removed
condition matches MORE rows. When it was the only one, `{ owner: '{record.ownr}' }`
became `{}` — and `{}` handed to `deleteMany` is every row in the table. So one
mistyped field name in a `delete_record` node silently emptied the object.
Reproduced with all four causes: a typo, an input the run never received, a lookup
hop, and a filter placeholder. The CRUD nodes now refuse to execute when
interpolation erased any authored condition, naming the template. The guard keys on
LOSS, not emptiness, so a deliberately empty filter still runs and losing one of two
conditions still fails.
2. FILTER PLACEHOLDERS NEVER REACHED THE ENGINE THAT RESOLVES THEM. `config.filter`
is where the flow template dialect and the filter placeholder dialect meet, and
evaluation order picked the winner by accident. `interpolateFilter()` hands that
position back to the dialect that owns it: a whole-string token no flow variable
resolves, that IS a recognised placeholder, passes through verbatim for the engine.
Flow variables keep precedence, so no working template changes meaning.
3. THE ENGINE RESOLVED PLACEHOLDERS ON READS BUT NOT ON WRITES. The same filter
selected different rows depending on the verb — `find` matched the signed-in user's
rows while `update`/`delete` compared the literal token text. The #3106 shape one
layer down: the evaluator existed, only some call sites reached it. `update` and
`delete` now resolve too, before the by-id fast path claims a scalar `where.id`.
Note the directions: (3) was fail-closed (matched zero rows); (1) and (2) were
fail-open (matched all). The latter is the data-destroying one.
0 commit comments