@elevasis/core 0.28.0 → 0.30.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.
Files changed (36) hide show
  1. package/dist/auth/index.d.ts +5289 -0
  2. package/dist/auth/index.js +595 -0
  3. package/dist/index.d.ts +11 -11
  4. package/dist/knowledge/index.d.ts +1 -1
  5. package/dist/organization-model/index.d.ts +11 -11
  6. package/dist/test-utils/index.d.ts +24 -1
  7. package/package.json +7 -3
  8. package/src/__tests__/publish.test.ts +8 -7
  9. package/src/auth/__tests__/access-key-coverage.test.ts +42 -0
  10. package/src/auth/__tests__/access-key-scan.ts +117 -0
  11. package/src/auth/__tests__/access-keys.test.ts +81 -0
  12. package/src/auth/__tests__/access-model.test.ts +257 -0
  13. package/src/auth/__tests__/access-test-fixtures.ts +50 -0
  14. package/src/auth/__tests__/key-catalog-drift.test.ts +33 -0
  15. package/src/auth/__tests__/platform-admin-bypass-parity.test.ts +67 -0
  16. package/src/auth/access-keys.ts +173 -0
  17. package/src/auth/access-model.ts +185 -0
  18. package/src/auth/index.ts +6 -2
  19. package/src/auth/multi-tenancy/memberships/membership.ts +2 -4
  20. package/src/auth/multi-tenancy/permissions.ts +1 -1
  21. package/src/auth/multi-tenancy/types.ts +3 -12
  22. package/src/business/acquisition/api-schemas.test.ts +59 -8
  23. package/src/business/acquisition/api-schemas.ts +10 -5
  24. package/src/business/acquisition/build-templates.test.ts +187 -240
  25. package/src/business/acquisition/build-templates.ts +87 -98
  26. package/src/business/acquisition/types.ts +390 -389
  27. package/src/execution/engine/index.ts +6 -4
  28. package/src/execution/engine/tools/lead-service-types.ts +63 -34
  29. package/src/execution/engine/tools/platform/acquisition/types.ts +7 -8
  30. package/src/execution/engine/tools/registry.ts +6 -4
  31. package/src/execution/engine/tools/tool-maps.ts +23 -1
  32. package/src/organization-model/domains/prospecting.ts +2 -327
  33. package/src/organization-model/migration-helpers.ts +16 -12
  34. package/src/reference/_generated/contracts.md +352 -328
  35. package/src/reference/glossary.md +8 -6
  36. package/src/supabase/database.types.ts +13 -0
package/dist/index.d.ts CHANGED
@@ -2238,9 +2238,9 @@ declare const OmTopologyNodeKindSchema: z.ZodEnum<{
2238
2238
  externalResource: "externalResource";
2239
2239
  }>;
2240
2240
  declare const OmTopologyRelationshipKindSchema: z.ZodEnum<{
2241
- approval: "approval";
2242
2241
  triggers: "triggers";
2243
2242
  uses: "uses";
2243
+ approval: "approval";
2244
2244
  }>;
2245
2245
  declare const OmTopologyNodeRefSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2246
2246
  kind: z.ZodLiteral<"system">;
@@ -2295,9 +2295,9 @@ declare const OmTopologyRelationshipSchema: z.ZodObject<{
2295
2295
  id: z.ZodString;
2296
2296
  }, z.core.$strip>], "kind">;
2297
2297
  kind: z.ZodEnum<{
2298
- approval: "approval";
2299
2298
  triggers: "triggers";
2300
2299
  uses: "uses";
2300
+ approval: "approval";
2301
2301
  }>;
2302
2302
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
2303
2303
  kind: z.ZodLiteral<"system">;
@@ -2357,9 +2357,9 @@ declare const OmTopologyDomainSchema: z.ZodDefault<z.ZodObject<{
2357
2357
  id: z.ZodString;
2358
2358
  }, z.core.$strip>], "kind">;
2359
2359
  kind: z.ZodEnum<{
2360
- approval: "approval";
2361
2360
  triggers: "triggers";
2362
2361
  uses: "uses";
2362
+ approval: "approval";
2363
2363
  }>;
2364
2364
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
2365
2365
  kind: z.ZodLiteral<"system">;
@@ -2634,7 +2634,7 @@ declare const topologyRelationship: {
2634
2634
  kind: "externalResource";
2635
2635
  id: string;
2636
2636
  };
2637
- kind: "approval" | "triggers" | "uses";
2637
+ kind: "triggers" | "uses" | "approval";
2638
2638
  to: {
2639
2639
  kind: "system";
2640
2640
  id: string;
@@ -2690,7 +2690,7 @@ declare const topologyRelationship: {
2690
2690
  kind: "externalResource";
2691
2691
  id: string;
2692
2692
  };
2693
- kind: "approval" | "triggers" | "uses";
2693
+ kind: "triggers" | "uses" | "approval";
2694
2694
  to: {
2695
2695
  kind: "system";
2696
2696
  id: string;
@@ -2746,7 +2746,7 @@ declare const topologyRelationship: {
2746
2746
  kind: "externalResource";
2747
2747
  id: string;
2748
2748
  };
2749
- kind: "approval" | "triggers" | "uses";
2749
+ kind: "triggers" | "uses" | "approval";
2750
2750
  to: {
2751
2751
  kind: "system";
2752
2752
  id: string;
@@ -2802,7 +2802,7 @@ declare const topologyRelationship: {
2802
2802
  kind: "externalResource";
2803
2803
  id: string;
2804
2804
  };
2805
- kind: "approval" | "triggers" | "uses";
2805
+ kind: "triggers" | "uses" | "approval";
2806
2806
  to: {
2807
2807
  kind: "system";
2808
2808
  id: string;
@@ -2858,7 +2858,7 @@ declare const topologyRelationship: {
2858
2858
  kind: "externalResource";
2859
2859
  id: string;
2860
2860
  };
2861
- kind: "approval" | "triggers" | "uses";
2861
+ kind: "triggers" | "uses" | "approval";
2862
2862
  to: {
2863
2863
  kind: "system";
2864
2864
  id: string;
@@ -2914,7 +2914,7 @@ declare const topologyRelationship: {
2914
2914
  kind: "externalResource";
2915
2915
  id: string;
2916
2916
  };
2917
- kind: "approval" | "triggers" | "uses";
2917
+ kind: "triggers" | "uses" | "approval";
2918
2918
  to: {
2919
2919
  kind: "system";
2920
2920
  id: string;
@@ -3672,7 +3672,6 @@ declare function scaffoldOrganizationModel(model: OrganizationModel, spec: OmSca
3672
3672
  declare const LinkSchema: z.ZodObject<{
3673
3673
  nodeId: z.ZodString;
3674
3674
  kind: z.ZodEnum<{
3675
- approval: "approval";
3676
3675
  affects: "affects";
3677
3676
  actions: "actions";
3678
3677
  effects: "effects";
@@ -3682,6 +3681,7 @@ declare const LinkSchema: z.ZodObject<{
3682
3681
  emits: "emits";
3683
3682
  triggers: "triggers";
3684
3683
  uses: "uses";
3684
+ approval: "approval";
3685
3685
  contains: "contains";
3686
3686
  references: "references";
3687
3687
  maps_to: "maps_to";
@@ -4528,9 +4528,9 @@ declare const OrganizationModelSchema: z.ZodObject<{
4528
4528
  id: z.ZodString;
4529
4529
  }, z.core.$strip>], "kind">;
4530
4530
  kind: z.ZodEnum<{
4531
- approval: "approval";
4532
4531
  triggers: "triggers";
4533
4532
  uses: "uses";
4533
+ approval: "approval";
4534
4534
  }>;
4535
4535
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
4536
4536
  kind: z.ZodLiteral<"system">;
@@ -997,9 +997,9 @@ declare const OrganizationModelSchema: z.ZodObject<{
997
997
  id: z.ZodString;
998
998
  }, z.core.$strip>], "kind">;
999
999
  kind: z.ZodEnum<{
1000
- approval: "approval";
1001
1000
  triggers: "triggers";
1002
1001
  uses: "uses";
1002
+ approval: "approval";
1003
1003
  }>;
1004
1004
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
1005
1005
  kind: z.ZodLiteral<"system">;
@@ -2238,9 +2238,9 @@ declare const OmTopologyNodeKindSchema: z.ZodEnum<{
2238
2238
  externalResource: "externalResource";
2239
2239
  }>;
2240
2240
  declare const OmTopologyRelationshipKindSchema: z.ZodEnum<{
2241
- approval: "approval";
2242
2241
  triggers: "triggers";
2243
2242
  uses: "uses";
2243
+ approval: "approval";
2244
2244
  }>;
2245
2245
  declare const OmTopologyNodeRefSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
2246
2246
  kind: z.ZodLiteral<"system">;
@@ -2295,9 +2295,9 @@ declare const OmTopologyRelationshipSchema: z.ZodObject<{
2295
2295
  id: z.ZodString;
2296
2296
  }, z.core.$strip>], "kind">;
2297
2297
  kind: z.ZodEnum<{
2298
- approval: "approval";
2299
2298
  triggers: "triggers";
2300
2299
  uses: "uses";
2300
+ approval: "approval";
2301
2301
  }>;
2302
2302
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
2303
2303
  kind: z.ZodLiteral<"system">;
@@ -2357,9 +2357,9 @@ declare const OmTopologyDomainSchema: z.ZodDefault<z.ZodObject<{
2357
2357
  id: z.ZodString;
2358
2358
  }, z.core.$strip>], "kind">;
2359
2359
  kind: z.ZodEnum<{
2360
- approval: "approval";
2361
2360
  triggers: "triggers";
2362
2361
  uses: "uses";
2362
+ approval: "approval";
2363
2363
  }>;
2364
2364
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
2365
2365
  kind: z.ZodLiteral<"system">;
@@ -2634,7 +2634,7 @@ declare const topologyRelationship: {
2634
2634
  kind: "externalResource";
2635
2635
  id: string;
2636
2636
  };
2637
- kind: "approval" | "triggers" | "uses";
2637
+ kind: "triggers" | "uses" | "approval";
2638
2638
  to: {
2639
2639
  kind: "system";
2640
2640
  id: string;
@@ -2690,7 +2690,7 @@ declare const topologyRelationship: {
2690
2690
  kind: "externalResource";
2691
2691
  id: string;
2692
2692
  };
2693
- kind: "approval" | "triggers" | "uses";
2693
+ kind: "triggers" | "uses" | "approval";
2694
2694
  to: {
2695
2695
  kind: "system";
2696
2696
  id: string;
@@ -2746,7 +2746,7 @@ declare const topologyRelationship: {
2746
2746
  kind: "externalResource";
2747
2747
  id: string;
2748
2748
  };
2749
- kind: "approval" | "triggers" | "uses";
2749
+ kind: "triggers" | "uses" | "approval";
2750
2750
  to: {
2751
2751
  kind: "system";
2752
2752
  id: string;
@@ -2802,7 +2802,7 @@ declare const topologyRelationship: {
2802
2802
  kind: "externalResource";
2803
2803
  id: string;
2804
2804
  };
2805
- kind: "approval" | "triggers" | "uses";
2805
+ kind: "triggers" | "uses" | "approval";
2806
2806
  to: {
2807
2807
  kind: "system";
2808
2808
  id: string;
@@ -2858,7 +2858,7 @@ declare const topologyRelationship: {
2858
2858
  kind: "externalResource";
2859
2859
  id: string;
2860
2860
  };
2861
- kind: "approval" | "triggers" | "uses";
2861
+ kind: "triggers" | "uses" | "approval";
2862
2862
  to: {
2863
2863
  kind: "system";
2864
2864
  id: string;
@@ -2914,7 +2914,7 @@ declare const topologyRelationship: {
2914
2914
  kind: "externalResource";
2915
2915
  id: string;
2916
2916
  };
2917
- kind: "approval" | "triggers" | "uses";
2917
+ kind: "triggers" | "uses" | "approval";
2918
2918
  to: {
2919
2919
  kind: "system";
2920
2920
  id: string;
@@ -3672,7 +3672,6 @@ declare function scaffoldOrganizationModel(model: OrganizationModel, spec: OmSca
3672
3672
  declare const LinkSchema: z.ZodObject<{
3673
3673
  nodeId: z.ZodString;
3674
3674
  kind: z.ZodEnum<{
3675
- approval: "approval";
3676
3675
  affects: "affects";
3677
3676
  actions: "actions";
3678
3677
  effects: "effects";
@@ -3682,6 +3681,7 @@ declare const LinkSchema: z.ZodObject<{
3682
3681
  emits: "emits";
3683
3682
  triggers: "triggers";
3684
3683
  uses: "uses";
3684
+ approval: "approval";
3685
3685
  contains: "contains";
3686
3686
  references: "references";
3687
3687
  maps_to: "maps_to";
@@ -4528,9 +4528,9 @@ declare const OrganizationModelSchema: z.ZodObject<{
4528
4528
  id: z.ZodString;
4529
4529
  }, z.core.$strip>], "kind">;
4530
4530
  kind: z.ZodEnum<{
4531
- approval: "approval";
4532
4531
  triggers: "triggers";
4533
4532
  uses: "uses";
4533
+ approval: "approval";
4534
4534
  }>;
4535
4535
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
4536
4536
  kind: z.ZodLiteral<"system">;
@@ -2786,6 +2786,7 @@ type Database = {
2786
2786
  title: string | null;
2787
2787
  updated_at: string;
2788
2788
  user_id: string;
2789
+ visibility: string;
2789
2790
  };
2790
2791
  Insert: {
2791
2792
  content: string;
@@ -2800,6 +2801,7 @@ type Database = {
2800
2801
  title?: string | null;
2801
2802
  updated_at?: string;
2802
2803
  user_id: string;
2804
+ visibility?: string;
2803
2805
  };
2804
2806
  Update: {
2805
2807
  content?: string;
@@ -2814,6 +2816,7 @@ type Database = {
2814
2816
  title?: string | null;
2815
2817
  updated_at?: string;
2816
2818
  user_id?: string;
2819
+ visibility?: string;
2817
2820
  };
2818
2821
  Relationships: [
2819
2822
  {
@@ -2982,6 +2985,12 @@ type Database = {
2982
2985
  Args: never;
2983
2986
  Returns: boolean;
2984
2987
  };
2988
+ current_user_shares_org_with: {
2989
+ Args: {
2990
+ other_user_id: string;
2991
+ };
2992
+ Returns: boolean;
2993
+ };
2985
2994
  current_user_supabase_id: {
2986
2995
  Args: never;
2987
2996
  Returns: string;
@@ -3021,6 +3030,20 @@ type Database = {
3021
3030
  Args: never;
3022
3031
  Returns: string;
3023
3032
  };
3033
+ has_org_access: {
3034
+ Args: {
3035
+ action?: string;
3036
+ org_id: string;
3037
+ system_path: string;
3038
+ };
3039
+ Returns: boolean;
3040
+ } | {
3041
+ Args: {
3042
+ action?: string;
3043
+ system_path: string;
3044
+ };
3045
+ Returns: boolean;
3046
+ };
3024
3047
  has_org_permission: {
3025
3048
  Args: {
3026
3049
  org_id: string;
@@ -4398,9 +4421,9 @@ declare const OrganizationModelSchema: z.ZodObject<{
4398
4421
  id: z.ZodString;
4399
4422
  }, z.core.$strip>], "kind">;
4400
4423
  kind: z.ZodEnum<{
4401
- approval: "approval";
4402
4424
  triggers: "triggers";
4403
4425
  uses: "uses";
4426
+ approval: "approval";
4404
4427
  }>;
4405
4428
  to: z.ZodDiscriminatedUnion<[z.ZodObject<{
4406
4429
  kind: z.ZodLiteral<"system">;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/core",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "license": "MIT",
5
5
  "description": "Minimal shared constants across Elevasis monorepo",
6
6
  "sideEffects": false,
@@ -16,6 +16,10 @@
16
16
  "types": "./dist/index.d.ts",
17
17
  "import": "./dist/index.js"
18
18
  },
19
+ "./auth": {
20
+ "types": "./dist/auth/index.d.ts",
21
+ "import": "./dist/auth/index.js"
22
+ },
19
23
  "./test-utils": {
20
24
  "types": "./dist/test-utils/index.d.ts",
21
25
  "import": "./dist/test-utils/index.js"
@@ -41,8 +45,8 @@
41
45
  "rollup-plugin-dts": "^6.3.0",
42
46
  "tsup": "^8.0.0",
43
47
  "typescript": "5.9.2",
44
- "@repo/typescript-config": "0.0.0",
45
- "@repo/eslint-config": "0.0.0"
48
+ "@repo/eslint-config": "0.0.0",
49
+ "@repo/typescript-config": "0.0.0"
46
50
  },
47
51
  "dependencies": {
48
52
  "@anthropic-ai/sdk": "^0.62.0",
@@ -13,12 +13,13 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
13
13
  describe('core publish surface', () => {
14
14
  it('publishes the curated @elevasis/core organization-model wrapper', () => {
15
15
  expect(packageJson.publishConfig?.name).toBe('@elevasis/core')
16
- expect(Object.keys(packageJson.publishConfig?.exports ?? {})).toEqual([
17
- '.',
18
- './test-utils',
19
- './organization-model',
20
- './entities',
21
- './knowledge'
22
- ])
16
+ expect(Object.keys(packageJson.publishConfig?.exports ?? {})).toEqual([
17
+ '.',
18
+ './auth',
19
+ './test-utils',
20
+ './organization-model',
21
+ './entities',
22
+ './knowledge'
23
+ ])
23
24
  })
24
25
  })
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { AccessKeys, deriveAccessKeyCatalog, findAccessCatalogEntry, normalizeAccessKey } from '../access-keys'
4
+ import { accessTestOrganizationModel } from './access-test-fixtures'
5
+ import { scanAccessKeyUsages } from './access-key-scan'
6
+
7
+ const INTENTIONALLY_UNCONSUMED_ACCESS_KEYS = new Set([
8
+ 'secretsManage',
9
+ 'operationsManage',
10
+ 'acquisitionManage',
11
+ 'projectsManage',
12
+ 'clientsManage',
13
+ 'operationsRecentExecutions'
14
+ ])
15
+
16
+ describe('access key coverage', () => {
17
+ it('keeps every runtime consumer key present in the catalog', () => {
18
+ const catalog = deriveAccessKeyCatalog(accessTestOrganizationModel)
19
+ const usages = scanAccessKeyUsages()
20
+ const consumerKeys = [
21
+ ...usages.stringLiterals.map((usage) => ({ file: usage.file, key: normalizeAccessKey(usage.key) })),
22
+ ...usages.accessKeySymbols.map((usage) => ({
23
+ file: usage.file,
24
+ key: AccessKeys[usage.symbol as keyof typeof AccessKeys]
25
+ }))
26
+ ]
27
+
28
+ const missingCatalogEntries = consumerKeys
29
+ .filter((usage) => findAccessCatalogEntry(catalog, usage.key) === undefined)
30
+ .map((usage) => `${usage.file}: ${usage.key.systemPath}:${usage.key.action}`)
31
+
32
+ expect(missingCatalogEntries).toEqual([])
33
+ })
34
+
35
+ it('keeps exported static AccessKeys either consumed or explicitly reserved', () => {
36
+ const usages = scanAccessKeyUsages()
37
+ const consumedSymbols = new Set(usages.accessKeySymbols.map((usage) => usage.symbol))
38
+ const unconsumedSymbols = Object.keys(AccessKeys).filter((symbol) => !consumedSymbols.has(symbol))
39
+
40
+ expect(unconsumedSymbols).toEqual([...INTENTIONALLY_UNCONSUMED_ACCESS_KEYS])
41
+ })
42
+ })
@@ -0,0 +1,117 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
2
+ import { dirname, join, relative } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ import type { AccessKeyInput } from '../access-keys'
6
+
7
+ export interface AccessKeyUsage {
8
+ file: string
9
+ key: AccessKeyInput
10
+ }
11
+
12
+ export interface AccessKeySymbolUsage {
13
+ file: string
14
+ symbol: string
15
+ }
16
+
17
+ const repoRoot = findRepoRoot(dirname(fileURLToPath(import.meta.url)))
18
+
19
+ const RUNTIME_SOURCE_ROOTS = [
20
+ join(repoRoot, 'apps', 'command-center', 'src'),
21
+ join(repoRoot, 'packages', 'ui', 'src'),
22
+ join(repoRoot, 'apps', 'api', 'src'),
23
+ join(repoRoot, 'external', '_template', 'ui', 'src')
24
+ ]
25
+
26
+ const STRING_LITERAL_PATTERNS = [
27
+ /<AccessGuard\b[\s\S]*?\baccessKey\s*=\s*["']([^"']+)["']/g,
28
+ /<AccessGuard\b[\s\S]*?\baccessKey\s*=\s*\{\s*["']([^"']+)["']\s*\}/g,
29
+ /\buseAccess\(\s*["']([^"']+)["']\s*\)/g,
30
+ /\brequireAccess\(\s*["']([^"']+)["']\s*[,)]/g
31
+ ]
32
+
33
+ const ACCESS_KEYS_PATTERN = /\bAccessKeys\.([A-Za-z_$][A-Za-z0-9_$]*)\b/g
34
+
35
+ function findRepoRoot(startDirectory: string): string {
36
+ let current = startDirectory
37
+
38
+ while (current !== dirname(current)) {
39
+ if (existsSync(join(current, 'pnpm-workspace.yaml'))) return current
40
+ current = dirname(current)
41
+ }
42
+
43
+ throw new Error('Could not find monorepo root from access-key scan test')
44
+ }
45
+
46
+ function isRuntimeSourceFile(file: string): boolean {
47
+ if (!/\.(ts|tsx)$/.test(file)) return false
48
+ if (file.includes(`${join('', '__tests__').slice(1)}`)) return false
49
+ if (file.includes('__tests__')) return false
50
+ if (/\.(test|spec)\.(ts|tsx)$/.test(file)) return false
51
+ return !file.endsWith('routeTree.gen.ts')
52
+ }
53
+
54
+ function listRuntimeSourceFiles(directory: string): string[] {
55
+ if (!existsSync(directory)) return []
56
+
57
+ const entries = readdirSync(directory, { withFileTypes: true })
58
+ const files: string[] = []
59
+
60
+ for (const entry of entries) {
61
+ const entryPath = join(directory, entry.name)
62
+
63
+ if (entry.isDirectory()) {
64
+ if (['node_modules', 'dist', '.next', 'coverage'].includes(entry.name)) continue
65
+ files.push(...listRuntimeSourceFiles(entryPath))
66
+ continue
67
+ }
68
+
69
+ if (isRuntimeSourceFile(entryPath)) files.push(entryPath)
70
+ }
71
+
72
+ return files
73
+ }
74
+
75
+ function relativePath(file: string): string {
76
+ return relative(repoRoot, file).replaceAll('\\', '/')
77
+ }
78
+
79
+ function collectMatches<T>(
80
+ content: string,
81
+ pattern: RegExp,
82
+ build: (match: RegExpExecArray) => T
83
+ ): T[] {
84
+ const matches: T[] = []
85
+ pattern.lastIndex = 0
86
+
87
+ for (let match = pattern.exec(content); match !== null; match = pattern.exec(content)) {
88
+ matches.push(build(match))
89
+ }
90
+
91
+ return matches
92
+ }
93
+
94
+ export function scanAccessKeyUsages(): {
95
+ stringLiterals: AccessKeyUsage[]
96
+ accessKeySymbols: AccessKeySymbolUsage[]
97
+ } {
98
+ const stringLiterals: AccessKeyUsage[] = []
99
+ const accessKeySymbols: AccessKeySymbolUsage[] = []
100
+
101
+ for (const sourceRoot of RUNTIME_SOURCE_ROOTS) {
102
+ for (const file of listRuntimeSourceFiles(sourceRoot)) {
103
+ const content = readFileSync(file, 'utf8')
104
+ const path = relativePath(file)
105
+
106
+ for (const pattern of STRING_LITERAL_PATTERNS) {
107
+ stringLiterals.push(...collectMatches(content, pattern, (match) => ({ file: path, key: match[1] })))
108
+ }
109
+
110
+ accessKeySymbols.push(
111
+ ...collectMatches(content, ACCESS_KEYS_PATTERN, (match) => ({ file: path, symbol: match[1] }))
112
+ )
113
+ }
114
+ }
115
+
116
+ return { stringLiterals, accessKeySymbols }
117
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ AccessKeys,
4
+ DIAGNOSTIC_VIEW_ACCESS_KEYS,
5
+ PLATFORM_ADMIN_ACCESS_KEY,
6
+ deriveAccessKeyCatalog,
7
+ findAccessCatalogEntry,
8
+ listAccessKeys,
9
+ normalizeAccessKey
10
+ } from '../access-keys'
11
+ import type { OrganizationModel } from '../../organization-model/types'
12
+
13
+ const model = {
14
+ systems: {
15
+ sales: {
16
+ id: 'sales',
17
+ label: 'Sales',
18
+ lifecycle: 'active',
19
+ order: 10,
20
+ systems: {
21
+ crm: {
22
+ id: 'crm',
23
+ label: 'CRM',
24
+ lifecycle: 'active',
25
+ order: 10
26
+ }
27
+ }
28
+ },
29
+ operations: {
30
+ id: 'operations',
31
+ label: 'Operations',
32
+ lifecycle: 'active',
33
+ order: 20
34
+ }
35
+ }
36
+ } as OrganizationModel
37
+
38
+ describe('access keys', () => {
39
+ it('normalizes string shorthand to view access keys', () => {
40
+ expect(normalizeAccessKey('sales.crm')).toEqual({ systemPath: 'sales.crm', action: 'view' })
41
+ expect(normalizeAccessKey({ systemPath: 'sales.crm' })).toEqual({ systemPath: 'sales.crm', action: 'view' })
42
+ })
43
+
44
+ it('normalizes platformAdmin string shorthand to the platform special-case key', () => {
45
+ expect(normalizeAccessKey('platformAdmin')).toEqual(AccessKeys.platformAdmin)
46
+ expect(normalizeAccessKey('platformAdmin').systemPath).toBe(PLATFORM_ADMIN_ACCESS_KEY)
47
+ })
48
+
49
+ it('derives view and manage access keys from nested Organization Model systems', () => {
50
+ const catalog = deriveAccessKeyCatalog(model)
51
+ const keys = listAccessKeys(catalog)
52
+
53
+ expect(keys).toContainEqual({ systemPath: 'sales', action: 'view' })
54
+ expect(keys).toContainEqual({ systemPath: 'sales.crm', action: 'view' })
55
+ expect(keys).toContainEqual({ systemPath: 'sales.crm', action: 'manage' })
56
+ expect(keys).toContainEqual({ systemPath: 'operations', action: 'manage' })
57
+ })
58
+
59
+ it('includes diagnostic allowlist keys and platformAdmin in the catalog', () => {
60
+ const catalog = deriveAccessKeyCatalog(model)
61
+
62
+ for (const systemPath of DIAGNOSTIC_VIEW_ACCESS_KEYS) {
63
+ expect(findAccessCatalogEntry(catalog, { systemPath, action: 'view' })?.source).toBe('diagnostic')
64
+ }
65
+
66
+ expect(findAccessCatalogEntry(catalog, AccessKeys.platformAdmin)?.source).toBe('platform')
67
+ })
68
+
69
+ it('includes permission-only access keys with their legacy role-permission mapping', () => {
70
+ const catalog = deriveAccessKeyCatalog(model)
71
+
72
+ expect(findAccessCatalogEntry(catalog, AccessKeys.organizationManage)).toMatchObject({
73
+ source: 'permission',
74
+ rolePermission: 'org.manage'
75
+ })
76
+ expect(findAccessCatalogEntry(catalog, AccessKeys.membersManage)).toMatchObject({
77
+ source: 'permission',
78
+ rolePermission: 'members.manage'
79
+ })
80
+ })
81
+ })