@company-semantics/contracts 5.0.0 → 6.0.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/__tests__/resource-keys.test.ts +64 -0
- package/src/index.ts +0 -5
- package/src/org/index.ts +0 -5
- package/src/org/schemas.ts +0 -37
- package/src/resource-keys.ts +31 -1
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
toQueryKey,
|
|
4
|
+
fromQueryKey,
|
|
5
|
+
matchesResourceKey,
|
|
6
|
+
type ResourceKey,
|
|
7
|
+
} from '../resource-keys.js';
|
|
8
|
+
|
|
9
|
+
const ORG_ID = '11111111-1111-4111-8111-111111111111';
|
|
10
|
+
|
|
11
|
+
describe('resource-keys: orgUnitOwners (org-scoped)', () => {
|
|
12
|
+
const key: ResourceKey = { type: 'orgUnitOwners', orgId: ORG_ID };
|
|
13
|
+
|
|
14
|
+
it('toQueryKey produces [type, orgId] with no unitId', () => {
|
|
15
|
+
const qk = toQueryKey(key);
|
|
16
|
+
expect(qk).toEqual(['orgUnitOwners', ORG_ID]);
|
|
17
|
+
// org-scoped: exactly two segments, no per-unit identity
|
|
18
|
+
expect(qk).toHaveLength(2);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('roundtrips through fromQueryKey', () => {
|
|
22
|
+
const parsed = fromQueryKey(toQueryKey(key));
|
|
23
|
+
expect(parsed).toEqual(key);
|
|
24
|
+
expect('unitId' in parsed).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('matchesResourceKey is org-scoped: rejects a different orgId', () => {
|
|
28
|
+
const other = '22222222-2222-4222-8222-222222222222';
|
|
29
|
+
expect(matchesResourceKey(toQueryKey(key), key)).toBe(true);
|
|
30
|
+
expect(matchesResourceKey(['orgUnitOwners', other], key)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('resource-keys: system-scoped internalAdmin* types', () => {
|
|
35
|
+
const systemTypes = [
|
|
36
|
+
'internalAdminAiProviders',
|
|
37
|
+
'internalAdminPrompts',
|
|
38
|
+
'internalAdminAiRuntimeDefaults',
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
for (const type of systemTypes) {
|
|
42
|
+
const key = { type, scope: 'system' } as ResourceKey;
|
|
43
|
+
|
|
44
|
+
it(`${type}: toQueryKey produces [type, 'system']`, () => {
|
|
45
|
+
expect(toQueryKey(key)).toEqual([type, 'system']);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it(`${type}: roundtrips through fromQueryKey with scope: 'system'`, () => {
|
|
49
|
+
const parsed = fromQueryKey(toQueryKey(key));
|
|
50
|
+
expect(parsed).toEqual(key);
|
|
51
|
+
expect('orgId' in parsed).toBe(false);
|
|
52
|
+
expect('userId' in parsed).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it(`${type}: matchesResourceKey accepts a matching system key`, () => {
|
|
56
|
+
expect(matchesResourceKey(toQueryKey(key), key)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
it('matchesResourceKey rejects a mismatched system type', () => {
|
|
61
|
+
const target: ResourceKey = { type: 'internalAdminPrompts', scope: 'system' };
|
|
62
|
+
expect(matchesResourceKey(['internalAdminAiProviders', 'system'], target)).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -354,14 +354,11 @@ export {
|
|
|
354
354
|
AuthorityMechanismSchema,
|
|
355
355
|
AuthorityLocalitySchema,
|
|
356
356
|
AuthorityMutabilitySchema,
|
|
357
|
-
UnitAuthorityScopeSchema,
|
|
358
|
-
ALL_UNIT_AUTHORITY_SCOPES,
|
|
359
357
|
OwnerAuthoritySchema,
|
|
360
358
|
OrgUnitOwnerSchema,
|
|
361
359
|
OrgUnitOwnersResponseSchema,
|
|
362
360
|
DelegationSchema,
|
|
363
361
|
CreateDelegationRequestSchema,
|
|
364
|
-
UpdateDelegationScopesRequestSchema,
|
|
365
362
|
DelegationListResponseSchema,
|
|
366
363
|
} from './org/index'
|
|
367
364
|
export type {
|
|
@@ -371,13 +368,11 @@ export type {
|
|
|
371
368
|
AuthorityMechanism,
|
|
372
369
|
AuthorityLocality,
|
|
373
370
|
AuthorityMutability,
|
|
374
|
-
UnitAuthorityScope,
|
|
375
371
|
OwnerAuthority,
|
|
376
372
|
OrgUnitOwner,
|
|
377
373
|
OrgUnitOwnersResponse,
|
|
378
374
|
Delegation,
|
|
379
375
|
CreateDelegationRequest,
|
|
380
|
-
UpdateDelegationScopesRequest,
|
|
381
376
|
DelegationListResponse,
|
|
382
377
|
} from './org/index'
|
|
383
378
|
|
package/src/org/index.ts
CHANGED
|
@@ -277,14 +277,11 @@ export {
|
|
|
277
277
|
AuthorityMechanismSchema,
|
|
278
278
|
AuthorityLocalitySchema,
|
|
279
279
|
AuthorityMutabilitySchema,
|
|
280
|
-
UnitAuthorityScopeSchema,
|
|
281
|
-
ALL_UNIT_AUTHORITY_SCOPES,
|
|
282
280
|
OwnerAuthoritySchema,
|
|
283
281
|
OrgUnitOwnerSchema,
|
|
284
282
|
OrgUnitOwnersResponseSchema,
|
|
285
283
|
DelegationSchema,
|
|
286
284
|
CreateDelegationRequestSchema,
|
|
287
|
-
UpdateDelegationScopesRequestSchema,
|
|
288
285
|
DelegationListResponseSchema,
|
|
289
286
|
} from './schemas';
|
|
290
287
|
export type {
|
|
@@ -294,12 +291,10 @@ export type {
|
|
|
294
291
|
AuthorityMechanism,
|
|
295
292
|
AuthorityLocality,
|
|
296
293
|
AuthorityMutability,
|
|
297
|
-
UnitAuthorityScope,
|
|
298
294
|
OwnerAuthority,
|
|
299
295
|
OrgUnitOwner,
|
|
300
296
|
OrgUnitOwnersResponse,
|
|
301
297
|
Delegation,
|
|
302
298
|
CreateDelegationRequest,
|
|
303
|
-
UpdateDelegationScopesRequest,
|
|
304
299
|
DelegationListResponse,
|
|
305
300
|
} from './schemas';
|
package/src/org/schemas.ts
CHANGED
|
@@ -820,24 +820,6 @@ export type AuthorityLocality = z.infer<typeof AuthorityLocalitySchema>;
|
|
|
820
820
|
export const AuthorityMutabilitySchema = z.enum(['derived', 'user_managed']);
|
|
821
821
|
export type AuthorityMutability = z.infer<typeof AuthorityMutabilitySchema>;
|
|
822
822
|
|
|
823
|
-
/**
|
|
824
|
-
* Permission scopes a unit-authority grant entitles. A leader (structural or
|
|
825
|
-
* rbac mechanism) implicitly holds all three. A delegate holds the subset
|
|
826
|
-
* recorded on its `org_unit_delegations` row.
|
|
827
|
-
*/
|
|
828
|
-
export const UnitAuthorityScopeSchema = z.enum([
|
|
829
|
-
'doc.edit',
|
|
830
|
-
'doc.acl',
|
|
831
|
-
'members.manage',
|
|
832
|
-
]);
|
|
833
|
-
export type UnitAuthorityScope = z.infer<typeof UnitAuthorityScopeSchema>;
|
|
834
|
-
|
|
835
|
-
export const ALL_UNIT_AUTHORITY_SCOPES: readonly UnitAuthorityScope[] = [
|
|
836
|
-
'doc.edit',
|
|
837
|
-
'doc.acl',
|
|
838
|
-
'members.manage',
|
|
839
|
-
];
|
|
840
|
-
|
|
841
823
|
export const OwnerAuthoritySchema = z.object({
|
|
842
824
|
mechanism: AuthorityMechanismSchema,
|
|
843
825
|
locality: AuthorityLocalitySchema,
|
|
@@ -862,12 +844,6 @@ export const OwnerAuthoritySchema = z.object({
|
|
|
862
844
|
* the revoke action.
|
|
863
845
|
*/
|
|
864
846
|
delegationId: z.string().uuid().optional(),
|
|
865
|
-
/**
|
|
866
|
-
* Scopes this authority entitles. Derived mechanisms (structural, rbac) hold
|
|
867
|
-
* the full set; delegated authorities hold the subset stored on the
|
|
868
|
-
* delegation row. Non-empty.
|
|
869
|
-
*/
|
|
870
|
-
scopes: z.array(UnitAuthorityScopeSchema).min(1),
|
|
871
847
|
});
|
|
872
848
|
export type OwnerAuthority = z.infer<typeof OwnerAuthoritySchema>;
|
|
873
849
|
|
|
@@ -910,28 +886,15 @@ export const DelegationSchema = z.object({
|
|
|
910
886
|
revokedAt: z.string().nullable(),
|
|
911
887
|
status: z.enum(['active', 'revoked']),
|
|
912
888
|
note: z.string().nullable(),
|
|
913
|
-
scopes: z.array(UnitAuthorityScopeSchema).min(1),
|
|
914
889
|
});
|
|
915
890
|
export type Delegation = z.infer<typeof DelegationSchema>;
|
|
916
891
|
|
|
917
892
|
export const CreateDelegationRequestSchema = z.object({
|
|
918
893
|
userId: z.string().uuid(),
|
|
919
894
|
note: z.string().max(2000).optional(),
|
|
920
|
-
/**
|
|
921
|
-
* Optional. Omit to grant the full leader-equivalent scope set.
|
|
922
|
-
* Provide a non-empty subset to narrow the delegation (e.g. doc edit only).
|
|
923
|
-
*/
|
|
924
|
-
scopes: z.array(UnitAuthorityScopeSchema).min(1).optional(),
|
|
925
895
|
});
|
|
926
896
|
export type CreateDelegationRequest = z.infer<typeof CreateDelegationRequestSchema>;
|
|
927
897
|
|
|
928
|
-
export const UpdateDelegationScopesRequestSchema = z.object({
|
|
929
|
-
scopes: z.array(UnitAuthorityScopeSchema).min(1),
|
|
930
|
-
});
|
|
931
|
-
export type UpdateDelegationScopesRequest = z.infer<
|
|
932
|
-
typeof UpdateDelegationScopesRequestSchema
|
|
933
|
-
>;
|
|
934
|
-
|
|
935
898
|
export const DelegationListResponseSchema = z.object({
|
|
936
899
|
delegations: z.array(DelegationSchema),
|
|
937
900
|
});
|
package/src/resource-keys.ts
CHANGED
|
@@ -36,8 +36,16 @@ export type ResourceKey =
|
|
|
36
36
|
| { type: 'orgUnitAncestors'; orgId: string; unitId: string }
|
|
37
37
|
| { type: 'orgUnitMemberships'; orgId: string; unitId: string }
|
|
38
38
|
| { type: 'orgUnitPermissions'; orgId: string; unitId: string }
|
|
39
|
+
// Org-unit owners list (ADR-CONTRACTS-052) — owners are an org-wide
|
|
40
|
+
// projection, not a per-unit collection, so unitId is intentionally excluded.
|
|
41
|
+
| { type: 'orgUnitOwners'; orgId: string }
|
|
39
42
|
// People reporting (ADR-BE-166) — drives the settings Org chart drill-down
|
|
40
43
|
| { type: 'peopleOrgChart'; orgId: string }
|
|
44
|
+
// System-scoped (ADR-CONTRACTS-052) — tenant-less super-admin resources.
|
|
45
|
+
// No orgId/userId: these live above any single org. scope is the literal 'system'.
|
|
46
|
+
| { type: 'internalAdminAiProviders'; scope: 'system' }
|
|
47
|
+
| { type: 'internalAdminPrompts'; scope: 'system' }
|
|
48
|
+
| { type: 'internalAdminAiRuntimeDefaults'; scope: 'system' }
|
|
41
49
|
// User-scoped
|
|
42
50
|
| { type: 'dismissedBanners'; userId: string }
|
|
43
51
|
| { type: 'userOrgs'; userId: string }
|
|
@@ -64,11 +72,20 @@ const ORG_SCOPED_TYPES = [
|
|
|
64
72
|
'members', 'departments', 'chats', 'teams', 'integrations', 'invites',
|
|
65
73
|
'auditEvents', 'timeline', 'workspace', 'workspaceDomains', 'authSettings',
|
|
66
74
|
'billing', 'aiUsage', 'deletionEligibility', 'transferOwnership', 'companyMdDocs',
|
|
67
|
-
'orgTree', 'orgLevelConfig', 'peopleOrgChart',
|
|
75
|
+
'orgTree', 'orgLevelConfig', 'peopleOrgChart', 'orgUnitOwners',
|
|
68
76
|
] as const;
|
|
69
77
|
|
|
70
78
|
const USER_SCOPED_TYPES = ['dismissedBanners', 'userOrgs', 'sessions', 'viewer'] as const;
|
|
71
79
|
|
|
80
|
+
/**
|
|
81
|
+
* System-scoped types (ADR-CONTRACTS-052) — tenant-less super-admin resources.
|
|
82
|
+
* Their query key is [type, 'system']; they carry no orgId or userId.
|
|
83
|
+
* Intentionally NOT exported — internal to the parser like the other scope arrays.
|
|
84
|
+
*/
|
|
85
|
+
const SYSTEM_SCOPED_TYPES = [
|
|
86
|
+
'internalAdminAiProviders', 'internalAdminPrompts', 'internalAdminAiRuntimeDefaults',
|
|
87
|
+
] as const;
|
|
88
|
+
|
|
72
89
|
/**
|
|
73
90
|
* Canonical ResourceKey → query key conversion.
|
|
74
91
|
* This is the ONLY function that constructs query keys — no ad-hoc key
|
|
@@ -128,8 +145,15 @@ export function toQueryKey(key: ResourceKey): readonly string[] {
|
|
|
128
145
|
case 'orgTree':
|
|
129
146
|
case 'orgLevelConfig':
|
|
130
147
|
case 'peopleOrgChart':
|
|
148
|
+
case 'orgUnitOwners':
|
|
131
149
|
return [key.type, key.orgId] as const;
|
|
132
150
|
|
|
151
|
+
// System-scoped (ADR-CONTRACTS-052) — tenant-less super-admin resources
|
|
152
|
+
case 'internalAdminAiProviders':
|
|
153
|
+
case 'internalAdminPrompts':
|
|
154
|
+
case 'internalAdminAiRuntimeDefaults':
|
|
155
|
+
return [key.type, key.scope] as const;
|
|
156
|
+
|
|
133
157
|
default: {
|
|
134
158
|
const _exhaustive: never = key;
|
|
135
159
|
throw new Error(`Unknown resource type: ${JSON.stringify(_exhaustive)}`);
|
|
@@ -170,6 +194,11 @@ export function fromQueryKey(queryKey: readonly string[]): ResourceKey {
|
|
|
170
194
|
return { type, orgId: rest[0], [identityFields[type]]: rest[1] } as ResourceKey;
|
|
171
195
|
}
|
|
172
196
|
|
|
197
|
+
// System-scoped types (ADR-CONTRACTS-052) — query key is [type, 'system']
|
|
198
|
+
if ((SYSTEM_SCOPED_TYPES as readonly string[]).includes(type)) {
|
|
199
|
+
return { type, scope: 'system' } as ResourceKey;
|
|
200
|
+
}
|
|
201
|
+
|
|
173
202
|
// User-scoped types
|
|
174
203
|
if ((USER_SCOPED_TYPES as readonly string[]).includes(type)) {
|
|
175
204
|
return { type, userId: rest[0] } as ResourceKey;
|
|
@@ -238,6 +267,7 @@ export function matchesResourceKey(queryKey: readonly unknown[], targetKey: Reso
|
|
|
238
267
|
if ('chatId' in parsed && 'chatId' in targetKey && parsed.chatId !== targetKey.chatId) return false;
|
|
239
268
|
if ('slug' in parsed && 'slug' in targetKey && parsed.slug !== targetKey.slug) return false;
|
|
240
269
|
if ('unitId' in parsed && 'unitId' in targetKey && parsed.unitId !== targetKey.unitId) return false;
|
|
270
|
+
if ('scope' in parsed && 'scope' in targetKey && parsed.scope !== targetKey.scope) return false;
|
|
241
271
|
|
|
242
272
|
return true;
|
|
243
273
|
}
|