feat(approvals): quorum + per-group sign-off (会签) + decision attachments (#3266)#3268
Merged
Merged
Conversation
…nts (#3266) Node-internal multi-approver semantics — the钉钉/Salesforce-style patterns that deliver "legal AND finance both approve" and "M-of-N" without any flow-engine change (topology-level parallel branches remain #3267). Community-core; weighted voting + approval-matrix governance stay enterprise (objectstack-ai/cloud#861). Spec (approval.zod.ts): - behavior enum += 'quorum' (M-of-N) and 'per_group' (one — or minApprovals — from EACH group), plus a `minApprovals` threshold and an optional `group` label on each approver. A single rejection is always a veto. Corrected the stale "quorum is enterprise" comment. Engine (approval-service.ts): - expandApprovers refactored to resolveApproverSpec + a group collector, so each resolved approver (OOO-substituted included) is tagged with its group; the map is snapshotted at open so the tally is stable. - decideNode: unified quorum/per_group/unanimous finalization via isApprovalSatisfied; thresholds clamp to the resolvable count / group size so a mis-set value can never deadlock. reassign transfers a slot's group so the new approver still counts. Attachments (#3266): - sys_approval_action gains a `Field.file` `attachments` column; decide/comment accept + persist file references, threaded through the contract, REST decide/comment routes, and the client approve/reject helpers. Showcase: ExpenseSignoffFlow — manager + finance per_group 会签 on a submitted expense report. Tests: 9 new (quorum boundary + clamp + veto, per_group each-group / two-in-one-group / minApprovals=2 / OOO-substituted member counts, attachments); 132 green in plugin-approvals. 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 4 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
… quorum/per_group (#3266) CI follow-ups on the #3266 spec change: - Regenerate content/docs/references/automation/approval.mdx (generated from the spec schema; the new behavior enum / minApprovals / approver group made it stale). - approval.test.ts: the "rejects an unknown behavior" case hard-coded 'quorum' as its unknown value — now a valid behavior. Switch it to 'weighted' and add positive coverage that quorum / per_group / minApprovals / grouped approvers parse. - approvals.mdx: document the behavior matrix (first_response / unanimous / quorum / per_group), the veto rule, minApprovals clamping, and decision attachments. 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 19, 2026 11:26
os-zhuang
pushed a commit
that referenced
this pull request
Jul 19, 2026
…contract mapping (#3266) Browser verification caught a #3268 gap: the `attachments` column landed on sys_approval_action and decide() persisted it, but rowFromAction never mapped it — every listActions consumer (REST /actions, the console timeline) saw none while the raw engine row carried the fileIds. Map it (and type it on the ApprovalActionRow contract) so the audit trail actually exposes what was attached. Regression test added: 137 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ypkQikZ55oWUHUnMebwXA
os-zhuang
added a commit
that referenced
this pull request
Jul 19, 2026
…p links (#3274) objectui#2678 P1.5 backend: getRequest attaches decision_progress (unanimous/quorum got/need; per_group per-group breakdown from the open-time snapshot) for pending multi-approver requests; notify() centrally rewrites inbox actionUrls to /system/approvals?request=<id> deep links; rowFromAction now maps decision attachments through the listActions contract (browser-caught #3268 gap). 137 tests green.
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 #3266 (Phase 1 of gap #5). Node-internal multi-approver semantics — the钉钉/Salesforce-style patterns — delivering "legal AND finance both approve" and "M-of-N" with zero flow-engine changes. Topology-level parallel branches remain a separate engine ADR (#3267); weighted voting + approval-matrix governance stay enterprise (objectstack-ai/cloud#861).
Why this shape
Investigation showed serial multi-step, comments, recall, reassign, and OOO are already done; the real gaps were quorum and per-group (会签). Every mainstream admin-facing platform (钉钉/飞书/Salesforce/ServiceNow) delivers these as node-internal rules, not parallel gateways — which also sidesteps the "one pending request per record" / record-lock / status-mirror complications that true parallel branches introduce.
Changes
Spec (
approval.zod.ts)behavior+=quorum(M-of-N) andper_group(one — orminApprovals— from each group); newminApprovalsthreshold; optionalgrouplabel on each approver. A single rejection is always a veto. Corrected the stale "quorum is enterprise" comment.Engine (
approval-service.ts)expandApproversrefactored intoresolveApproverSpec+ a group collector, so each resolved approver (OOO-substituted included) is tagged with its group; the map is snapshotted at open so the tally is stable.decideNode: unifiedquorum/per_group/unanimousfinalization viaisApprovalSatisfied; thresholds clamp to the resolvable count / group size so a mis-set value can never deadlock.reassigntransfers a slot's group so the new approver still counts.Attachments
sys_approval_actiongains aField.fileattachmentscolumn;decide/commentaccept + persist file references, threaded through the contract, the REST decide/comment routes, and the clientapprove/rejecthelpers.Showcase —
ExpenseSignoffFlow: manager + financeper_group会签 on a submitted expense report.Tests & verification
plugin-approvals; full monorepo build clean.per_grouprequest pending on both a manager and an auditor; the manager's approval held the node (finalized=false, pending=[auditor]); the auditor's approval finalized it (approved, flow resumed); the finance decision's audit row persistedattachments:["file_receipt_scan"].Scope boundary
🤖 Generated with Claude Code
Generated by Claude Code