-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathsys-sharing-rule.object.ts
More file actions
191 lines (174 loc) · 6.48 KB
/
Copy pathsys-sharing-rule.object.ts
File metadata and controls
191 lines (174 loc) · 6.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { ObjectSchema, Field } from '@objectstack/spec/data';
/**
* sys_sharing_rule — Declarative record-sharing rule.
*
* Salesforce-style criteria-based sharing: "any record on object O that
* matches criteria C is granted access level A to recipient R". Rules
* are evaluated by `@objectstack/plugin-sharing` and materialise their
* grants as rows in `sys_record_share` with `source='rule'` and
* `source_id={rule.id}` so the evaluator can reconcile (delete + re-
* insert) on rule updates without touching manual grants.
*
* Evaluation triggers:
* - `afterInsert` / `afterUpdate` on the target object (per-record,
* incremental — the hot path).
* - REST `POST /sharing/rules/:id/evaluate` (admin-initiated
* bulk reconcile — used after rule edits).
*
* Criteria are stored as JSON (a normal `FilterCondition`) so the
* existing engine `find()` can do the matching natively. v1 supports
* simple `{field, op, value}` style filters; CEL predicates are a
* follow-up.
*
* @namespace sys
*/
export const SysSharingRule = ObjectSchema.create({
name: 'sys_sharing_rule',
label: 'Sharing Rule',
pluralLabel: 'Sharing Rules',
icon: 'shield-check',
isSystem: true,
managedBy: 'config',
// Sharing rules can now be authored visually via the Studio criteria
// builder (apps/studio/src/components/SharingCriteriaBuilder.tsx).
// We still recommend `defineSharingRule({...})` for repo-controlled
// baselines, but admins can safely create/edit/delete from the UI.
userActions: { create: true, edit: true, delete: true, import: false },
description: 'Declarative sharing rule that auto-materialises sys_record_share grants. Authored via defineSharingRule() in code or the Studio criteria builder.',
displayNameField: 'name',
nameField: 'name', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{label}',
highlightFields: ['name', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'active'],
listViews: {
active: {
type: 'grid',
name: 'active',
label: 'Active',
data: { provider: 'object', object: 'sys_sharing_rule' },
columns: ['label', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'updated_at'],
filter: [{ field: 'active', operator: 'equals', value: true }],
sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],
pagination: { pageSize: 50 },
},
inactive: {
type: 'grid',
name: 'inactive',
label: 'Inactive',
data: { provider: 'object', object: 'sys_sharing_rule' },
columns: ['label', 'object_name', 'recipient_type', 'recipient_id', 'updated_at'],
filter: [{ field: 'active', operator: 'equals', value: false }],
sort: [{ field: 'label', order: 'asc' }],
pagination: { pageSize: 50 },
},
by_object: {
type: 'grid',
name: 'by_object',
label: 'By Object',
data: { provider: 'object', object: 'sys_sharing_rule' },
columns: ['object_name', 'label', 'recipient_type', 'access_level', 'active'],
sort: [{ field: 'object_name', order: 'asc' }, { field: 'label', order: 'asc' }],
grouping: { fields: [{ field: 'object_name', order: 'asc', collapsed: false }] },
pagination: { pageSize: 100 },
},
all_rules: {
type: 'grid',
name: 'all_rules',
label: 'All',
data: { provider: 'object', object: 'sys_sharing_rule' },
columns: ['label', 'object_name', 'recipient_type', 'recipient_id', 'access_level', 'active', 'updated_at'],
sort: [{ field: 'label', order: 'asc' }],
pagination: { pageSize: 50 },
},
},
fields: {
id: Field.text({ label: 'Rule ID', required: true, readonly: true, group: 'System' }),
organization_id: Field.lookup('sys_organization', {
label: 'Organization',
required: false,
group: 'System',
description: 'Tenant that owns this rule; null = global',
}),
name: Field.text({
label: 'Name',
required: true,
maxLength: 100,
description: 'Unique snake_case rule name',
group: 'Identity',
}),
label: Field.text({
label: 'Display Label',
required: true,
maxLength: 200,
group: 'Identity',
}),
description: Field.textarea({
label: 'Description',
required: false,
group: 'Identity',
}),
object_name: Field.text({
label: 'Object',
required: true,
maxLength: 100,
description: 'Short object name (e.g. opportunity, account)',
group: 'Target',
}),
criteria_json: Field.textarea({
label: 'Criteria (FilterCondition JSON)',
required: false,
description: 'JSON FilterCondition matched against records of object_name. Empty = match all.',
group: 'Target',
}),
recipient_type: Field.select(
['user', 'team', 'business_unit', 'role', 'role_and_subordinates', 'queue'],
{
label: 'Recipient Type',
required: true,
defaultValue: 'business_unit',
description: 'Kind of principal that receives access — expanded to user grants at evaluation time. `department` walks the parent_business_unit_id tree; `team` is flat (better-auth); `role` is the role\'s direct members; `role_and_subordinates` walks the sys_role.parent hierarchy to also include every subordinate role (ADR-0056 D6).',
group: 'Recipient',
},
),
recipient_id: Field.text({
label: 'Recipient',
required: true,
maxLength: 200,
description: 'department id / team id / role name / queue name / user id depending on recipient_type',
group: 'Recipient',
}),
access_level: Field.select(
['read', 'edit', 'full'],
{
label: 'Access Level',
required: true,
defaultValue: 'read',
group: 'Recipient',
},
),
active: Field.boolean({
label: 'Active',
required: false,
defaultValue: true,
description: 'Only active rules participate in lifecycle evaluation',
group: 'Lifecycle',
}),
created_at: Field.datetime({
label: 'Created At',
required: true,
defaultValue: 'NOW()',
readonly: true,
group: 'System',
}),
updated_at: Field.datetime({
label: 'Updated At',
required: false,
group: 'System',
}),
},
indexes: [
{ fields: ['object_name', 'active'] },
{ fields: ['name'], unique: true },
{ fields: ['organization_id'] },
],
});