Skip to content

feat(approvals): server-computed viewer capability for precise decision-action gating (#3310) - #3344

Merged
os-zhuang merged 1 commit into
mainfrom
claude/enterprise-delegation-ooo-proxy-1v3m8l
Jul 20, 2026
Merged

feat(approvals): server-computed viewer capability for precise decision-action gating (#3310)#3344
os-zhuang merged 1 commit into
mainfrom
claude/enterprise-delegation-ooo-proxy-1v3m8l

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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 on sys_approval_request gated only on record.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 / listRequests now attach a per-viewer block, computed from the caller's context:

ApprovalRequestRow.viewer?: { can_act: boolean; is_submitter: boolean}
  • 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 same check the decision methods authorize with — and because expandApprovers resolves 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:

actionsvisible
approve / reject / reassign / send-back / request-inforecord.viewer.can_act
remindrecord.status == "pending" && record.viewer.is_submitter
recall(pending || returned) && record.viewer.is_submitter
resubmitrecord.status == "returned" && record.viewer.is_submitter

Where viewer is absent (a row surfaced outside a service read with a user context — e.g. a raw data-API grid), the record.viewer.* predicate fails closed.

Cheap + synchronous (attachViewers), safe on list reads. Mirrors the attachDecisionProgress (#3266) enrichment pattern.

Verification

  • plugin-approvals suite green (147 tests): new service 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. Object contract test pins the new predicates.
  • Browser (paired objectui change, live backend): a submitter viewing their own pending request now sees only remind + recall — approve/reject/reassign/send-back/request-info are all hidden; getRequest returned viewer: {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

…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
@vercel

vercelBot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 20, 2026 3:31am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-approvals, @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/plugin-approvals, packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/plugin-approvals, @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 20, 2026 03:36
@os-zhuang
os-zhuang merged commit 8efa395 into mainJul 20, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/enterprise-delegation-ooo-proxy-1v3m8l branch 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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude