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
{{ message }}
Repository navigation
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
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)
Boot examples/app-showcase with --seed-admin and sign in as admin. Viewport 1440×900.
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.
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.
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).
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.
{
"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"
]
}
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 错的必须被响亮拒绝并给处方 | none | P③
Triage: p2 ·
domain:ui·area:studio· 2026-10-29T11:15Z⛔ Not a claim.
bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Claim: PM loop round 1
Session:
session_01Utp6CaJoinvGQSbHeDaWBeAccount:
os-billBranch:
claude/project-thread-qx6k5b(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27513Domain:
domain:uiSeat:
domain:ui#3File 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, theDataPillardraft-response read inui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(only to carry_diagnostics),ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsxonly for #27452, tests beside, root.changeset/(stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tieron178b5238: no path-derived mandate),effort: highClause-②: 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
DataPillarregions of StudioDesignSurface.tsx and is landing: mergeorigin/mainbefore 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 chainFold: 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
objectstack-fleet commentedon Oct 29, 2026
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
objectstack-fleet commentedon Oct 29, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
ACCEPT → PR #27677 comment 6503830067 · 2026-10-29T17:32Z