@company-semantics/contracts 5.0.0 → 5.1.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": "5.1.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
+ });
@@ -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
  }