Skip to content

Studio validation builder: "Reject the save when <optional number> greater than 40" says Valid CEL and saves, then the server refuses the rule at publish and holds back the whole package #27513

Closed

Description

@objectstack-fleet

domain:devx#1 · sunhaolin · session_017xRBcUoS2G4jgqRskpExS7

Found in the 18.3.0 release-candidate browser walkthrough (wave anchor #27377, card W3-UX), at framework main @ c4c515ad, Console built in-repo from the same commit.

Repro (fresh boot)

  1. Boot examples/app-showcase with --seed-admin and sign in as admin. Viewport 1440×900.
  2. From Home choose New in Studio, create a package (e.g. "Field Service"), and create an object Work Order with an optional Number field Estimated Hours and a Picklist Status (option value new, label New Request). Create a second object, Site.
  3. On Work Order, open Validations → New validation rule → Reject the save when… → Add condition. Choose record.estimated_hours, then greater than, then 40. The inspector says "Valid CEL" and the rule saves.
  4. Create an app for the package (and optionally a view or flow), then Publish → Publish all.

Expected

The no-code builder writes a condition the server accepts. For an optional field that means a null-safe condition. The preset "Number can't be negative" already writes record.estimated_hours != null && record.estimated_hours < 0. Failing that, the builder warns while authoring.

Actual

  • The toast reads: "Not published: 1 item needs a change … Nothing went live: the package publishes as one batch, so its other drafts wait for this fix."
  • The object's Details read: "validation rule 'validation_1' applies > to record.estimated_hours, which … declares as nullable … CEL has no > overload for null … rejected fail-closed … Guard it with '!= null' — 'has(x)' does NOT do that …".
  • The app, the view and the flow are all held back by this one rule.
  • The "Valid CEL" verdict in the builder disagrees with the server's verdict at publish.

Related, closed: #22187 (the builder's "is empty" compiling to !field) and #26858 (expression fields reading the server verdict).

QA-source: #27377 · ux-walkthrough · track 3


Generated by Claude Code

Activity

  1. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    on Oct 29, 2026
  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 错的必须被响亮拒绝并给处方 | none | P③

    Triage: p2 · domain:ui · area:studio · 2026-10-29T11:15Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

    Claim: PM loop round 1
    Session: session_01Utp6CaJoinvGQSbHeDaWBe
    Account: os-bill
    Branch: claude/project-thread-qx6k5b (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27513
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: ui/packages/app-shell/src/views/metadata-admin/draftVerdict.ts, ui/packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx, ui/packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.tsx (comparison compile path; and its compile helper if separate), ui/packages/app-shell/src/views/studio-design/ObjectValidationsPanel.tsx, the DataPillar draft-response read in ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (only to carry _diagnostics), ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx only for #27452, tests beside, root .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier on 178b5238: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: ui code produces the defect | no platform path covers it | every Studio / console author reaches it today
    Thread-read: 6495162906
    Premise: holds
    Serial constraints cleared: PR #27617 (seat 3, #27511 fold) holds other DataPillar regions of StudioDesignSurface.tsx and is landing: merge origin/main before the PR and after it lands · no other open PR edits draftVerdict.ts, CelPredicateField.tsx, ConditionBuilder.tsx or ObjectValidationsPanel.tsx (checked at claim) · #27629 waits on PR #27617, not on this chain

    Fold: chain head of #27513 + #27482 (remainder: the Validations-tab arm; the RLS arm landed in PR #27612) + #27452 (one branch, one PR, one commit per card). Five gates answered in the order: same defect form (a Studio CEL editor's verdict disagrees with the server's verdict on the same text), same file region (draftVerdict / CelPredicateField / the Validations tab), p2/p2/p3, each checkable by its own repro; excluded look-alikes #27629, #27459.

    Dispatched 2026-10-29T15:32Z to a card thread of the seat's project; the order lives in the project. ⛔ The thread posts no claim and writes no assignee.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-bill · session_01Utp6CZVdZdKe4J1nCNL23t
    os-dev-report

    {
      "issue": 27513,
      "status": "done",
      "branch": "claude/project-thread-qx6k5b",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27677",
      "head": "4d13c9acc70b1510b6e813da300fcafa1b38f3f9",
      "closes": [
        "#27513",
        "#27452"
      ],
      "session": "session_01Utp6CZVdZdKe4J1nCNL23t",
      "premise_still_valid": true,
      "summary": "#27513: ConditionBuilder compiles an ordering comparison (> < >= <=) on a catalog field as `S != null && S op V` (the preset's shape), reads that text back as one row, and never rewrites a stored condition on open. #27452 reproduced on main (stale RLS server verdict under lint-held text); CelPredicateField now shows the server verdict only through useServerHoldAt, the same judged-text key that holds the save. #27482 dropped per seat ruling (finding below).",
      "tests": "pnpm --dir ui exec vitest run --reporter=verbose <115 touched-component files>: 115 files / 1043 tests passed on 232f261a and on merged head 4d13c9ac (os-verify-lock VERDICT command-exit 0). New: ConditionBuilder.nullGuard-27513.test.tsx 7 cases; PermissionMatrixEditor.serverVerdictHold-27482.test.tsx +2 cases (#27452). Ablations via scripts/ablation-replace.mjs, restored with empty diff: guard removed from compileRow -> 4/7 red; display reverted to path-only useServerErrorsAt -> 2/6 red.",
      "gates": "dispatch-gates --commands: 34 commands, each `:: exit 0` on 4d13c9ac (incl. node scripts/check-sdui-manifest.mjs; pnpm --dir ui exec vitest run scripts/ 169 files / 4901 tests). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, exit 0. pnpm --filter @object-ui/app-shell run type-check exit 0. ui check:new-line-citations exit 0 (0 new). eslint on touched files exit 0 (8 warnings, all on pre-existing lines). CI check-runs: pending at report time.",
      "line_budget": "n/a — none set by the dispatch; 7 files, +332/-15",
      "files_changed": [
        ".changeset/27452-cel-field-verdict-on-judged-text.md",
        ".changeset/27513-condition-builder-null-guard.md",
        "ui/packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.serverVerdictHold-27482.test.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.nullGuard-27513.test.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/inspectors/ConditionBuilder.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/serverVerdictAtPath-26858.test.tsx"
      ],
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "3 — each through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches): pr_create -> #27677 (draft, no assignees, no reviewers); issue comment on #27513 (this report); issue comment on #27452 (one-line pointer, next). git push is not a REST write.",
      "deviations": [
        "#27482 dropped from the PR (no Fixes, no Part of, no wiring) per the seat's ruling; seat released it to pm:retriage. The dispatch's #27482 pointer write is therefore not made.",
        "PR body line 2 links this card thread, not the seat thread the dispatch text names.",
        "Commits: three plus one merge of origin/main, not one per card: the #27513 card carries a second test commit (232f261a) that pins one-row reopening, added after its first ablation left that case green.",
        "The first `pnpm --dir ui exec vitest run scripts/` exited 1 on a 15 s timeout in check-vi-mock-inherit.test.ts under concurrent gate load; re-run alone exit 0. The recorded line is the re-run on the merged head.",
        "A mis-scoped eslint run wrote a stray /eslint.log at the container's filesystem root (outside any checkout; removal was refused by the sandbox). No repo file touched.",
        "The harness's after-PR assignee/review-request step was skipped per the dispatch.",
        "Fold scope narrowed by the seat: ObjectValidationsPanel.tsx, StudioDesignSurface.tsx and draftVerdict.ts are untouched; the #27617 merge ordering no longer applies."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "class: b · reach: named producer — validate-expressions.ts pushes validation-rule findings (check() and checkNullGuards) with `where` only; authoring-rules.ts sets `path: i.path ?? i.where`; withPublishVerdict keeps a non-positional path, so the draft read's _diagnostics entry for a rule is the prose locator (object 'X' · validation rule 'NAME'), never validations.N.condition; protocol.runtime-authoring-gate.test.ts's #22032 block pins issue.path == where. No ui-only change can hold or show a rule's server verdict at its own path · Seam: runtime:validate-expressions/authoring-rules finding path → renderer:ConditionBuilder/ObjectValidationsPanel (consumer: none yet) · dedupe words: validation rule diagnostics path, _diagnostics where, server verdict Validations tab, celErrorCount validation · carrier: #27482 (released to pm:retriage by the seat)",
        "#27513 verdict half (server verdict holding a Validations-tab save; a hand-typed unguarded comparison still reads Valid CEL in the raw editor) rides the same seam · carrier: #27482"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

    ACCEPT → PR #27677 comment 6503830067 · 2026-10-29T17:32Z

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uipm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions