Repository navigation
Description
Filing gate ① — a product defect with a reproduction and a named site, filed on the maintainer's word: 「给 ConditionBuilder 那个 !x 缺陷立卡」.
Who acts on it: an objectui app-shell executor. The fix lands in objectstack-ai/objectui (packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.tsx). This card sits on the objectstack board because the session that found it could not read or search objectstack-ai/objectui issues; triage moves it to objectui if that is where it belongs.
What happens
Studio's visual condition builder offers the operators is set / true and is empty / false. They compile to a bare subject and a negated subject:
record.due_date+ is empty / false →!record.due_daterecord.status+ is set / true →record.status
CEL's ! and a bare predicate need a bool operand. On a date, text, select or lookup field the predicate can never be evaluated. The runtime rejects every write that reaches it: a value gives no such overload: !string, a null gives no such overload: !null. Nothing warns the author. The draft saves and publish answers 200.
In the Studio task benchmark this made the rule Due date required when Done (record.status == 'done' && !record.due_date) block every move to Done, including tickets that had a due date. The end user saw this as a red toast.
Reach — measured on the public door
On a fresh showcase boot (objectstack 8fc50b7647d30db2a0837877cf251c1163a3239e, console at the .objectui-sha pin a58626c88dc85954bd0af24f16ebb69454c03eee):
- Studio → an object → Advanced → Validations → New → Script — CEL fail condition.
- Condition row 1:
record.statusequalsdone. Add condition →record.due_dateis empty / false. Runs on Create and Update. The expression preview readsrecord.status == 'done' && !record.due_date. - Publish →
POST /api/v1/packages/PKG/publish-draftsanswers 200. The publish advisories do not name the rule. POST /api/v1/data/OBJECTwith{"status":"done"}(no due date) → 400VALIDATION_FAILED: Validation rule 'validation_1' could not be evaluated (runtime: no such overload: !null) — write rejected.POST /api/v1/data/OBJECTwith{"status":"done","due_date":"2026-10-20"}→ 400,fault: runtime: no such overload: !string,reason: unevaluable.- An end user editing a ticket in the console to Done with a due date gets
PATCH /api/v1/data/OBJECT/ID→ 400, with the same message as a red toast.
Reproduced three times (steps 4, 5 and 6). Rewriting the condition in Expression mode as record.status == 'done' && record.due_date == null makes step 4 answer Set a due date before marking the ticket Done. and step 6 succeed.
Mechanism
objectui ConditionBuilder.tsx, compile() — the same lines at the pin a58626c8 and on main b6f8fc8718e4 (2026-10-08):
if (r.op === 'truthy') return r.subject; // line 483
if (r.op === 'falsy') return `!${r.subject}`; // line 484The parser mirrors it: line 508 reads !x back as falsy, line 510 reads a bare identifier as truthy. A new row also defaults to op: 'truthy' (line 824), so a row whose operator the author never touches compiles to a bare non-boolean subject too.
The builder knows nothing about the field's type, and it has no is null / is not null operator. So an author cannot express "this date is empty" without typing CEL.
It reaches every inspector that renders ConditionBuilder:
ObjectValidationsPanel.tsx(validation rules)FlowNodeConfigField.tsx(the flow Start entry condition, decision branches)ActionDefaultInspector.tsxHookDefaultInspector.tsxPageBlockInspector.tsxwidgets.tsx
Expected
The builder emits only predicates the engine can evaluate for the subject's declared type. For a non-boolean field, is empty compiles to a null check (SUBJECT == null, plus == '' for text if that is the intended meaning), and is set to its negation. !SUBJECT and a bare SUBJECT stay for boolean fields only. A row with no chosen operator should not compile to a predicate at all.
Seam: renderer:objectui ConditionBuilder compile() → runtime: validation rule evaluation (VALIDATION_FAILED reason unevaluable)
Related, not duplicates
- finding(lint): the object save door gives no build verdict on validation conditions, field-rule slots (requiredWhen etc.), option visibleWhen or action predicates; os build refuses them, a metadata save stores them (#22019's sibling) #22032 (open) — the object save door gives no build verdict on validation conditions. That is the server half: once the door judges these predicates, a builder-produced
!dateshould be refused at save instead of at the first end-user write. This card is the producer half. - flow designer: the Start node's Entry condition is raw-CEL-only — ConditionBuilder ships in five other inspectors but not the one condition most users meet #11940 (closed) — brought
ConditionBuilderto the Start node's entry condition, widening the reach above. - qa: a repeatable Studio task benchmark — "build a repair-ticket app with no code", timed, counting red errors — as the Studio UX baseline #22137 — the Studio task benchmark whose baseline met this: red error 16 and failed write 13 in
docs/qa/studio-benchmark/baselines/2026-10-08-8fc50b76-a58626c8.md(PR docs(qa): a repeatable Studio task benchmark — repair-ticket scenario, scoring sheet, user-session protocol and the first baseline #22180).
Duplicate check
- Semantic issue search on objectstack-ai/objectstack (open and closed), condition builder "is empty" emits negation CEL no such overload validation rule unevaluable: 13 hits, none about the builder's emission. The nearest is finding(lint): the object save door gives no build verdict on validation conditions, field-rule slots (requiredWhen etc.), option visibleWhen or action predicates; os build refuses them, a metadata save stores them (#22019's sibling) #22032, the server half above.
- ConditionBuilder falsy truthy operator compiles negation for non-boolean field: 2 hits (objectql: a string comparand against a boolean field answers wrong rows under 200 — "true"/"false" match nothing, $ne "true" returns the true row, and ?f=true / ?f=false on the GET door both answer zero rows #21333, A boolean comparand is judged only at the engine door: the RLS compile seam and analytics NativeSQL pass a string against a declared boolean field as written (the family of #21333) #21376 — boolean comparands in objectql), neither this.
- validation rule could not be evaluated no such overload write rejected date field empty check: 2 hits (A system-context write skips value-shape validation for readonly fields: a seed's malformed readonly datetime (
'yesterday', an unresolvedcelenvelope) is stored verbatim, while the same value on a non-readonly field is refused #21663, record validator: adatefield written as a non-ISO string ("2026/07/15") answers 201 and is stored verbatim as"2026/07/15", a non-day, on memory and SQLite, because thedatearm admits anyDate.parse-readable string #20481 — value-shape validation), neither this. - objectstack-ai/objectui issues: NOT MEASURED — this session had no read access to that repository's issues. objectui
maincommit messages (last 50) mention no change to the falsy/truthy compile; the code is unchanged onmain.
Dedupe words: condition builder falsy negation CEL · no such overload !string validation rule · is empty operator non-boolean field
Filed by Claude Code (session session_01VenZErfhzG51sJBHRM1pWJ) from the Studio task benchmark baseline run on #22137.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 8, 2026
Triage: closed as a duplicate of objectstack-ai/objectui#11894 (same file, same mechanism). That card is where the fix lands, and its seat already moved this card's evidence there.
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T05:54Z. ⛔ Not a claim, ⛔ not a dispatch.