feat(approvals): server-computed viewer capability for precise decision-action gating (#3310)#3344
Merged
Conversation
…viewer capability
`getRequest` / `listRequests` now attach a per-viewer block to each row from the
caller's context — `viewer: { can_act, is_submitter }` (ApprovalRequestRow):
- can_act — the caller is a current pending approver (their user id is in
the resolved pending_approvers while status is pending). This is
the exact check the decision methods authorize with, so it
already reflects position/team/manager resolution.
- is_submitter — the caller submitted the request.
The declared decision actions on sys_approval_request now gate their `visible`
CEL on it: approver actions (approve/reject/reassign/send-back/request-info) use
`record.viewer.can_act`; submitter levers (remind/recall/resubmit) use
`record.viewer.is_submitter`. Before this, approver actions only trimmed the
non-pending case, so a submitter viewing their own pending request saw buttons
they couldn't use (the backend 403'd), and a position-addressed approver could
be wrongly hidden by a client-side identity heuristic. Where `viewer` is absent
(a row surfaced outside a service read with a user context), the predicate fails
closed.
Tests: getRequest returns can_act true for a pending approver, false for the
submitter and strangers, and false once finalized; listRequests attaches viewer
to every row; the object contract test pins the new predicates.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ypkQikZ55oWUHUnMebwXA
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 20, 2026 03:36
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Jul 20, 2026
…d + Console) Continues the v16 page from the latest front/back-end code. Since the rc.0 cut, 9 backend changesets landed and the objectui pin advanced 94d4876 -> af1b0db (objectui objectstack-ai#2706-objectstack-ai#2736). Sourced from each repo's own changesets. - Retitles the now-stale 'pending the next console pin' subsection: those 4 frontend items (objectui#2701/objectstack-ai#2706/objectstack-ai#2707/objectstack-ai#2708) are now bundled as the pin advanced; points to the new running section. - Adds a 'Landed since 16.0.0-rc.0' section: - Breaking/behavior: aiStudio/aiSeat alias removal (objectstack-ai#3308); date-arithmetic now a build-time error (objectstack-ai#3306); checkboxes per-option visibleWhen + string option matching (objectstack-ai#3350). - New backend: formula null-guard/floor/ceil (objectstack-ai#3306); engine-owned managedBy bucket (ADR-0103 addendum, objectstack-ai#3343); approvals viewer gating (objectstack-ai#3344) + decision attachments (objectstack-ai#3332); discovery transactionalBatch bit (objectstack-ai#3298); action resultDialog i18n (objectstack-ai#3347); collab-notification/storage localization + wired notifications REST routes (objectstack-ai#3354). - Console (now bundled): option-widget visibleWhen parity, approvals-inbox server-viewer gating + composer retirement, related-list pagination, inline-analytics renderer retirement, authenticated api-fetch threading, result-dialog i18n, engine-owned bucket UI, write-failure surfacing. - Adds aiStudio/aiSeat and date-arithmetic to the upgrade checklist; updates the Console-section intro for the advanced pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AHVoqyH9VAjmbyMN1Ri2jf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3310. Pairs with objectui#… (the console contract sync + inbox hint alignment).
Problem
After the inbox retired its hand-wired decision buttons for
DeclaredActionsBar(objectui#2697/#2710), the approver actions onsys_approval_requestgated only onrecord.status == "pending". So a submitter viewing their own pending request saw Approve / Reject / Reassign / Send-back / Request-info — buttons that just 403'd — and the old client-side identity heuristic could wrongly hide a position/team-addressed approver.What
getRequest/listRequestsnow attach a per-viewer block, computed from the caller's context:can_act— the caller is a current pending approver (their user id is in the resolvedpending_approverswhile status ispending). This is the same check the decision methods authorize with — and becauseexpandApproversresolves position/team/manager specs to concrete user ids at open time, a plain membership test is faithful (strictly better than a client identity guess).is_submitter— the caller submitted the request.The declared actions now gate on it:
visiblerecord.viewer.can_actrecord.status == "pending" && record.viewer.is_submitter(pending || returned) && record.viewer.is_submitterrecord.status == "returned" && record.viewer.is_submitterWhere
vieweris absent (a row surfaced outside a service read with a user context — e.g. a raw data-API grid), therecord.viewer.*predicate fails closed.Cheap + synchronous (
attachViewers), safe on list reads. Mirrors theattachDecisionProgress(#3266) enrichment pattern.Verification
plugin-approvalssuite green (147 tests): new service tests —getRequestreturnscan_act:truefor a pending approver,falsefor the submitter and strangers, andfalseonce finalized;listRequestsattachesviewerto every row. Object contract test pins the new predicates.getRequestreturnedviewer: {can_act:false, is_submitter:true}.Refs
#3310 · objectui#2697 / #2710 (button retirement) · #3266 (
decision_progress, the enrichment pattern mirrored) · framework#3300 (declared action set)🤖 Generated with Claude Code
Generated by Claude Code