Skip to content

Commit 28b6d27

Browse files
committed
chore: changeset for excludeSubmitter runtime; drop an unused test binding
Claude-Session: https://claude.ai/code/session_01LXX9EBHog6ZMo6apV2jvXX Co-authored-by: Claude <noreply@anthropic.com>
1 parent 357ad56 commit 28b6d27

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/plugin-approvals': patch
3+
'@objectstack/spec': patch
4+
---
5+
6+
feat(plugin-approvals): an approval step's `excludeSubmitter: true` keeps the submitter from approving their own request
7+
8+
Clause-②: no
9+
Metadata-protocol: none
10+
11+
- **Before.** `excludeSubmitter` was declared on the approval node config and its describe opened with `[PLANNED — not yet enforced]`: the approval engine parsed it and read nothing, so a category manager who submitted their own admission was step 1's `field` approver and approved it.
12+
- **At the open.** The submitter (the run's trigger user, `sys_approval_request.submitter_id`) is taken off the resolved slate, whatever approver type produced them, by their user id or their account's email, an out-of-office hop that lands on them included. Everyone else stays on it, per_group tags included. A slate this empties takes the node's `onEmptyApprovers` route: the organization's administrators by default, the declared `fallbackApprovers` under `'fallback'`, or the `NO_APPROVERS` refusal under `'fail'`, which now names the exclusion. The fallback slate and the administrators' slate are screened the same way, so an organization whose only administrator is the submitter opens the request on the slate that names no one.
13+
- **On the record.** A request whose slate lost its submitter carries a system note on its timeline naming the route it took (the remaining approvers, the fallback approvers, the administrators, or no one), rendered in the reader's language by the approvals door. The administrators' routing note says the only approver was the submitter instead of "no approver resolved to a person".
14+
- **After the open.** An approval decided by the submitter is refused with `FORBIDDEN`, the privileged override included; their rejection stands. A reassign to the submitter, or to a position only they hold, is refused with `VALIDATION_FAILED`. An SLA escalation that reassigns leaves them off the new slate, and one whose only target is the submitter is not applied: the pending approvers keep the request and are told the SLA lapsed.
15+
- **Spec.** The describe drops the `[PLANNED — not yet enforced]` marker and its last sentence; nothing else in the schema changes.
16+
- **Unchanged.** With the option off or omitted (the default), every slate, timeline, decision, reassign and escalation behaves as before.
17+
18+
What to do: nothing to migrate. A step that set `excludeSubmitter: true` now enforces it; an `expression` approver or validation rule added by hand to keep the submitter off can be removed.

‎packages/plugins/plugin-approvals/src/exclude-submitter.integration.test.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ const SOLO_ORG = 'org_solo';
6060
const as = (userId: string, org: string, permissions: string[] = []) =>
6161
({ userId, tenantId: org, positions: [], permissions }) as any;
6262
const SUB = as('u_sub', ORG);
63-
const OWNER = as('u_owner', ORG, ['organization_admin']);
6463

6564
const USERS = ['u_sub', 'u_owner', 'u_admin', 'u_alice', 'u_bob', 'u_fallback', 'u_dir', 'u_away']
6665
.map((id) => ({ id, name: id, email: `${id}@example.com` }));

0 commit comments

Comments
 (0)