@company-semantics/contracts 1.14.0 → 1.16.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/package.json +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +109 -1
- package/src/dispatch/index.ts +99 -0
- package/src/index.ts +16 -0
- package/src/org/index.ts +8 -0
- package/src/org/schemas.ts +36 -0
- package/src/org/types.ts +66 -0
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = 'e337542ea39d' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = 'e337542ea39de6f11b03f87d300e3e4c842825267c6918b1a37f2f86502124f8' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -636,7 +636,8 @@ export interface paths {
|
|
|
636
636
|
path?: never;
|
|
637
637
|
cookie?: never;
|
|
638
638
|
};
|
|
639
|
-
|
|
639
|
+
/** Get workspace member detail (roles, units, effective scopes, recent actions) */
|
|
640
|
+
get: operations["getWorkspaceMemberDetail"];
|
|
640
641
|
put?: never;
|
|
641
642
|
post?: never;
|
|
642
643
|
/** Remove workspace member */
|
|
@@ -663,6 +664,23 @@ export interface paths {
|
|
|
663
664
|
patch: operations["changeMemberRole"];
|
|
664
665
|
trace?: never;
|
|
665
666
|
};
|
|
667
|
+
"/api/rbac/roles": {
|
|
668
|
+
parameters: {
|
|
669
|
+
query?: never;
|
|
670
|
+
header?: never;
|
|
671
|
+
path?: never;
|
|
672
|
+
cookie?: never;
|
|
673
|
+
};
|
|
674
|
+
/** List RBAC roles catalog (system + custom) with scopes and member counts */
|
|
675
|
+
get: operations["getRbacRoles"];
|
|
676
|
+
put?: never;
|
|
677
|
+
post?: never;
|
|
678
|
+
delete?: never;
|
|
679
|
+
options?: never;
|
|
680
|
+
head?: never;
|
|
681
|
+
patch?: never;
|
|
682
|
+
trace?: never;
|
|
683
|
+
};
|
|
666
684
|
"/api/user/orgs": {
|
|
667
685
|
parameters: {
|
|
668
686
|
query?: never;
|
|
@@ -2344,7 +2362,18 @@ export interface components {
|
|
|
2344
2362
|
email: string;
|
|
2345
2363
|
/** @enum {string} */
|
|
2346
2364
|
role: "owner" | "admin" | "member" | "auditor";
|
|
2365
|
+
roleNames: string[];
|
|
2347
2366
|
joinedAt: string;
|
|
2367
|
+
lastActiveAt: string | null;
|
|
2368
|
+
unitMemberships: {
|
|
2369
|
+
unitId: string;
|
|
2370
|
+
unitName: string;
|
|
2371
|
+
unitPath: string;
|
|
2372
|
+
/** @enum {string} */
|
|
2373
|
+
role: "owner" | "manager" | "member";
|
|
2374
|
+
}[];
|
|
2375
|
+
unitMembershipsTruncated: boolean;
|
|
2376
|
+
inviteStatus: ("active" | "pending" | "expired") | null;
|
|
2348
2377
|
}[];
|
|
2349
2378
|
nextCursor: string | null;
|
|
2350
2379
|
hasMore: boolean;
|
|
@@ -2514,6 +2543,32 @@ export interface components {
|
|
|
2514
2543
|
/** @enum {string} */
|
|
2515
2544
|
errorCode?: "IDENTITY_CONFLICT" | "DOMAIN_MISMATCH" | "ISSUER_MISMATCH" | "CALLBACK_ERROR";
|
|
2516
2545
|
};
|
|
2546
|
+
WorkspaceMemberDetail: {
|
|
2547
|
+
id: string;
|
|
2548
|
+
name: string;
|
|
2549
|
+
email: string;
|
|
2550
|
+
/** @enum {string} */
|
|
2551
|
+
role: "owner" | "admin" | "member" | "auditor";
|
|
2552
|
+
roleNames: string[];
|
|
2553
|
+
joinedAt: string;
|
|
2554
|
+
lastActiveAt: string | null;
|
|
2555
|
+
unitMemberships: {
|
|
2556
|
+
unitId: string;
|
|
2557
|
+
unitName: string;
|
|
2558
|
+
unitPath: string;
|
|
2559
|
+
/** @enum {string} */
|
|
2560
|
+
role: "owner" | "manager" | "member";
|
|
2561
|
+
}[];
|
|
2562
|
+
unitMembershipsTruncated: boolean;
|
|
2563
|
+
inviteStatus: ("active" | "pending" | "expired") | null;
|
|
2564
|
+
effectiveScopes: string[];
|
|
2565
|
+
recentActions: {
|
|
2566
|
+
id: string;
|
|
2567
|
+
timestamp: string;
|
|
2568
|
+
action: string;
|
|
2569
|
+
summary: string;
|
|
2570
|
+
}[];
|
|
2571
|
+
};
|
|
2517
2572
|
RemoveMemberResponse: {
|
|
2518
2573
|
success: boolean;
|
|
2519
2574
|
memberId: string;
|
|
@@ -2532,6 +2587,16 @@ export interface components {
|
|
|
2532
2587
|
/** @enum {string} */
|
|
2533
2588
|
newRole: "admin" | "member";
|
|
2534
2589
|
};
|
|
2590
|
+
RoleCatalogResponse: {
|
|
2591
|
+
roles: {
|
|
2592
|
+
name: string;
|
|
2593
|
+
/** @enum {string} */
|
|
2594
|
+
type: "system" | "custom";
|
|
2595
|
+
description: string;
|
|
2596
|
+
scopes: string[];
|
|
2597
|
+
memberCount: number;
|
|
2598
|
+
}[];
|
|
2599
|
+
};
|
|
2535
2600
|
UserOrgsResponse: {
|
|
2536
2601
|
orgs: {
|
|
2537
2602
|
userId: string;
|
|
@@ -2750,6 +2815,7 @@ export interface components {
|
|
|
2750
2815
|
target: {
|
|
2751
2816
|
type: string;
|
|
2752
2817
|
workspaceId?: string;
|
|
2818
|
+
service?: string;
|
|
2753
2819
|
};
|
|
2754
2820
|
connectionId?: string;
|
|
2755
2821
|
returnUrl?: string;
|
|
@@ -4371,6 +4437,28 @@ export interface operations {
|
|
|
4371
4437
|
};
|
|
4372
4438
|
};
|
|
4373
4439
|
};
|
|
4440
|
+
getWorkspaceMemberDetail: {
|
|
4441
|
+
parameters: {
|
|
4442
|
+
query?: never;
|
|
4443
|
+
header?: never;
|
|
4444
|
+
path: {
|
|
4445
|
+
id: string;
|
|
4446
|
+
};
|
|
4447
|
+
cookie?: never;
|
|
4448
|
+
};
|
|
4449
|
+
requestBody?: never;
|
|
4450
|
+
responses: {
|
|
4451
|
+
/** @description Workspace member detail */
|
|
4452
|
+
200: {
|
|
4453
|
+
headers: {
|
|
4454
|
+
[name: string]: unknown;
|
|
4455
|
+
};
|
|
4456
|
+
content: {
|
|
4457
|
+
"application/json": components["schemas"]["WorkspaceMemberDetail"];
|
|
4458
|
+
};
|
|
4459
|
+
};
|
|
4460
|
+
};
|
|
4461
|
+
};
|
|
4374
4462
|
removeMember: {
|
|
4375
4463
|
parameters: {
|
|
4376
4464
|
query?: never;
|
|
@@ -4419,6 +4507,26 @@ export interface operations {
|
|
|
4419
4507
|
};
|
|
4420
4508
|
};
|
|
4421
4509
|
};
|
|
4510
|
+
getRbacRoles: {
|
|
4511
|
+
parameters: {
|
|
4512
|
+
query?: never;
|
|
4513
|
+
header?: never;
|
|
4514
|
+
path?: never;
|
|
4515
|
+
cookie?: never;
|
|
4516
|
+
};
|
|
4517
|
+
requestBody?: never;
|
|
4518
|
+
responses: {
|
|
4519
|
+
/** @description Role catalog */
|
|
4520
|
+
200: {
|
|
4521
|
+
headers: {
|
|
4522
|
+
[name: string]: unknown;
|
|
4523
|
+
};
|
|
4524
|
+
content: {
|
|
4525
|
+
"application/json": components["schemas"]["RoleCatalogResponse"];
|
|
4526
|
+
};
|
|
4527
|
+
};
|
|
4528
|
+
};
|
|
4529
|
+
};
|
|
4422
4530
|
listUserOrgs: {
|
|
4423
4531
|
parameters: {
|
|
4424
4532
|
query?: never;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch-deny vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* The four deny paths on the agent-action dispatch flow — consent missing,
|
|
5
|
+
* write-tier disabled, per-provider kill switch, and execution-budget
|
|
6
|
+
* exhausted — all return the canonical `ErrorResponse` envelope with one of
|
|
7
|
+
* the codes defined below plus a per-code `meta` payload. The shapes are
|
|
8
|
+
* shared between the backend (which emits) and the app (which renders a
|
|
9
|
+
* recovery affordance), so they live in contracts per the promotion rule.
|
|
10
|
+
*
|
|
11
|
+
* HTTP status is carried by the envelope, not duplicated here; conventional
|
|
12
|
+
* mapping in the backend:
|
|
13
|
+
* - `incremental_consent_required` → 402 (grant required to proceed)
|
|
14
|
+
* - `write_tier_disabled` → 403
|
|
15
|
+
* - `provider_access_denied` → 403
|
|
16
|
+
* - `execution_budget_exceeded` → 429
|
|
17
|
+
*
|
|
18
|
+
* @see ../errors/index.ts for the `ErrorResponse` envelope.
|
|
19
|
+
* @see PRD-00519/00521/00522 — the PRDs that introduced these denies.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const DISPATCH_DENY_CODES = [
|
|
23
|
+
'incremental_consent_required',
|
|
24
|
+
'write_tier_disabled',
|
|
25
|
+
'provider_access_denied',
|
|
26
|
+
'execution_budget_exceeded',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
export type DispatchDenyCode = (typeof DISPATCH_DENY_CODES)[number];
|
|
30
|
+
|
|
31
|
+
export type ActionTier = 'SAFE' | 'EXTERNAL' | 'DESTRUCTIVE';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The user has no active grant row in `incremental_consents` for the
|
|
35
|
+
* `(user_id, org_id, action_id)` triple. Recoverable by the user: they
|
|
36
|
+
* POST `grantPath` to create the grant, then retry the original action.
|
|
37
|
+
*/
|
|
38
|
+
export interface IncrementalConsentRequiredMeta {
|
|
39
|
+
actionId: string;
|
|
40
|
+
tier: ActionTier;
|
|
41
|
+
requiredScopes: string[];
|
|
42
|
+
/** HTTP path the app can POST to with `{ actionId }` to create the grant. */
|
|
43
|
+
grantPath: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The global `WRITE_TIER_ENABLED[tier]` kill switch is off. Recoverable
|
|
48
|
+
* only by an operator flipping the config — not user-actionable.
|
|
49
|
+
*/
|
|
50
|
+
export interface WriteTierDisabledMeta {
|
|
51
|
+
actionId: string;
|
|
52
|
+
tier: ActionTier;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Distinct reasons `ProviderAccessGate.canDispatchAction` can deny.
|
|
57
|
+
* Kept separate from the global `WRITE_TIER_ENABLED` codes so the app
|
|
58
|
+
* can phrase the message by cause (provider paused vs. org override vs.
|
|
59
|
+
* tier paused for this provider).
|
|
60
|
+
*/
|
|
61
|
+
export type ProviderAccessDenyReason =
|
|
62
|
+
| 'provider-disabled'
|
|
63
|
+
| 'org-override'
|
|
64
|
+
| 'tier-SAFE-disabled'
|
|
65
|
+
| 'tier-EXTERNAL-disabled'
|
|
66
|
+
| 'tier-DESTRUCTIVE-disabled'
|
|
67
|
+
| 'write-tier-SAFE-globally-disabled'
|
|
68
|
+
| 'write-tier-EXTERNAL-globally-disabled'
|
|
69
|
+
| 'write-tier-DESTRUCTIVE-globally-disabled';
|
|
70
|
+
|
|
71
|
+
export interface ProviderAccessDeniedMeta {
|
|
72
|
+
reason: ProviderAccessDenyReason;
|
|
73
|
+
provider: string;
|
|
74
|
+
tier: ActionTier;
|
|
75
|
+
actionType: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The signed execution has hit one of its atomic per-run caps. `kind` tells
|
|
80
|
+
* the app which cap tripped so it can phrase the message correctly; the
|
|
81
|
+
* recovery is always "start a new session / execution".
|
|
82
|
+
*/
|
|
83
|
+
export interface ExecutionBudgetExceededMeta {
|
|
84
|
+
kind: 'token_uses' | 'time_window' | 'provider_calls' | 'rows_returned';
|
|
85
|
+
executionId: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Type-level map: deny code → meta shape. Drives the app's deny renderer. */
|
|
89
|
+
export interface DispatchDenyMetaByCode {
|
|
90
|
+
incremental_consent_required: IncrementalConsentRequiredMeta;
|
|
91
|
+
write_tier_disabled: WriteTierDisabledMeta;
|
|
92
|
+
provider_access_denied: ProviderAccessDeniedMeta;
|
|
93
|
+
execution_budget_exceeded: ExecutionBudgetExceededMeta;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Narrow `unknown` to a `DispatchDenyCode` for error-code dispatch switches. */
|
|
97
|
+
export function isDispatchDenyCode(code: unknown): code is DispatchDenyCode {
|
|
98
|
+
return typeof code === 'string' && (DISPATCH_DENY_CODES as readonly string[]).includes(code);
|
|
99
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -166,6 +166,22 @@ export type {
|
|
|
166
166
|
export type { AuthStartMode, AuthStartResponse } from './auth/index'
|
|
167
167
|
export { OTPErrorCode } from './auth/index'
|
|
168
168
|
|
|
169
|
+
// Dispatch deny vocabulary (PRD-00519/00521/00522)
|
|
170
|
+
export {
|
|
171
|
+
DISPATCH_DENY_CODES,
|
|
172
|
+
isDispatchDenyCode,
|
|
173
|
+
} from './dispatch/index'
|
|
174
|
+
export type {
|
|
175
|
+
ActionTier,
|
|
176
|
+
DispatchDenyCode,
|
|
177
|
+
DispatchDenyMetaByCode,
|
|
178
|
+
ExecutionBudgetExceededMeta,
|
|
179
|
+
IncrementalConsentRequiredMeta,
|
|
180
|
+
ProviderAccessDenyReason,
|
|
181
|
+
ProviderAccessDeniedMeta,
|
|
182
|
+
WriteTierDisabledMeta,
|
|
183
|
+
} from './dispatch/index'
|
|
184
|
+
|
|
169
185
|
// Email domain types
|
|
170
186
|
// @see ADR-CONT-034 for design rationale
|
|
171
187
|
export type {
|
package/src/org/index.ts
CHANGED
|
@@ -15,6 +15,11 @@ export type {
|
|
|
15
15
|
WorkspaceRole,
|
|
16
16
|
WorkspaceOverview,
|
|
17
17
|
WorkspaceMember,
|
|
18
|
+
WorkspaceMemberUnitSummary,
|
|
19
|
+
WorkspaceMemberInviteStatus,
|
|
20
|
+
WorkspaceMemberDetail,
|
|
21
|
+
MemberRecentAction,
|
|
22
|
+
RoleCatalogEntry,
|
|
18
23
|
AuthMethodConfig,
|
|
19
24
|
WorkspaceAuthConfig,
|
|
20
25
|
// SSO self-service setup types (PRD-00193)
|
|
@@ -120,6 +125,9 @@ export {
|
|
|
120
125
|
WorkspaceAccessResponseSchema,
|
|
121
126
|
WorkspaceOverviewSchema,
|
|
122
127
|
WorkspaceMembersResponseSchema,
|
|
128
|
+
WorkspaceMemberDetailSchema,
|
|
129
|
+
RoleCatalogEntrySchema,
|
|
130
|
+
RoleCatalogResponseSchema,
|
|
123
131
|
WorkspaceAuthConfigSchema,
|
|
124
132
|
WorkspaceAuditEventSchema,
|
|
125
133
|
WorkspaceAuditResponseSchema,
|
package/src/org/schemas.ts
CHANGED
|
@@ -17,12 +17,48 @@ import { CursorPageSchema } from '../api/primitives';
|
|
|
17
17
|
// Sub-schemas
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
|
|
20
|
+
const WorkspaceMemberUnitSummarySchema = z.object({
|
|
21
|
+
unitId: z.string(),
|
|
22
|
+
unitName: z.string(),
|
|
23
|
+
unitPath: z.string(),
|
|
24
|
+
role: z.enum(['owner', 'manager', 'member']),
|
|
25
|
+
});
|
|
26
|
+
|
|
20
27
|
const WorkspaceMemberSchema = z.object({
|
|
21
28
|
id: z.string(),
|
|
22
29
|
name: z.string(),
|
|
23
30
|
email: z.string(),
|
|
24
31
|
role: z.enum(['owner', 'admin', 'member', 'auditor']),
|
|
32
|
+
roleNames: z.array(z.string()),
|
|
25
33
|
joinedAt: z.string(),
|
|
34
|
+
lastActiveAt: z.string().nullable(),
|
|
35
|
+
unitMemberships: z.array(WorkspaceMemberUnitSummarySchema),
|
|
36
|
+
unitMembershipsTruncated: z.boolean(),
|
|
37
|
+
inviteStatus: z.enum(['active', 'pending', 'expired']).nullable(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const MemberRecentActionSchema = z.object({
|
|
41
|
+
id: z.string(),
|
|
42
|
+
timestamp: z.string(),
|
|
43
|
+
action: z.string(),
|
|
44
|
+
summary: z.string(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const WorkspaceMemberDetailSchema = WorkspaceMemberSchema.extend({
|
|
48
|
+
effectiveScopes: z.array(z.string()),
|
|
49
|
+
recentActions: z.array(MemberRecentActionSchema),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const RoleCatalogEntrySchema = z.object({
|
|
53
|
+
name: z.string(),
|
|
54
|
+
type: z.enum(['system', 'custom']),
|
|
55
|
+
description: z.string(),
|
|
56
|
+
scopes: z.array(z.string()),
|
|
57
|
+
memberCount: z.number().int().nonnegative(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const RoleCatalogResponseSchema = z.object({
|
|
61
|
+
roles: z.array(RoleCatalogEntrySchema),
|
|
26
62
|
});
|
|
27
63
|
|
|
28
64
|
const WorkspaceOwnerSchema = z.object({
|
package/src/org/types.ts
CHANGED
|
@@ -92,6 +92,24 @@ export interface WorkspaceOverview {
|
|
|
92
92
|
claimable: boolean;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Single OrgUnit membership summary attached to a WorkspaceMember row.
|
|
97
|
+
* See ADR-BE-120 for the canonical OrgUnit tree model.
|
|
98
|
+
*/
|
|
99
|
+
export interface WorkspaceMemberUnitSummary {
|
|
100
|
+
unitId: string;
|
|
101
|
+
unitName: string;
|
|
102
|
+
/** Human-readable path from the OrgUnit tree (e.g. "Sales / Enterprise"). */
|
|
103
|
+
unitPath: string;
|
|
104
|
+
role: 'owner' | 'manager' | 'member';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Invite status for a workspace member row. `null` for members that have no
|
|
109
|
+
* associated invite record (joined pre-invite system).
|
|
110
|
+
*/
|
|
111
|
+
export type WorkspaceMemberInviteStatus = 'active' | 'pending' | 'expired';
|
|
112
|
+
|
|
95
113
|
/**
|
|
96
114
|
* Workspace member for the members list.
|
|
97
115
|
* Human users only (no agent actors).
|
|
@@ -100,8 +118,56 @@ export interface WorkspaceMember {
|
|
|
100
118
|
id: string;
|
|
101
119
|
name: string;
|
|
102
120
|
email: string;
|
|
121
|
+
/** Display role — derived collapse of RBAC into {owner,admin,member,auditor}. */
|
|
103
122
|
role: WorkspaceRole;
|
|
123
|
+
/** Raw RBAC role names (e.g. 'org_owner', 'org_admin', 'auditor'). Superset of `role`. */
|
|
124
|
+
roleNames: string[];
|
|
104
125
|
joinedAt: string;
|
|
126
|
+
/** ISO timestamp of last activity; null if never recorded. */
|
|
127
|
+
lastActiveAt: string | null;
|
|
128
|
+
/** OrgUnit memberships for this member, capped at a small number server-side. */
|
|
129
|
+
unitMemberships: WorkspaceMemberUnitSummary[];
|
|
130
|
+
/** True when server truncated `unitMemberships`; detail endpoint returns the full list. */
|
|
131
|
+
unitMembershipsTruncated: boolean;
|
|
132
|
+
/** Invite lifecycle status. `null` for members without an invite record. */
|
|
133
|
+
inviteStatus: WorkspaceMemberInviteStatus | null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Recent audit action attached to a member-detail response.
|
|
138
|
+
* Lighter projection than WorkspaceAuditEvent — no actor block (implied by caller).
|
|
139
|
+
*/
|
|
140
|
+
export interface MemberRecentAction {
|
|
141
|
+
id: string;
|
|
142
|
+
timestamp: string;
|
|
143
|
+
action: string;
|
|
144
|
+
summary: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Full member detail, returned by GET /api/workspace/members/:userId and also
|
|
149
|
+
* the shape projected from /api/me for self-view.
|
|
150
|
+
*/
|
|
151
|
+
export interface WorkspaceMemberDetail extends WorkspaceMember {
|
|
152
|
+
/** Full resolved scope patterns — union of grants from all assigned roles. */
|
|
153
|
+
effectiveScopes: string[];
|
|
154
|
+
/** Up to N most recent audit actions attributed to this member. */
|
|
155
|
+
recentActions: MemberRecentAction[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Entry in the RBAC roles catalog (GET /api/rbac/roles).
|
|
160
|
+
*/
|
|
161
|
+
export interface RoleCatalogEntry {
|
|
162
|
+
/** Canonical role name, e.g. 'org_owner'. */
|
|
163
|
+
name: string;
|
|
164
|
+
type: 'system' | 'custom';
|
|
165
|
+
/** One-line description sourced from system-roles.ts (or org-provided for custom). */
|
|
166
|
+
description: string;
|
|
167
|
+
/** Scope patterns granted by this role. May contain wildcards (e.g. 'org.view_*'). */
|
|
168
|
+
scopes: string[];
|
|
169
|
+
/** Number of active assignments in the current org. */
|
|
170
|
+
memberCount: number;
|
|
105
171
|
}
|
|
106
172
|
|
|
107
173
|
/**
|