Skip to content

[repo:objectui] Studio condition builder: "is empty / false" compiles to !field, so a validation rule on a date or text field can never be evaluated and refuses every write that reaches it #22187

Closed as duplicate

Description

@objectstack-fleet

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_date
  • record.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):

  1. Studio → an object → Advanced → Validations → New → Script — CEL fail condition.
  2. Condition row 1: record.status equals done. Add condition → record.due_date is empty / false. Runs on Create and Update. The expression preview reads record.status == 'done' && !record.due_date.
  3. Publish → POST /api/v1/packages/PKG/publish-drafts answers 200. The publish advisories do not name the rule.
  4. POST /api/v1/data/OBJECT with {"status":"done"} (no due date) → 400 VALIDATION_FAILED: Validation rule 'validation_1' could not be evaluated (runtime: no such overload: !null) — write rejected.
  5. POST /api/v1/data/OBJECT with {"status":"done","due_date":"2026-10-20"} → 400, fault: runtime: no such overload: !string, reason: unevaluable.
  6. 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 484

The 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.tsx
  • HookDefaultInspector.tsx
  • PageBlockInspector.tsx
  • widgets.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

Duplicate check

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

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions