@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
@@ -0,0 +1,257 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { AccessKeys, deriveAccessKeyCatalog } from '../access-keys'
3
+ import { checkAccess } from '../access-model'
4
+ import type { AccessContext } from '../access-model'
5
+ import type { OrganizationModel } from '../../organization-model/types'
6
+
7
+ const organizationModel = {
8
+ systems: {
9
+ sales: {
10
+ id: 'sales',
11
+ label: 'Sales',
12
+ lifecycle: 'active',
13
+ order: 10,
14
+ systems: {
15
+ crm: {
16
+ id: 'crm',
17
+ label: 'CRM',
18
+ lifecycle: 'active',
19
+ order: 10
20
+ }
21
+ }
22
+ },
23
+ operations: {
24
+ id: 'operations',
25
+ label: 'Operations',
26
+ lifecycle: 'beta',
27
+ order: 20
28
+ },
29
+ drafts: {
30
+ id: 'drafts',
31
+ label: 'Drafts',
32
+ lifecycle: 'draft',
33
+ order: 30
34
+ },
35
+ retired: {
36
+ id: 'retired',
37
+ label: 'Retired',
38
+ lifecycle: 'deprecated',
39
+ order: 40
40
+ },
41
+ missingLifecycle: {
42
+ id: 'missingLifecycle',
43
+ label: 'Missing Lifecycle',
44
+ order: 50
45
+ }
46
+ }
47
+ } as OrganizationModel
48
+
49
+ const baseContext: AccessContext = {
50
+ organizationId: 'org-1',
51
+ organizationModel,
52
+ membership: {
53
+ id: 'membership-1',
54
+ organizationId: 'org-1',
55
+ effectivePermissions: ['sales.crm.manage']
56
+ },
57
+ profile: {
58
+ id: 'user-1',
59
+ isPlatformAdmin: false
60
+ }
61
+ }
62
+
63
+ function context(overrides: Partial<AccessContext> = {}): AccessContext {
64
+ return { ...baseContext, ...overrides }
65
+ }
66
+
67
+ describe('checkAccess', () => {
68
+ it('allows active system view access by lifecycle alone', () => {
69
+ expect(checkAccess('sales.crm', context())).toEqual({
70
+ allowed: true,
71
+ restrictedBy: null,
72
+ reason: 'allowed'
73
+ })
74
+ })
75
+
76
+ it('denies unknown keys through catalog validation', () => {
77
+ expect(checkAccess('unknown.system', context())).toEqual({
78
+ allowed: false,
79
+ restrictedBy: 'catalog',
80
+ reason: 'unknown-access-key'
81
+ })
82
+ })
83
+
84
+ it('denies missing membership before evaluating lifecycle or permissions', () => {
85
+ expect(checkAccess('sales.crm', context({ membership: null }))).toEqual({
86
+ allowed: false,
87
+ restrictedBy: 'membership',
88
+ reason: 'missing-membership'
89
+ })
90
+ })
91
+
92
+ it('denies organization mismatches', () => {
93
+ expect(
94
+ checkAccess(
95
+ 'sales.crm',
96
+ context({
97
+ membership: { id: 'membership-2', organizationId: 'org-2', effectivePermissions: ['sales.crm.manage'] }
98
+ })
99
+ )
100
+ ).toEqual({
101
+ allowed: false,
102
+ restrictedBy: 'membership',
103
+ reason: 'organization-mismatch'
104
+ })
105
+ })
106
+
107
+ it('denies draft, deprecated, archived, and missing lifecycle systems', () => {
108
+ const archivedModel = {
109
+ systems: {
110
+ archived: {
111
+ id: 'archived',
112
+ label: 'Archived',
113
+ lifecycle: 'archived',
114
+ order: 10
115
+ }
116
+ }
117
+ } as OrganizationModel
118
+
119
+ expect(checkAccess('drafts', context())).toMatchObject({
120
+ allowed: false,
121
+ restrictedBy: 'system-lifecycle',
122
+ reason: 'system-not-active'
123
+ })
124
+ expect(checkAccess('retired', context())).toMatchObject({
125
+ allowed: false,
126
+ restrictedBy: 'system-lifecycle',
127
+ reason: 'system-not-active'
128
+ })
129
+ expect(checkAccess('missingLifecycle', context())).toMatchObject({
130
+ allowed: false,
131
+ restrictedBy: 'system-lifecycle',
132
+ reason: 'system-not-active'
133
+ })
134
+ expect(checkAccess('archived', context({ organizationModel: archivedModel }))).toMatchObject({
135
+ allowed: false,
136
+ restrictedBy: 'system-lifecycle',
137
+ reason: 'system-not-active'
138
+ })
139
+ })
140
+
141
+ it('allows beta systems only when beta access or development mode is enabled', () => {
142
+ expect(checkAccess('operations', context())).toEqual({
143
+ allowed: false,
144
+ restrictedBy: 'system-lifecycle',
145
+ reason: 'system-not-active'
146
+ })
147
+
148
+ expect(checkAccess('operations', context({ betaAccessEnabled: true }))).toEqual({
149
+ allowed: true,
150
+ restrictedBy: null,
151
+ reason: 'allowed'
152
+ })
153
+
154
+ expect(checkAccess('operations', context({ isDevelopment: true }))).toEqual({
155
+ allowed: true,
156
+ restrictedBy: null,
157
+ reason: 'allowed'
158
+ })
159
+ })
160
+
161
+ it('requires matching role permission for manage when permissions are supplied', () => {
162
+ expect(checkAccess({ systemPath: 'sales.crm', action: 'manage' }, context())).toEqual({
163
+ allowed: true,
164
+ restrictedBy: null,
165
+ reason: 'allowed'
166
+ })
167
+
168
+ expect(
169
+ checkAccess(
170
+ { systemPath: 'sales.crm', action: 'manage' },
171
+ context({
172
+ membership: { id: 'membership-1', organizationId: 'org-1', effectivePermissions: [] }
173
+ })
174
+ )
175
+ ).toEqual({
176
+ allowed: false,
177
+ restrictedBy: 'role-permission',
178
+ reason: 'role-permission-denied'
179
+ })
180
+ })
181
+
182
+ it('requires explicit role permissions for permission-only access keys', () => {
183
+ expect(
184
+ checkAccess(
185
+ AccessKeys.organizationManage,
186
+ context({
187
+ membership: { id: 'membership-1', organizationId: 'org-1', effectivePermissions: ['org.manage'] }
188
+ })
189
+ )
190
+ ).toEqual({
191
+ allowed: true,
192
+ restrictedBy: null,
193
+ reason: 'allowed'
194
+ })
195
+
196
+ expect(checkAccess(AccessKeys.organizationManage, context())).toEqual({
197
+ allowed: false,
198
+ restrictedBy: 'role-permission',
199
+ reason: 'role-permission-denied'
200
+ })
201
+ })
202
+
203
+ it('checks diagnostic keys against the runtime diagnostic allowlist', () => {
204
+ expect(checkAccess(AccessKeys.operationsOverview, context())).toEqual({
205
+ allowed: false,
206
+ restrictedBy: 'diagnostic-allowlist',
207
+ reason: 'diagnostic-key-not-allowed'
208
+ })
209
+
210
+ expect(
211
+ checkAccess(
212
+ AccessKeys.operationsOverview,
213
+ context({ diagnosticAllowlist: [AccessKeys.operationsOverview.systemPath] })
214
+ )
215
+ ).toEqual({
216
+ allowed: true,
217
+ restrictedBy: null,
218
+ reason: 'allowed'
219
+ })
220
+ })
221
+
222
+ it('keeps diagnostic catalog validation separate from runtime diagnostic allowlist', () => {
223
+ const accessCatalog = deriveAccessKeyCatalog(organizationModel, { diagnosticKeys: ['diagnostic.custom'] })
224
+
225
+ expect(
226
+ checkAccess('diagnostic.custom', context({ accessCatalog, diagnosticAllowlist: ['diagnostic.custom'] }))
227
+ ).toEqual({
228
+ allowed: true,
229
+ restrictedBy: null,
230
+ reason: 'allowed'
231
+ })
232
+
233
+ expect(checkAccess('diagnostic.not-cataloged', context({ diagnosticAllowlist: ['diagnostic.not-cataloged'] }))).toEqual({
234
+ allowed: false,
235
+ restrictedBy: 'catalog',
236
+ reason: 'unknown-access-key'
237
+ })
238
+ })
239
+
240
+ it('centralizes platform-admin bypass with the platform-admin-bypass reason', () => {
241
+ expect(
242
+ checkAccess('unknown.system', context({ profile: { id: 'admin', isPlatformAdmin: true }, membership: null }))
243
+ ).toEqual({
244
+ allowed: true,
245
+ restrictedBy: null,
246
+ reason: 'platform-admin-bypass'
247
+ })
248
+ })
249
+
250
+ it('denies platformAdmin special-case for non-platform admins', () => {
251
+ expect(checkAccess('platformAdmin', context())).toEqual({
252
+ allowed: false,
253
+ restrictedBy: 'role-permission',
254
+ reason: 'role-permission-denied'
255
+ })
256
+ })
257
+ })
@@ -0,0 +1,50 @@
1
+ import type { OrganizationModel } from '../../organization-model/types'
2
+
3
+ export const accessTestOrganizationModel = {
4
+ systems: {
5
+ platform: {
6
+ id: 'platform',
7
+ label: 'Platform',
8
+ lifecycle: 'active',
9
+ order: 10
10
+ },
11
+ sales: {
12
+ id: 'sales',
13
+ label: 'Sales',
14
+ lifecycle: 'active',
15
+ order: 20,
16
+ systems: {
17
+ crm: {
18
+ id: 'crm',
19
+ label: 'CRM',
20
+ lifecycle: 'active',
21
+ order: 10
22
+ },
23
+ 'lead-gen': {
24
+ id: 'lead-gen',
25
+ label: 'Lead Gen',
26
+ lifecycle: 'active',
27
+ order: 20
28
+ }
29
+ }
30
+ },
31
+ projects: {
32
+ id: 'projects',
33
+ label: 'Projects',
34
+ lifecycle: 'active',
35
+ order: 30
36
+ },
37
+ clients: {
38
+ id: 'clients',
39
+ label: 'Clients',
40
+ lifecycle: 'active',
41
+ order: 40
42
+ },
43
+ seo: {
44
+ id: 'seo',
45
+ label: 'SEO',
46
+ lifecycle: 'active',
47
+ order: 50
48
+ }
49
+ }
50
+ } as unknown as OrganizationModel
@@ -0,0 +1,33 @@
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
+ describe('access key catalog drift', () => {
8
+ it('keeps runtime literal access keys in the derived catalog', () => {
9
+ const catalog = deriveAccessKeyCatalog(accessTestOrganizationModel)
10
+ const usages = scanAccessKeyUsages()
11
+
12
+ const unknownLiterals = usages.stringLiterals
13
+ .map((usage) => {
14
+ const key = normalizeAccessKey(usage.key)
15
+ return findAccessCatalogEntry(catalog, key) === undefined
16
+ ? `${usage.file}: ${JSON.stringify(usage.key)}`
17
+ : null
18
+ })
19
+ .filter((usage): usage is string => usage !== null)
20
+
21
+ expect(unknownLiterals).toEqual([])
22
+ })
23
+
24
+ it('keeps runtime AccessKeys references backed by exported symbols', () => {
25
+ const usages = scanAccessKeyUsages()
26
+ const exportedSymbols = new Set(Object.keys(AccessKeys))
27
+ const unknownSymbols = usages.accessKeySymbols
28
+ .filter((usage) => !exportedSymbols.has(usage.symbol))
29
+ .map((usage) => `${usage.file}: AccessKeys.${usage.symbol}`)
30
+
31
+ expect(unknownSymbols).toEqual([])
32
+ })
33
+ })
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { AccessKeys } from '../access-keys'
4
+ import { checkAccess, createAccessModel, type AccessContext } from '../access-model'
5
+ import { accessTestOrganizationModel } from './access-test-fixtures'
6
+
7
+ const bypassAnswer = {
8
+ allowed: true,
9
+ restrictedBy: null,
10
+ reason: 'platform-admin-bypass'
11
+ } as const
12
+
13
+ const baseContext: AccessContext = {
14
+ organizationId: 'org-1',
15
+ organizationModel: accessTestOrganizationModel,
16
+ membership: {
17
+ id: 'membership-1',
18
+ organizationId: 'org-1',
19
+ effectivePermissions: []
20
+ },
21
+ profile: {
22
+ id: 'user-1',
23
+ isPlatformAdmin: false
24
+ }
25
+ }
26
+
27
+ describe('platform admin bypass parity', () => {
28
+ it('short-circuits unknown keys before catalog and membership checks', () => {
29
+ expect(
30
+ checkAccess('unknown.system', {
31
+ ...baseContext,
32
+ membership: null,
33
+ profile: { id: 'admin', isPlatformAdmin: true }
34
+ })
35
+ ).toEqual(bypassAnswer)
36
+ })
37
+
38
+ it('supports both camelCase and database-shaped platform-admin profile flags', () => {
39
+ expect(
40
+ checkAccess('unknown.system', {
41
+ ...baseContext,
42
+ membership: null,
43
+ profile: { id: 'admin', is_platform_admin: true }
44
+ })
45
+ ).toEqual(bypassAnswer)
46
+ })
47
+
48
+ it('returns the same bypass answer through createAccessModel', () => {
49
+ const accessModel = createAccessModel(accessTestOrganizationModel)
50
+
51
+ expect(
52
+ accessModel.checkAccess('unknown.system', {
53
+ ...baseContext,
54
+ membership: null,
55
+ profile: { id: 'admin', isPlatformAdmin: true }
56
+ })
57
+ ).toEqual(bypassAnswer)
58
+ })
59
+
60
+ it('does not grant platformAdmin access to non-platform admins', () => {
61
+ expect(checkAccess(AccessKeys.platformAdmin, baseContext)).toEqual({
62
+ allowed: false,
63
+ restrictedBy: 'role-permission',
64
+ reason: 'role-permission-denied'
65
+ })
66
+ })
67
+ })
@@ -0,0 +1,173 @@
1
+ import { z } from 'zod'
2
+ import { listAllSystems } from '../organization-model/helpers'
3
+ import type { OrganizationModel } from '../organization-model/types'
4
+ import { PERMISSIONS, type PermissionKey } from './multi-tenancy/permissions'
5
+
6
+ export const DEFAULT_ACCESS_ACTION = 'view' as const
7
+ export const PLATFORM_ADMIN_ACCESS_KEY = 'platform.admin' as const
8
+ export const PLATFORM_ADMIN_ACCESS_KEY_SHORTHAND = 'platformAdmin' as const
9
+
10
+ export const AccessActionSchema = z.enum(['view', 'manage'])
11
+
12
+ export const AccessKeyObjectSchema = z
13
+ .object({
14
+ systemPath: z.string().trim().min(1),
15
+ action: AccessActionSchema.default(DEFAULT_ACCESS_ACTION)
16
+ })
17
+ .strict()
18
+
19
+ export const AccessKeyInputSchema = z.union([z.string().trim().min(1), AccessKeyObjectSchema])
20
+ export const NormalizedAccessKeySchema = AccessKeyObjectSchema
21
+ export const AccessKeySchema = AccessKeyInputSchema
22
+
23
+ export type AccessAction = z.infer<typeof AccessActionSchema>
24
+ export type AccessKeyObject = z.infer<typeof AccessKeyObjectSchema>
25
+ export type AccessKeyInput = z.input<typeof AccessKeyInputSchema>
26
+ export type NormalizedAccessKey = z.infer<typeof NormalizedAccessKeySchema>
27
+ export type AccessKey = NormalizedAccessKey
28
+
29
+ export type AccessCatalogEntrySource = 'om-system' | 'diagnostic' | 'platform' | 'permission'
30
+
31
+ export interface AccessCatalogEntry {
32
+ key: NormalizedAccessKey
33
+ source: AccessCatalogEntrySource
34
+ rolePermission?: string
35
+ }
36
+
37
+ export interface AccessKeyCatalog {
38
+ bySystemPath: ReadonlyMap<string, readonly AccessCatalogEntry[]>
39
+ entries: readonly AccessCatalogEntry[]
40
+ }
41
+
42
+ export const DIAGNOSTIC_VIEW_ACCESS_KEYS = [
43
+ 'diagnostic.operations.overview',
44
+ 'diagnostic.operations.recent-executions',
45
+ 'diagnostic.monitoring.execution-logs',
46
+ 'diagnostic.monitoring.notifications'
47
+ ] as const
48
+
49
+ export const AccessKeys = {
50
+ platformAdmin: { systemPath: PLATFORM_ADMIN_ACCESS_KEY, action: DEFAULT_ACCESS_ACTION },
51
+ organizationManage: { systemPath: 'permission.org', action: 'manage' },
52
+ membersManage: { systemPath: 'permission.members', action: 'manage' },
53
+ rolesManage: { systemPath: 'permission.roles', action: 'manage' },
54
+ secretsManage: { systemPath: 'permission.secrets', action: 'manage' },
55
+ operationsRead: { systemPath: 'permission.operations', action: DEFAULT_ACCESS_ACTION },
56
+ operationsManage: { systemPath: 'permission.operations', action: 'manage' },
57
+ acquisitionManage: { systemPath: 'permission.acquisition', action: 'manage' },
58
+ projectsManage: { systemPath: 'permission.projects', action: 'manage' },
59
+ clientsManage: { systemPath: 'permission.clients', action: 'manage' },
60
+ operationsOverview: { systemPath: 'diagnostic.operations.overview', action: DEFAULT_ACCESS_ACTION },
61
+ operationsRecentExecutions: { systemPath: 'diagnostic.operations.recent-executions', action: DEFAULT_ACCESS_ACTION },
62
+ monitoringExecutionLogs: { systemPath: 'diagnostic.monitoring.execution-logs', action: DEFAULT_ACCESS_ACTION },
63
+ monitoringNotifications: { systemPath: 'diagnostic.monitoring.notifications', action: DEFAULT_ACCESS_ACTION }
64
+ } as const satisfies Record<string, NormalizedAccessKey>
65
+
66
+ const PERMISSION_ACCESS_KEY_DEFINITIONS: readonly {
67
+ key: NormalizedAccessKey
68
+ rolePermission: PermissionKey
69
+ }[] = [
70
+ { key: AccessKeys.organizationManage, rolePermission: PERMISSIONS.ORG_MANAGE },
71
+ { key: AccessKeys.membersManage, rolePermission: PERMISSIONS.MEMBERS_MANAGE },
72
+ { key: AccessKeys.rolesManage, rolePermission: PERMISSIONS.ROLES_MANAGE },
73
+ { key: AccessKeys.secretsManage, rolePermission: PERMISSIONS.SECRETS_MANAGE },
74
+ { key: AccessKeys.operationsRead, rolePermission: PERMISSIONS.OPERATIONS_READ },
75
+ { key: AccessKeys.operationsManage, rolePermission: PERMISSIONS.OPERATIONS_MANAGE },
76
+ { key: AccessKeys.acquisitionManage, rolePermission: PERMISSIONS.ACQUISITION_MANAGE },
77
+ { key: AccessKeys.projectsManage, rolePermission: PERMISSIONS.PROJECTS_MANAGE },
78
+ { key: AccessKeys.clientsManage, rolePermission: PERMISSIONS.CLIENTS_MANAGE }
79
+ ]
80
+
81
+ export function normalizeAccessKey(input: AccessKeyInput): NormalizedAccessKey {
82
+ const parsed = AccessKeyInputSchema.parse(input)
83
+ const normalized =
84
+ typeof parsed === 'string'
85
+ ? {
86
+ systemPath: parsed === PLATFORM_ADMIN_ACCESS_KEY_SHORTHAND ? PLATFORM_ADMIN_ACCESS_KEY : parsed,
87
+ action: DEFAULT_ACCESS_ACTION
88
+ }
89
+ : parsed
90
+
91
+ return NormalizedAccessKeySchema.parse(normalized)
92
+ }
93
+
94
+ export function accessKeyToString(input: AccessKeyInput): string {
95
+ const key = normalizeAccessKey(input)
96
+ return `${key.systemPath}:${key.action}`
97
+ }
98
+
99
+ export function rolePermissionForAccessKey(key: NormalizedAccessKey): string | undefined {
100
+ if (key.action === DEFAULT_ACCESS_ACTION) return undefined
101
+ return `${key.systemPath}.${key.action}`
102
+ }
103
+
104
+ export interface DeriveAccessKeyCatalogOptions {
105
+ diagnosticKeys?: readonly string[]
106
+ includeManageActions?: boolean
107
+ }
108
+
109
+ function groupCatalogEntries(entries: readonly AccessCatalogEntry[]): ReadonlyMap<string, readonly AccessCatalogEntry[]> {
110
+ const grouped = new Map<string, AccessCatalogEntry[]>()
111
+
112
+ for (const entry of entries) {
113
+ const existing = grouped.get(entry.key.systemPath) ?? []
114
+ existing.push(entry)
115
+ grouped.set(entry.key.systemPath, existing)
116
+ }
117
+
118
+ return grouped
119
+ }
120
+
121
+ function buildCatalogEntry(systemPath: string, action: AccessAction, source: AccessCatalogEntrySource): AccessCatalogEntry {
122
+ const key = normalizeAccessKey({ systemPath, action })
123
+ return {
124
+ key,
125
+ source,
126
+ rolePermission: rolePermissionForAccessKey(key)
127
+ }
128
+ }
129
+
130
+ export function deriveAccessKeyCatalog(
131
+ organizationModel: OrganizationModel,
132
+ options: DeriveAccessKeyCatalogOptions = {}
133
+ ): AccessKeyCatalog {
134
+ const { diagnosticKeys = DIAGNOSTIC_VIEW_ACCESS_KEYS, includeManageActions = true } = options
135
+ const entries: AccessCatalogEntry[] = [
136
+ buildCatalogEntry(PLATFORM_ADMIN_ACCESS_KEY, DEFAULT_ACCESS_ACTION, 'platform')
137
+ ]
138
+
139
+ for (const { path } of listAllSystems(organizationModel)) {
140
+ entries.push(buildCatalogEntry(path, DEFAULT_ACCESS_ACTION, 'om-system'))
141
+ if (includeManageActions) {
142
+ entries.push(buildCatalogEntry(path, 'manage', 'om-system'))
143
+ }
144
+ }
145
+
146
+ for (const { key, rolePermission } of PERMISSION_ACCESS_KEY_DEFINITIONS) {
147
+ entries.push({
148
+ key,
149
+ source: 'permission',
150
+ rolePermission
151
+ })
152
+ }
153
+
154
+ for (const systemPath of diagnosticKeys) {
155
+ entries.push(buildCatalogEntry(systemPath, DEFAULT_ACCESS_ACTION, 'diagnostic'))
156
+ }
157
+
158
+ return {
159
+ bySystemPath: groupCatalogEntries(entries),
160
+ entries
161
+ }
162
+ }
163
+
164
+ export function findAccessCatalogEntry(
165
+ catalog: AccessKeyCatalog,
166
+ key: NormalizedAccessKey
167
+ ): AccessCatalogEntry | undefined {
168
+ return catalog.bySystemPath.get(key.systemPath)?.find((entry) => entry.key.action === key.action)
169
+ }
170
+
171
+ export function listAccessKeys(catalog: AccessKeyCatalog): NormalizedAccessKey[] {
172
+ return catalog.entries.map((entry) => entry.key)
173
+ }