@friggframework/core 2.0.0--canary.684.9d19f43.0 → 2.0.0--canary.681.2df9ef0.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 (86) hide show
  1. package/README.md +1 -37
  2. package/application/commands/integration-commands.js +10 -20
  3. package/application/index.js +2 -6
  4. package/credential/use-cases/credential-operations-for-user.js +25 -110
  5. package/credential/use-cases/reauthorize-credential-for-user.js +8 -15
  6. package/database/encryption/encryption-schema-registry.js +0 -7
  7. package/generated/prisma-mongodb/edge.js +3 -5
  8. package/generated/prisma-mongodb/index-browser.js +0 -2
  9. package/generated/prisma-mongodb/index.d.ts +2 -115
  10. package/generated/prisma-mongodb/index.js +3 -5
  11. package/generated/prisma-mongodb/package.json +1 -1
  12. package/generated/prisma-mongodb/schema.prisma +0 -15
  13. package/generated/prisma-mongodb/wasm.js +3 -5
  14. package/generated/prisma-postgresql/edge.js +3 -5
  15. package/generated/prisma-postgresql/index-browser.js +0 -2
  16. package/generated/prisma-postgresql/index.d.ts +2 -131
  17. package/generated/prisma-postgresql/index.js +3 -5
  18. package/generated/prisma-postgresql/package.json +1 -1
  19. package/generated/prisma-postgresql/schema.prisma +0 -15
  20. package/generated/prisma-postgresql/wasm.js +3 -5
  21. package/handlers/app-definition-loader.js +8 -25
  22. package/handlers/backend-utils.js +25 -31
  23. package/handlers/routers/health.js +4 -1
  24. package/handlers/workers/integration-defined-workers.js +2 -5
  25. package/index.js +2 -10
  26. package/integrations/index.js +4 -0
  27. package/integrations/integration-router.js +52 -101
  28. package/integrations/repositories/integration-repository-documentdb.js +3 -34
  29. package/integrations/repositories/integration-repository-interface.js +1 -17
  30. package/integrations/repositories/integration-repository-mongo.js +76 -59
  31. package/integrations/repositories/integration-repository-postgres.js +76 -62
  32. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  33. package/integrations/use-cases/create-integration.js +20 -55
  34. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  35. package/integrations/use-cases/index.js +1 -5
  36. package/integrations/use-cases/integration-operations-for-user.js +24 -55
  37. package/integrations/utils/map-integration-dto.js +15 -1
  38. package/management-api/management-api-config.js +0 -3
  39. package/management-api/openapi/components.js +1 -47
  40. package/management-api/openapi/v2-operations.js +1 -17
  41. package/management-api/v2/dependencies.js +14 -58
  42. package/management-api/v2/dto.js +0 -20
  43. package/management-api/v2/handlers/authorization.js +0 -1
  44. package/management-api/v2/handlers/credentials.js +2 -6
  45. package/management-api/v2/handlers/entities.js +7 -10
  46. package/management-api/v2/handlers/integrations.js +3 -6
  47. package/management-api/v2/request.js +1 -32
  48. package/modules/index.js +0 -14
  49. package/modules/repositories/module-repository-documentdb.js +1 -41
  50. package/modules/repositories/module-repository-interface.js +2 -19
  51. package/modules/repositories/module-repository-mongo.js +73 -64
  52. package/modules/repositories/module-repository-postgres.js +73 -63
  53. package/modules/use-cases/entity-operations-for-user.js +13 -50
  54. package/modules/use-cases/entity-types.js +14 -62
  55. package/modules/use-cases/execute-entity-proxy-request.js +0 -15
  56. package/modules/use-cases/get-entities-for-user.js +5 -16
  57. package/modules/use-cases/get-entity-module-for-user.js +3 -23
  58. package/modules/use-cases/get-entity-options-by-id.js +2 -5
  59. package/modules/use-cases/get-module.js +2 -5
  60. package/modules/use-cases/multi-step-authorization.js +12 -39
  61. package/modules/use-cases/process-authorization-callback.js +18 -52
  62. package/modules/use-cases/refresh-entity-options.js +2 -5
  63. package/modules/use-cases/test-module-auth.js +2 -5
  64. package/package.json +5 -5
  65. package/prisma-mongodb/schema.prisma +0 -15
  66. package/prisma-postgresql/schema.prisma +0 -15
  67. package/types/index.d.ts +0 -1
  68. package/types/user/index.d.ts +0 -7
  69. package/user/repositories/organization-membership-repository-documentdb.js +4 -2
  70. package/user/repositories/user-repository-documentdb.js +4 -2
  71. package/user/use-cases/authenticate-user.js +1 -7
  72. package/user/user.js +1 -24
  73. package/database/organization-where.js +0 -18
  74. package/integrations/repositories/integration-not-found.js +0 -14
  75. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  76. package/modules/entity-scope.js +0 -15
  77. package/modules/entity-type-registry.js +0 -311
  78. package/modules/scope-config.js +0 -177
  79. package/modules/scope-policy.js +0 -111
  80. package/modules/scope-resolution.js +0 -288
  81. package/modules/scope-visibility.js +0 -212
  82. package/modules/tests/doubles/ownership-matching.js +0 -18
  83. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  84. package/modules/use-cases/manage-access.js +0 -21
  85. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  86. package/types/modules/index.d.ts +0 -145
package/README.md CHANGED
@@ -393,7 +393,7 @@ value that is not a three-part JWT is not offered to the verifier at all; it fal
393
393
  | `clockToleranceSec` | `30` | Skew allowed on `exp` and `nbf`. |
394
394
  | `userIdClaim` | `sub` | App user id (required in the token). |
395
395
  | `orgIdClaim` | `org_id` | App organization id (optional). |
396
- | `roleClaim` | `org_role` | Exposed as `user.getOrgRole()` for the request, and stored as the `orgRole` of a person's membership the first time they join that organization (`MEMBER` when the token names none). A token with no role is a member for permissions too: it cannot create or change organization-level entities or integrations; `user.orgAdminRoles` (default `['ADMIN']`) names the roles that can. |
396
+ | `roleClaim` | `org_role` | Exposed as `user.getOrgRole()` for the request, and stored as the `orgRole` of a person's membership the first time they join that organization (`MEMBER` when the token names none). |
397
397
 
398
398
  Rules worth knowing:
399
399
 
@@ -421,42 +421,6 @@ Rules worth knowing:
421
421
  - Two first logins for the same new person or organization, even in different organizations, both
422
422
  succeed: a create that loses to a unique constraint finds the winner's row.
423
423
 
424
- #### Entity scopes and ownership (ADR-056 §3)
425
-
426
- Who owns an entity (a connected account) or an integration follows its **scope**, not `user.primary`:
427
-
428
- | Scope | Owned by | Created with |
429
- |---|---|---|
430
- | `individual` | the person (`individualUser`) | the active organization (`organizationId`), or none under `individualAcross: 'all'` |
431
- | `organization` | the active organization's user | that organization |
432
- | `global` | nobody (the app owner) | none; refused by the tenant API (admin routes only) |
433
-
434
- The scope of a new connection is the first of: the request (`scope` on `POST /api/v2/authorize`, only from
435
- the deciding level's `allow` list), the integration binding (`Definition.modules[key].scope` or
436
- `Definition.entities[key].scope`), `Definition.ownership.scope`, the entity type
437
- (`appDefinition.entities[name].scope`), `appDefinition.ownership.scope`, then `organization` for an
438
- organization-primary app and `individual` otherwise. A setting is `'organization'` or
439
- `{ default: 'individual', allow: ['individual', 'organization'] }`.
440
-
441
- ```javascript
442
- const appDefinition = {
443
- user: { primary: 'individual', authModes: { adopterJwt: true } },
444
- ownership: { scope: 'individual', individualAcross: 'organization' }, // the defaults
445
- entities: {
446
- google: { definition: google, scope: 'individual' },
447
- xero: { definition: xero, scope: { default: 'organization', allow: ['organization', 'individual'] } },
448
- },
449
- integrations: [Sync], // Sync.Definition.ownership = { owner: 'derive' }
450
- };
451
- ```
452
-
453
- `GET /api/v2/entities` and `/integrations` return, for the active organization, its own
454
- rows and the caller's, each with `scope`, `owner`, `organizationId` and `access`, and take `?scope=` and
455
- `?owner=me|org`. An individual entity is only visible and usable in the organization it was connected in
456
- (or in all of them, with `individualAcross: 'all'`). Rows written before this existed keep working: see
457
- [Entity scopes](../../docs/guides/entity-scopes.md), which also covers the schema (`organizationId` on
458
- `Entity` and `Integration`, no `scope` column) and what is deferred.
459
-
460
424
  ### 8. Lambda Utilities (`/lambda`)
461
425
 
462
426
  AWS Lambda-specific utilities and helpers.
@@ -18,7 +18,7 @@ const {
18
18
  ListIntegrationsByEntityExternalIdUseCase,
19
19
  } = require('../../integrations/use-cases/list-integrations-by-entity-external-id');
20
20
  const {
21
- GetIntegrationsByUserId,
21
+ GetIntegrationsForUser,
22
22
  } = require('../../integrations/use-cases/get-integrations-for-user');
23
23
  const {
24
24
  CreateIntegration,
@@ -30,9 +30,8 @@ const {
30
30
  PatchIntegrationConfig,
31
31
  } = require('../../integrations/use-cases/patch-integration-config');
32
32
  const {
33
- getEntityTypeRegistry,
34
- getEntityTypeDefinitions,
35
- } = require('../../modules/entity-type-registry');
33
+ getModulesDefinitionFromIntegrationClasses,
34
+ } = require('../../integrations/utils/map-integration-dto');
36
35
 
37
36
  const ERROR_CODE_MAP = {
38
37
  ENTITY_NOT_FOUND: 401,
@@ -52,16 +51,7 @@ function mapErrorToResponse(error) {
52
51
  };
53
52
  }
54
53
 
55
- /**
56
- * @param {Object} [params]
57
- * @param {Function} [params.integrationClass]
58
- * @param {Array<object>} [params.moduleDefinitions] - the app's offered module
59
- * definitions (`loadAppDefinition().moduleDefinitions`). Integration code
60
- * calls this from a constructor with no app definition in hand, so when it
61
- * is omitted the commands hydrate with this integration's own modules only
62
- * (a registry built from `{ integrations: [integrationClass] }`).
63
- */
64
- function createIntegrationCommands({ integrationClass, moduleDefinitions } = {}) {
54
+ function createIntegrationCommands({ integrationClass } = {}) {
65
55
  // Always use Frigg's default repositories and use cases
66
56
  const integrationRepository = createIntegrationRepository();
67
57
 
@@ -118,13 +108,13 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
118
108
 
119
109
  const moduleRepository = createModuleRepository();
120
110
 
111
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses([
112
+ integrationClass,
113
+ ]);
114
+
121
115
  const moduleFactory = new ModuleFactory({
122
116
  moduleRepository,
123
- moduleDefinitions:
124
- moduleDefinitions ??
125
- getEntityTypeDefinitions(
126
- getEntityTypeRegistry({ integrations: [integrationClass] })
127
- ),
117
+ moduleDefinitions,
128
118
  });
129
119
 
130
120
  const loadIntegrationContextUseCase = new LoadIntegrationContextUseCase({
@@ -152,7 +142,7 @@ function createIntegrationCommands({ integrationClass, moduleDefinitions } = {})
152
142
  moduleRepository,
153
143
  });
154
144
 
155
- const getIntegrationsForUserUseCase = new GetIntegrationsByUserId({
145
+ const getIntegrationsForUserUseCase = new GetIntegrationsForUser({
156
146
  integrationRepository,
157
147
  integrationClasses: [integrationClass],
158
148
  moduleFactory,
@@ -17,7 +17,6 @@ const { createUsageCommands } = require('./commands/usage-commands');
17
17
  *
18
18
  * @param {Object} params
19
19
  * @param {Object} params.integrationClass - Integration class (required)
20
- * @param {Array<object>} [params.moduleDefinitions] - the app's offered module definitions; defaults to the integration's own modules
21
20
  * @returns {Object} Unified commands object with all CRUD operations
22
21
  *
23
22
  * @example
@@ -25,12 +24,9 @@ const { createUsageCommands } = require('./commands/usage-commands');
25
24
  * const user = await commands.createUser({ username: 'user@example.com' });
26
25
  * const credential = await commands.createCredential({ userId: user.id, ... });
27
26
  */
28
- function createFriggCommands({ integrationClass, moduleDefinitions }) {
27
+ function createFriggCommands({ integrationClass }) {
29
28
  // All commands use Frigg's default repositories and use cases
30
- const integrationCommands = createIntegrationCommands({
31
- integrationClass,
32
- moduleDefinitions,
33
- });
29
+ const integrationCommands = createIntegrationCommands({ integrationClass });
34
30
 
35
31
  const userCommands = createUserCommands();
36
32
 
@@ -1,28 +1,10 @@
1
1
  const Boom = require('@hapi/boom');
2
- const {
3
- createScopePolicy,
4
- describeRecord,
5
- visibilityClauses,
6
- matchesListFilters,
7
- SCOPES,
8
- } = require('../../modules/entity-scope');
9
- const { assertCanManage } = require('../../modules/use-cases/manage-access');
2
+ const { userOwns } = require('../../modules/use-cases/get-entity-module-for-user');
10
3
 
11
4
  /**
12
- * Management API v2 credential operations (ADR-056 §3.4, §3.6). A credential
13
- * has no module name and no organization of its own:
14
- *
15
- * - its `type` and `entityIds` come from the entities that use it that the
16
- * caller may see, so the same person's account connected in two
17
- * organizations (one credential, two entities) lists in each organization
18
- * with that organization's entity only;
19
- * - who it belongs to is its OWNER (`credential.userId`), never the entities:
20
- * a credential an individual entity and an organization entity both point at
21
- * (possible only through legacy data) is described by whoever owns the
22
- * credential, so the answer cannot change with the order the entities load;
23
- * - a credential whose entities are all out of sight (another organization's)
24
- * is a 404, and one with no entities at all (its entity was deleted) is
25
- * visible to its owner only, as it was before scoping.
5
+ * Management API v2 credential operations. A credential has no module name
6
+ * of its own; its `type` and the entities that use it come from the
7
+ * caller's entities. A missing or foreign credential is a 404.
26
8
  */
27
9
 
28
10
  const credentialIdOf = (entity) => {
@@ -55,82 +37,31 @@ function withEntityUsage(credentials, entities) {
55
37
  });
56
38
  }
57
39
 
58
- /**
59
- * `{scope, owner, organizationId, access}` of a credential for this caller, or
60
- * null when its owner is neither the caller nor the active organization. Built
61
- * by `describeRecord` over the row a connection by this owner has: the
62
- * organization of a visible entity, else the one a new entity would get.
63
- */
64
- function credentialTenancy(credential, visibleEntities, ctx) {
65
- const entityOrg = visibleEntities.find(
66
- (entity) => entity.organizationId !== undefined && entity.organizationId !== null
67
- )?.organizationId;
68
- const organizationId =
69
- entityOrg ?? (ctx.individualAcross === 'all' ? null : ctx.activeOrgId);
70
- return describeRecord({ userId: credential.userId, organizationId }, ctx, 'entity');
71
- }
72
-
73
- /** The entities of `entities` the caller may see, each with its `tenancy`. */
74
- function visibleOf(entities, ctx) {
75
- const visible = [];
76
- for (const entity of entities || []) {
77
- const tenancy = describeRecord(entity, ctx, 'entity');
78
- if (tenancy) visible.push({ ...entity, tenancy });
79
- }
80
- return visible;
81
- }
82
-
83
- /**
84
- * The caller's credentials with their type, the entities of them they may see
85
- * and `tenancy`. Credentials with no entity at all are listed too, for the
86
- * person and the active organization that own them.
87
- */
40
+ /** The caller's credentials with their type and entity usage. */
88
41
  class ListCredentialsForUser {
89
- constructor({ credentialRepository, moduleRepository, scopePolicy }) {
42
+ constructor({ credentialRepository, moduleRepository }) {
90
43
  this.credentialRepository = credentialRepository;
91
44
  this.moduleRepository = moduleRepository;
92
- this.scopePolicy = scopePolicy || createScopePolicy();
93
45
  }
94
46
 
95
- /** @param {{scope?: string, owner?: string}} [filters] */
96
- async execute(user, filters = {}) {
97
- const ctx = this.scopePolicy.accessContext(user);
98
- const owners = [...new Set([ctx.callerId, ctx.activeOrgId].filter(Boolean))];
99
- const [found, perOwner, owned] = await Promise.all([
100
- this.moduleRepository.findEntitiesByOwnership(visibilityClauses(ctx, 'entity')),
101
- Promise.all(owners.map((id) => this.credentialRepository.findCredentialsByUserId(id))),
102
- // Every entity of those owners, in any organization: only to tell a credential
103
- // with no entity at all from one used elsewhere.
104
- Promise.all(owners.map((id) => this.moduleRepository.findEntitiesByUserId(id))),
47
+ async execute(user) {
48
+ const userId = user.getId();
49
+ const [credentials, entities] = await Promise.all([
50
+ this.credentialRepository.findCredentialsByUserId(userId),
51
+ this.moduleRepository.findEntitiesByUserId(userId),
105
52
  ]);
106
- const visible = visibleOf(found, ctx);
107
- const usedAnywhere = new Set(owned.flat().map(credentialIdOf).filter(Boolean));
108
- const listed = [];
109
- for (const credential of perOwner.flat().filter(Boolean)) {
110
- const id = String(credential.id);
111
- const referencing = visible.filter((entity) => credentialIdOf(entity) === id);
112
- if (referencing.length === 0 && usedAnywhere.has(id)) continue;
113
- const tenancy = credentialTenancy(credential, referencing, ctx);
114
- if (!tenancy || !matchesListFilters(tenancy, filters)) continue;
115
- listed.push({ ...withEntityUsage([credential], referencing)[0], tenancy });
116
- }
117
- return listed;
53
+ return withEntityUsage(credentials || [], entities);
118
54
  }
119
55
  }
120
56
 
121
- /** One credential the caller may see: see the header. 404 otherwise. */
57
+ /** One credential the caller owns, with its type and entity usage. */
122
58
  class GetCredentialForUserV2 {
123
- constructor({ credentialRepository, moduleRepository, scopePolicy }) {
59
+ constructor({ credentialRepository, moduleRepository }) {
124
60
  this.credentialRepository = credentialRepository;
125
61
  this.moduleRepository = moduleRepository;
126
- this.scopePolicy = scopePolicy || createScopePolicy();
127
62
  }
128
63
 
129
64
  async execute(credentialId, user) {
130
- const notFound = () =>
131
- Boom.notFound(`Credential ${credentialId} not found`, {
132
- code: 'CREDENTIAL_NOT_FOUND',
133
- });
134
65
  let credential = null;
135
66
  try {
136
67
  credential = await this.credentialRepository.findCredentialById(
@@ -141,29 +72,22 @@ class GetCredentialForUserV2 {
141
72
  throw error;
142
73
  }
143
74
  }
144
- if (!credential) throw notFound();
145
- const ctx = this.scopePolicy.accessContext(user);
146
-
147
- const referencing =
148
- (await this.moduleRepository.findEntities({ credentialId: credential.id })) || [];
149
- const visible = visibleOf(referencing, ctx);
150
- if (referencing.length > 0 && visible.length === 0) throw notFound();
151
- const tenancy = credentialTenancy(credential, visible, ctx);
152
- if (!tenancy) throw notFound();
153
- return { ...withEntityUsage([credential], visible)[0], tenancy };
75
+ if (!credential || !userOwns(user, credential.userId)) {
76
+ throw Boom.notFound(`Credential ${credentialId} not found`, {
77
+ code: 'CREDENTIAL_NOT_FOUND',
78
+ });
79
+ }
80
+ const entities = await this.moduleRepository.findEntitiesByUserId(
81
+ credential.userId
82
+ );
83
+ return withEntityUsage([credential], entities)[0];
154
84
  }
155
85
  }
156
86
 
157
87
  /**
158
- * Deletes a credential the caller may manage. The entities that used it stay,
159
- * with their credential unset, so they show as needing re-authorization rather
88
+ * Deletes a credential the caller owns. The entities that used it stay, with
89
+ * their credential unset, so they show as needing re-authorization rather
160
90
  * than disappearing from integrations.
161
- *
162
- * Refuses with 409 `CREDENTIAL_SHARED` while an entity the caller cannot see
163
- * still uses it: one credential row serves the same provider account in every
164
- * organization, and deleting it from one would break the other. The message
165
- * says nothing about the other connections (not even how many): remove the
166
- * entities that use it, and it goes with the last one.
167
91
  */
168
92
  class DeleteCredentialForUser {
169
93
  constructor({ credentialRepository, moduleRepository, getCredentialForUser }) {
@@ -177,15 +101,6 @@ class DeleteCredentialForUser {
177
101
  credentialId,
178
102
  user
179
103
  );
180
- assertCanManage(credential.tenancy.access, `credential ${credential.id}`);
181
- const usedBy =
182
- (await this.moduleRepository.findEntities({ credentialId: credential.id })) || [];
183
- if (usedBy.length > credential.entityIds.length) {
184
- throw Boom.conflict(
185
- `Credential ${credential.id} is still in use by other connections and cannot be deleted from here`,
186
- { code: 'CREDENTIAL_SHARED' }
187
- );
188
- }
189
104
  for (const entityId of credential.entityIds) {
190
105
  await this.moduleRepository.unsetCredential(entityId);
191
106
  }
@@ -1,5 +1,4 @@
1
1
  const Boom = require('@hapi/boom');
2
- const { assertCanManage } = require('../../modules/use-cases/manage-access');
3
2
 
4
3
  /**
5
4
  * Re-authorization of a credential the caller owns (Management API v2). It
@@ -15,13 +14,7 @@ async function credentialEntityType(getCredentialForUser, credentialId, user) {
15
14
  { code: 'CREDENTIAL_TYPE_UNKNOWN' }
16
15
  );
17
16
  }
18
- return {
19
- entityType: credential.type,
20
- credentialId: String(credential.id),
21
- // A re-authorization keeps the scope the connection already has.
22
- fixedScope: credential.tenancy?.scope,
23
- access: credential.tenancy?.access,
24
- };
17
+ return { entityType: credential.type, credentialId: String(credential.id) };
25
18
  }
26
19
 
27
20
  /** GET /api/v2/credentials/:credentialId/reauthorize */
@@ -32,12 +25,14 @@ class GetCredentialReauthorization {
32
25
  }
33
26
 
34
27
  async execute(credentialId, user, { step = 1, sessionId, state } = {}) {
35
- const { entityType, credentialId: boundCredentialId } =
36
- await credentialEntityType(this.getCredentialForUser, credentialId, user);
28
+ const binding = await credentialEntityType(
29
+ this.getCredentialForUser,
30
+ credentialId,
31
+ user
32
+ );
37
33
  return this.getAuthorizationStep.execute({
38
34
  user,
39
- entityType,
40
- credentialId: boundCredentialId,
35
+ ...binding,
41
36
  step,
42
37
  sessionId,
43
38
  state,
@@ -53,13 +48,11 @@ class ReauthorizeCredentialForUser {
53
48
  }
54
49
 
55
50
  async execute(credentialId, user, { data, step = 1, sessionId } = {}) {
56
- const { access, ...binding } = await credentialEntityType(
51
+ const binding = await credentialEntityType(
57
52
  this.getCredentialForUser,
58
53
  credentialId,
59
54
  user
60
55
  );
61
- // A credential an organization owns: the organization's admins only.
62
- assertCanManage(access, `credential ${credentialId}`);
63
56
  return this.submitAuthorizationStep.execute({
64
57
  user,
65
58
  ...binding,
@@ -180,13 +180,6 @@ function loadModuleEncryptionSchemas(integrations) {
180
180
  return;
181
181
  }
182
182
 
183
- // NOTE: this path does not exist ('../integrations' resolves to
184
- // database/integrations), so today this function throws MODULE_NOT_FOUND,
185
- // loadCustomEncryptionSchema swallows it, and module credentialFields are
186
- // effectively NOT registered. Fixing it changes which fields are encrypted
187
- // and needs a decrypt that tolerates plaintext, so it ships separately:
188
- // branch claude/internal-mcp-03b-module-credential-encryption. Left as is
189
- // here on purpose (ADR-059 must not change what gets encrypted).
190
183
  const { getModulesDefinitionFromIntegrationClasses } = require('../integrations/utils/map-integration-dto');
191
184
 
192
185
  const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrations);