@cat-factory/orchestration 0.124.0 → 0.124.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/container.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createAppCaches } from '@cat-factory/caching';
2
- import { getFragment } from '@cat-factory/prompt-fragments';
2
+ import { ModuleRegistry } from './container/module-registry.js';
3
+ import { createServicesModule, createGitHubModule, createDocumentsModule, createTasksModule, createEnvironmentsModule, createRunnersModule, createBootstrapModule, createTesterQualityReviewer, createDocInterviewService, createForkChatService, resolveBlockRunContext, createRequirementsModule, createBrainstormModule, createKaizenModule, createClarityModule, createNotificationsModule, createSlackModule, createRiskPoliciesModule, createSharedStacksModule, createPreflightModule, createSandboxModule, createWorkspaceSettingsModule, createReleaseHealthModule, createPackageRegistriesModule, createPreviewModule, createIncidentEnrichmentModule, createModelPresetsModule, createServiceFragmentDefaultsModule, createTrackerModule, createRecurringModule, } from './container/modules.js';
3
4
  import { ServiceMountService } from './modules/services/ServiceMountService.js';
4
5
  import { NoopWorkRunner } from '@cat-factory/kernel';
5
6
  import { NoopEventPublisher } from '@cat-factory/kernel';
@@ -18,15 +19,13 @@ import { LlmObservabilityService } from './modules/observability/LlmObservabilit
18
19
  import { AgentContextObservabilityService } from './modules/observability/AgentContextObservabilityService.js';
19
20
  import { SearchQueryObservabilityService } from './modules/observability/SearchQueryObservabilityService.js';
20
21
  import { PlatformObservabilityService } from './modules/observability/PlatformObservabilityService.js';
21
- import { GitHubInstallationService, RepoProvisioningService, GitHubService, GitHubSyncService, WebhookService, DocumentConnectionService, DocumentContentResolverService, DocumentImportService, DocumentPlannerService, DocumentLinkService, MapDocumentSourceRegistry, TaskConnectionService, TaskImportService, TaskLinkService, BugIntakeService, MapTaskSourceRegistry, EnvironmentConnectionService, EnvironmentProvisioningService, EnvironmentTeardownService, EnvironmentUserHandlerService, RunnerPoolConnectionService, PreflightService, ProvisioningLogRecorder, ProvisioningLogService, SharedStackService, SlackConnectionService, SlackSettingsService, SlackMemberMappingService, defaultEnvironmentBackendRegistry, defaultRunnerBackendRegistry, } from '@cat-factory/integrations';
22
+ import { GitHubInstallationService, RepoProvisioningService, GitHubService, GitHubSyncService, WebhookService, DocumentConnectionService, DocumentImportService, DocumentPlannerService, DocumentLinkService, TaskConnectionService, TaskImportService, TaskLinkService, BugIntakeService, EnvironmentConnectionService, EnvironmentProvisioningService, EnvironmentTeardownService, EnvironmentUserHandlerService, RunnerPoolConnectionService, PreflightService, ProvisioningLogRecorder, ProvisioningLogService, SharedStackService, SlackConnectionService, SlackSettingsService, SlackMemberMappingService, } from '@cat-factory/integrations';
22
23
  import { BootstrapService } from './modules/bootstrap/BootstrapService.js';
23
24
  import { EnvConfigRepairService } from './modules/envConfigRepair/EnvConfigRepairService.js';
24
25
  import { EnvironmentTestService } from './modules/environments/EnvironmentTestService.js';
25
26
  import { BoardScanService } from './modules/boardScan/BoardScanService.js';
26
27
  import { RequirementReviewService } from './modules/requirements/RequirementReviewService.js';
27
- import { DocInterviewService } from './modules/docInterview/DocInterviewService.js';
28
- import { ForkChatService } from './modules/execution/ForkChatService.js';
29
- import { TesterQualityReviewService, } from './modules/execution/TesterQualityReviewService.js';
28
+ import {} from './modules/execution/TesterQualityReviewService.js';
30
29
  import { KaizenService } from './modules/kaizen/KaizenService.js';
31
30
  import { ClarityReviewService } from './modules/clarity/ClarityReviewService.js';
32
31
  import { BrainstormService } from './modules/brainstorm/BrainstormService.js';
@@ -52,1023 +51,6 @@ import { defaultAgentKindRegistry, defaultInitiativePresetRegistry, FragmentLibr
52
51
  import { createFragmentLibraryModule, createSkillLibraryModule, } from './container-content-libraries.js';
53
52
  import { defaultGateRegistry, defaultStepResolverRegistry } from '@cat-factory/kernel';
54
53
  /** Assemble the in-org service-sharing module when its repositories are wired. */
55
- function createServicesModule(deps) {
56
- const { serviceRepository, workspaceMountRepository } = deps;
57
- if (!serviceRepository || !workspaceMountRepository)
58
- return undefined;
59
- const service = new ServiceMountService({
60
- serviceRepository,
61
- workspaceMountRepository,
62
- workspaceRepository: deps.workspaceRepository,
63
- idGenerator: deps.idGenerator,
64
- clock: deps.clock,
65
- });
66
- return { service };
67
- }
68
- /**
69
- * Assemble the GitHub module when every dependency it needs is present;
70
- * otherwise return undefined so the feature stays cleanly opt-in.
71
- */
72
- function createGitHubModule(deps, caches) {
73
- const { githubClient, githubInstallationRepository, repoProjectionRepository, branchProjectionRepository, pullRequestProjectionRepository, issueProjectionRepository, commitProjectionRepository, checkRunProjectionRepository, webhookVerifier, } = deps;
74
- if (!githubClient ||
75
- !githubInstallationRepository ||
76
- !repoProjectionRepository ||
77
- !branchProjectionRepository ||
78
- !pullRequestProjectionRepository ||
79
- !issueProjectionRepository ||
80
- !commitProjectionRepository ||
81
- !checkRunProjectionRepository ||
82
- !webhookVerifier) {
83
- return undefined;
84
- }
85
- const installationService = new GitHubInstallationService({
86
- githubClient,
87
- githubInstallationRepository,
88
- workspaceRepository: deps.workspaceRepository,
89
- clock: deps.clock,
90
- canCreateRepos: deps.canCreateRepos,
91
- workflowsGranted: deps.workflowsGranted,
92
- });
93
- const syncService = new GitHubSyncService({
94
- githubClient,
95
- githubInstallationRepository,
96
- repoProjectionRepository,
97
- branchProjectionRepository,
98
- pullRequestProjectionRepository,
99
- issueProjectionRepository,
100
- commitProjectionRepository,
101
- checkRunProjectionRepository,
102
- userRepoAccessRepository: deps.userRepoAccessRepository,
103
- clock: deps.clock,
104
- commitBackfillHorizonMs: deps.commitBackfillHorizonMs,
105
- // Drop a workspace's cached repo projection (slice 3) after any link/sync write.
106
- repoProjectionCache: caches.repoProjection,
107
- // Serve the add-service picker's PAT typeahead from a per-user cache (filter in memory)
108
- // instead of re-walking `/user/repos` on every keystroke.
109
- viewerReposCache: caches.viewerRepos,
110
- });
111
- const webhookService = new WebhookService({
112
- githubInstallationRepository,
113
- repoProjectionRepository,
114
- branchProjectionRepository,
115
- pullRequestProjectionRepository,
116
- issueProjectionRepository,
117
- commitProjectionRepository,
118
- checkRunProjectionRepository,
119
- clock: deps.clock,
120
- repoProjectionCache: caches.repoProjection,
121
- // Drop a pushed branch's cached RepoFiles reads (slice 4) when a branch moves out-of-band.
122
- repoFilesCache: caches.repoFiles,
123
- // Repo-sourced skill freshness fan-out (slice 4): on a push, resync every skill source
124
- // linked to the repo. Both are facade-provided (the queue-backed enqueue only exists where
125
- // a runtime has a sync queue); unwired ⇒ the dispatch-time probe is the freshness backstop.
126
- skillSourceRepository: deps.skillSourceRepository,
127
- enqueueSkillResync: deps.enqueueSkillResync,
128
- });
129
- const service = new GitHubService({
130
- githubClient,
131
- repoProjectionRepository,
132
- branchProjectionRepository,
133
- pullRequestProjectionRepository,
134
- issueProjectionRepository,
135
- clock: deps.clock,
136
- });
137
- const provisioningService = deps.repoProvisioningClient
138
- ? new RepoProvisioningService({ client: deps.repoProvisioningClient })
139
- : undefined;
140
- return {
141
- installationService,
142
- syncService,
143
- webhookService,
144
- service,
145
- webhookVerifier,
146
- provisioningService,
147
- };
148
- }
149
- /**
150
- * Assemble the document-source module when at least one provider + both
151
- * repositories are present. The model provider is optional: with it the planner
152
- * uses an LLM, and without it the deterministic heading parser — so the module
153
- * stays usable for import/link/spawn even when no LLM is configured.
154
- */
155
- function createDocumentsModule(deps, boardService) {
156
- const { documentSourceProviders, documentConnectionRepository, documentRepository } = deps;
157
- if (!documentSourceProviders ||
158
- documentSourceProviders.length === 0 ||
159
- !documentConnectionRepository ||
160
- !documentRepository) {
161
- return undefined;
162
- }
163
- const registry = new MapDocumentSourceRegistry(documentSourceProviders);
164
- const connectionService = new DocumentConnectionService({
165
- documentConnectionRepository,
166
- registry,
167
- workspaceRepository: deps.workspaceRepository,
168
- clock: deps.clock,
169
- });
170
- const importService = new DocumentImportService({
171
- registry,
172
- documentRepository,
173
- connectionService,
174
- workspaceRepository: deps.workspaceRepository,
175
- clock: deps.clock,
176
- });
177
- const plannerService = new DocumentPlannerService({
178
- modelProviderResolver: deps.modelProviderResolver,
179
- modelProvider: deps.modelProvider,
180
- modelRef: deps.documentPlannerModel,
181
- });
182
- const linkService = new DocumentLinkService({
183
- boardService,
184
- blockRepository: deps.blockRepository,
185
- documentRepository,
186
- });
187
- const contentResolver = new DocumentContentResolverService({ registry, connectionService });
188
- return { connectionService, importService, plannerService, linkService, contentResolver };
189
- }
190
- /**
191
- * Assemble the task-source module when at least one provider + both repositories
192
- * are present; otherwise return undefined so the feature stays cleanly opt-in.
193
- * Unlike the documents module there is no planner — issues are linked for
194
- * context, not expanded into board structure.
195
- */
196
- function createTasksModule(deps, boardService) {
197
- const { taskSourceProviders, taskConnectionRepository, taskSourceSettingsRepository, taskRepository, } = deps;
198
- if (!taskSourceProviders ||
199
- taskSourceProviders.length === 0 ||
200
- !taskConnectionRepository ||
201
- !taskSourceSettingsRepository ||
202
- !taskRepository) {
203
- return undefined;
204
- }
205
- const registry = new MapTaskSourceRegistry(taskSourceProviders);
206
- const connectionService = new TaskConnectionService({
207
- taskConnectionRepository,
208
- taskSourceSettingsRepository,
209
- registry,
210
- workspaceRepository: deps.workspaceRepository,
211
- clock: deps.clock,
212
- // GitHub Issues' availability is the installed GitHub App's presence; absent when
213
- // the GitHub integration isn't wired (the provider then isn't registered anyway).
214
- ...(deps.githubInstallationRepository
215
- ? { installations: deps.githubInstallationRepository }
216
- : {}),
217
- // Linear OAuth app credentials live in per-account deployment settings (sealed),
218
- // resolved dynamically — mirroring the Slack OAuth model. Absent ⇒ the "Connect with
219
- // Linear" flow isn't offered (manual API-key paste still works).
220
- ...(deps.accountSettings
221
- ? {
222
- resolveLinearOAuth: (accountKey) => deps.accountSettings.resolve(accountKey).then((s) => s.linearOAuth),
223
- }
224
- : {}),
225
- });
226
- const importService = new TaskImportService({
227
- registry,
228
- taskRepository,
229
- connectionService,
230
- workspaceRepository: deps.workspaceRepository,
231
- clock: deps.clock,
232
- });
233
- const linkService = new TaskLinkService({
234
- boardService,
235
- blockRepository: deps.blockRepository,
236
- taskRepository,
237
- importService,
238
- });
239
- // The recurring bug-intake step's read-and-claim helper — wired only when a schedule
240
- // repository is present (an intake fire resolves the schedule's `issueIntake` config by
241
- // block). Composes the just-built import/link services + the source registry, so it stays
242
- // provider-neutral and runtime-symmetric.
243
- const bugIntakeService = deps.pipelineScheduleRepository
244
- ? new BugIntakeService({
245
- pipelineScheduleRepository: deps.pipelineScheduleRepository,
246
- taskSourceRegistry: registry,
247
- taskConnectionRepository,
248
- importService,
249
- linkService,
250
- taskRepository,
251
- })
252
- : undefined;
253
- return {
254
- connectionService,
255
- importService,
256
- linkService,
257
- ...(bugIntakeService ? { bugIntakeService } : {}),
258
- };
259
- }
260
- /**
261
- * Assemble the environment integration when its provider, both repositories and
262
- * the secret cipher are present; otherwise return undefined so the feature stays
263
- * cleanly opt-in (the deterministic deployer and env discovery in the engine are
264
- * gated on the provisioning service being wired).
265
- */
266
- function createEnvironmentsModule(deps, provisioningLog, eventPublisher, sharedStackService, preflightService) {
267
- const { environmentConnectionRepository, environmentRegistryRepository, secretCipher } = deps;
268
- if (!environmentConnectionRepository || !environmentRegistryRepository || !secretCipher) {
269
- return undefined;
270
- }
271
- // Durable async config repair is wired when both the dispatcher (the side-effecting
272
- // container plumbing) and the kind-scoped job repository are present. The repair service
273
- // and the connection service are mutually dependent: the connection service's
274
- // `dispatchConfigRepair` seam STARTS a repair run (→ repairService), and the repair run's
275
- // success path RE-VALIDATES via the connection service. We break the cycle by capturing
276
- // `repairService` in a closure that is only invoked at request time (after assignment).
277
- const canRepair = !!(deps.envConfigRepairer && deps.envConfigRepairJobRepository);
278
- let repairService;
279
- const connectionService = new EnvironmentConnectionService({
280
- environmentConnectionRepository,
281
- workspaceRepository: deps.workspaceRepository,
282
- secretCipher,
283
- clock: deps.clock,
284
- environmentBackendRegistry: deps.environmentBackendRegistry ?? defaultEnvironmentBackendRegistry(),
285
- ...(deps.customManifestTypeRepository
286
- ? { customManifestTypeRepository: deps.customManifestTypeRepository }
287
- : {}),
288
- ...(deps.customManifestTypeRegistry
289
- ? { customManifestTypeRegistry: deps.customManifestTypeRegistry }
290
- : {}),
291
- ...(deps.environmentCustomTlsSupported !== undefined
292
- ? { customTlsSupported: deps.environmentCustomTlsSupported }
293
- : {}),
294
- ...(deps.environmentProvider ? { environmentProvider: deps.environmentProvider } : {}),
295
- ...(deps.environmentUrlSafetyPolicy ? { urlPolicy: deps.environmentUrlSafetyPolicy } : {}),
296
- ...(deps.resolveRepoFilesForCoords
297
- ? { resolveRepoFilesForWorkspace: deps.resolveRepoFilesForCoords }
298
- : {}),
299
- ...(deps.detectionConventions ? { detectionConventions: deps.detectionConventions } : {}),
300
- ...(canRepair
301
- ? {
302
- dispatchConfigRepair: (input) => repairService
303
- .start(input.workspaceId, {
304
- owner: input.owner,
305
- repo: input.repo,
306
- gitRef: input.gitRef,
307
- issues: input.issues,
308
- ...(input.inputs ? { inputs: input.inputs } : {}),
309
- ...(input.promptOverride ? { promptOverride: input.promptOverride } : {}),
310
- ...(input.manifestPath ? { manifestPath: input.manifestPath } : {}),
311
- })
312
- .then((job) => ({ jobId: job.id })),
313
- }
314
- : {}),
315
- ...(provisioningLog ? { provisioningLog } : {}),
316
- });
317
- if (canRepair) {
318
- repairService = new EnvConfigRepairService({
319
- envConfigRepairJobRepository: deps.envConfigRepairJobRepository,
320
- workspaceRepository: deps.workspaceRepository,
321
- idGenerator: deps.idGenerator,
322
- clock: deps.clock,
323
- repairer: deps.envConfigRepairer,
324
- ...(deps.envConfigRepairRunner ? { runner: deps.envConfigRepairRunner } : {}),
325
- ...(eventPublisher ? { eventPublisher } : {}),
326
- revalidate: (input) => connectionService.revalidate(input),
327
- });
328
- }
329
- // The per-USER override store is wired ONLY when its repository is present — which, by
330
- // design, ONLY the local facade does (so per-user overrides + the per-user controller are
331
- // local-mode-only, with no runtime branch in shared code). Its `resolveOverrides` is the
332
- // `resolveUserHandlerOverrides` seam the provisioning service layers over the workspace
333
- // handlers for the run initiator.
334
- const userHandlerService = deps.environmentUserHandlerRepository
335
- ? new EnvironmentUserHandlerService({
336
- userHandlerRepository: deps.environmentUserHandlerRepository,
337
- environmentBackendRegistry: deps.environmentBackendRegistry ?? defaultEnvironmentBackendRegistry(),
338
- secretCipher,
339
- clock: deps.clock,
340
- ...(deps.environmentCustomTlsSupported !== undefined
341
- ? { customTlsSupported: deps.environmentCustomTlsSupported }
342
- : {}),
343
- ...(deps.environmentUrlSafetyPolicy ? { urlPolicy: deps.environmentUrlSafetyPolicy } : {}),
344
- ...(deps.logger ? { logger: deps.logger } : {}),
345
- })
346
- : undefined;
347
- // Built BEFORE the provisioning service so it can be injected as `environmentTeardown` there:
348
- // a deployer re-run that supersedes a prior env with a DIFFERENT provider identity tears the old
349
- // infra down through this service (best-effort; the TTL reaper is the backstop).
350
- const teardownService = new EnvironmentTeardownService({
351
- connectionService,
352
- environmentRegistryRepository,
353
- secretCipher,
354
- clock: deps.clock,
355
- ...(provisioningLog ? { provisioningLog } : {}),
356
- });
357
- const provisioningService = new EnvironmentProvisioningService({
358
- connectionService,
359
- environmentRegistryRepository,
360
- secretCipher,
361
- idGenerator: deps.idGenerator,
362
- clock: deps.clock,
363
- environmentTeardown: teardownService,
364
- ...(deps.environmentUrlSafetyPolicy ? { urlPolicy: deps.environmentUrlSafetyPolicy } : {}),
365
- ...(deps.resolveRunRepoContext ? { resolveRunRepoContext: deps.resolveRunRepoContext } : {}),
366
- ...(deps.resolveRepoFilesForCoords
367
- ? { resolveRepoFilesForWorkspace: deps.resolveRepoFilesForCoords }
368
- : {}),
369
- ...(userHandlerService
370
- ? {
371
- resolveUserHandlerOverrides: (userId, ws) => userHandlerService.resolveOverrides(userId, ws),
372
- }
373
- : {}),
374
- // The async, container-backed deploy lifecycle (kustomize/helm) is wired when the facade
375
- // supplies the runner transport + the clone-target resolver; absent ⇒ only the synchronous
376
- // raw-manifest REST path runs (a render-needing config fails loudly).
377
- ...(deps.deployJobClient ? { deployJobClient: deps.deployJobClient } : {}),
378
- ...(deps.resolveDeployCloneTarget
379
- ? { resolveDeployCloneTarget: deps.resolveDeployCloneTarget }
380
- : {}),
381
- // A compose stack recipe's `sharedStackRefs` are brought up (provider-before-consumer) through
382
- // the shared-stack service, whose managed networks the compose provider attaches the per-PR
383
- // project to. Wired only when the shared-stacks module exists (its repository is present on
384
- // every facade); the lifecycle itself refuses without a host daemon.
385
- ...(sharedStackService
386
- ? { ensureSharedStacks: (ws, refs) => sharedStackService.ensureRefsUp(ws, refs) }
387
- : {}),
388
- // A compose stack recipe's `prerequisites` are re-run at provision start through the preflight
389
- // service, whose host probes exist only on the local facade; absent ⇒ a recipe that declares
390
- // them fails loudly instead of silently skipping a machine-prerequisite gate.
391
- ...(preflightService ? { runPreflights: (_ws, refs) => preflightService.run(refs) } : {}),
392
- ...(provisioningLog ? { provisioningLog } : {}),
393
- });
394
- // The ephemeral-environment self-test: needs its own run store + a git provider (to
395
- // create/delete the throwaway branch). Absent either ⇒ no self-test (the controller 503s).
396
- const environmentTest = deps.environmentTestRunRepository && deps.resolveRunRepoContext
397
- ? new EnvironmentTestService({
398
- environmentTestRunRepository: deps.environmentTestRunRepository,
399
- workspaceRepository: deps.workspaceRepository,
400
- blockRepository: deps.blockRepository,
401
- provisioning: provisioningService,
402
- teardown: teardownService,
403
- environmentRegistry: environmentRegistryRepository,
404
- resolveRunRepoContext: deps.resolveRunRepoContext,
405
- idGenerator: deps.idGenerator,
406
- clock: deps.clock,
407
- ...(deps.environmentTestRunner ? { runner: deps.environmentTestRunner } : {}),
408
- ...(eventPublisher ? { eventPublisher } : {}),
409
- })
410
- : undefined;
411
- return {
412
- connectionService,
413
- provisioningService,
414
- teardownService,
415
- ...(userHandlerService ? { userHandlerService } : {}),
416
- ...(repairService ? { envConfigRepair: { service: repairService } } : {}),
417
- ...(environmentTest ? { environmentTest } : {}),
418
- };
419
- }
420
- /**
421
- * Assemble the self-hosted runner-pool module when its connection repository and
422
- * the secret cipher are present; otherwise return undefined so the feature stays
423
- * cleanly opt-in. Per-tenant scheduler-API secrets are encrypted via the cipher.
424
- */
425
- function createRunnersModule(deps) {
426
- const { runnerPoolConnectionRepository, runnerSecretCipher } = deps;
427
- if (!runnerPoolConnectionRepository || !runnerSecretCipher)
428
- return undefined;
429
- const connectionService = new RunnerPoolConnectionService({
430
- runnerPoolConnectionRepository,
431
- workspaceRepository: deps.workspaceRepository,
432
- secretCipher: runnerSecretCipher,
433
- clock: deps.clock,
434
- runnerBackendRegistry: deps.runnerBackendRegistry ?? defaultRunnerBackendRegistry(),
435
- ...(deps.runnerPoolProvider ? { runnerPoolProvider: deps.runnerPoolProvider } : {}),
436
- ...(deps.runnerUrlSafetyPolicy ? { urlPolicy: deps.runnerUrlSafetyPolicy } : {}),
437
- ...(deps.runnerCustomTlsSupported !== undefined
438
- ? { customTlsSupported: deps.runnerCustomTlsSupported }
439
- : {}),
440
- });
441
- return { connectionService };
442
- }
443
- /**
444
- * Assemble the repo-bootstrap module when both its repositories are present (the
445
- * worker wires them unconditionally). The `repoBootstrapper` is passed through
446
- * but optional: the service exposes CRUD regardless and only gates the run path
447
- * on its presence.
448
- */
449
- function createBootstrapModule(deps, eventPublisher, onBootstrapSucceeded) {
450
- const { referenceArchitectureRepository, bootstrapJobRepository } = deps;
451
- if (!referenceArchitectureRepository || !bootstrapJobRepository)
452
- return undefined;
453
- const service = new BootstrapService({
454
- referenceArchitectureRepository,
455
- bootstrapJobRepository,
456
- workspaceRepository: deps.workspaceRepository,
457
- blockRepository: deps.blockRepository,
458
- serviceRepository: deps.serviceRepository,
459
- workspaceMountRepository: deps.workspaceMountRepository,
460
- serviceFragmentDefaultsRepository: deps.serviceFragmentDefaultsRepository,
461
- idGenerator: deps.idGenerator,
462
- clock: deps.clock,
463
- repoBootstrapper: deps.repoBootstrapper,
464
- bootstrapRunner: deps.bootstrapRunner,
465
- eventPublisher,
466
- ...(onBootstrapSucceeded ? { onBootstrapSucceeded } : {}),
467
- });
468
- return { service };
469
- }
470
- /**
471
- * Assemble the requirements-review module when its repository is present (the
472
- * worker wires it unconditionally). The model provider/ref are optional within
473
- * the module — reads work without them and the run paths surface a clear error —
474
- * and the document/task repositories are reused, when wired, to fold linked PRDs
475
- * and tracker issues into the reviewed requirements.
476
- */
477
- /**
478
- * Build the inline reviewer for the test quality-control companion. It resolves its model
479
- * exactly like the requirements reviewer (block pin → workspace per-kind default → routing
480
- * default). Returns `undefined` when no model provider is configured, so the Tester gate's QC
481
- * step is a pass-through in unconfigured facades / tests.
482
- */
483
- function createTesterQualityReviewer(deps) {
484
- if (!deps.modelProviderResolver && !deps.modelProvider)
485
- return undefined;
486
- return new TesterQualityReviewService({
487
- modelProviderResolver: deps.modelProviderResolver,
488
- modelProvider: deps.modelProvider,
489
- modelRef: deps.requirementReviewModel ?? deps.documentPlannerModel,
490
- resolveBlockModel: deps.requirementReviewResolveModel,
491
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
492
- resolveWorkspaceModelDefault: deps.modelPresetRepository
493
- ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(deps.modelPresetRepository, workspaceId, agentKind, modelPresetId)
494
- : undefined,
495
- resolveRunContext: resolveBlockRunContext(deps),
496
- });
497
- }
498
- /**
499
- * Build the interactive document-interview service (WS5). Self-contained (owns its session
500
- * store + the inline LLM); resolves its model exactly like the requirements reviewer (block
501
- * pin → workspace per-kind default → routing default). Returns `undefined` when no session
502
- * store is wired, so the `doc-interviewer` step passes through in unconfigured facades / tests.
503
- * The LLM is optional within the service (the `enabled` getter is false without a model), so a
504
- * store-but-no-model deployment still short-circuits the interviewer.
505
- */
506
- function createDocInterviewService(deps) {
507
- const { docInterviewRepository } = deps;
508
- if (!docInterviewRepository)
509
- return undefined;
510
- return new DocInterviewService({
511
- docInterviewRepository,
512
- idGenerator: deps.idGenerator,
513
- clock: deps.clock,
514
- modelProviderResolver: deps.modelProviderResolver,
515
- modelProvider: deps.modelProvider,
516
- modelRef: deps.requirementReviewModel ?? deps.documentPlannerModel,
517
- resolveBlockModel: deps.requirementReviewResolveModel,
518
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
519
- resolveWorkspaceModelDefault: deps.modelPresetRepository
520
- ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(deps.modelPresetRepository, workspaceId, agentKind, modelPresetId)
521
- : undefined,
522
- resolveRunContext: resolveBlockRunContext(deps),
523
- });
524
- }
525
- /**
526
- * Build the inline grounded-chat responder for the implementation-fork decision phase. Resolves
527
- * its model exactly like the requirements reviewer / doc interviewer (block pin → workspace
528
- * per-kind default → routing default). Returns `undefined` when no model provider is configured,
529
- * so the fork chat degrades to a canned "chat unavailable" reply in unconfigured facades / tests
530
- * while pick / custom keep working. Stateless — the chat rides the coder step, no session store.
531
- */
532
- function createForkChatService(deps) {
533
- if (!deps.modelProviderResolver && !deps.modelProvider)
534
- return undefined;
535
- return new ForkChatService({
536
- modelProviderResolver: deps.modelProviderResolver,
537
- modelProvider: deps.modelProvider,
538
- modelRef: deps.requirementReviewModel ?? deps.documentPlannerModel,
539
- resolveBlockModel: deps.requirementReviewResolveModel,
540
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
541
- resolveWorkspaceModelDefault: deps.modelPresetRepository
542
- ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(deps.modelPresetRepository, workspaceId, agentKind, modelPresetId)
543
- : undefined,
544
- resolveRunContext: resolveBlockRunContext(deps),
545
- });
546
- }
547
- /**
548
- * Resolve a block's active run (execution id + initiator) for the iterative reviewers, so an
549
- * inline subscription reviewer served through a leased per-run activation can lease it. Reads
550
- * the block's `executionId` and the run's `initiatedBy`; `{}` when the block has no active run
551
- * (an off-path inspector review with no pipeline) — the reviewer then resolves on a
552
- * workspace-only scope (pooled lease), unchanged.
553
- */
554
- function resolveBlockRunContext(deps) {
555
- return async (workspaceId, block) => {
556
- if (!block.executionId)
557
- return {};
558
- const instance = await deps.executionRepository.get(workspaceId, block.executionId);
559
- return {
560
- executionId: block.executionId,
561
- ...(instance?.initiatedBy ? { userId: instance.initiatedBy } : {}),
562
- };
563
- };
564
- }
565
- function createRequirementsModule(deps, notificationService, fragmentLibrary) {
566
- const { requirementReviewRepository } = deps;
567
- if (!requirementReviewRepository)
568
- return undefined;
569
- const service = new RequirementReviewService({
570
- requirementReviewRepository,
571
- blockRepository: deps.blockRepository,
572
- idGenerator: deps.idGenerator,
573
- clock: deps.clock,
574
- // Tell product people + the task creator to react to a review's findings (when
575
- // the notifications subsystem is wired). Best-effort; absent → no notification.
576
- notificationService,
577
- modelProviderResolver: deps.modelProviderResolver,
578
- modelProvider: deps.modelProvider,
579
- // The dedicated reviewer ref, else the document planner's (both the agents' default).
580
- modelRef: deps.requirementReviewModel ?? deps.documentPlannerModel,
581
- // Honour a block's pinned model with the direct/Cloudflare fallback, like the executor.
582
- resolveBlockModel: deps.requirementReviewResolveModel,
583
- // In local mode, run the reviewer inline through the ambient Claude Code / Codex CLI on a
584
- // subscription model instead of degrading to the routing default.
585
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
586
- // Honour the workspace's model presets for the `requirements` kind too, so the
587
- // reviewer resolves its model exactly like a pipeline step. Reuses the already
588
- // wired model-preset repository (the workspace default preset); absent → only
589
- // block-pin + routing default.
590
- resolveWorkspaceModelDefault: deps.modelPresetRepository
591
- ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(deps.modelPresetRepository, workspaceId, agentKind, modelPresetId)
592
- : undefined,
593
- // The reviewer runs during a parked run, so its execution + initiator come from the
594
- // block's active run — threaded into the model scope so an inline subscription ref served
595
- // through a leased per-run activation (local container inline backend) can lease it.
596
- resolveRunContext: resolveBlockRunContext(deps),
597
- documentRepository: deps.documentRepository,
598
- taskRepository: deps.taskRepository,
599
- // The Requirement Writer (second companion) grounds recommendations on the run's repo
600
- // (`spec/` + `tech-spec/` via the checkout-free RepoFiles) — wired in all three facades.
601
- resolveRunRepoContext: deps.resolveRunRepoContext,
602
- // …and on the block's best-practice fragments (team/org standards), checked FIRST. Walk
603
- // the owning frame's service standards then union the block's own pins (same precedence
604
- // as the agent context builder), resolved against the merged tenant catalog when the
605
- // fragment library is wired (so managed + document-backed fragments ground the review
606
- // exactly like they reach a code-aware run), else the static universal pool.
607
- resolveBlockFragments: async (workspaceId, blockId) => {
608
- const block = await deps.blockRepository.get(workspaceId, blockId);
609
- if (!block)
610
- return [];
611
- const ids = [];
612
- const seen = new Set();
613
- const add = (id) => {
614
- if (!seen.has(id)) {
615
- seen.add(id);
616
- ids.push(id);
617
- }
618
- };
619
- let current = block;
620
- for (let i = 0; current && i < 8; i++) {
621
- if (current.level === 'frame' || !current.parentId) {
622
- for (const id of current.serviceFragmentIds ?? [])
623
- add(id);
624
- break;
625
- }
626
- current = await deps.blockRepository.get(workspaceId, current.parentId);
627
- }
628
- for (const id of block.fragmentIds ?? [])
629
- add(id);
630
- if (fragmentLibrary) {
631
- // Resolve the merged tenant catalog ONCE and reuse it for both the titles map and
632
- // the body resolution (which would otherwise re-resolve the same catalog).
633
- const catalog = await fragmentLibrary.libraryService.resolveCatalog(workspaceId);
634
- const titles = new Map(catalog.map((e) => [e.id, e.title]));
635
- const bodies = await fragmentLibrary.libraryService.resolveBodiesForRun(workspaceId, ids, catalog);
636
- return bodies.map(({ id, body }) => ({ id, title: titles.get(id) ?? id, body }));
637
- }
638
- const out = [];
639
- for (const id of ids) {
640
- const fragment = getFragment(id);
641
- if (fragment)
642
- out.push({ id, title: fragment.title, body: fragment.body });
643
- }
644
- return out;
645
- },
646
- // `webSearch` (gateway-RAG) is wired by the web-search-connection workstream; until then
647
- // the Writer still gets provider-hosted web search on Anthropic/OpenAI models.
648
- // When an upstream `requirements-brainstorm` dialogue settled a converged direction, the
649
- // reviewer critiques THAT (the refined requirements) instead of the raw description.
650
- resolveBrainstormDirection: deps.brainstormSessionRepository
651
- ? async (workspaceId, blockId) => {
652
- const session = await deps.brainstormSessionRepository.getByBlockStage(workspaceId, blockId, 'requirements');
653
- return session?.status === 'incorporated' && session.convergedDirection
654
- ? session.convergedDirection
655
- : undefined;
656
- }
657
- : undefined,
658
- });
659
- return { service };
660
- }
661
- /**
662
- * Assemble the brainstorm (structured-dialogue) module when its repository is present (both
663
- * runtime facades wire it unconditionally). Mirrors {@link createClarityModule}: it builds ONE
664
- * {@link BrainstormService} per stage (sharing the repository) and reuses the requirements
665
- * reviewer's model config since all the inline reviewers resolve their model identically. The
666
- * architecture stage seeds from the refined requirements (a requirements review's incorporated
667
- * doc, else the requirements-brainstorm's converged direction).
668
- */
669
- function createBrainstormModule(deps, notificationService) {
670
- const { brainstormSessionRepository } = deps;
671
- if (!brainstormSessionRepository)
672
- return undefined;
673
- const resolveWorkspaceModelDefault = deps.modelPresetRepository
674
- ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(deps.modelPresetRepository, workspaceId, agentKind, modelPresetId)
675
- : undefined;
676
- // The architecture stage's seed: the most refined requirements available — a settled
677
- // requirements review's incorporated doc, else the requirements-brainstorm's direction.
678
- const resolveRefinedRequirements = async (workspaceId, blockId) => {
679
- const review = await deps.requirementReviewRepository?.getByBlock(workspaceId, blockId);
680
- if (review?.status === 'incorporated' && review.incorporatedRequirements) {
681
- return review.incorporatedRequirements;
682
- }
683
- const session = await brainstormSessionRepository.getByBlockStage(workspaceId, blockId, 'requirements');
684
- return session?.status === 'incorporated' && session.convergedDirection
685
- ? session.convergedDirection
686
- : undefined;
687
- };
688
- const common = {
689
- brainstormSessionRepository,
690
- blockRepository: deps.blockRepository,
691
- idGenerator: deps.idGenerator,
692
- clock: deps.clock,
693
- notificationService,
694
- modelProviderResolver: deps.modelProviderResolver,
695
- modelProvider: deps.modelProvider,
696
- modelRef: deps.requirementReviewModel ?? deps.documentPlannerModel,
697
- resolveBlockModel: deps.requirementReviewResolveModel,
698
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
699
- // Brainstorm stages are pipeline gate steps that run during a parked run, so their
700
- // execution + initiator come from the block's active run — threaded into the model scope
701
- // so an inline subscription ref served through a leased per-run activation (local container
702
- // inline backend) can lease it, exactly like the requirements/clarity reviewers.
703
- resolveRunContext: resolveBlockRunContext(deps),
704
- resolveWorkspaceModelDefault,
705
- };
706
- return {
707
- services: {
708
- requirements: new BrainstormService({ ...common, stage: 'requirements' }),
709
- architecture: new BrainstormService({
710
- ...common,
711
- stage: 'architecture',
712
- resolveRefinedRequirements,
713
- }),
714
- },
715
- };
716
- }
717
- /**
718
- * Assemble the Kaizen module when its repositories are wired (both runtime facades wire them
719
- * unconditionally). The grader resolves its model for the `kaizen` kind the same way the
720
- * requirements reviewer does — block pin > workspace per-kind default > routing default —
721
- * so operators configure it in Model Configuration alongside every other agent. Needs the
722
- * telemetry repos (LLM-call metrics + agent-context snapshots) to read what each step was
723
- * given; absent → the module isn't built and no grading is scheduled.
724
- */
725
- function createKaizenModule(deps) {
726
- const { kaizenGradingRepository, kaizenVerifiedComboRepository } = deps;
727
- if (!kaizenGradingRepository || !kaizenVerifiedComboRepository)
728
- return undefined;
729
- if (!deps.llmCallMetricRepository || !deps.agentContextObservability)
730
- return undefined;
731
- const service = new KaizenService({
732
- kaizenGradingRepository,
733
- kaizenVerifiedComboRepository,
734
- blockRepository: deps.blockRepository,
735
- llmCallMetricRepository: deps.llmCallMetricRepository,
736
- agentContextObservability: deps.agentContextObservability,
737
- workspaceSettingsRepository: deps.workspaceSettingsRepository,
738
- idGenerator: deps.idGenerator,
739
- clock: deps.clock,
740
- events: deps.executionEventPublisher,
741
- modelProviderResolver: deps.modelProviderResolver,
742
- modelProvider: deps.modelProvider,
743
- // Reuse the reviewer's routing default ref + block-model resolver (the agents' default).
744
- modelRef: deps.requirementReviewModel ?? deps.documentPlannerModel,
745
- resolveBlockModel: deps.requirementReviewResolveModel,
746
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
747
- // Resolve the workspace's per-kind default for `kaizen`, like a pipeline step.
748
- resolveWorkspaceModelDefault: deps.modelPresetRepository
749
- ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(deps.modelPresetRepository, workspaceId, agentKind, modelPresetId)
750
- : undefined,
751
- });
752
- return { service };
753
- }
754
- /**
755
- * Assemble the clarity-review module when its repository is present (both runtime facades
756
- * wire it unconditionally). Mirrors {@link createRequirementsModule}: it reuses the
757
- * requirements reviewer's model config (the same routing default) since both reviewers
758
- * resolve their model identically.
759
- */
760
- function createClarityModule(deps, notificationService) {
761
- const { clarityReviewRepository } = deps;
762
- if (!clarityReviewRepository)
763
- return undefined;
764
- const service = new ClarityReviewService({
765
- clarityReviewRepository,
766
- blockRepository: deps.blockRepository,
767
- idGenerator: deps.idGenerator,
768
- clock: deps.clock,
769
- notificationService,
770
- modelProviderResolver: deps.modelProviderResolver,
771
- modelProvider: deps.modelProvider,
772
- modelRef: deps.requirementReviewModel ?? deps.documentPlannerModel,
773
- resolveBlockModel: deps.requirementReviewResolveModel,
774
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
775
- resolveWorkspaceModelDefault: deps.modelPresetRepository
776
- ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(deps.modelPresetRepository, workspaceId, agentKind, modelPresetId)
777
- : undefined,
778
- resolveRunContext: resolveBlockRunContext(deps),
779
- });
780
- return { service };
781
- }
782
- /**
783
- * Assemble the notifications module when its repository is present (the worker
784
- * wires it unconditionally). The delivery channel is optional within the module —
785
- * without it the rows still persist (the inbox + snapshot work) but nothing is
786
- * pushed; the worker wires the in-app channel, and email/Slack compose in later.
787
- */
788
- function createNotificationsModule(deps) {
789
- const { notificationRepository } = deps;
790
- if (!notificationRepository)
791
- return undefined;
792
- const service = new NotificationService({
793
- notificationRepository,
794
- workspaceRepository: deps.workspaceRepository,
795
- idGenerator: deps.idGenerator,
796
- clock: deps.clock,
797
- channel: deps.notificationChannel,
798
- });
799
- return { service };
800
- }
801
- /**
802
- * Assemble the Slack integration module when its three repositories and the
803
- * secret cipher are present. Powers the management API (connect/settings/member
804
- * map); the actual Slack delivery is a `notificationChannel` composed in by the
805
- * facade. OAuth is optional — manual-token onboarding works without it.
806
- */
807
- function createSlackModule(deps) {
808
- const { slackConnectionRepository, slackSettingsRepository, slackMemberMappingRepository, slackSecretCipher, } = deps;
809
- if (!slackConnectionRepository ||
810
- !slackSettingsRepository ||
811
- !slackMemberMappingRepository ||
812
- !slackSecretCipher) {
813
- return undefined;
814
- }
815
- return {
816
- connectionService: new SlackConnectionService({
817
- slackConnectionRepository,
818
- workspaceRepository: deps.workspaceRepository,
819
- secretCipher: slackSecretCipher,
820
- clock: deps.clock,
821
- resolveOAuth: deps.accountSettings
822
- ? (accountKey) => deps.accountSettings.resolve(accountKey).then((s) => s.slackOAuth)
823
- : undefined,
824
- }),
825
- settingsService: new SlackSettingsService({
826
- slackSettingsRepository,
827
- workspaceRepository: deps.workspaceRepository,
828
- clock: deps.clock,
829
- }),
830
- memberMappingService: new SlackMemberMappingService({
831
- slackMemberMappingRepository,
832
- workspaceRepository: deps.workspaceRepository,
833
- clock: deps.clock,
834
- }),
835
- };
836
- }
837
- /** Assemble the merge-preset module when its repository is present. */
838
- function createRiskPoliciesModule(deps, caches) {
839
- const { riskPolicyRepository } = deps;
840
- if (!riskPolicyRepository)
841
- return undefined;
842
- const service = new RiskPolicyService({
843
- riskPolicyRepository,
844
- workspaceRepository: deps.workspaceRepository,
845
- idGenerator: deps.idGenerator,
846
- clock: deps.clock,
847
- // Invalidate the read-through slice the engine's `resolveRiskPolicy` uses on every write.
848
- riskPolicyCache: caches.riskPolicy,
849
- });
850
- return { service };
851
- }
852
- /**
853
- * Assemble the shared-stacks module when its repository is present. The `composeRuntime` is
854
- * optional — wired only on the local facade, so CRUD works everywhere but the lifecycle
855
- * (ensureUp/teardown) refuses without a host daemon (the documented compose runtime-binding
856
- * exception). Persistence is fully runtime-symmetric.
857
- */
858
- function createSharedStacksModule(deps, preflightService) {
859
- const { sharedStackRepository } = deps;
860
- if (!sharedStackRepository)
861
- return undefined;
862
- const service = new SharedStackService({
863
- sharedStackRepository,
864
- workspaceRepository: deps.workspaceRepository,
865
- idGenerator: deps.idGenerator,
866
- clock: deps.clock,
867
- ...(deps.composeRuntime ? { composeRuntime: deps.composeRuntime } : {}),
868
- ...(deps.sharedStackCloneToken ? { cloneToken: deps.sharedStackCloneToken } : {}),
869
- // Enables the checkout-free repo autodetection (`detect`); wired from the same coords-bound
870
- // RepoFiles resolver the environment detector uses, so both facades get it for free.
871
- ...(deps.resolveRepoFilesForCoords
872
- ? { resolveRepoFilesForWorkspace: deps.resolveRepoFilesForCoords }
873
- : {}),
874
- // Same deployment-level detection-convention extensions the environment detector honours, so
875
- // shared-stack `detect` recognises the org's house compose layout too.
876
- ...(deps.detectionConventions ? { detectionConventions: deps.detectionConventions } : {}),
877
- // Re-run a stack's declared machine-prerequisite checks at bring-up start. Present only where
878
- // the host-probe seam is wired (the local facade — same runtime binding as `composeRuntime`).
879
- ...(preflightService ? { runPreflights: (refs) => preflightService.run(refs) } : {}),
880
- });
881
- return { service };
882
- }
883
- /**
884
- * Assemble the preflight module when the host-probe seam is present — wired ONLY on the local
885
- * facade (a host Docker daemon), the documented compose runtime-binding exception. Absent elsewhere
886
- * ⇒ the preflight API 503s and a stack recipe that declares `prerequisites` fails loudly at
887
- * provision (rather than silently skipping a declared machine-prerequisite gate).
888
- */
889
- function createPreflightModule(deps) {
890
- if (!deps.preflightHostProbes)
891
- return undefined;
892
- return { service: new PreflightService({ hostProbes: deps.preflightHostProbes }) };
893
- }
894
- /**
895
- * Assemble the Sandbox module when its five repositories are present (both runtime
896
- * facades wire them together). Reuses the requirements reviewer's inline model config —
897
- * the per-scope provider resolver, the routing default ref, and the block-model resolver
898
- * — so a Sandbox cell (and the judge) resolves its catalog id exactly like a pipeline step.
899
- */
900
- function createSandboxModule(deps, agentKindRegistry) {
901
- const { sandboxPromptVersionRepository, sandboxFixtureRepository, sandboxExperimentRepository, sandboxRunRepository, sandboxGradeRepository, } = deps;
902
- if (!sandboxPromptVersionRepository ||
903
- !sandboxFixtureRepository ||
904
- !sandboxExperimentRepository ||
905
- !sandboxRunRepository ||
906
- !sandboxGradeRepository) {
907
- return undefined;
908
- }
909
- const repositories = {
910
- sandboxPromptVersionRepository,
911
- sandboxFixtureRepository,
912
- sandboxExperimentRepository,
913
- sandboxRunRepository,
914
- sandboxGradeRepository,
915
- workspaceRepository: deps.workspaceRepository,
916
- idGenerator: deps.idGenerator,
917
- clock: deps.clock,
918
- agentKindRegistry,
919
- };
920
- const defaultModelRef = deps.requirementReviewModel ?? deps.documentPlannerModel;
921
- const service = new SandboxService({ ...repositories, defaultModelRef });
922
- const runService = new SandboxRunService({
923
- ...repositories,
924
- modelProviderResolver: deps.modelProviderResolver,
925
- modelProvider: deps.modelProvider,
926
- resolveModelId: deps.requirementReviewResolveModel,
927
- defaultModelRef,
928
- ...(deps.inlineHarnessRef ? { runsInline: deps.inlineHarnessRef } : {}),
929
- });
930
- return { service, runService };
931
- }
932
- /** Assemble the workspace-settings module when its repository is present. */
933
- function createWorkspaceSettingsModule(deps, workspaceSettingsCache) {
934
- const { workspaceSettingsRepository } = deps;
935
- if (!workspaceSettingsRepository)
936
- return undefined;
937
- const service = new WorkspaceSettingsService({
938
- workspaceSettingsRepository,
939
- workspaceRepository: deps.workspaceRepository,
940
- workspaceSettingsCache,
941
- });
942
- return { service };
943
- }
944
- /** Assemble the release-health (observability) module when its repos + cipher are present. */
945
- function createReleaseHealthModule(deps) {
946
- const { observabilityConnectionRepository, releaseHealthConfigRepository, observabilitySecretCipher, } = deps;
947
- if (!observabilityConnectionRepository ||
948
- !releaseHealthConfigRepository ||
949
- !observabilitySecretCipher) {
950
- return undefined;
951
- }
952
- const service = new ReleaseHealthService({
953
- observabilityConnectionRepository,
954
- releaseHealthConfigRepository,
955
- observabilitySecretCipher,
956
- workspaceRepository: deps.workspaceRepository,
957
- blockRepository: deps.blockRepository,
958
- clock: deps.clock,
959
- });
960
- return { service };
961
- }
962
- /** Assemble the package-registries module when its repo + cipher are present. */
963
- function createPackageRegistriesModule(deps) {
964
- const { packageRegistryConnectionRepository, packageRegistrySecretCipher } = deps;
965
- if (!packageRegistryConnectionRepository || !packageRegistrySecretCipher) {
966
- return undefined;
967
- }
968
- const service = new PackageRegistryService({
969
- packageRegistryConnectionRepository,
970
- packageRegistrySecretCipher,
971
- workspaceRepository: deps.workspaceRepository,
972
- clock: deps.clock,
973
- idGenerator: deps.idGenerator,
974
- });
975
- return { service };
976
- }
977
- /**
978
- * Assemble the browsable-frontend-preview module when its per-runtime transport + the facade's
979
- * job builder + the env registry are all wired (local/node with a host-port-publish runtime).
980
- * Absent on the Worker (no preview transport) ⇒ the controller 503s there.
981
- */
982
- function createPreviewModule(deps) {
983
- const { previewTransport, buildPreviewJob, environmentRegistryRepository } = deps;
984
- if (!previewTransport || !buildPreviewJob || !environmentRegistryRepository)
985
- return undefined;
986
- const service = new PreviewService({
987
- previewTransport,
988
- buildPreviewJob,
989
- environmentRegistryRepository,
990
- idGenerator: deps.idGenerator,
991
- clock: deps.clock,
992
- });
993
- return { service };
994
- }
995
- /** Assemble the incident-enrichment settings module when its repo + cipher are present. */
996
- function createIncidentEnrichmentModule(deps) {
997
- const { incidentEnrichmentConnectionRepository, incidentEnrichmentSecretCipher } = deps;
998
- if (!incidentEnrichmentConnectionRepository || !incidentEnrichmentSecretCipher)
999
- return undefined;
1000
- const service = new IncidentEnrichmentService({
1001
- incidentEnrichmentConnectionRepository,
1002
- incidentEnrichmentSecretCipher,
1003
- workspaceRepository: deps.workspaceRepository,
1004
- clock: deps.clock,
1005
- });
1006
- return { service };
1007
- }
1008
- /** Assemble the model-presets module when its repository is present. */
1009
- function createModelPresetsModule(deps) {
1010
- const { modelPresetRepository } = deps;
1011
- if (!modelPresetRepository)
1012
- return undefined;
1013
- const service = new ModelPresetService({
1014
- modelPresetRepository,
1015
- workspaceRepository: deps.workspaceRepository,
1016
- idGenerator: deps.idGenerator,
1017
- clock: deps.clock,
1018
- ...(deps.defaultModelPresetId ? { defaultPresetId: deps.defaultModelPresetId } : {}),
1019
- });
1020
- return { service };
1021
- }
1022
- /** Assemble the service-fragment-defaults module when its repository is present. */
1023
- function createServiceFragmentDefaultsModule(deps) {
1024
- const { serviceFragmentDefaultsRepository } = deps;
1025
- if (!serviceFragmentDefaultsRepository)
1026
- return undefined;
1027
- const service = new ServiceFragmentDefaultsService({
1028
- serviceFragmentDefaultsRepository,
1029
- workspaceRepository: deps.workspaceRepository,
1030
- });
1031
- return { service };
1032
- }
1033
- /** Assemble the tracker-settings module when its repository is present. */
1034
- function createTrackerModule(deps) {
1035
- const { trackerSettingsRepository } = deps;
1036
- if (!trackerSettingsRepository)
1037
- return undefined;
1038
- const service = new TrackerSettingsService({
1039
- trackerSettingsRepository,
1040
- workspaceRepository: deps.workspaceRepository,
1041
- clock: deps.clock,
1042
- });
1043
- return { service };
1044
- }
1045
- /**
1046
- * Assemble the recurring-pipeline module when its repository is present. Built
1047
- * after the execution engine since each fire starts a pipeline through it.
1048
- */
1049
- function createRecurringModule(deps, executionService, executionEventPublisher, taskConnectionService) {
1050
- const { pipelineScheduleRepository } = deps;
1051
- if (!pipelineScheduleRepository)
1052
- return undefined;
1053
- const service = new RecurringPipelineService({
1054
- pipelineScheduleRepository,
1055
- workspaceRepository: deps.workspaceRepository,
1056
- pipelineRepository: deps.pipelineRepository,
1057
- blockRepository: deps.blockRepository,
1058
- executionRepository: deps.executionRepository,
1059
- executionService,
1060
- idGenerator: deps.idGenerator,
1061
- clock: deps.clock,
1062
- serviceRepository: deps.serviceRepository,
1063
- workspaceMountRepository: deps.workspaceMountRepository,
1064
- // Validates a `bug-intake` pipeline's schedule carries an `issueIntake` config whose source
1065
- // is a connected task source. Absent (no task sources wired) → the presence check still runs.
1066
- taskConnectionService,
1067
- // Pushes a `block-added` board event when the reused block is created, so it appears live.
1068
- executionEventPublisher,
1069
- });
1070
- return { service };
1071
- }
1072
54
  export function createCore(dependencies) {
1073
55
  // Resolve the app-owned agent-kind registry ONCE: the facade's injected instance (so a
1074
56
  // deployment's custom kinds are visible) else a fresh built-ins-only registry. The SAME
@@ -1090,6 +72,15 @@ export function createCore(dependencies) {
1090
72
  // write-site invalidation — is exercised everywhere. Built here (before the services that
1091
73
  // invalidate through it) so it can be threaded into all of them.
1092
74
  const caches = dependencies.caches ?? createAppCaches();
75
+ // The optional-module registry: every feature that is wired only when its prerequisites are
76
+ // configured is `build`-declared through this, instead of a scattered `const x = createX(...)`
77
+ // + a matching `...(x ? { x } : {})` return spread. Registration order below IS dependency
78
+ // order: `build` returns the value, so a module consumed downstream is kept in a local and
79
+ // threaded into the later factories that need it (`modules.get(...)` is there for a reader that
80
+ // holds no local). The whole set is emitted in one place via `...modules.assemble()` at the
81
+ // return. The core spine (below) stays explicit —
82
+ // it carries the genuine circular late-bindings (account ⇄ spend, engine ⇄ initiative loop).
83
+ const modules = new ModuleRegistry();
1093
84
  // Pass the resolved publisher so board mutations push a coarse `boardChanged` to every
1094
85
  // user on the workspace (and every board mounting a shared service) — both facades route
1095
86
  // here, so the wiring is symmetric by construction. The repo-projection cache lets
@@ -1107,7 +98,7 @@ export function createCore(dependencies) {
1107
98
  // Workspace-RBAC roster + access-mode management (workspace-rbac, slice 5). Present only when
1108
99
  // the member repository is wired (both facades wire it; tests/no-roster leave it absent, so the
1109
100
  // members controller 503s). Every roster/access-mode write drops the board's access cache group.
1110
- const workspaceMemberService = dependencies.workspaceMemberRepository
101
+ modules.build('workspaceMemberService', () => dependencies.workspaceMemberRepository
1111
102
  ? new WorkspaceMemberService({
1112
103
  workspaceMemberRepository: dependencies.workspaceMemberRepository,
1113
104
  workspaceRepository: dependencies.workspaceRepository,
@@ -1116,7 +107,7 @@ export function createCore(dependencies) {
1116
107
  clock: dependencies.clock,
1117
108
  workspaceAccessCache: caches.workspaceAccess,
1118
109
  })
1119
- : undefined;
110
+ : undefined);
1120
111
  // Late-bound so the account service can invalidate the spend service's cached
1121
112
  // account-budget limit on an account-budget edit (spendService is built below).
1122
113
  let spendServiceRef;
@@ -1140,14 +131,14 @@ export function createCore(dependencies) {
1140
131
  idGenerator: dependencies.idGenerator,
1141
132
  clock: dependencies.clock,
1142
133
  });
1143
- const email = dependencies.emailConnectionRepository && dependencies.emailSecretCipher
134
+ const email = modules.build('email', () => dependencies.emailConnectionRepository && dependencies.emailSecretCipher
1144
135
  ? new EmailConnectionService({
1145
136
  emailConnectionRepository: dependencies.emailConnectionRepository,
1146
137
  secretCipher: dependencies.emailSecretCipher,
1147
138
  clock: dependencies.clock,
1148
139
  })
1149
- : undefined;
1150
- const invitations = dependencies.invitationRepository
140
+ : undefined);
141
+ modules.build('invitations', () => dependencies.invitationRepository
1151
142
  ? new InvitationService({
1152
143
  invitationRepository: dependencies.invitationRepository,
1153
144
  accountRepository: dependencies.accountRepository,
@@ -1160,8 +151,8 @@ export function createCore(dependencies) {
1160
151
  // Accepting an invitation grants membership ⇒ drop the workspace-access cache (workspace-rbac).
1161
152
  onAccountMembershipChanged: () => caches.workspaceAccess.invalidateAll(),
1162
153
  })
1163
- : undefined;
1164
- const passwordReset = dependencies.passwordResetTokenRepository
154
+ : undefined);
155
+ modules.build('passwordReset', () => dependencies.passwordResetTokenRepository
1165
156
  ? new PasswordResetService({
1166
157
  passwordResetTokenRepository: dependencies.passwordResetTokenRepository,
1167
158
  userRepository: dependencies.userRepository,
@@ -1172,7 +163,7 @@ export function createCore(dependencies) {
1172
163
  appBaseUrl: dependencies.appBaseUrl,
1173
164
  logger: dependencies.logger,
1174
165
  })
1175
- : undefined;
166
+ : undefined);
1176
167
  const pipelineService = new PipelineService(dependencies);
1177
168
  const spendService = new SpendService({
1178
169
  tokenUsageRepository: dependencies.tokenUsageRepository,
@@ -1191,7 +182,7 @@ export function createCore(dependencies) {
1191
182
  userBudgetLimitCache: caches.userBudgetLimit,
1192
183
  });
1193
184
  spendServiceRef = spendService;
1194
- const userSettings = dependencies.userSettingsRepository
185
+ modules.build('userSettings', () => dependencies.userSettingsRepository
1195
186
  ? {
1196
187
  service: new UserSettingsService({
1197
188
  userSettingsRepository: dependencies.userSettingsRepository,
@@ -1200,8 +191,8 @@ export function createCore(dependencies) {
1200
191
  resolveUserBudgetCap: () => spendService.budgetCaps().userMonthlyLimitMax,
1201
192
  }),
1202
193
  }
1203
- : undefined;
1204
- const llmObservability = dependencies.llmCallMetricRepository
194
+ : undefined);
195
+ const llmObservability = modules.build('llmObservability', () => dependencies.llmCallMetricRepository
1205
196
  ? new LlmObservabilityService({
1206
197
  llmCallMetricRepository: dependencies.llmCallMetricRepository,
1207
198
  idGenerator: dependencies.idGenerator,
@@ -1211,13 +202,13 @@ export function createCore(dependencies) {
1211
202
  workspaceSettingsRepository: dependencies.workspaceSettingsRepository,
1212
203
  workspaceSettingsCache: caches.workspaceSettings,
1213
204
  })
1214
- : undefined;
1215
- const platformObservability = dependencies.platformMetricsRepository
205
+ : undefined);
206
+ modules.build('platformObservability', () => dependencies.platformMetricsRepository
1216
207
  ? new PlatformObservabilityService({
1217
208
  platformMetricsRepository: dependencies.platformMetricsRepository,
1218
209
  clock: dependencies.clock,
1219
210
  })
1220
- : undefined;
211
+ : undefined);
1221
212
  // The provisioning event log lives in a separate high-churn store. When its
1222
213
  // repository is wired, build a best-effort recorder (threaded into the env
1223
214
  // services) + the read service (exposed for the logs controller). The container
@@ -1229,28 +220,30 @@ export function createCore(dependencies) {
1229
220
  clock: dependencies.clock,
1230
221
  })
1231
222
  : undefined;
1232
- const provisioningLogs = dependencies.provisioningLogRepository
223
+ modules.build('provisioningLogs', () => dependencies.provisioningLogRepository
1233
224
  ? {
1234
- service: new ProvisioningLogService({ repository: dependencies.provisioningLogRepository }),
225
+ service: new ProvisioningLogService({
226
+ repository: dependencies.provisioningLogRepository,
227
+ }),
1235
228
  }
1236
- : undefined;
229
+ : undefined);
1237
230
  // Built before the shared-stacks + environments modules so a compose stack recipe's
1238
231
  // `prerequisites` (and a shared stack's own prerequisites) are re-run at provision / bring-up
1239
232
  // start through this service. The host probes exist only on the local facade; absent ⇒ a recipe /
1240
233
  // stack that declares prerequisites fails loudly (the preflight API 503s too).
1241
- const preflight = createPreflightModule(dependencies);
234
+ const preflight = modules.build('preflight', () => createPreflightModule(dependencies));
1242
235
  // Built before the environments module so a compose stack recipe's `sharedStackRefs` can be
1243
236
  // brought up (provider-before-consumer) through this service during provisioning. Persistence is
1244
237
  // runtime-symmetric (present on every facade); the lifecycle only runs where a host daemon is
1245
238
  // wired (`composeRuntime` — the local facade), else `ensureRefsUp` returns a clean error. It gets
1246
239
  // the preflight service so a shared stack re-checks its own machine prerequisites at bring-up.
1247
- const sharedStacks = createSharedStacksModule(dependencies, preflight?.service);
1248
- const environments = createEnvironmentsModule(dependencies, provisioningLogRecorder, executionEventPublisher, sharedStacks?.service, preflight?.service);
240
+ const sharedStacks = modules.build('sharedStacks', () => createSharedStacksModule(dependencies, preflight?.service));
241
+ const environments = modules.build('environments', () => createEnvironmentsModule(dependencies, provisioningLogRecorder, executionEventPublisher, sharedStacks?.service, preflight?.service));
1249
242
  // Built before the fragment library so a document-backed fragment can re-resolve
1250
243
  // its linked Confluence/Notion/GitHub page through the document module's reader.
1251
- const documents = createDocumentsModule(dependencies, boardService);
1252
- const fragmentLibrary = createFragmentLibraryModule(dependencies, documents?.contentResolver, caches);
1253
- const skillLibrary = createSkillLibraryModule(dependencies, caches);
244
+ const documents = modules.build('documents', () => createDocumentsModule(dependencies, boardService));
245
+ const fragmentLibrary = modules.build('fragmentLibrary', () => createFragmentLibraryModule(dependencies, documents?.contentResolver, caches));
246
+ const skillLibrary = modules.build('skillLibrary', () => createSkillLibraryModule(dependencies, caches));
1254
247
  // Reconciles a `blueprints` step's decomposition onto the board. Needs only the
1255
248
  // board service + block repository (both always present), so it is wired
1256
249
  // unconditionally — there is no standalone scan command or persisted blueprint store.
@@ -1260,19 +253,19 @@ export function createCore(dependencies) {
1260
253
  });
1261
254
  // Built before the execution engine so it can raise merge-review / CI-failed /
1262
255
  // pipeline-complete notifications during a run (when the module is configured).
1263
- const notifications = createNotificationsModule(dependencies);
1264
- const slack = createSlackModule(dependencies);
1265
- const riskPolicies = createRiskPoliciesModule(dependencies, caches);
1266
- const sandbox = createSandboxModule(dependencies, agentKindRegistry);
256
+ const notifications = modules.build('notifications', () => createNotificationsModule(dependencies));
257
+ modules.build('slack', () => createSlackModule(dependencies));
258
+ modules.build('riskPolicies', () => createRiskPoliciesModule(dependencies, caches));
259
+ modules.build('sandbox', () => createSandboxModule(dependencies, agentKindRegistry));
1267
260
  // Built before the execution engine so the per-service running-task limit can be
1268
261
  // enforced at start() (and the escalation sweep can read the waiting threshold).
1269
- const settings = createWorkspaceSettingsModule(dependencies, caches.workspaceSettings);
1270
- const releaseHealth = createReleaseHealthModule(dependencies);
1271
- const packageRegistries = createPackageRegistriesModule(dependencies);
1272
- const preview = createPreviewModule(dependencies);
1273
- const incidentEnrichmentSettings = createIncidentEnrichmentModule(dependencies);
1274
- const modelPresets = createModelPresetsModule(dependencies);
1275
- const serviceFragmentDefaults = createServiceFragmentDefaultsModule(dependencies);
262
+ const settings = modules.build('settings', () => createWorkspaceSettingsModule(dependencies, caches.workspaceSettings));
263
+ modules.build('releaseHealth', () => createReleaseHealthModule(dependencies));
264
+ modules.build('packageRegistries', () => createPackageRegistriesModule(dependencies));
265
+ modules.build('preview', () => createPreviewModule(dependencies));
266
+ modules.build('incidentEnrichmentSettings', () => createIncidentEnrichmentModule(dependencies));
267
+ modules.build('modelPresets', () => createModelPresetsModule(dependencies));
268
+ modules.build('serviceFragmentDefaults', () => createServiceFragmentDefaultsModule(dependencies));
1276
269
  // Built before the execution engine so the planning pipeline's plan ingest + the
1277
270
  // committer step's tracker mirror can run through it.
1278
271
  const initiativeService = dependencies.initiativeRepository
@@ -1308,14 +301,14 @@ export function createCore(dependencies) {
1308
301
  });
1309
302
  // Built before the execution engine so the special `requirements-review` gate step can
1310
303
  // drive the inline reviewer + the iterative answer → incorporate → re-review loop.
1311
- const requirements = createRequirementsModule(dependencies, notifications?.service, fragmentLibrary);
304
+ const requirements = modules.build('requirements', () => createRequirementsModule(dependencies, notifications?.service, fragmentLibrary));
1312
305
  const docInterview = createDocInterviewService(dependencies);
1313
306
  const forkChat = createForkChatService(dependencies);
1314
- const clarity = createClarityModule(dependencies, notifications?.service);
1315
- const brainstorm = createBrainstormModule(dependencies, notifications?.service);
307
+ const clarity = modules.build('clarity', () => createClarityModule(dependencies, notifications?.service));
308
+ const brainstorm = modules.build('brainstorm', () => createBrainstormModule(dependencies, notifications?.service));
1316
309
  // Built before the execution engine so the engine's terminal hook can schedule a
1317
310
  // post-run Kaizen grading for each completed agent step.
1318
- const kaizen = createKaizenModule(dependencies);
311
+ const kaizen = modules.build('kaizen', () => createKaizenModule(dependencies));
1319
312
  // Late-bound so the engine's terminal hooks can poke the execution loop, which is built AFTER
1320
313
  // the engine (the loop depends on `executionService.start`). Fire-and-forget; a null ref (the
1321
314
  // loop unwired, or the settled block not part of an initiative) is a no-op.
@@ -1326,7 +319,7 @@ export function createCore(dependencies) {
1326
319
  // Built before the execution engine so the engine's `bug-intake` step can drive the
1327
320
  // read-and-claim intake helper (`tasks.bugIntakeService`). Also feeds the recurring module's
1328
321
  // schedule intake-config validation below.
1329
- const tasks = createTasksModule(dependencies, boardService);
322
+ const tasks = modules.build('tasks', () => createTasksModule(dependencies, boardService));
1330
323
  const executionService = new ExecutionService({
1331
324
  ...dependencies,
1332
325
  agentKindRegistry,
@@ -1394,13 +387,21 @@ export function createCore(dependencies) {
1394
387
  ? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(dependencies.modelPresetRepository, workspaceId, agentKind, modelPresetId)
1395
388
  : undefined,
1396
389
  });
1397
- const github = createGitHubModule(dependencies, caches);
1398
- const runners = createRunnersModule(dependencies);
390
+ modules.build('github', () => createGitHubModule(dependencies, caches));
391
+ modules.build('runners', () => createRunnersModule(dependencies));
1399
392
  // After a bootstrap succeeds, map the new repo into a blueprint + the board by
1400
393
  // starting the blueprint-only pipeline against the service frame.
1401
- const bootstrap = createBootstrapModule(dependencies, executionEventPublisher, (ws, blockId) => executionService.start(ws, blockId, BLUEPRINT_PIPELINE_ID).then(() => undefined));
1402
- const tracker = createTrackerModule(dependencies);
1403
- const recurring = createRecurringModule(dependencies, executionService, executionEventPublisher, tasks?.connectionService);
394
+ modules.build('bootstrap', () => createBootstrapModule(dependencies, executionEventPublisher, (ws, blockId) => executionService.start(ws, blockId, BLUEPRINT_PIPELINE_ID).then(() => undefined)));
395
+ modules.build('tracker', () => createTrackerModule(dependencies));
396
+ modules.build('recurring', () => createRecurringModule(dependencies, executionService, executionEventPublisher, tasks?.connectionService));
397
+ // The env-config-repair module is a sub-module of `environments` — surfaced as its own top-level
398
+ // `Core.envConfigRepair` key. Registered here (not in the `environments` build above) so it emits
399
+ // through the same registry rather than a bespoke return spread.
400
+ modules.build('envConfigRepair', () => environments?.envConfigRepair);
401
+ // Observability + per-account settings that a facade builds and passes through on the deps bag.
402
+ modules.build('agentContextObservability', () => dependencies.agentContextObservability);
403
+ modules.build('searchQueryObservability', () => dependencies.searchQueryObservability);
404
+ modules.build('accountSettings', () => dependencies.accountSettings ? { service: dependencies.accountSettings } : undefined);
1404
405
  // The initiative EXECUTION LOOP (slice 3): built after the engine (it drives
1405
406
  // `executionService.start` to spawn tasks), then late-bound into the terminal poke above so a
1406
407
  // settling child run advances its owning initiative immediately. Present only when initiatives
@@ -1421,19 +422,17 @@ export function createCore(dependencies) {
1421
422
  })
1422
423
  : undefined;
1423
424
  initiativeLoopRef = initiativeLoop;
1424
- const initiatives = initiativeService && initiativeLoop
425
+ modules.build('initiatives', () => initiativeService && initiativeLoop
1425
426
  ? { service: initiativeService, loop: initiativeLoop }
1426
- : undefined;
1427
- const services = createServicesModule(dependencies);
427
+ : undefined);
428
+ modules.build('services', () => createServicesModule(dependencies));
429
+ // The always-present spine, plus every optional module the registry assembled in ONE place
430
+ // (unwired keys absent) — replacing the ~40 hand-written `...(x ? { x } : {})` return spreads.
1428
431
  return {
1429
432
  caches,
1430
433
  workspaceService,
1431
- workspaceMemberService,
1432
434
  accountService,
1433
435
  userService,
1434
- ...(invitations ? { invitations } : {}),
1435
- ...(passwordReset ? { passwordReset } : {}),
1436
- ...(email ? { email } : {}),
1437
436
  boardService,
1438
437
  pipelineService,
1439
438
  executionService,
@@ -1442,49 +441,7 @@ export function createCore(dependencies) {
1442
441
  gateRegistry,
1443
442
  initiativePresetRegistry,
1444
443
  executionEventPublisher,
1445
- ...(llmObservability ? { llmObservability } : {}),
1446
- ...(platformObservability ? { platformObservability } : {}),
1447
- ...(dependencies.agentContextObservability
1448
- ? { agentContextObservability: dependencies.agentContextObservability }
1449
- : {}),
1450
- ...(dependencies.searchQueryObservability
1451
- ? { searchQueryObservability: dependencies.searchQueryObservability }
1452
- : {}),
1453
- ...(github ? { github } : {}),
1454
- ...(documents ? { documents } : {}),
1455
- ...(tasks ? { tasks } : {}),
1456
- ...(environments ? { environments } : {}),
1457
- ...(environments?.envConfigRepair ? { envConfigRepair: environments.envConfigRepair } : {}),
1458
- ...(runners ? { runners } : {}),
1459
- ...(provisioningLogs ? { provisioningLogs } : {}),
1460
- ...(bootstrap ? { bootstrap } : {}),
1461
- ...(requirements ? { requirements } : {}),
1462
- ...(kaizen ? { kaizen } : {}),
1463
- ...(clarity ? { clarity } : {}),
1464
- ...(brainstorm ? { brainstorm } : {}),
1465
- ...(notifications ? { notifications } : {}),
1466
- ...(slack ? { slack } : {}),
1467
- ...(riskPolicies ? { riskPolicies } : {}),
1468
- ...(sharedStacks ? { sharedStacks } : {}),
1469
- ...(preflight ? { preflight } : {}),
1470
- ...(sandbox ? { sandbox } : {}),
1471
- ...(settings ? { settings } : {}),
1472
- ...(userSettings ? { userSettings } : {}),
1473
- ...(releaseHealth ? { releaseHealth } : {}),
1474
- ...(packageRegistries ? { packageRegistries } : {}),
1475
- ...(preview ? { preview } : {}),
1476
- ...(incidentEnrichmentSettings ? { incidentEnrichmentSettings } : {}),
1477
- ...(dependencies.accountSettings
1478
- ? { accountSettings: { service: dependencies.accountSettings } }
1479
- : {}),
1480
- ...(modelPresets ? { modelPresets } : {}),
1481
- ...(serviceFragmentDefaults ? { serviceFragmentDefaults } : {}),
1482
- ...(fragmentLibrary ? { fragmentLibrary } : {}),
1483
- ...(skillLibrary ? { skillLibrary } : {}),
1484
- ...(initiatives ? { initiatives } : {}),
1485
- ...(recurring ? { recurring } : {}),
1486
- ...(tracker ? { tracker } : {}),
1487
- ...(services ? { services } : {}),
444
+ ...modules.assemble(),
1488
445
  };
1489
446
  }
1490
447
  //# sourceMappingURL=container.js.map