@objectstack/plugin-approvals 12.6.0 → 14.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +121 -4
- package/dist/index.d.mts +48 -16
- package/dist/index.d.ts +48 -16
- package/dist/index.js +62 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/approval-node.test.ts +2 -2
- package/src/approval-node.ts +1 -1
- package/src/approval-revise.test.ts +3 -3
- package/src/approval-service.test.ts +103 -10
- package/src/approval-service.ts +60 -14
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/plugin-approvals@
|
|
2
|
+
> @objectstack/plugin-approvals@14.3.0 build /home/runner/work/framework/framework/packages/plugins/plugin-approvals
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m123.99 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m220.47 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 241ms
|
|
16
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m122.33 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m219.27 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 244ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 28424ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m382.64 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m382.64 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,122 @@
|
|
|
1
1
|
# @objectstack/plugin-approvals
|
|
2
2
|
|
|
3
|
+
## 14.3.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [2a71f48]
|
|
8
|
+
- Updated dependencies [02f6af4]
|
|
9
|
+
- Updated dependencies [c1064f1]
|
|
10
|
+
- @objectstack/platform-objects@14.3.0
|
|
11
|
+
- @objectstack/spec@14.3.0
|
|
12
|
+
- @objectstack/core@14.3.0
|
|
13
|
+
- @objectstack/formula@14.3.0
|
|
14
|
+
- @objectstack/metadata-core@14.3.0
|
|
15
|
+
|
|
16
|
+
## 14.2.0
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [ac8f029]
|
|
21
|
+
- Updated dependencies [4ab9958]
|
|
22
|
+
- @objectstack/spec@14.2.0
|
|
23
|
+
- @objectstack/platform-objects@14.2.0
|
|
24
|
+
- @objectstack/core@14.2.0
|
|
25
|
+
- @objectstack/formula@14.2.0
|
|
26
|
+
- @objectstack/metadata-core@14.2.0
|
|
27
|
+
|
|
28
|
+
## 14.1.0
|
|
29
|
+
|
|
30
|
+
### Minor Changes
|
|
31
|
+
|
|
32
|
+
- 5a8465f: SLA escalation `escalateTo` is position-first (ADR-0090 D3 follow-up to the `position` approver type).
|
|
33
|
+
|
|
34
|
+
- **spec**: `ApprovalEscalationSchema.escalateTo` is documented as a position machine name or a
|
|
35
|
+
specific user id (was "User id, role, or manager level" — the same pre-D3 'role' trap the
|
|
36
|
+
`position` approver type fixed); the Studio xRef picker kind moves `role` → `position`.
|
|
37
|
+
- **plugin-approvals**: on escalation, `escalateTo` now expands position holders via
|
|
38
|
+
`sys_user_position` ∪ the `sys_member.role` transition source (ADR-0057 D4) for both the
|
|
39
|
+
`reassign` approver hand-off and the `notify` audience. An empty expansion falls back to
|
|
40
|
+
treating the value as a literal user id, so configs naming a specific user keep working
|
|
41
|
+
unchanged. The audit trail keeps the authored target.
|
|
42
|
+
- **lint**: new `approval-escalation-reassign-no-target` warning — `escalation.action: 'reassign'`
|
|
43
|
+
with no `escalateTo` silently degrades to a notify at runtime; the fix-it prescribes a position
|
|
44
|
+
or user id target (or `action: 'notify'`).
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- Updated dependencies [5a8465f]
|
|
49
|
+
- Updated dependencies [7f8620b]
|
|
50
|
+
- Updated dependencies [82ba3a6]
|
|
51
|
+
- @objectstack/spec@14.1.0
|
|
52
|
+
- @objectstack/core@14.1.0
|
|
53
|
+
- @objectstack/formula@14.1.0
|
|
54
|
+
- @objectstack/metadata-core@14.1.0
|
|
55
|
+
- @objectstack/platform-objects@14.1.0
|
|
56
|
+
|
|
57
|
+
## 14.0.0
|
|
58
|
+
|
|
59
|
+
### Minor Changes
|
|
60
|
+
|
|
61
|
+
- 216fa9a: Add a `position` approver type so approvals can route to org positions (ADR-0090 D3 fallout).
|
|
62
|
+
|
|
63
|
+
Post ADR-0090 D3 the `role` approver type resolves against the better-auth org-membership
|
|
64
|
+
tier (`sys_member.role`: `owner`/`admin`/`member`) — it was never a position. Downstream
|
|
65
|
+
apps that authored `{ type: 'role', value: 'sales_manager' }` silently routed approvals to
|
|
66
|
+
nobody. Now:
|
|
67
|
+
|
|
68
|
+
- **spec**: `ApproverType` gains `'position'` — `value` is the position machine name; the
|
|
69
|
+
approver expands to its holders via `sys_user_position`. Authoring guidance: keep
|
|
70
|
+
`type: 'role'` ONLY for membership tiers; for org positions use
|
|
71
|
+
`{ type: 'position', value: '<position_name>' }` (one-line fix for the mismatch above).
|
|
72
|
+
- **plugin-approvals**: the engine resolves `position` approvers via `sys_user_position` ∪
|
|
73
|
+
the `sys_member.role` transition source (same semantics as `PositionGraphService` in
|
|
74
|
+
plugin-sharing). The `department` approver type is now honored by its spec spelling
|
|
75
|
+
(previously only the off-spec `business_unit`/`bu` dialect matched).
|
|
76
|
+
- **lint**: new `validateApprovalApprovers` rule — `approval-role-not-membership-tier`
|
|
77
|
+
warns when a `role` approver's value is not a membership tier and prescribes the
|
|
78
|
+
`position` rewrite; `approval-approver-type-unknown` flags off-spec approver types
|
|
79
|
+
(with a `business_unit` → `department` fix-it). Wired into `os lint`.
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- Updated dependencies [0a8e685]
|
|
84
|
+
- Updated dependencies [afa8115]
|
|
85
|
+
- Updated dependencies [80f12ca]
|
|
86
|
+
- Updated dependencies [332b711]
|
|
87
|
+
- Updated dependencies [e2fa074]
|
|
88
|
+
- Updated dependencies [23c8668]
|
|
89
|
+
- Updated dependencies [29f017d]
|
|
90
|
+
- Updated dependencies [216fa9a]
|
|
91
|
+
- Updated dependencies [6c22b12]
|
|
92
|
+
- Updated dependencies [d0531c4]
|
|
93
|
+
- Updated dependencies [cff5aac]
|
|
94
|
+
- @objectstack/spec@14.0.0
|
|
95
|
+
- @objectstack/platform-objects@14.0.0
|
|
96
|
+
- @objectstack/core@14.0.0
|
|
97
|
+
- @objectstack/formula@14.0.0
|
|
98
|
+
- @objectstack/metadata-core@14.0.0
|
|
99
|
+
|
|
100
|
+
## 13.0.0
|
|
101
|
+
|
|
102
|
+
### Patch Changes
|
|
103
|
+
|
|
104
|
+
- Updated dependencies [6d83431]
|
|
105
|
+
- Updated dependencies [01917c2]
|
|
106
|
+
- Updated dependencies [b271691]
|
|
107
|
+
- Updated dependencies [a5a1e41]
|
|
108
|
+
- Updated dependencies [466adf6]
|
|
109
|
+
- Updated dependencies [5be00c3]
|
|
110
|
+
- Updated dependencies [466adf6]
|
|
111
|
+
- Updated dependencies [2bee609]
|
|
112
|
+
- Updated dependencies [9fa84f9]
|
|
113
|
+
- Updated dependencies [fc7e7f7]
|
|
114
|
+
- @objectstack/spec@13.0.0
|
|
115
|
+
- @objectstack/core@13.0.0
|
|
116
|
+
- @objectstack/formula@13.0.0
|
|
117
|
+
- @objectstack/platform-objects@13.0.0
|
|
118
|
+
- @objectstack/metadata-core@13.0.0
|
|
119
|
+
|
|
3
120
|
## 12.6.0
|
|
4
121
|
|
|
5
122
|
### Patch Changes
|
|
@@ -355,8 +472,8 @@
|
|
|
355
472
|
ADR-0057 — ERP authorization core. Adds permission-grant access DEPTH
|
|
356
473
|
(`own`/`own_and_reports`/`unit`/`unit_and_below`/`org`), renames `sys_department`
|
|
357
474
|
→ `sys_business_unit` (no aliases — see BREAKING above), introduces the platform-owned
|
|
358
|
-
`
|
|
359
|
-
`
|
|
475
|
+
`sys_user_position` assignment, and seeds stack-declared `roles`/`sharingRules` into
|
|
476
|
+
`sys_position`/`sys_sharing_rule` at boot (closes #2077). Hierarchy-relative scopes are
|
|
360
477
|
delegated to a pluggable `IHierarchyScopeResolver` (open edition fails closed to
|
|
361
478
|
owner-only; `defineStack` errors without `requires: ['hierarchy-security']`). Also
|
|
362
479
|
fixes a latent over-grant where `engine.find({ filter })` was ignored (driver reads
|
|
@@ -765,8 +882,8 @@
|
|
|
765
882
|
`i18n.loadTranslations` (the i18n service is optional — load is best-effort).
|
|
766
883
|
- `plugin-webhooks` ← `sys_webhook`, `sys_webhook_delivery`
|
|
767
884
|
- `plugin-approvals` ← `sys_approval_request`, `sys_approval_action`
|
|
768
|
-
- `plugin-security` ← `
|
|
769
|
-
`sys_user_permission_set`, `
|
|
885
|
+
- `plugin-security` ← `sys_position`, `sys_permission_set`,
|
|
886
|
+
`sys_user_permission_set`, `sys_position_permission_set`
|
|
770
887
|
- `plugin-sharing` ← `sys_record_share`, `sys_sharing_rule`, `sys_share_link`
|
|
771
888
|
- `@objectstack/platform-objects` translation bundles are regenerated to drop
|
|
772
889
|
those objects' keys (its extract config already excluded them); all other
|
package/dist/index.d.mts
CHANGED
|
@@ -320,7 +320,12 @@ declare const SysApprovalRequest: Omit<{
|
|
|
320
320
|
access?: {
|
|
321
321
|
default: "public" | "private";
|
|
322
322
|
} | undefined;
|
|
323
|
-
requiredPermissions?: string[] |
|
|
323
|
+
requiredPermissions?: string[] | {
|
|
324
|
+
read?: string[] | undefined;
|
|
325
|
+
create?: string[] | undefined;
|
|
326
|
+
update?: string[] | undefined;
|
|
327
|
+
delete?: string[] | undefined;
|
|
328
|
+
} | undefined;
|
|
324
329
|
softDelete?: {
|
|
325
330
|
enabled: boolean;
|
|
326
331
|
field: string;
|
|
@@ -666,9 +671,10 @@ declare const SysApprovalRequest: Omit<{
|
|
|
666
671
|
trash: boolean;
|
|
667
672
|
mru: boolean;
|
|
668
673
|
clone: boolean;
|
|
669
|
-
apiMethods?: ("search" | "create" | "import" | "delete" | "
|
|
674
|
+
apiMethods?: ("search" | "create" | "import" | "delete" | "update" | "get" | "upsert" | "list" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
670
675
|
} | undefined;
|
|
671
|
-
sharingModel?: "
|
|
676
|
+
sharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
677
|
+
externalSharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
672
678
|
publicSharing?: {
|
|
673
679
|
enabled: boolean;
|
|
674
680
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -4507,7 +4513,12 @@ declare const SysApprovalAction: Omit<{
|
|
|
4507
4513
|
access?: {
|
|
4508
4514
|
default: "public" | "private";
|
|
4509
4515
|
} | undefined;
|
|
4510
|
-
requiredPermissions?: string[] |
|
|
4516
|
+
requiredPermissions?: string[] | {
|
|
4517
|
+
read?: string[] | undefined;
|
|
4518
|
+
create?: string[] | undefined;
|
|
4519
|
+
update?: string[] | undefined;
|
|
4520
|
+
delete?: string[] | undefined;
|
|
4521
|
+
} | undefined;
|
|
4511
4522
|
softDelete?: {
|
|
4512
4523
|
enabled: boolean;
|
|
4513
4524
|
field: string;
|
|
@@ -4853,9 +4864,10 @@ declare const SysApprovalAction: Omit<{
|
|
|
4853
4864
|
trash: boolean;
|
|
4854
4865
|
mru: boolean;
|
|
4855
4866
|
clone: boolean;
|
|
4856
|
-
apiMethods?: ("search" | "create" | "import" | "delete" | "
|
|
4867
|
+
apiMethods?: ("search" | "create" | "import" | "delete" | "update" | "get" | "upsert" | "list" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
4857
4868
|
} | undefined;
|
|
4858
|
-
sharingModel?: "
|
|
4869
|
+
sharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
4870
|
+
externalSharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
4859
4871
|
publicSharing?: {
|
|
4860
4872
|
enabled: boolean;
|
|
4861
4873
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -7028,7 +7040,12 @@ declare const SysApprovalApprover: Omit<{
|
|
|
7028
7040
|
access?: {
|
|
7029
7041
|
default: "public" | "private";
|
|
7030
7042
|
} | undefined;
|
|
7031
|
-
requiredPermissions?: string[] |
|
|
7043
|
+
requiredPermissions?: string[] | {
|
|
7044
|
+
read?: string[] | undefined;
|
|
7045
|
+
create?: string[] | undefined;
|
|
7046
|
+
update?: string[] | undefined;
|
|
7047
|
+
delete?: string[] | undefined;
|
|
7048
|
+
} | undefined;
|
|
7032
7049
|
softDelete?: {
|
|
7033
7050
|
enabled: boolean;
|
|
7034
7051
|
field: string;
|
|
@@ -7374,9 +7391,10 @@ declare const SysApprovalApprover: Omit<{
|
|
|
7374
7391
|
trash: boolean;
|
|
7375
7392
|
mru: boolean;
|
|
7376
7393
|
clone: boolean;
|
|
7377
|
-
apiMethods?: ("search" | "create" | "import" | "delete" | "
|
|
7394
|
+
apiMethods?: ("search" | "create" | "import" | "delete" | "update" | "get" | "upsert" | "list" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
7378
7395
|
} | undefined;
|
|
7379
|
-
sharingModel?: "
|
|
7396
|
+
sharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
7397
|
+
externalSharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
7380
7398
|
publicSharing?: {
|
|
7381
7399
|
enabled: boolean;
|
|
7382
7400
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -8442,8 +8460,8 @@ declare const SysApprovalApprover: Omit<{
|
|
|
8442
8460
|
* `approve` / `reject` edge.
|
|
8443
8461
|
*
|
|
8444
8462
|
* This service owns the durable approval *state* — `sys_approval_request` /
|
|
8445
|
-
* `sys_approval_action`, approver resolution (team / department /
|
|
8446
|
-
* manager graph), and the optional status-field mirror — plus the decision
|
|
8463
|
+
* `sys_approval_action`, approver resolution (team / department / position /
|
|
8464
|
+
* role / manager graph), and the optional status-field mirror — plus the decision
|
|
8447
8465
|
* API. It does not author processes, submit, or walk multi-step machinery
|
|
8448
8466
|
* anymore; that orchestration lives on the one automation engine.
|
|
8449
8467
|
*/
|
|
@@ -8548,16 +8566,20 @@ declare class ApprovalService implements IApprovalService {
|
|
|
8548
8566
|
private loadPendingRow;
|
|
8549
8567
|
/**
|
|
8550
8568
|
* Expand the approvers on an Approval node into user IDs by querying the
|
|
8551
|
-
* graph tables for `team:` / `department:` / `
|
|
8552
|
-
* types. Falls back to a prefixed literal
|
|
8553
|
-
* produce nothing — so existing fixtures
|
|
8554
|
-
* matching keep working.
|
|
8569
|
+
* graph tables for `team:` / `department:` / `position:` / `role:` /
|
|
8570
|
+
* `manager:` approver types. Falls back to a prefixed literal
|
|
8571
|
+
* (`type:value`) when graph lookups produce nothing — so existing fixtures
|
|
8572
|
+
* and flows that rely on substring matching keep working.
|
|
8555
8573
|
*
|
|
8556
8574
|
* **Graph semantics:**
|
|
8557
8575
|
* - `team` → flat members of `sys_team` (better-auth; no BFS)
|
|
8558
8576
|
* - `department` → recursive BFS of `sys_business_unit.parent_business_unit_id`
|
|
8559
8577
|
* → members of every descendant via `sys_business_unit_member`
|
|
8560
|
-
* - `
|
|
8578
|
+
* - `position` → holders via `sys_user_position` ∪ `sys_member.role`
|
|
8579
|
+
* transition source (ADR-0090 D3 / ADR-0057 D4)
|
|
8580
|
+
* - `role` → users with `sys_member.role = value` in tenant — the
|
|
8581
|
+
* better-auth MEMBERSHIP TIER (owner/admin/member), not a
|
|
8582
|
+
* position; author `position` for org positions
|
|
8561
8583
|
* - `manager` → `sys_user.manager_id` of `record[value] ?? record.owner_id`
|
|
8562
8584
|
* - `field` → literal user id stored in `record[value]`
|
|
8563
8585
|
* - `user` → literal value
|
|
@@ -8567,6 +8589,16 @@ declare class ApprovalService implements IApprovalService {
|
|
|
8567
8589
|
private expandTeamUsers;
|
|
8568
8590
|
/** Recursive department — walks `sys_business_unit.parent_business_unit_id`. */
|
|
8569
8591
|
private expandBusinessUnitUsers;
|
|
8592
|
+
/**
|
|
8593
|
+
* Position holders (ADR-0090 D3): `sys_user_position` is the platform-owned
|
|
8594
|
+
* assignment table, keyed by the position's machine name (ADR-0057 D4),
|
|
8595
|
+
* unioned with the better-auth membership string (`sys_member.role`) as a
|
|
8596
|
+
* transition source — the same semantics as `PositionGraphService` in
|
|
8597
|
+
* `plugin-sharing`, so an approval routes to exactly the users the sharing
|
|
8598
|
+
* engine would expand for the same position.
|
|
8599
|
+
*/
|
|
8600
|
+
private expandPositionUsers;
|
|
8601
|
+
/** better-auth org-membership tier (`sys_member.role`) — NOT positions. */
|
|
8570
8602
|
private expandRoleUsers;
|
|
8571
8603
|
private lookupManager;
|
|
8572
8604
|
/** Mirror a request status onto a business-object field, if configured. */
|
package/dist/index.d.ts
CHANGED
|
@@ -320,7 +320,12 @@ declare const SysApprovalRequest: Omit<{
|
|
|
320
320
|
access?: {
|
|
321
321
|
default: "public" | "private";
|
|
322
322
|
} | undefined;
|
|
323
|
-
requiredPermissions?: string[] |
|
|
323
|
+
requiredPermissions?: string[] | {
|
|
324
|
+
read?: string[] | undefined;
|
|
325
|
+
create?: string[] | undefined;
|
|
326
|
+
update?: string[] | undefined;
|
|
327
|
+
delete?: string[] | undefined;
|
|
328
|
+
} | undefined;
|
|
324
329
|
softDelete?: {
|
|
325
330
|
enabled: boolean;
|
|
326
331
|
field: string;
|
|
@@ -666,9 +671,10 @@ declare const SysApprovalRequest: Omit<{
|
|
|
666
671
|
trash: boolean;
|
|
667
672
|
mru: boolean;
|
|
668
673
|
clone: boolean;
|
|
669
|
-
apiMethods?: ("search" | "create" | "import" | "delete" | "
|
|
674
|
+
apiMethods?: ("search" | "create" | "import" | "delete" | "update" | "get" | "upsert" | "list" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
670
675
|
} | undefined;
|
|
671
|
-
sharingModel?: "
|
|
676
|
+
sharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
677
|
+
externalSharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
672
678
|
publicSharing?: {
|
|
673
679
|
enabled: boolean;
|
|
674
680
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -4507,7 +4513,12 @@ declare const SysApprovalAction: Omit<{
|
|
|
4507
4513
|
access?: {
|
|
4508
4514
|
default: "public" | "private";
|
|
4509
4515
|
} | undefined;
|
|
4510
|
-
requiredPermissions?: string[] |
|
|
4516
|
+
requiredPermissions?: string[] | {
|
|
4517
|
+
read?: string[] | undefined;
|
|
4518
|
+
create?: string[] | undefined;
|
|
4519
|
+
update?: string[] | undefined;
|
|
4520
|
+
delete?: string[] | undefined;
|
|
4521
|
+
} | undefined;
|
|
4511
4522
|
softDelete?: {
|
|
4512
4523
|
enabled: boolean;
|
|
4513
4524
|
field: string;
|
|
@@ -4853,9 +4864,10 @@ declare const SysApprovalAction: Omit<{
|
|
|
4853
4864
|
trash: boolean;
|
|
4854
4865
|
mru: boolean;
|
|
4855
4866
|
clone: boolean;
|
|
4856
|
-
apiMethods?: ("search" | "create" | "import" | "delete" | "
|
|
4867
|
+
apiMethods?: ("search" | "create" | "import" | "delete" | "update" | "get" | "upsert" | "list" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
4857
4868
|
} | undefined;
|
|
4858
|
-
sharingModel?: "
|
|
4869
|
+
sharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
4870
|
+
externalSharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
4859
4871
|
publicSharing?: {
|
|
4860
4872
|
enabled: boolean;
|
|
4861
4873
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -7028,7 +7040,12 @@ declare const SysApprovalApprover: Omit<{
|
|
|
7028
7040
|
access?: {
|
|
7029
7041
|
default: "public" | "private";
|
|
7030
7042
|
} | undefined;
|
|
7031
|
-
requiredPermissions?: string[] |
|
|
7043
|
+
requiredPermissions?: string[] | {
|
|
7044
|
+
read?: string[] | undefined;
|
|
7045
|
+
create?: string[] | undefined;
|
|
7046
|
+
update?: string[] | undefined;
|
|
7047
|
+
delete?: string[] | undefined;
|
|
7048
|
+
} | undefined;
|
|
7032
7049
|
softDelete?: {
|
|
7033
7050
|
enabled: boolean;
|
|
7034
7051
|
field: string;
|
|
@@ -7374,9 +7391,10 @@ declare const SysApprovalApprover: Omit<{
|
|
|
7374
7391
|
trash: boolean;
|
|
7375
7392
|
mru: boolean;
|
|
7376
7393
|
clone: boolean;
|
|
7377
|
-
apiMethods?: ("search" | "create" | "import" | "delete" | "
|
|
7394
|
+
apiMethods?: ("search" | "create" | "import" | "delete" | "update" | "get" | "upsert" | "list" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
7378
7395
|
} | undefined;
|
|
7379
|
-
sharingModel?: "
|
|
7396
|
+
sharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
7397
|
+
externalSharingModel?: "private" | "public_read" | "public_read_write" | "controlled_by_parent" | undefined;
|
|
7380
7398
|
publicSharing?: {
|
|
7381
7399
|
enabled: boolean;
|
|
7382
7400
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -8442,8 +8460,8 @@ declare const SysApprovalApprover: Omit<{
|
|
|
8442
8460
|
* `approve` / `reject` edge.
|
|
8443
8461
|
*
|
|
8444
8462
|
* This service owns the durable approval *state* — `sys_approval_request` /
|
|
8445
|
-
* `sys_approval_action`, approver resolution (team / department /
|
|
8446
|
-
* manager graph), and the optional status-field mirror — plus the decision
|
|
8463
|
+
* `sys_approval_action`, approver resolution (team / department / position /
|
|
8464
|
+
* role / manager graph), and the optional status-field mirror — plus the decision
|
|
8447
8465
|
* API. It does not author processes, submit, or walk multi-step machinery
|
|
8448
8466
|
* anymore; that orchestration lives on the one automation engine.
|
|
8449
8467
|
*/
|
|
@@ -8548,16 +8566,20 @@ declare class ApprovalService implements IApprovalService {
|
|
|
8548
8566
|
private loadPendingRow;
|
|
8549
8567
|
/**
|
|
8550
8568
|
* Expand the approvers on an Approval node into user IDs by querying the
|
|
8551
|
-
* graph tables for `team:` / `department:` / `
|
|
8552
|
-
* types. Falls back to a prefixed literal
|
|
8553
|
-
* produce nothing — so existing fixtures
|
|
8554
|
-
* matching keep working.
|
|
8569
|
+
* graph tables for `team:` / `department:` / `position:` / `role:` /
|
|
8570
|
+
* `manager:` approver types. Falls back to a prefixed literal
|
|
8571
|
+
* (`type:value`) when graph lookups produce nothing — so existing fixtures
|
|
8572
|
+
* and flows that rely on substring matching keep working.
|
|
8555
8573
|
*
|
|
8556
8574
|
* **Graph semantics:**
|
|
8557
8575
|
* - `team` → flat members of `sys_team` (better-auth; no BFS)
|
|
8558
8576
|
* - `department` → recursive BFS of `sys_business_unit.parent_business_unit_id`
|
|
8559
8577
|
* → members of every descendant via `sys_business_unit_member`
|
|
8560
|
-
* - `
|
|
8578
|
+
* - `position` → holders via `sys_user_position` ∪ `sys_member.role`
|
|
8579
|
+
* transition source (ADR-0090 D3 / ADR-0057 D4)
|
|
8580
|
+
* - `role` → users with `sys_member.role = value` in tenant — the
|
|
8581
|
+
* better-auth MEMBERSHIP TIER (owner/admin/member), not a
|
|
8582
|
+
* position; author `position` for org positions
|
|
8561
8583
|
* - `manager` → `sys_user.manager_id` of `record[value] ?? record.owner_id`
|
|
8562
8584
|
* - `field` → literal user id stored in `record[value]`
|
|
8563
8585
|
* - `user` → literal value
|
|
@@ -8567,6 +8589,16 @@ declare class ApprovalService implements IApprovalService {
|
|
|
8567
8589
|
private expandTeamUsers;
|
|
8568
8590
|
/** Recursive department — walks `sys_business_unit.parent_business_unit_id`. */
|
|
8569
8591
|
private expandBusinessUnitUsers;
|
|
8592
|
+
/**
|
|
8593
|
+
* Position holders (ADR-0090 D3): `sys_user_position` is the platform-owned
|
|
8594
|
+
* assignment table, keyed by the position's machine name (ADR-0057 D4),
|
|
8595
|
+
* unioned with the better-auth membership string (`sys_member.role`) as a
|
|
8596
|
+
* transition source — the same semantics as `PositionGraphService` in
|
|
8597
|
+
* `plugin-sharing`, so an approval routes to exactly the users the sharing
|
|
8598
|
+
* engine would expand for the same position.
|
|
8599
|
+
*/
|
|
8600
|
+
private expandPositionUsers;
|
|
8601
|
+
/** better-auth org-membership tier (`sys_member.role`) — NOT positions. */
|
|
8570
8602
|
private expandRoleUsers;
|
|
8571
8603
|
private lookupManager;
|
|
8572
8604
|
/** Mirror a request status onto a business-object field, if configured. */
|
package/dist/index.js
CHANGED
|
@@ -1029,7 +1029,7 @@ var ESCALATION_JOB_NAME = "approvals-sla-escalation";
|
|
|
1029
1029
|
var ESCALATION_SCAN_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1030
1030
|
var SLA_ACTOR_ID = "system:sla";
|
|
1031
1031
|
var ACTION_TOKEN_TTL_MS = 72 * 60 * 60 * 1e3;
|
|
1032
|
-
var SYSTEM_CTX = { isSystem: true,
|
|
1032
|
+
var SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] };
|
|
1033
1033
|
function uid(prefix) {
|
|
1034
1034
|
const g = globalThis;
|
|
1035
1035
|
if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;
|
|
@@ -1152,16 +1152,20 @@ var _ApprovalService = class _ApprovalService {
|
|
|
1152
1152
|
}
|
|
1153
1153
|
/**
|
|
1154
1154
|
* Expand the approvers on an Approval node into user IDs by querying the
|
|
1155
|
-
* graph tables for `team:` / `department:` / `
|
|
1156
|
-
* types. Falls back to a prefixed literal
|
|
1157
|
-
* produce nothing — so existing fixtures
|
|
1158
|
-
* matching keep working.
|
|
1155
|
+
* graph tables for `team:` / `department:` / `position:` / `role:` /
|
|
1156
|
+
* `manager:` approver types. Falls back to a prefixed literal
|
|
1157
|
+
* (`type:value`) when graph lookups produce nothing — so existing fixtures
|
|
1158
|
+
* and flows that rely on substring matching keep working.
|
|
1159
1159
|
*
|
|
1160
1160
|
* **Graph semantics:**
|
|
1161
1161
|
* - `team` → flat members of `sys_team` (better-auth; no BFS)
|
|
1162
1162
|
* - `department` → recursive BFS of `sys_business_unit.parent_business_unit_id`
|
|
1163
1163
|
* → members of every descendant via `sys_business_unit_member`
|
|
1164
|
-
* - `
|
|
1164
|
+
* - `position` → holders via `sys_user_position` ∪ `sys_member.role`
|
|
1165
|
+
* transition source (ADR-0090 D3 / ADR-0057 D4)
|
|
1166
|
+
* - `role` → users with `sys_member.role = value` in tenant — the
|
|
1167
|
+
* better-auth MEMBERSHIP TIER (owner/admin/member), not a
|
|
1168
|
+
* position; author `position` for org positions
|
|
1165
1169
|
* - `manager` → `sys_user.manager_id` of `record[value] ?? record.owner_id`
|
|
1166
1170
|
* - `field` → literal user id stored in `record[value]`
|
|
1167
1171
|
* - `user` → literal value
|
|
@@ -1186,12 +1190,18 @@ var _ApprovalService = class _ApprovalService {
|
|
|
1186
1190
|
for (const u of users) out.push(u);
|
|
1187
1191
|
continue;
|
|
1188
1192
|
}
|
|
1189
|
-
} else if (a.type === "business_unit" || a.type === "bu") {
|
|
1193
|
+
} else if (a.type === "department" || a.type === "business_unit" || a.type === "bu") {
|
|
1190
1194
|
const users = await this.expandBusinessUnitUsers(String(a.value), organizationId);
|
|
1191
1195
|
if (users.length) {
|
|
1192
1196
|
for (const u of users) out.push(u);
|
|
1193
1197
|
continue;
|
|
1194
1198
|
}
|
|
1199
|
+
} else if (a.type === "position") {
|
|
1200
|
+
const users = await this.expandPositionUsers(String(a.value), organizationId);
|
|
1201
|
+
if (users.length) {
|
|
1202
|
+
for (const u of users) out.push(u);
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1195
1205
|
} else if (a.type === "role") {
|
|
1196
1206
|
const users = await this.expandRoleUsers(String(a.value), organizationId);
|
|
1197
1207
|
if (users.length) {
|
|
@@ -1278,6 +1288,36 @@ var _ApprovalService = class _ApprovalService {
|
|
|
1278
1288
|
}
|
|
1279
1289
|
return Array.from(new Set((rows ?? []).map((r) => String(r.user_id ?? "")).filter(Boolean)));
|
|
1280
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Position holders (ADR-0090 D3): `sys_user_position` is the platform-owned
|
|
1293
|
+
* assignment table, keyed by the position's machine name (ADR-0057 D4),
|
|
1294
|
+
* unioned with the better-auth membership string (`sys_member.role`) as a
|
|
1295
|
+
* transition source — the same semantics as `PositionGraphService` in
|
|
1296
|
+
* `plugin-sharing`, so an approval routes to exactly the users the sharing
|
|
1297
|
+
* engine would expand for the same position.
|
|
1298
|
+
*/
|
|
1299
|
+
async expandPositionUsers(positionName, organizationId) {
|
|
1300
|
+
if (!positionName) return [];
|
|
1301
|
+
const users = /* @__PURE__ */ new Set();
|
|
1302
|
+
const filter = { position: positionName };
|
|
1303
|
+
if (organizationId) filter.organization_id = organizationId;
|
|
1304
|
+
try {
|
|
1305
|
+
const rows = await this.engine.find("sys_user_position", {
|
|
1306
|
+
filter,
|
|
1307
|
+
fields: ["user_id"],
|
|
1308
|
+
limit: 1e4,
|
|
1309
|
+
context: SYSTEM_CTX
|
|
1310
|
+
});
|
|
1311
|
+
for (const r of rows ?? []) {
|
|
1312
|
+
const uid2 = String(r.user_id ?? "");
|
|
1313
|
+
if (uid2) users.add(uid2);
|
|
1314
|
+
}
|
|
1315
|
+
} catch {
|
|
1316
|
+
}
|
|
1317
|
+
for (const uid2 of await this.expandRoleUsers(positionName, organizationId)) users.add(uid2);
|
|
1318
|
+
return Array.from(users);
|
|
1319
|
+
}
|
|
1320
|
+
/** better-auth org-membership tier (`sys_member.role`) — NOT positions. */
|
|
1281
1321
|
async expandRoleUsers(roleName, organizationId) {
|
|
1282
1322
|
if (!roleName) return [];
|
|
1283
1323
|
const filter = { role: roleName };
|
|
@@ -2199,6 +2239,15 @@ var _ApprovalService = class _ApprovalService {
|
|
|
2199
2239
|
const escalateTo = typeof esc2.escalateTo === "string" && esc2.escalateTo.trim() ? esc2.escalateTo.trim() : void 0;
|
|
2200
2240
|
const now = this.clock.now().toISOString();
|
|
2201
2241
|
const pending = csvSplit(raw.pending_approvers);
|
|
2242
|
+
let escalatees = [];
|
|
2243
|
+
if (escalateTo) {
|
|
2244
|
+
try {
|
|
2245
|
+
escalatees = await this.expandPositionUsers(escalateTo, raw.organization_id ?? null);
|
|
2246
|
+
} catch {
|
|
2247
|
+
escalatees = [];
|
|
2248
|
+
}
|
|
2249
|
+
if (!escalatees.length) escalatees = [escalateTo];
|
|
2250
|
+
}
|
|
2202
2251
|
await this.engine.insert("sys_approval_action", {
|
|
2203
2252
|
id: uid("aact"),
|
|
2204
2253
|
request_id: raw.id,
|
|
@@ -2210,16 +2259,16 @@ var _ApprovalService = class _ApprovalService {
|
|
|
2210
2259
|
comment: `${action}${escalateTo ? ` \u2192 ${escalateTo}` : ""}`,
|
|
2211
2260
|
created_at: now
|
|
2212
2261
|
}, { context: SYSTEM_CTX });
|
|
2213
|
-
if (action === "reassign" &&
|
|
2262
|
+
if (action === "reassign" && escalatees.length) {
|
|
2214
2263
|
await this.engine.update("sys_approval_request", {
|
|
2215
2264
|
id: raw.id,
|
|
2216
|
-
pending_approvers:
|
|
2265
|
+
pending_approvers: escalatees.join(","),
|
|
2217
2266
|
updated_at: now
|
|
2218
2267
|
}, { context: SYSTEM_CTX });
|
|
2219
|
-
await this.syncApproverIndex(raw.id,
|
|
2268
|
+
await this.syncApproverIndex(raw.id, escalatees, raw.organization_id ?? null, now);
|
|
2220
2269
|
await this.notify({
|
|
2221
2270
|
topic: "approval.escalated",
|
|
2222
|
-
audience:
|
|
2271
|
+
audience: escalatees,
|
|
2223
2272
|
actorId: SLA_ACTOR_ID,
|
|
2224
2273
|
source: { object: "sys_approval_request", id: raw.id },
|
|
2225
2274
|
payload: {
|
|
@@ -2237,7 +2286,7 @@ var _ApprovalService = class _ApprovalService {
|
|
|
2237
2286
|
} else {
|
|
2238
2287
|
await this.notify({
|
|
2239
2288
|
topic: "approval.sla_breached",
|
|
2240
|
-
audience: [...pending, ...
|
|
2289
|
+
audience: [...pending, ...escalatees],
|
|
2241
2290
|
actorId: SLA_ACTOR_ID,
|
|
2242
2291
|
source: { object: "sys_approval_request", id: raw.id },
|
|
2243
2292
|
payload: {
|
|
@@ -2937,7 +2986,7 @@ function unbindAllHooks(engine) {
|
|
|
2937
2986
|
|
|
2938
2987
|
// src/approval-node.ts
|
|
2939
2988
|
var import_automation2 = require("@objectstack/spec/automation");
|
|
2940
|
-
var SYSTEM_CTX2 = { isSystem: true,
|
|
2989
|
+
var SYSTEM_CTX2 = { isSystem: true, positions: [], permissions: [] };
|
|
2941
2990
|
function registerApprovalNode(automation, service, logger) {
|
|
2942
2991
|
automation.registerNodeExecutor({
|
|
2943
2992
|
type: import_automation2.APPROVAL_NODE_TYPE,
|