Repository navigation
Studio: an expression the engine refuses (RLS USING on a formula field, a null-unsafe validation predicate) keeps autosaving — the server verdict is shown on the field but never holds the save #27482
Description
domain:devx#1 · sunhaolin · session_017xRBcSwDXZrZg7nGjP6P3U
Filing gate: ① a product defect with a reproduction. It was found by QA run #27438 (wave 3 of the 18.3.0 sweep, anchor #27377) and re-derived by an independent verifier on a fresh boot (RUNNER rule 7). reach: measured at the Studio save door (PUT /api/v1/meta/permission/SET?mode=draft answers 200).
Business reason: authors, and AI authors in particular, treat a field that saves as a field that is fine. A row-level security predicate the engine will refuse keeps saving into the draft. The author only learns at Publish, and the published policy set never contains the rule they believe they wrote.
Reader who acts: the triage seat (domain, area, level), then the implementer for domain:ui (ui/packages/app-shell, the CEL authoring fields).
Dedupe (open AND closed):
- "RLS editor Valid CEL formula field USING predicate rls-predicate-unenforceable Studio": 16 hits. The nearest is Studio: an expression field that says "Valid CEL" should read the draft's server verdict at its own path (RLS predicate, flow edge condition) #26858 (closed by PR fix(app-shell): an expression field shows the draft's server verdict at its own path #26999, which is in the run subject as e6e4199). It made the field DISPLAY the server verdict, and that half holds. Its save-gate half is this card.
- "field inspector close Escape saves invalid formula draft held autosave bypass": 5 hits, none the same.
Batch-of: 16 · QA run #27438
Symptom
In Studio, an expression the engine refuses is never held by the save gate. The server's verdict is shown, but it is not counted as an error.
Reproduction (subject c4c515a, stock showcase, os dev)
- As admin, create a writable package with
POST /api/v1/packages. In it, publish:- an object with a number field
est_hoursand a formula fieldf_total(any formula overest_hours); - a permission set.
- an object with a number field
- Open
/_console/studio/PKG/access?surface=permission:SET, then the Row-level security tab. Add a policy on the object with USINGf_total > 5(record.f_total > 5behaves the same). - Observed:
- The field reads "Valid CEL", and
perm-autosave-heldnever appears. - About 3 s later,
PUT /api/v1/meta/permission/SET?mode=draft&package=PKG&surface=studioanswers 200.
- The field reads "Valid CEL", and
- The re-read about 0.1 s later (
GET …?state=draft) carries_diagnosticsvalid:falsewithrls-predicate-unenforceableatrowLevelSecurity.0.using. The field now shows the server's message witharia-invalid=true, and it still does after navigating away and back, and after a reload. - With that message on screen, edit the predicate to
f_total > 7. It autosaves again (200). POST …/publishanswers 422rls-predicate-unenforceable.
The same class on the Validations tab (measured by the sibling runner on studio-authoring.validation-rules-authoring): a rule predicate comparing a nullable field without a null check reads "Valid CEL" and autosaves (PUT 200), while the draft read already answers _diagnostics.valid:false. The author learns it only from "Not published… Details".
Expected (studio-authoring.expression-editors acceptance[4]): a predicate the engine refuses raises a blocking error, and the save is held (celErrorCount above 0).
Actual: the client lint passes the predicate, so the save is not held. The server verdict reaches the field's display but not the hold.
Mechanism (read from source at c4c515a, to be confirmed by the implementer)
celAuthoring.tslints the RLS predicate as a one-clause stack that names no object, so the virtual-field branch of the engine's rule cannot fire on the client.- fix(app-shell): an expression field shows the draft's server verdict at its own path #26999 feeds the draft read's verdict into
CelPredicateFieldby path, for display only. It never feeds the count that holds the autosave (celErrorCount). - So any rule the client cannot replicate (formula-field USING, the nullable-compare rule) saves until Publish refuses it.
The fix likely lets a server error at the field's own path hold the save, the same as a client error does. ⛔ It should not add a second client copy of the publish rule (#26858's constraint).
QA-source: #27438 · studio-authoring.expression-editors · acceptance[4]
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 错的必须被响亮拒绝并给处方 | studio-authoring.expression-editors | P③
Triage: p2 ·
domain:ui·area:studio· 2026-10-29T12:10Z⛔ 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-yv2fej(the card thread's designated push branch, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27482Domain:
domain:uiSeat:
domain:ui#3File surface:
ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx(RLS save-hold region),ui/packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx,ui/packages/app-shell/src/views/metadata-admin/celAuthoring.tsonly if needed, the Validations tab hold site, tests beside, root.changeset/(stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tieronee9a8d6e: 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: 6496277111
Premise: holds
Serial constraints cleared: no open PR edits PermissionMatrixEditor.tsx, CelPredicateField.tsx or celAuthoring.ts · seat-3 sibling #27511 fold holds StudioDesignSurface.tsx's selection/autosave region; a Validations-tab hold site there is another region · #27513 waits for this card (its verdict half rides this fix)
Dispatched 2026-10-29T13:13Z 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_01Utp6CaN9smHqkJcUWF4adR
os-dev-report
{ "issue": 27482, "status": "done", "branch": "claude/project-thread-yv2fej", "pr": "https://github.com/objectstack-ai/objectstack/pull/27612", "head": "48c334adb568203891d2b5aaff40dbdb67511f34", "session": "session_01Utp6CaN9smHqkJcUWF4adR (card thread; seat session_01Utp6CaJoinvGQSbHeDaWBe)", "premise_still_valid": true, "summary": "RLS arm done: a served _diagnostics error at a CelPredicateField's own verdictPath now counts in PermissionMatrixEditor's celErrorCount (reported through the existing onLintChange, so PermissionAdvancedFacets is untouched), which holds the package door's autosave and disables the environment door's Save. The hold applies only while the field shows the text the verdict judged (new DraftVerdictBodyContext + serverHoldAt in draftVerdict.ts, seat-approved surface widening); editing the text away releases, so the save goes out and its re-read brings the next verdict, avoiding the deadlock a path-only hold would cause (no validate-only door exists). No client copy of the publish rule. Validations-tab arm NOT done (deviation): no server verdict reaches the Data pillar. PR body says Part of #27482.", "tests": "48c334ad (post-merge): pnpm --dir ui exec vitest run over 92 files (every metadata-admin test of PermissionMatrixEditor/CelPredicateField/draftVerdict/ConditionalFormatting/ResourceEditPage verdict + every ui test importing CelPredicateField/draftVerdict/ConditionBuilder, incl. the new PermissionMatrixEditor.serverVerdictHold-27482.test.tsx) -> Test Files 92 passed, Tests 811 passed, lock VERDICT command-exit 0; pnpm --filter @object-ui/app-shell type-check -> VERDICT command-exit 0. Same set on bee1b7d1 before the merge: 92/92, 811/811. Ablation 1 (CelPredicateField report ignores held server errors, anchor 'server.length === 0' made always true, via scripts/ablation-replace.mjs, src resolved directly so no build): cases a,b,d red, c green as expected (release path); restored blob == HEAD, git diff HEAD empty. Ablation 2 (draftVerdict serverHoldAt ignores the text): unit 'holds on the judged text, and not on any other' red + integration (c) red (the deadlock); restored blob == HEAD. eslint on the 5 changed files: 0 errors, 20 warnings all on pre-existing lines.", "gates": "dispatch-gates --commands (tree 8bd055e5, 6 paths): 34 families, each recorded '<command> :: exit <code>' before any pipe, all exit 0 (pnpm --dir ui exec vitest run scripts/ first returned 99 behind my own lock hold, re-run alone: exit 0, 169 files / 4901 tests passed). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED. node scripts/check-sdui-manifest.mjs exit 0 (in the list). ui pnpm check:new-line-citations: 0 new citations, exit 0. CI families: in_progress at report time, read from PR #27612 check-runs by the seat.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "line_budget": "n/a — no skills/** touched", "files_changed": [ ".changeset/27482-server-verdict-holds-cel-autosave.md", "ui/packages/app-shell/src/views/metadata-admin/CelPredicateField.tsx", "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx", "ui/packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.serverVerdictHold-27482.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/draftVerdict.ts", "ui/packages/app-shell/src/views/metadata-admin/draftVerdict.test.ts" ], "deviations": [ "Validations-tab arm not implemented: ObjectValidationsPanel has a hold counter (per-rule celErrors -> onBlockingIssuesChange -> DataPillar Save gate) but DataPillar in StudioDesignSurface.tsx reads the object draft via readDraftForSave(client, 'object', ...) and keeps only extractDraftBody, dropping _diagnostics; it provides no DraftVerdictContext, and ConditionBuilder passes no verdictPath to its CelPredicateField. Wiring: keep the object draft read's _diagnostics + body in DataPillar (load and post-save), provide DraftVerdictContext + DraftVerdictBodyContext, give ConditionBuilder a per-rule verdictPath. Server path spelling for a validation rule condition NOT MEASURED. Region held by sibling card #27511 fold; seat agreed to a follow-up (same chain as #27513). Hence 'Part of #27482', not 'Fixes'.", "File surface widened to ui/packages/app-shell/src/views/metadata-admin/draftVerdict.ts, seat-approved in thread before the edit.", "The full metadata-admin + studio-design directory run produced no output for 40 min and was killed (my own PID); replaced by the 92-file set above. Directory-wide run left to CI Test UI.", "Harness after-PR assign/review step skipped per the dispatch order (maintainer rule: no os-bill assignee or review request at open).", "PR body Human Minimum item 3 reads 'pnpm lint passes on the changed files': measured by eslint on those 5 files only; repo-wide lint is CI's." ], "mcp_calls": "0 — no GitHub MCP tool used", "api_writes": "2 — git push (not REST); relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches -> PR #27612); this report comment via post-stamped (POST /repos/objectstack-ai/objectstack/issues/27482/comments)", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — the permission-set held status line reuses perm.cel.saveBlocked ('Fix the CEL syntax errors before saving.') for the server-verdict hold too; the field shows the server's own message, so not misleading enough to file; wording lives in the studio i18n namespace a sibling card is editing", "carrier: none · noted, not filed — field-level when conditions in the permission matrix pass no verdictPath, so they neither show nor hold a server verdict (the server judges fields[].when with the RLS rule); dotted object.field keys make the path spelling ambiguous, unmeasured" ] }objectstack-fleet commentedon Oct 29, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
ACCEPT → PR #27612 comment 6500236027 · 2026-10-29T15:01Z
objectstack-fleet commentedon Oct 29, 2026
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Release: partial landing. PR #27612 (
Part of #27482) MERGED; back topm:queue, assignee cleared, same stroke.Done: the Permission matrix RLS arm. A server CEL error held at a verdict path keeps the draft unsaved while the server verdict on that exact text stands (
draftVerdict.tsserverHoldAt/useServerHoldAt, reported throughCelPredicateField'sonLintChange); editing the text releases it.Remaining: the Validations-tab arm.
DataPillardrops the served_diagnostics, andConditionBuildercarries noverdictPath, so a server CEL verdict on a validation rule is not held yet. Stays indomain:ui(Studio, seat 3); the seat dispatches it together with #27513 (same chain). Resume-from:main(the RLS arm's helpers are on it).· 2026-10-29T15:27Z
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-27482Domain:
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: 6500922670
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: member of the #27513 chain (chain head #27513 carries the full serial check); shared branch named above. This claim covers the remainder named in the seat's
Release:comment (the Validations-tab arm).Dispatched 2026-10-29T15:33Z 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_01Utp6CaJoinvGQSbHeDaWBe
Release: premise disproved for a ui-only fix of the remaining arm;
pm:retriage, assignee cleared, same stroke. The #27513 chain (branchclaude/project-thread-qx6k5b) keeps #27513 and #27452 and drops this card.Done (earlier): the RLS arm, PR #27612.
Remaining: the Validations-tab arm, which needs a producer change first. Measured by the card thread on
main@178b5238: the server never addresses a validation rule's condition by key path.validate-expressions.tspushes validation-rule findings (check()and the #4763checkNullGuards, the #27513 refusal) withwhereonly;authoring-rules.tssetspath: i.path ?? i.where;withPublishVerdictkeeps a non-positional path as written. So the draft read's_diagnosticspath for a rule is prose (object 'X' · validation rule 'NAME'), nevervalidations.N.condition;protocol.runtime-authoring-gate.test.ts(#22032 block) pinsissue.pathto that prose. Only flow edges got a key path (#26858).Why retriage:
packages/lintanchors to the spec lane, and the fix is a cross-layer seam (lint emits a key path for top-level validation-rule findings, edge precedent; the ui then holds and shows the verdict throughserverHoldAt/useServerHoldAtfrom #27612,DataPillarkeeping_diagnostics,ConditionBuildercarrying averdictPath). Triage files the parent and per-layer cards; ⛔ no client copy of the publish rule (#26858). Resume-from:main.· 2026-10-29T15:38Z
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: retriage answer · parent card, two layers · 2026-10-29T16:06Z
⛔ Not a claim. The seat's measurement
6501208028holds: the Validations-tab arm needs the producer to address a rule by key path first. That is a cross-layer seam, so this card becomes the coordination parent (pm:epic) and is never dispatched itself.validations.N.condition, not prose, so the draft read can address it (#27482 layer 1 of 2) #27643 (domain:spec, p2):packages/lintemitsvalidations.N.conditionon validation-rule expression findings, on the flow-edge precedent (Studio: an expression field that says "Valid CEL" should read the draft's server verdict at its own path (RLS predicate, flow edge condition) #26858). It carries a cross-domain exception for the onemetadata-protocolpin, and a contract review is owed.domain:ui, p2, Blocked-by lint: a validation rule's expression finding carries the key pathvalidations.N.condition, not prose, so the draft read can address it (#27482 layer 1 of 2) #27643):DataPillarkeeps_diagnostics, andConditionBuildercarriesverdictPath. The hold goes through fix(app-shell): a server error at an RLS clause's path holds the permission-set autosave #27612'sserverHoldAt.pm:retriageremoved.Generated by Claude Code