Skip to content

fix(app-shell): a server error at an RLS clause's path holds the permission-set autosave - #27612

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-yv2fej
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-yv2fej

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

维护者速读

  • 改了什么:Studio 权限集编辑器里,RLS 的 USING/CHECK 子句若被服务端草稿裁决判为发布时会拒绝(如 USING 读公式字段),现在会像 CEL 语法错误一样挡住自动保存,并在字段上显示服务端原话。
  • 为什么改:之前服务端裁决只用于显示、不计入保存门,作者看着报错却一直 200 保存,到 Publish 才 422。
  • 风险与回滚:只改 app-shell 三个文件;裁决只在「字段文本等于裁决所评判的文本」时挡保存,改掉文本即放行(下一次保存的重读带来新裁决),避免死锁。回滚即 revert 本 PR。
  • 先看:CelPredicateField.tsx 的 report 与 heldKey 效果;draftVerdict.ts 的 serverHoldAt;PermissionMatrixEditor.tsx 里与 servedVerdict 同步设置的 servedVerdictBody。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.serverVerdictHold-27482.test.tsx,预期 4 条全绿。
  • 需要你做的:无,席位落地。

Part of #27482

Clause-②: no
Metadata-protocol: none

This closes the RLS arm of the card. The Validations-tab arm is not done here and is listed under Acceptance notes, so the merge must not close the card.

What changes

  • draftVerdict.ts: a second context, DraftVerdictBodyContext, carries the body the served verdict judged. valueAtPath reads a dotted _diagnostics path in that body, and serverHoldAt / useServerHoldAt answer the served errors at a field's path only while the field shows the text that body holds there. No rule is restated: the server's errors are taken whole.
  • CelPredicateField.tsx: a field with a verdictPath reports those held server errors through onLintChange as blocking errors, beside its lint findings. It re-reports when the hold starts, changes or lapses, keyed on the messages rather than on the array identity. Display is unchanged.
  • PermissionMatrixEditor.tsx: servedVerdictBody is set beside servedVerdict at each of its three reads (the load, the package door's post-save draft read, the environment door's post-save layered read) and provided to the RLS facets. The existing count, celErrorCount, now includes those errors, so the package door's autosave holds and the environment door's Save is disabled.

Why

The draft read's _diagnostics reached the RLS clause's display (#26858) but never celErrorCount, so a predicate the publish refuses (a USING over a formula field, rls-predicate-unenforceable) kept autosaving with the server's message on screen. Triage asked for exactly this: a server error at the field's own path counts in celErrorCount, with no second client copy of the publish rule.

The verdict judges the stored draft and nothing else, and no door judges a text without storing it. Holding every edit while an error stands at the path would deadlock: the corrected clause could never be saved, so the next draft read could never clear it. So the hold applies while the clause still shows the judged text. Editing the clause away lets that one save go out, and its re-read brings the next verdict.

Tests

  • PermissionMatrixEditor.serverVerdictHold-27482.test.tsx (new, 4 cases, fake door decides the refusal): (a) a stored refused draft holds another edit, no PUT, message stays; (b) the card flow: the refused clause saves once, its re-read holds the next edit; (c) editing the clause away releases, the save goes out and its clean re-read clears the verdict; (d) typing back to the judged text before the autosave fires is held again, no PUT.
  • draftVerdict.test.ts: four new cases for valueAtPath and serverHoldAt (judged text holds, other text does not, no body / no path / no error at that exact path holds nothing, an absent value reads as empty text).

Acceptance notes

  • File surface widened, seat-approved: ui/packages/app-shell/src/views/metadata-admin/draftVerdict.ts, the home of the served-verdict helpers. PermissionAdvancedFacets.tsx is untouched: the held errors ride the existing onLintChange report.
  • Validations tab, not done (deviation): its hold counter exists (ObjectValidationsPanel's per-rule map, reported through onBlockingIssuesChange to the Data pillar's Save gate), but no server verdict reaches the Data pillar at all. DataPillar in StudioDesignSurface.tsx reads the object draft through readDraftForSave(client, 'object', …) and keeps only extractDraftBody, dropping _diagnostics; it provides no DraftVerdictContext, and ConditionBuilder passes no verdictPath to its CelPredicateField. Wiring it is: keep the object draft read's _diagnostics and body in DataPillar (load and post-save), provide both contexts, and give ConditionBuilder a verdictPath per rule. The hold then applies with no further change to the field. The server's path spelling for a validation rule's condition is not measured here. That region belongs to a sibling card in flight, so it is left to a follow-up.
  • The first save of a newly refused text still goes out. Nothing on the client knows the refusal before the server stores and judges the text, and a client copy of the rule is ruled out. So in the card's step 5 the edit to another refused text saves once, and its re-read holds what follows.
  • Status line wording: the held line reads the existing perm.cel.saveBlocked ("Fix the CEL syntax errors before saving."), which also covers this server-verdict hold; the field itself shows the server's message. Rewording it is left alone (the studio namespace is in a sibling card's diff).
  • Field-level when conditions in the permission matrix pass no verdictPath, so they show and hold no server verdict, as before.

Verification

Readings name the commit they ran on.

Tests
  • New and changed tests on 02780608: 2 files, 11/11 passed.
  • Neighbouring suites on bee1b7d (every metadata-admin test of PermissionMatrixEditor, CelPredicateField, draftVerdict, ConditionalFormatting, the ResourceEditPage verdict tests, and every ui test file that imports CelPredicateField, draftVerdict or ConditionBuilder): 92 files, 811/811 passed.
  • pnpm --filter @object-ui/app-shell type-check: exit 0.
  • Post-merge re-run on 48c334a (after origin/main merged, install and the app-shell dependency closure rebuilt): the same 92 files, 811/811 passed; type-check exit 0.
Ablations (each committed first, mutated through scripts/ablation-replace.mjs, restored with blob == HEAD and an empty git diff HEAD)
  1. CelPredicateField.tsx, the report ignores the held server errors (the anchor server.length === 0 made always true): cases (a), (b), (d) red, (c) green. Expected: (c) is the release path.
  2. draftVerdict.ts, the hold ignores the text (the judged-text comparison replaced by a constant true): the unit case "holds on the judged text, and not on any other" red and integration case (c) red, the deadlock this design avoids. Everything else green.
Derived gates

node scripts/pm/dispatch-gates.mjs --commands on this diff: 34 families, all run, all exit 0 (--ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED). node scripts/check-sdui-manifest.mjs exit 0. pnpm check:new-line-citations (ui): 0 new citations. Every family CI runs is read from this PR's check runs.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No spec change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes on the changed files (eslint, see the report).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

Generated by Claude Code

… a CEL clause's path

The draft read's verdict reached the RLS clause's display (objectstack#26858)
but never the count that holds the autosave, so a predicate the publish
refuses kept saving. The clause now reports the served errors at its own
path as blocking while it still shows the text that verdict judged.

Claude-Session: https://claude.ai/code/session_01Utp6CaN9smHqkJcUWF4adR
Co-authored-by: Claude <noreply@anthropic.com>
…s the CEL autosave)

Claude-Session: https://claude.ai/code/session_01Utp6CaN9smHqkJcUWF4adR
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head 48c334adb568203891d2b5aaff40dbdb67511f34 · 2026-10-29T15:00Z


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 15:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit ea684a0 Oct 29, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-yv2fej branch October 29, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant