• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alkem-io / server / #9251

06 Feb 2025 03:39PM UTC coverage: 14.118%. First build
#9251

Pull #4883

travis-ci

Pull Request #4883: NameID usage

84 of 5018 branches covered (1.67%)

Branch coverage included in aggregate %.

4 of 266 new or added lines in 20 files covered. (1.5%)

2301 of 11875 relevant lines covered (19.38%)

7.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.76
/src/core/bootstrap/bootstrap.service.ts
1
import {
1✔
2
  DEFAULT_HOST_ORG_DISPLAY_NAME,
1✔
3
  DEFAULT_HOST_ORG_NAMEID,
1✔
4
  DEFAULT_SPACE_DISPLAYNAME,
1✔
5
  DEFAULT_SPACE_NAMEID,
1✔
6
} from '@common/constants';
1✔
7
import { Profiling } from '@common/decorators';
1✔
8
import { LogContext } from '@common/enums';
1✔
9
import { AiPersonaEngine } from '@common/enums/ai.persona.engine';
1✔
10
import { RoleName } from '@common/enums/role.name';
1✔
11
import { SpaceLevel } from '@common/enums/space.level';
1✔
12
import { TemplateDefaultType } from '@common/enums/template.default.type';
1✔
13
import { TemplateType } from '@common/enums/template.type';
1✔
14
import { VirtualContributorBodyOfKnowledgeType } from '@common/enums/virtual.contributor.body.of.knowledge.type';
1✔
15
import { VirtualContributorDataAccessMode } from '@common/enums/virtual.contributor.data.access.mode';
16
import { VirtualContributorInteractionMode } from '@common/enums/virtual.contributor.interaction.mode';
17
import { VirtualContributorWellKnown } from '@common/enums/virtual.contributor.well.known';
18
import { EntityNotFoundException } from '@common/exceptions';
19
import { BootstrapException } from '@common/exceptions/bootstrap.exception';
20
import { ActorContext } from '@core/actor-context/actor.context';
1✔
21
import { ActorContextService } from '@core/actor-context/actor.context.service';
1✔
22
import { RoleSetService } from '@domain/access/role-set/role.set.service';
1✔
23
import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service';
1✔
24
import { LicenseService } from '@domain/common/license/license.service';
1✔
25
import { MessagingService } from '@domain/communication/messaging/messaging.service';
1✔
26
import { OrganizationService } from '@domain/community/organization/organization.service';
1✔
27
import { OrganizationAuthorizationService } from '@domain/community/organization/organization.service.authorization';
1✔
28
import { OrganizationLookupService } from '@domain/community/organization-lookup/organization.lookup.service';
1✔
29
import { UserService } from '@domain/community/user/user.service';
30
import { UserAuthorizationService } from '@domain/community/user/user.service.authorization';
1✔
31
import { UserLookupService } from '@domain/community/user-lookup/user.lookup.service';
1✔
32
import { IVirtualAssistant } from '@domain/community/virtual-assistant/virtual.assistant.interface';
1✔
33
import { VirtualAssistantService } from '@domain/community/virtual-assistant/virtual.assistant.service';
1✔
34
import { AccountService } from '@domain/space/account/account.service';
1✔
35
import { AccountAuthorizationService } from '@domain/space/account/account.service.authorization';
1✔
36
import { AccountLicenseService } from '@domain/space/account/account.service.license';
37
import { CreateSpaceOnAccountInput } from '@domain/space/account/dto/account.dto.create.space';
38
import { Space } from '@domain/space/space/space.entity';
1✔
39
import { SpaceService } from '@domain/space/space/space.service';
1✔
40
import { SpaceAuthorizationService } from '@domain/space/space/space.service.authorization';
1✔
41
import { CreateTemplateContentSpaceInput } from '@domain/template/template-content-space/dto/template.content.space.dto.create';
1✔
42
import { TemplateDefaultService } from '@domain/template/template-default/template.default.service';
1✔
43
import { TemplatesSetService } from '@domain/template/templates-set/templates.set.service';
1✔
44
import { Inject, Injectable, LoggerService } from '@nestjs/common';
1✔
45
import { ConfigService } from '@nestjs/config';
1✔
46
import { InjectRepository } from '@nestjs/typeorm';
47
import { LicensePlanService } from '@platform/licensing/credential-based/license-plan/license.plan.service';
48
import { LicensingFrameworkService } from '@platform/licensing/credential-based/licensing-framework/licensing.framework.service';
49
import { PlatformService } from '@platform/platform/platform.service';
1✔
50
import { PlatformAuthorizationService } from '@platform/platform/platform.service.authorization';
1✔
51
import { PlatformWellKnownVirtualContributorsService } from '@platform/platform.well.known.virtual.contributors/platform.well.known.virtual.contributors.service';
1✔
52
import { PlatformTemplatesService } from '@platform/platform-templates/platform.templates.service';
1✔
53
import { AiServerService } from '@services/ai-server/ai-server/ai.server.service';
1✔
54
import { AiServerAuthorizationService } from '@services/ai-server/ai-server/ai.server.service.authorization';
1✔
55
import { McpApiKeyService } from '@services/mcp-server/auth/mcp-api-key.service';
1✔
56
import { AdminAuthorizationService } from '@src/platform-admin/domain/authorization/admin.authorization.service';
1✔
57
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
1✔
58
import { Repository } from 'typeorm';
1✔
59
import { bootstrapTemplateSpaceContentCalloutsSpaceL0Tutorials } from './platform-template-definitions/default-templates/bootstrap.template.space.content.callouts.space.l0.tutorials';
1✔
60
import { bootstrapTemplateSpaceContentCalloutsVcKnowledgeBase } from './platform-template-definitions/default-templates/bootstrap.template.space.content.callouts.vc.knowledge.base';
1✔
61
import { bootstrapTemplateSpaceContentSpaceL0 } from './platform-template-definitions/default-templates/bootstrap.template.space.content.space.l0';
1✔
62
import { bootstrapTemplateSpaceContentSubspace } from './platform-template-definitions/default-templates/bootstrap.template.space.content.subspace';
1✔
63
import * as defaultLicensePlan from './platform-template-definitions/license-plan/license-plans.json';
1✔
64
import * as defaultRoles from './platform-template-definitions/user/users.json';
1✔
65

1✔
66
@Injectable()
1✔
67
export class BootstrapService {
1✔
68
  constructor(
69
    private accountService: AccountService,
70
    private accountAuthorizationService: AccountAuthorizationService,
1✔
71
    private actorContextService: ActorContextService,
72
    private spaceService: SpaceService,
1✔
73
    private userService: UserService,
1✔
74
    private userLookupService: UserLookupService,
1✔
75
    private userAuthorizationService: UserAuthorizationService,
1✔
76
    private organizationService: OrganizationService,
1✔
77
    private organizationLookupService: OrganizationLookupService,
1✔
78
    private organizationAuthorizationService: OrganizationAuthorizationService,
1✔
79
    private spaceAuthorizationService: SpaceAuthorizationService,
1✔
80
    private adminAuthorizationService: AdminAuthorizationService,
1✔
81
    private configService: ConfigService,
1✔
82
    private platformService: PlatformService,
1✔
83
    private platformAuthorizationService: PlatformAuthorizationService,
1✔
84
    private authorizationPolicyService: AuthorizationPolicyService,
1✔
85
    @InjectRepository(Space)
1✔
86
    private spaceRepository: Repository<Space>,
1✔
87
    @Inject(WINSTON_MODULE_NEST_PROVIDER)
1✔
88
    private readonly logger: LoggerService,
89
    private aiServer: AiServerService,
1✔
90
    private aiServerAuthorizationService: AiServerAuthorizationService,
91
    private templatesSetService: TemplatesSetService,
1✔
92
    private templateDefaultService: TemplateDefaultService,
1✔
93
    private platformTemplatesService: PlatformTemplatesService,
1✔
94
    private accountLicenseService: AccountLicenseService,
1✔
95
    private licenseService: LicenseService,
1✔
96
    private licensingFrameworkService: LicensingFrameworkService,
1✔
97
    private licensePlanService: LicensePlanService,
1✔
98
    private readonly messagingService: MessagingService,
1✔
99
    private platformWellKnownVirtualContributorsService: PlatformWellKnownVirtualContributorsService,
1✔
100
    private roleSetService: RoleSetService,
1✔
101
    private readonly virtualAssistantService: VirtualAssistantService,
1✔
102
    private readonly mcpApiKeyService: McpApiKeyService
103
  ) {}
104

105
  async bootstrap() {
106
    // this.ingestService.ingest(); // todo remove later
×
107
    try {
×
108
      this.logger.verbose?.('Bootstrapping...', LogContext.BOOTSTRAP);
109

×
110
      Profiling.logger = this.logger;
×
111
      const profilingEnabled = this.configService.get(
112
        'monitoring.logging.profiling_enabled',
113
        { infer: true }
114
      );
×
115
      if (profilingEnabled) {
×
116
        Profiling.profilingEnabled = profilingEnabled;
117
      }
118

×
119
      const anonymousActorContext = this.actorContextService.createAnonymous();
×
120

×
121
      // Order matters:
×
122
      // 1. Infrastructure: Forum, Messaging
×
123
      // 2. Templates (needed for VC creation)
×
124
      // 3. Organization (created without admin first)
×
125
      // 4. Guidance VC (needs organization and templates)
×
126
      // 5. Users (including Admin) - will get guidance conversation created successfully
×
127
      // 6. Link Admin to Organization
128
      // 7. License plans
129
      // 8. Authorization policies
130
      // 9. Space
×
131

132
      await this.platformService.ensureForumCreated();
×
133
      await this.ensureMessagingCreated();
134
      await this.ensurePlatformTemplatesArePresent();
135

×
136
      // Create Org first (without admin if needed)
137
      await this.ensureOrganizationSingleton();
138

139
      // Create VC (needs Org)
140
      await this.ensureGuidanceChat();
141

×
142
      // Create Users (including Admin)
143
      await this.bootstrapUserProfiles();
×
144

145
      // Ensure Admin is linked to Org
146
      await this.ensureAdminUserLinkedToOrganization();
147

×
148
      await this.bootstrapLicensePlans();
149
      await this.ensureAuthorizationsPopulated();
150
      // Register the virtual-assistant MCP trust-anchor key from the shared
×
151
      // ASSISTANT_MCP_API_KEY secret so delegated MCP works on a fresh deploy
152
      // with no manual DB surgery (issue #1937).
153
      await this.ensureAssistantMcpApiKey();
154
      await this.ensureSpaceSingleton(anonymousActorContext);
155
      // reset auth as last in the actions
156
      // await this.ensureSpaceNamesInElastic();
157
    } catch (error: any) {
158
      this.logger.error(
159
        `Unable to complete bootstrap process: ${error}`,
×
160
        error?.stack,
×
161
        LogContext.BOOTSTRAP
162
      );
163
      throw new BootstrapException(error.message, { originalException: error });
164
    }
165
  }
166

167
  /**
168
   * Ensure the `virtual-assistant` actor's MCP API key exists, derived from the
169
   * shared `ASSISTANT_MCP_API_KEY` secret (issue #1937). The assistant-service
×
170
   * sends this same plaintext as its delegation bearer; the server stores only
×
171
   * its SHA-256 hash, bound to the virtual-assistant actor. Idempotent (a no-op
172
   * once the row matches). Skipped — with a warning, never failing bootstrap —
173
   * when the secret is unset, malformed, or the actor is absent.
174
   */
175
  private async ensureAssistantMcpApiKey(): Promise<void> {
176
    const plaintext = process.env.ASSISTANT_MCP_API_KEY?.trim();
177
    if (!plaintext) {
178
      this.logger.warn?.(
179
        'ASSISTANT_MCP_API_KEY is not set — skipping virtual-assistant MCP key bootstrap; delegated MCP (the Web AI Assistant) is unavailable until it is provisioned',
×
180
        LogContext.BOOTSTRAP
×
181
      );
182
      return;
183
    }
184
    // The MCP API-key strategy only engages `Authorization: Bearer mcp_…`
185
    // headers, so a key without the prefix would bootstrap a row that can
186
    // never authenticate: every asvc call 401s while bootstrap logs success.
187
    if (!plaintext.startsWith('mcp_')) {
188
      this.logger.warn?.(
189
        "ASSISTANT_MCP_API_KEY does not start with 'mcp_' — skipping virtual-assistant MCP key bootstrap; the MCP host only accepts 'Bearer mcp_…' keys, so this key could never authenticate. Provision a key in the format mcp_<base64url(32)>",
×
190
        LogContext.BOOTSTRAP
×
191
      );
192
      return;
193
    }
194

195
    let virtualAssistant: IVirtualAssistant;
×
196
    try {
×
197
      virtualAssistant =
198
        await this.virtualAssistantService.getSingletonOrFail();
199
    } catch (error) {
200
      if (error instanceof EntityNotFoundException) {
201
        this.logger.warn?.(
202
          'virtual-assistant actor not found — skipping MCP key bootstrap (the actor is created by migration; ensure migrations have run)',
203
          LogContext.BOOTSTRAP
204
        );
205
        return;
206
      }
207
      // A transient/DB error must NOT masquerade as "actor absent" and silently
208
      // disable delegated MCP — surface it to bootstrap's error handler.
209
      throw error;
×
210
    }
×
211

212
    await this.mcpApiKeyService.ensureActorKeyFromPlaintext(
×
213
      virtualAssistant.id,
×
214
      plaintext,
215
      [{ operations: ['read', 'tools'] }]
216
    );
217
    this.logger.verbose?.(
×
218
      `Ensured virtual-assistant MCP API key from ASSISTANT_MCP_API_KEY (actor ${virtualAssistant.id})`,
×
219
      LogContext.BOOTSTRAP
220
    );
221
  }
222

223
  /**
×
224
   * Ensures the platform Messaging exists.
225
   * Creates it if missing (should happen only on fresh deployments).
226
   */
227
  private async ensureMessagingCreated(): Promise<void> {
228
    const messaging = await this.platformService.ensureMessagingCreated();
229
    this.logger.verbose?.(
230
      `Platform Messaging ensured: ${messaging.id}`,
231
      LogContext.BOOTSTRAP
232
    );
233
  }
234

235
  private async ensurePlatformTemplatesArePresent() {
236
    let authResetNeeded = await this.ensureSpaceTemplateIsPresent(
237
      TemplateDefaultType.PLATFORM_SPACE,
238
      'space',
239
      bootstrapTemplateSpaceContentSpaceL0
240
    );
241
    authResetNeeded =
242
      (await this.ensureSpaceTemplateIsPresent(
243
        TemplateDefaultType.PLATFORM_SUBSPACE,
244
        'subspace',
245
        bootstrapTemplateSpaceContentSubspace
246
      )) || authResetNeeded;
×
247
    authResetNeeded =
×
248
      (await this.ensureSpaceTemplateIsPresent(
×
249
        TemplateDefaultType.PLATFORM_SPACE_TUTORIALS,
250
        'space-tutorials',
×
251
        bootstrapTemplateSpaceContentCalloutsSpaceL0Tutorials
252
      )) || authResetNeeded;
253
    authResetNeeded =
254
      (await this.ensureSpaceTemplateIsPresent(
×
255
        TemplateDefaultType.PLATFORM_SUBSPACE_KNOWLEDGE,
256
        'knowledge',
257
        bootstrapTemplateSpaceContentCalloutsVcKnowledgeBase
258
      )) || authResetNeeded;
×
259
    if (authResetNeeded) {
260
      this.logger.verbose?.(
261
        '=== Identified that template defaults had not been reset; resetting auth now ===',
262
        LogContext.BOOTSTRAP
263
      );
×
264
      const updatedAuthorizations =
×
265
        await this.platformAuthorizationService.applyAuthorizationPolicy();
×
266
      await this.authorizationPolicyService.saveAll(updatedAuthorizations);
267
    }
268
  }
269

270
  private async ensureSpaceTemplateIsPresent(
×
271
    templateDefaultType: TemplateDefaultType,
272
    nameID: string,
273
    spaceContentData: CreateTemplateContentSpaceInput
274
  ): Promise<boolean> {
275
    const templatesSet =
×
276
      await this.platformTemplatesService.getPlatformTemplatesSet();
277
    const templateDefault =
278
      await this.platformTemplatesService.getPlatformTemplateDefault(
279
        templateDefaultType
×
280
      );
×
281

×
282
    if (!templateDefault) {
283
      throw new BootstrapException(
284
        `Unable to load Template Default for ${templateDefaultType}`
285
      );
286
    }
×
287
    if (!templateDefault.template) {
288
      this.logger.verbose?.(
289
        `No template set for ${templateDefaultType}, setting it...`,
290
        LogContext.BOOTSTRAP
291
      );
×
292
      // No template set, so create one and then set it
293
      const template = await this.templatesSetService.createTemplate(
×
294
        templatesSet,
×
295
        {
296
          profileData: {
×
297
            displayName: `${nameID}-Template`,
298
          },
299
          type: TemplateType.SPACE,
×
300
          contentSpaceData: spaceContentData,
×
301
        }
302
      );
303
      // Set the default template
304
      templateDefault.template = template;
305
      await this.templateDefaultService.save(templateDefault);
306
      return true;
307
    }
×
308
    return false;
309
  }
310

311
  async bootstrapUserProfiles() {
312
    const bootstrapAuthorizationRolesJson = {
313
      ...defaultRoles,
314
    };
1✔
315

×
316
    this.logger.verbose?.(
×
317
      'Authorization bootstrap: default configuration being loaded',
×
318
      LogContext.BOOTSTRAP
319
    );
320

×
321
    const users = bootstrapAuthorizationRolesJson.users;
×
322
    if (!users) {
323
      this.logger.verbose?.(
324
        'No users section in the authorization bootstrap file!',
325
        LogContext.BOOTSTRAP
326
      );
327
    } else {
328
      await this.createUserProfiles(users);
329
    }
330
  }
331

332
  async bootstrapLicensePlans() {
333
    const bootstrapLicensePlans = {
×
334
      ...defaultLicensePlan,
335
    };
336

×
337
    const licensePlans = bootstrapLicensePlans.licensePlans;
338
    if (!licensePlans) {
×
339
      this.logger.verbose?.(
340
        'No licensePlans section in the license plans bootstrap file!',
×
341
        LogContext.BOOTSTRAP
342
      );
343
    } else {
×
344
      await this.createLicensePlans(licensePlans);
345
    }
×
346
  }
×
347

×
348
  async createLicensePlans(licensePlansData: any[]) {
349
    try {
350
      const licensing =
351
        await this.licensingFrameworkService.getDefaultLicensingOrFail();
352
      for (const licensePlanData of licensePlansData) {
353
        const planExists =
×
354
          await this.licensePlanService.licensePlanByNameExists(
355
            licensePlanData.name
356
          );
357
        if (!planExists) {
358
          await this.licensingFrameworkService.createLicensePlan({
359
            ...licensePlanData,
×
360
            licensingID: licensing.id,
361
          });
362
        }
363
      }
364
    } catch (error: any) {
365
      throw new BootstrapException(
366
        `Unable to create license plans ${error.message}`
367
      );
368
    }
369
  }
370

371
  async createUserProfiles(usersData: any[]) {
372
    try {
373
      for (const userData of usersData) {
374
        const userExists = await this.userLookupService.isRegisteredUser(
375
          userData.email
376
        );
377
        if (!userExists) {
378
          const user = await this.userService.createUser({
379
            email: userData.email,
380
            firstName: userData.firstName,
381
            lastName: userData.lastName,
382
            profileData: {
383
              displayName: `${userData.firstName} ${userData.lastName}`,
384
            },
×
385
          });
×
386

×
387
          // Once all is done, reset the user authorizations
388
          const userAuthorizations =
389
            await this.userAuthorizationService.applyAuthorizationPolicy(
390
              user.id
391
            );
392
          await this.authorizationPolicyService.saveAll(userAuthorizations);
×
393

394
          const account = await this.userService.getAccount(user);
×
395
          const accountAuthorizations =
396
            await this.accountAuthorizationService.applyAuthorizationPolicy(
397
              account
398
            );
×
399
          await this.authorizationPolicyService.saveAll(accountAuthorizations);
400

×
401
          const credentialsData = userData.credentials;
×
402
          for (const credentialData of credentialsData) {
403
            await this.adminAuthorizationService.grantCredentialToUser({
404
              userID: user.id,
405
              type: credentialData.type,
406
              resourceID: credentialData.resourceID,
×
407
            });
×
408
          }
409
          await this.userAuthorizationService.grantCredentialsAllUsersReceive(
410
            user.id
411
          );
×
412
        }
413
      }
×
414
    } catch (error: any) {
415
      throw new BootstrapException(
416
        `Unable to create profiles ${error.message}`
417
      );
×
418
    }
419
  }
×
420

×
421
  private async ensureAuthorizationsPopulated() {
422
    // For platform
423
    const platform = await this.platformService.getPlatformOrFail();
424
    const platformAuthorization =
425
      this.authorizationPolicyService.validateAuthorization(
×
426
        platform.authorization
×
427
      );
428
    const platformCredentialRules =
429
      this.authorizationPolicyService.getCredentialRules(platformAuthorization);
430
    // Assume that zero rules means that the policy has not been reset
431
    if (platformCredentialRules.length == 0) {
×
432
      this.logger.verbose?.(
433
        '=== Identified that platform authorization had not been reset; resetting now ===',
434
        LogContext.BOOTSTRAP
435
      );
436
      const updatedAuthorizations =
437
        await this.platformAuthorizationService.applyAuthorizationPolicy();
438
      await this.authorizationPolicyService.saveAll(updatedAuthorizations);
×
439
    }
×
440

441
    // Also do same for AI Server until it is moved out of the server
442
    const aiServer = await this.aiServer.getAiServerOrFail();
443
    const aiServerAuthorization =
444
      this.authorizationPolicyService.validateAuthorization(
445
        aiServer.authorization
446
      );
447
    const aiServerCredentialRules =
NEW
448
      this.authorizationPolicyService.getCredentialRules(aiServerAuthorization);
×
449
    // Assume that zero rules means that the policy has not been reset
450
    if (aiServerCredentialRules.length == 0) {
451
      this.logger.verbose?.(
×
452
        '=== Identified that AI Server authorization had not been reset; resetting now ===',
×
453
        LogContext.BOOTSTRAP
×
454
      );
455
      const authorizations =
456
        await this.aiServerAuthorizationService.applyAuthorizationPolicy();
457
      await this.authorizationPolicyService.saveAll(authorizations);
458
    }
459
  }
460

461
  private async ensureOrganizationSingleton(actorContext?: ActorContext) {
462
    // create a default host org
463
    let hostOrganization =
×
464
      await this.organizationLookupService.getOrganizationByNameId(
465
        DEFAULT_HOST_ORG_NAMEID
466
      );
×
467
    if (!hostOrganization) {
468
      // If actorContext is not provided, we create without an admin initially
469
      // The admin will be linked later
×
470
      hostOrganization = await this.organizationService.createOrganization(
471
        {
×
472
          nameID: DEFAULT_HOST_ORG_NAMEID,
473
          profileData: {
474
            displayName: DEFAULT_HOST_ORG_DISPLAY_NAME,
×
475
          },
476
        },
477
        actorContext
×
478
      );
×
479
      const orgAuthorizations =
480
        await this.organizationAuthorizationService.applyAuthorizationPolicy(
481
          hostOrganization
482
        );
483
      await this.authorizationPolicyService.saveAll(orgAuthorizations);
×
484

×
485
      const account =
486
        await this.organizationService.getAccount(hostOrganization);
487
      const accountAuthorizations =
488
        await this.accountAuthorizationService.applyAuthorizationPolicy(
489
          account
×
490
        );
×
491
      await this.authorizationPolicyService.saveAll(accountAuthorizations);
492

493
      const accountEntitlements =
494
        await this.accountLicenseService.applyLicensePolicy(account.id);
×
495
      await this.licenseService.saveAll(accountEntitlements);
496
    }
497
  }
498

×
499
  private async ensureAdminUserLinkedToOrganization() {
500
    const adminActorContext = await this.getAdminActorContext();
501
    const hostOrganization =
502
      await this.organizationLookupService.getOrganizationByNameIdOrFail(
×
503
        DEFAULT_HOST_ORG_NAMEID
×
504
      );
×
505

×
506
    const roleSet = await this.organizationService.getRoleSet(hostOrganization);
507

508
    // Assign Admin as Associate and Admin
509
    await this.roleSetService.assignActorToRole(
NEW
510
      roleSet,
×
511
      RoleName.ASSOCIATE,
512
      adminActorContext.actorID,
513
      adminActorContext,
514
      false
515
    );
×
516

×
517
    await this.roleSetService.assignActorToRole(
518
      roleSet,
519
      RoleName.ADMIN,
520
      adminActorContext.actorID,
521
      adminActorContext,
522
      false
523
    );
524

525
    this.logger.verbose?.(
526
      `Ensured Admin user linked to Organization: ${hostOrganization.id}`,
527
      LogContext.BOOTSTRAP
528
    );
529
  }
530

×
531
  private async getAdminActorContext(): Promise<ActorContext> {
532
    const adminUserEmail = 'admin@alkem.io';
×
533
    const adminUser = await this.userService.getUserByEmail(adminUserEmail, {
×
534
      relations: {
535
        credentials: true,
536
      },
×
537
    });
×
538
    if (!adminUser) {
539
      throw new BootstrapException(
×
540
        `Unable to load fixed admin user for creating organization: ${adminUserEmail}`
541
      );
542
    }
543
    const ctx = new ActorContext();
544
    ctx.actorID = adminUser.id;
×
545
    ctx.isAnonymous = false;
546
    ctx.credentials = (adminUser.credentials || []).map(c => ({
547
      type: c.type,
×
548
      resourceID: c.resourceID,
549
    }));
×
550
    ctx.authenticationID = adminUser.authenticationID ?? undefined;
551
    return ctx;
552
  }
553

554
  private async ensureSpaceSingleton(actorContext: ActorContext) {
555
    this.logger.verbose?.(
556
      '=== Ensuring at least one Account with a space is present ===',
557
      LogContext.BOOTSTRAP
558
    );
559
    const spaceCount = await this.spaceRepository.count();
NEW
560
    if (spaceCount == 0) {
×
561
      this.logger.verbose?.('...No space present...', LogContext.BOOTSTRAP);
562
      this.logger.verbose?.(
563
        '........creating on default organization',
564
        LogContext.BOOTSTRAP
×
565
      );
566
      const hostOrganization =
567
        await this.organizationLookupService.getOrganizationByNameIdOrFail(
×
568
          DEFAULT_HOST_ORG_NAMEID
569
        );
570

571
      const account =
572
        await this.organizationService.getAccount(hostOrganization);
573
      const spaceInput: CreateSpaceOnAccountInput = {
574
        accountID: account.id,
575
        nameID: DEFAULT_SPACE_NAMEID,
576
        about: {
577
          profileData: {
578
            displayName: DEFAULT_SPACE_DISPLAYNAME,
579
            tagline: 'An empty space to be populated',
580
          },
581
        },
582
        level: SpaceLevel.L0,
583
        levelZeroSpaceID: '',
584
        collaborationData: {
×
585
          calloutsSetData: {},
×
586
        },
587
      };
588

589
      const space = await this.accountService.createSpaceOnAccount(
590
        spaceInput,
591
        actorContext
592
      );
593
      const spaceAuthorizations =
594
        await this.spaceAuthorizationService.applyAuthorizationPolicy(space.id);
595
      await this.authorizationPolicyService.saveAll(spaceAuthorizations);
596

597
      const accountEntitlements =
598
        await this.accountLicenseService.applyLicensePolicy(account.id);
599
      await this.licenseService.saveAll(accountEntitlements);
600

601
      return this.spaceService.getSpaceOrFail(space.id);
602
    }
603
  }
604

605
  private async ensureGuidanceChat() {
606
    // Check if the CHAT_GUIDANCE well-known VC is configured
607
    const wellKnownVCId =
608
      await this.platformWellKnownVirtualContributorsService.getVirtualContributorID(
609
        VirtualContributorWellKnown.CHAT_GUIDANCE
610
      );
611

612
    if (!wellKnownVCId) {
613
      // Get admin account:
614
      const hostOrganization =
615
        await this.organizationLookupService.getOrganizationByNameIdOrFail(
616
          DEFAULT_HOST_ORG_NAMEID
617
        );
618
      const account =
619
        await this.organizationService.getAccount(hostOrganization);
620

621
      // Create the VC
622
      const vc = await this.accountService.createVirtualContributorOnAccount({
623
        accountID: account.id,
624
        aiPersona: {
625
          engine: AiPersonaEngine.GUIDANCE,
626
          prompt: [],
627
          externalConfig: undefined,
628
        },
629
        profileData: {
630
          displayName: 'Guidance',
631
          description: 'Guidance Virtual Contributor',
632
        },
633
        dataAccessMode: VirtualContributorDataAccessMode.NONE,
634
        bodyOfKnowledgeType: VirtualContributorBodyOfKnowledgeType.WEBSITE,
635
        interactionModes: [
636
          VirtualContributorInteractionMode.DISCUSSION_TAGGING,
637
        ],
638
        knowledgeBaseData: {
639
          profile: {
640
            displayName: 'Knowledge Base for Virtual Contributor',
641
          },
642
          calloutsSetData: {},
643
        },
644
      });
645

646
      // Apply authorization for the newly created VC via account auth reset
647
      // (the earlier account auth reset in ensureOrganizationSingleton ran before
648
      // this VC existed, so it was skipped)
649
      const accountAuthorizations =
650
        await this.accountAuthorizationService.applyAuthorizationPolicy(
651
          account
652
        );
653
      await this.authorizationPolicyService.saveAll(accountAuthorizations);
654

655
      // Register the VC as the CHAT_GUIDANCE well-known VC
656
      await this.platformWellKnownVirtualContributorsService.setMapping(
657
        VirtualContributorWellKnown.CHAT_GUIDANCE,
658
        vc.id
659
      );
660
    }
661
  }
662
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc