feat(approvals): out-of-office auto-skip + self-service delegation (#1322)#3235
Merged
Merged
Conversation
…1322) Community-core continuity floor so approvals don't freeze when an approver is on leave. Enterprise governance (act-as inbox, SoD, admin-over-others, compliance reporting) is tracked separately in objectstack-ai/cloud#855. M1 — OOO auto-skip: - New self-service object `sys_approval_delegation` ({delegator_id, delegate_id, valid_from, valid_until, reason}), modelled as its own object (not a scalar on the better-auth-locked sys_user), mirroring the sys_user_position delegation precedent (ADR-0091). - ApprovalService.expandApprovers reroutes INDIVIDUALLY-routed approvers (type: user / field / manager) onto an active delegate at resolution time, reusing the shared isGrantActive half-open window predicate — never a background job (ADR-0049). Chain-following is bounded and cycle/self safe. Group/graph approvers (position/team/department/tier) are left untouched; position-routed leave stays ADR-0091's job. M2 — self-service task delegation: - The existing reassign service method + REST route already hand a single pending slot to another user with audit. Exposed it on the client SDK (approvals.reassign) so a "delegate to X" button has a clean entry point. M4 — audit + notification: - Each substitution hop is recorded as a new `ooo_substitute` audit action ("A -> B -- reason") and notifies both the delegate and the skipped approver via the existing messaging surface. Also: register the action kind in the spec contract + object enum, nav entry for self-service, and 14 unit tests covering user/field/manager skip, window boundaries, chain, cycle, self-delegation, tenant scope, and the group-approver boundary. Gap doc #20 annotated (community part). Co-Authored-By: Claude Opus 4.8 <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 3 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Add an "Out-of-office delegation" section to the approvals guide: how to declare a sys_approval_delegation row, which approver types reroute (individual: user/field/manager), the half-open resolution-time window, chain/cycle behaviour, the ooo_substitute audit + notification, and the group-approver boundary (position leave stays ADR-0091). Notes reassign for single in-flight requests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ypkQikZ55oWUHUnMebwXA
os-zhuang
marked this pull request as ready for review
July 18, 2026 16:23
os-zhuang
added a commit
that referenced
this pull request
Jul 19, 2026
Follow-up hardening on the merged OOO delegation feature (#3235). Security — delegator forge guard: sys_approval_delegation is apiEnabled CRUD, but as a system object it gets no owner_id anchor and defaults to a `public` sharing model, so an unguarded member could create a delegation naming SOMEONE ELSE as delegator and reroute that victim's individually-routed approvals to themselves. bindDelegationWrite- Guard (plugin-approvals beforeInsert/beforeUpdate, mirroring the ADR-0092 identity write-guard) forces a normal user's writes to name themselves as delegator: system context bypasses, admins (roles include 'admin') may set any delegator, everyone else is stamped-to-self on insert and rejected on a foreign delegator. Row ownership on update/delete is already covered by member_default's wildcard `created_by == current_user.id` RLS. i18n: Register sys_approval_delegation in the plugin's i18n extract config and add its object/field/view translations. zh-CN fully translated; en/ja-JP/es-ES carry the English baseline pending translation. (Blocks hand-added to avoid a full re-extract dropping existing enum/view keys.) Tests: 9 new guard cases (self-create, forge reject on insert/update, stamp on omit, unauthenticated reject, system + admin bypass, batch). plugin-approvals 123 passed. Claude-Session: https://claude.ai/code/session_016ypkQikZ55oWUHUnMebwXA Co-authored-by: Claude <noreply@anthropic.com>
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 the community-core part of #1322 — the "审批不冻结" continuity floor so approvals don't freeze while an approver is on leave. Enterprise governance (act-as inbox, SoD, admin-over-others, compliance reporting) is tracked separately in objectstack-ai/cloud#855.
What & why
An approval routes to a specific person, and people go on leave — freezing the flow and tanking SLAs. Position-routed leave was already covered by ADR-0091 (职务代理); the gap was individually-routed approvers (
type: user/field/manager). This PR closes it at the natural resolution seam.Changes
M1 — OOO auto-skip
sys_approval_delegation({delegator_id, delegate_id, valid_from, valid_until, reason}), modelled as its own object rather than a scalar on the better-auth-lockedsys_user, mirroring thesys_user_positiondelegation precedent (ADR-0091).ApprovalService.expandApproversreroutes individually-routed approvers onto an active delegate at resolution time, reusing the sharedisGrantActivehalf-open[from, until)predicate — never a background job (ADR-0049). Chain-following (A→B→C) is bounded (OOO_MAX_CHAIN) and cycle/self-reference safe. Group/graph approvers (position/team/department/tier) are deliberately left untouched.M2 — self-service task delegation
reassignservice method +POST /approvals/requests/:id/reassignroute already hand a single pending slot to another user with audit. Addedapprovals.reassignto the client SDK so a "delegate to X" button has a clean entry point. (Standing OOO delegation is CRUD on the new object via the generic data API — no bespoke endpoint.)M4 — audit + notification
ooo_substituteaudit action (A → B — reason) and notifies both the delegate and the skipped approver via the existing messaging surface.Also: registered the action kind in the spec contract + object enum, a Setup nav entry for self-service, and annotated gap doc #20 (community part) per its maintenance rule.
Semantics note
expandApproverssnapshots concrete approver ids at request-open time, so a delegation that starts after a request opens does not retroactively apply (position/tier are re-evaluated per decision; individual routing is not). Called out in code + issue.Tests
14 new unit tests: user/field/manager skip,
valid_from/valid_untilwindow boundaries (half-open), chain, cycle, self-delegation no-op, tenant scope (scoped vs null-org), the group-approver boundary, and audit+notify emission. Full suite green (114 inplugin-approvals, 6763 inspec); full monorepo build clean (71/71).Scope boundary (→ objectstack-ai/cloud#855)
act-as / proxy inbox · delegation governance + SoD · org-wide admin-over-others console · compliance reports / attestation. Multi-step / parallel approval is #5 (P1), separate.
🤖 Generated with Claude Code
Generated by Claude Code