Measured on @objectstack/objectql 17.2.0 / @objectstack/runtime 17.2.0, booting a real app with a declarative seed (objectstack-ai/duly, pnpm demo).
What happens
SelectOptionSchema.visibleWhen is the one authoring surface whose predicate scope binds the caller, and the spec is explicit that the server must enforce it ("Client-side hiding is UX, not authorization … the server MUST also reject writes of its value"). So an option gated for access-control reasons is written as, e.g.:
{label: 'Approved',value: 'approved',visibleWhen: P`record.owner != current_user.id`}A system write — the declarative seed loader, an in-process job, anything with no acting user — has no userId on the execution context, so buildEvalUser returns undefined, buildScope never mounts current_user, and the predicate cannot evaluate:
Unknown variable: current_user
evaluateOptionVisibility treats an unevaluable predicate as "allowed through" and logs a warning. That admission is correct and load-bearing — it is what lets a seed carry rows in a gated state, and what keeps in-process jobs working. The finding is only about the log.
Measured
One boot of a seeded demo (465 rows, 27 duty rows carrying a gated option value):
2026-09-02T04:48:41.595Z WARN option visibleWhen for 'review_status=approved' failed to evaluate — allowed through
2026-09-02T04:48:41.596Z WARN option visibleWhen for 'review_status=approved' failed to evaluate — allowed through
… ×27
grep -c "option visibleWhen" boot.log → 27, one per seeded row.
Why it is worth a look
The message says failed and allowed through, which reads as "a security predicate did not run and the write went in anyway" — the exact sentence an operator should escalate. Here it is the expected steady state of every system write against every gated option, on every boot. A signal that fires on the correct path this often stops being read, and takes the genuine case with it: a predicate that fails to evaluate for an authenticated caller (a typo'd field, a missing root) produces the identical line, and that one really is a hole.
The two cases are distinguishable at the call site — opts.currentUser === undefined means there is no user to bind and no rule to apply, as opposed to a user being present and the expression still faulting.
Not proposing a specific fix; the ones that come to mind are (a) skip evaluation entirely when there is no acting user, with no log, (b) demote that case to debug and keep WARN for the authenticated fault, or (c) keep the warning but say which case it is. Any of them separates "expected on system writes" from "a live predicate is not running".
Not a report about behaviour
The admission itself should stay: a seed that could not write a gated value would have to walk every row through the state machine, and a job with no acting user would be unable to write at all.
Found while implementing objectstack-ai/duly#107 (a confirmation/approval pipeline whose two verdict options are gated this way). Filed from there; no fix attempted in the application.
Measured on
@objectstack/objectql17.2.0 /@objectstack/runtime17.2.0, booting a real app with a declarative seed (objectstack-ai/duly,pnpm demo).What happens
SelectOptionSchema.visibleWhenis the one authoring surface whose predicate scope binds the caller, and the spec is explicit that the server must enforce it ("Client-side hiding is UX, not authorization … the server MUST also reject writes of its value"). So an option gated for access-control reasons is written as, e.g.:A system write — the declarative seed loader, an in-process job, anything with no acting user — has no
userIdon the execution context, sobuildEvalUserreturnsundefined,buildScopenever mountscurrent_user, and the predicate cannot evaluate:evaluateOptionVisibilitytreats an unevaluable predicate as "allowed through" and logs a warning. That admission is correct and load-bearing — it is what lets a seed carry rows in a gated state, and what keeps in-process jobs working. The finding is only about the log.Measured
One boot of a seeded demo (465 rows, 27 duty rows carrying a gated option value):
grep -c "option visibleWhen" boot.log→27, one per seeded row.Why it is worth a look
The message says failed and allowed through, which reads as "a security predicate did not run and the write went in anyway" — the exact sentence an operator should escalate. Here it is the expected steady state of every system write against every gated option, on every boot. A signal that fires on the correct path this often stops being read, and takes the genuine case with it: a predicate that fails to evaluate for an authenticated caller (a typo'd field, a missing root) produces the identical line, and that one really is a hole.
The two cases are distinguishable at the call site —
opts.currentUser === undefinedmeans there is no user to bind and no rule to apply, as opposed to a user being present and the expression still faulting.Not proposing a specific fix; the ones that come to mind are (a) skip evaluation entirely when there is no acting user, with no log, (b) demote that case to debug and keep WARN for the authenticated fault, or (c) keep the warning but say which case it is. Any of them separates "expected on system writes" from "a live predicate is not running".
Not a report about behaviour
The admission itself should stay: a seed that could not write a gated value would have to walk every row through the state machine, and a job with no acting user would be unable to write at all.
Found while implementing objectstack-ai/duly#107 (a confirmation/approval pipeline whose two verdict options are gated this way). Filed from there; no fix attempted in the application.