@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -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';
@@ -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
  });
@@ -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
  }