@cat-factory/orchestration 0.124.1 → 0.125.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/container/module-registry.d.ts +55 -0
- package/dist/container/module-registry.d.ts.map +1 -0
- package/dist/container/module-registry.js +61 -0
- package/dist/container/module-registry.js.map +1 -0
- package/dist/container/modules.d.ts +194 -0
- package/dist/container/modules.d.ts.map +1 -0
- package/dist/container/modules.js +1060 -0
- package/dist/container/modules.js.map +1 -0
- package/dist/container.d.ts +78 -21
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +88 -1118
- package/dist/container.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/execution/ExecutionService.d.ts +8 -2
- package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
- package/dist/modules/execution/ExecutionService.js +2 -1
- package/dist/modules/execution/ExecutionService.js.map +1 -1
- package/dist/modules/execution/RunDispatcher.d.ts +4 -1
- package/dist/modules/execution/RunDispatcher.d.ts.map +1 -1
- package/dist/modules/execution/RunDispatcher.js +8 -5
- package/dist/modules/execution/RunDispatcher.js.map +1 -1
- package/dist/modules/pipelines/PipelineService.d.ts +9 -1
- package/dist/modules/pipelines/PipelineService.d.ts.map +1 -1
- package/dist/modules/pipelines/PipelineService.js +16 -2
- package/dist/modules/pipelines/PipelineService.js.map +1 -1
- package/dist/validation/validateRegistrations.d.ts +9 -1
- package/dist/validation/validateRegistrations.d.ts.map +1 -1
- package/dist/validation/validateRegistrations.js +2 -2
- package/dist/validation/validateRegistrations.js.map +1 -1
- package/package.json +11 -11
package/dist/container.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAppCaches } from '@cat-factory/caching';
|
|
2
|
-
import {
|
|
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,
|
|
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 {
|
|
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';
|
|
@@ -50,1025 +49,8 @@ import { InitiativeInterviewService } from './modules/initiative/InitiativeInter
|
|
|
50
49
|
import { BLUEPRINT_PIPELINE_ID } from '@cat-factory/kernel';
|
|
51
50
|
import { defaultAgentKindRegistry, defaultInitiativePresetRegistry, FragmentLibraryService, FragmentSourceService, SkillCatalogService, SkillSourceService, SkillRunResolver, } from '@cat-factory/agents';
|
|
52
51
|
import { createFragmentLibraryModule, createSkillLibraryModule, } from './container-content-libraries.js';
|
|
53
|
-
import { defaultGateRegistry, defaultStepResolverRegistry } from '@cat-factory/kernel';
|
|
52
|
+
import { defaultGateRegistry, defaultPipelineRegistry, defaultProviderRegistry, 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
|
|
@@ -1079,6 +61,13 @@ export function createCore(dependencies) {
|
|
|
1079
61
|
// visible), else fresh empty registries so gate steps pass through in bare test builds.
|
|
1080
62
|
const gateRegistry = dependencies.gateRegistry ?? defaultGateRegistry();
|
|
1081
63
|
const stepResolverRegistry = dependencies.stepResolverRegistry ?? defaultStepResolverRegistry();
|
|
64
|
+
// The app-owned provider registry the gate machine reads through its GateContext. Resolved ONCE
|
|
65
|
+
// and threaded into the execution service alongside the gate registry (same instance the facade
|
|
66
|
+
// wired its gate providers on).
|
|
67
|
+
const providerRegistry = dependencies.providerRegistry ?? defaultProviderRegistry();
|
|
68
|
+
// The app-owned pipeline registry (deployment-registered extra pipelines). Resolved ONCE and
|
|
69
|
+
// threaded into the workspace + pipeline services so seeding + reseed see the same extras.
|
|
70
|
+
const pipelineRegistry = dependencies.pipelineRegistry ?? defaultPipelineRegistry();
|
|
1082
71
|
// Resolve the app-owned initiative-preset registry ONCE (same reasoning as the agent-kind one):
|
|
1083
72
|
// the facade's injected instance, else a fresh registry preloaded with the built-in presets.
|
|
1084
73
|
const initiativePresetRegistry = dependencies.initiativePresetRegistry ?? defaultInitiativePresetRegistry();
|
|
@@ -1090,6 +79,15 @@ export function createCore(dependencies) {
|
|
|
1090
79
|
// write-site invalidation — is exercised everywhere. Built here (before the services that
|
|
1091
80
|
// invalidate through it) so it can be threaded into all of them.
|
|
1092
81
|
const caches = dependencies.caches ?? createAppCaches();
|
|
82
|
+
// The optional-module registry: every feature that is wired only when its prerequisites are
|
|
83
|
+
// configured is `build`-declared through this, instead of a scattered `const x = createX(...)`
|
|
84
|
+
// + a matching `...(x ? { x } : {})` return spread. Registration order below IS dependency
|
|
85
|
+
// order: `build` returns the value, so a module consumed downstream is kept in a local and
|
|
86
|
+
// threaded into the later factories that need it (`modules.get(...)` is there for a reader that
|
|
87
|
+
// holds no local). The whole set is emitted in one place via `...modules.assemble()` at the
|
|
88
|
+
// return. The core spine (below) stays explicit —
|
|
89
|
+
// it carries the genuine circular late-bindings (account ⇄ spend, engine ⇄ initiative loop).
|
|
90
|
+
const modules = new ModuleRegistry();
|
|
1093
91
|
// Pass the resolved publisher so board mutations push a coarse `boardChanged` to every
|
|
1094
92
|
// user on the workspace (and every board mounting a shared service) — both facades route
|
|
1095
93
|
// here, so the wiring is symmetric by construction. The repo-projection cache lets
|
|
@@ -1101,13 +99,17 @@ export function createCore(dependencies) {
|
|
|
1101
99
|
});
|
|
1102
100
|
const workspaceService = new WorkspaceService({
|
|
1103
101
|
...dependencies,
|
|
102
|
+
// The resolved (defaulted) pipeline registry, so a new workspace is seeded with the built-in
|
|
103
|
+
// catalog + any deployment-registered pipelines (the raw `dependencies.pipelineRegistry` may be
|
|
104
|
+
// undefined; this is the same instance the pipeline service reseeds from).
|
|
105
|
+
pipelineRegistry,
|
|
1104
106
|
// A board delete drops its cached access decisions (workspace-rbac).
|
|
1105
107
|
workspaceAccessCache: caches.workspaceAccess,
|
|
1106
108
|
});
|
|
1107
109
|
// Workspace-RBAC roster + access-mode management (workspace-rbac, slice 5). Present only when
|
|
1108
110
|
// the member repository is wired (both facades wire it; tests/no-roster leave it absent, so the
|
|
1109
111
|
// members controller 503s). Every roster/access-mode write drops the board's access cache group.
|
|
1110
|
-
|
|
112
|
+
modules.build('workspaceMemberService', () => dependencies.workspaceMemberRepository
|
|
1111
113
|
? new WorkspaceMemberService({
|
|
1112
114
|
workspaceMemberRepository: dependencies.workspaceMemberRepository,
|
|
1113
115
|
workspaceRepository: dependencies.workspaceRepository,
|
|
@@ -1116,7 +118,7 @@ export function createCore(dependencies) {
|
|
|
1116
118
|
clock: dependencies.clock,
|
|
1117
119
|
workspaceAccessCache: caches.workspaceAccess,
|
|
1118
120
|
})
|
|
1119
|
-
: undefined;
|
|
121
|
+
: undefined);
|
|
1120
122
|
// Late-bound so the account service can invalidate the spend service's cached
|
|
1121
123
|
// account-budget limit on an account-budget edit (spendService is built below).
|
|
1122
124
|
let spendServiceRef;
|
|
@@ -1140,14 +142,14 @@ export function createCore(dependencies) {
|
|
|
1140
142
|
idGenerator: dependencies.idGenerator,
|
|
1141
143
|
clock: dependencies.clock,
|
|
1142
144
|
});
|
|
1143
|
-
const email = dependencies.emailConnectionRepository && dependencies.emailSecretCipher
|
|
145
|
+
const email = modules.build('email', () => dependencies.emailConnectionRepository && dependencies.emailSecretCipher
|
|
1144
146
|
? new EmailConnectionService({
|
|
1145
147
|
emailConnectionRepository: dependencies.emailConnectionRepository,
|
|
1146
148
|
secretCipher: dependencies.emailSecretCipher,
|
|
1147
149
|
clock: dependencies.clock,
|
|
1148
150
|
})
|
|
1149
|
-
: undefined;
|
|
1150
|
-
|
|
151
|
+
: undefined);
|
|
152
|
+
modules.build('invitations', () => dependencies.invitationRepository
|
|
1151
153
|
? new InvitationService({
|
|
1152
154
|
invitationRepository: dependencies.invitationRepository,
|
|
1153
155
|
accountRepository: dependencies.accountRepository,
|
|
@@ -1160,8 +162,8 @@ export function createCore(dependencies) {
|
|
|
1160
162
|
// Accepting an invitation grants membership ⇒ drop the workspace-access cache (workspace-rbac).
|
|
1161
163
|
onAccountMembershipChanged: () => caches.workspaceAccess.invalidateAll(),
|
|
1162
164
|
})
|
|
1163
|
-
: undefined;
|
|
1164
|
-
|
|
165
|
+
: undefined);
|
|
166
|
+
modules.build('passwordReset', () => dependencies.passwordResetTokenRepository
|
|
1165
167
|
? new PasswordResetService({
|
|
1166
168
|
passwordResetTokenRepository: dependencies.passwordResetTokenRepository,
|
|
1167
169
|
userRepository: dependencies.userRepository,
|
|
@@ -1172,8 +174,8 @@ export function createCore(dependencies) {
|
|
|
1172
174
|
appBaseUrl: dependencies.appBaseUrl,
|
|
1173
175
|
logger: dependencies.logger,
|
|
1174
176
|
})
|
|
1175
|
-
: undefined;
|
|
1176
|
-
const pipelineService = new PipelineService(dependencies);
|
|
177
|
+
: undefined);
|
|
178
|
+
const pipelineService = new PipelineService({ ...dependencies, pipelineRegistry });
|
|
1177
179
|
const spendService = new SpendService({
|
|
1178
180
|
tokenUsageRepository: dependencies.tokenUsageRepository,
|
|
1179
181
|
idGenerator: dependencies.idGenerator,
|
|
@@ -1191,7 +193,7 @@ export function createCore(dependencies) {
|
|
|
1191
193
|
userBudgetLimitCache: caches.userBudgetLimit,
|
|
1192
194
|
});
|
|
1193
195
|
spendServiceRef = spendService;
|
|
1194
|
-
|
|
196
|
+
modules.build('userSettings', () => dependencies.userSettingsRepository
|
|
1195
197
|
? {
|
|
1196
198
|
service: new UserSettingsService({
|
|
1197
199
|
userSettingsRepository: dependencies.userSettingsRepository,
|
|
@@ -1200,8 +202,8 @@ export function createCore(dependencies) {
|
|
|
1200
202
|
resolveUserBudgetCap: () => spendService.budgetCaps().userMonthlyLimitMax,
|
|
1201
203
|
}),
|
|
1202
204
|
}
|
|
1203
|
-
: undefined;
|
|
1204
|
-
const llmObservability = dependencies.llmCallMetricRepository
|
|
205
|
+
: undefined);
|
|
206
|
+
const llmObservability = modules.build('llmObservability', () => dependencies.llmCallMetricRepository
|
|
1205
207
|
? new LlmObservabilityService({
|
|
1206
208
|
llmCallMetricRepository: dependencies.llmCallMetricRepository,
|
|
1207
209
|
idGenerator: dependencies.idGenerator,
|
|
@@ -1211,13 +213,13 @@ export function createCore(dependencies) {
|
|
|
1211
213
|
workspaceSettingsRepository: dependencies.workspaceSettingsRepository,
|
|
1212
214
|
workspaceSettingsCache: caches.workspaceSettings,
|
|
1213
215
|
})
|
|
1214
|
-
: undefined;
|
|
1215
|
-
|
|
216
|
+
: undefined);
|
|
217
|
+
modules.build('platformObservability', () => dependencies.platformMetricsRepository
|
|
1216
218
|
? new PlatformObservabilityService({
|
|
1217
219
|
platformMetricsRepository: dependencies.platformMetricsRepository,
|
|
1218
220
|
clock: dependencies.clock,
|
|
1219
221
|
})
|
|
1220
|
-
: undefined;
|
|
222
|
+
: undefined);
|
|
1221
223
|
// The provisioning event log lives in a separate high-churn store. When its
|
|
1222
224
|
// repository is wired, build a best-effort recorder (threaded into the env
|
|
1223
225
|
// services) + the read service (exposed for the logs controller). The container
|
|
@@ -1229,28 +231,30 @@ export function createCore(dependencies) {
|
|
|
1229
231
|
clock: dependencies.clock,
|
|
1230
232
|
})
|
|
1231
233
|
: undefined;
|
|
1232
|
-
|
|
234
|
+
modules.build('provisioningLogs', () => dependencies.provisioningLogRepository
|
|
1233
235
|
? {
|
|
1234
|
-
service: new ProvisioningLogService({
|
|
236
|
+
service: new ProvisioningLogService({
|
|
237
|
+
repository: dependencies.provisioningLogRepository,
|
|
238
|
+
}),
|
|
1235
239
|
}
|
|
1236
|
-
: undefined;
|
|
240
|
+
: undefined);
|
|
1237
241
|
// Built before the shared-stacks + environments modules so a compose stack recipe's
|
|
1238
242
|
// `prerequisites` (and a shared stack's own prerequisites) are re-run at provision / bring-up
|
|
1239
243
|
// start through this service. The host probes exist only on the local facade; absent ⇒ a recipe /
|
|
1240
244
|
// stack that declares prerequisites fails loudly (the preflight API 503s too).
|
|
1241
|
-
const preflight = createPreflightModule(dependencies);
|
|
245
|
+
const preflight = modules.build('preflight', () => createPreflightModule(dependencies));
|
|
1242
246
|
// Built before the environments module so a compose stack recipe's `sharedStackRefs` can be
|
|
1243
247
|
// brought up (provider-before-consumer) through this service during provisioning. Persistence is
|
|
1244
248
|
// runtime-symmetric (present on every facade); the lifecycle only runs where a host daemon is
|
|
1245
249
|
// wired (`composeRuntime` — the local facade), else `ensureRefsUp` returns a clean error. It gets
|
|
1246
250
|
// 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);
|
|
251
|
+
const sharedStacks = modules.build('sharedStacks', () => createSharedStacksModule(dependencies, preflight?.service));
|
|
252
|
+
const environments = modules.build('environments', () => createEnvironmentsModule(dependencies, provisioningLogRecorder, executionEventPublisher, sharedStacks?.service, preflight?.service));
|
|
1249
253
|
// Built before the fragment library so a document-backed fragment can re-resolve
|
|
1250
254
|
// 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);
|
|
255
|
+
const documents = modules.build('documents', () => createDocumentsModule(dependencies, boardService));
|
|
256
|
+
const fragmentLibrary = modules.build('fragmentLibrary', () => createFragmentLibraryModule(dependencies, documents?.contentResolver, caches));
|
|
257
|
+
const skillLibrary = modules.build('skillLibrary', () => createSkillLibraryModule(dependencies, caches));
|
|
1254
258
|
// Reconciles a `blueprints` step's decomposition onto the board. Needs only the
|
|
1255
259
|
// board service + block repository (both always present), so it is wired
|
|
1256
260
|
// unconditionally — there is no standalone scan command or persisted blueprint store.
|
|
@@ -1260,19 +264,19 @@ export function createCore(dependencies) {
|
|
|
1260
264
|
});
|
|
1261
265
|
// Built before the execution engine so it can raise merge-review / CI-failed /
|
|
1262
266
|
// pipeline-complete notifications during a run (when the module is configured).
|
|
1263
|
-
const notifications = createNotificationsModule(dependencies);
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
267
|
+
const notifications = modules.build('notifications', () => createNotificationsModule(dependencies));
|
|
268
|
+
modules.build('slack', () => createSlackModule(dependencies));
|
|
269
|
+
modules.build('riskPolicies', () => createRiskPoliciesModule(dependencies, caches));
|
|
270
|
+
modules.build('sandbox', () => createSandboxModule(dependencies, agentKindRegistry));
|
|
1267
271
|
// Built before the execution engine so the per-service running-task limit can be
|
|
1268
272
|
// enforced at start() (and the escalation sweep can read the waiting threshold).
|
|
1269
|
-
const settings = createWorkspaceSettingsModule(dependencies, caches.workspaceSettings);
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
273
|
+
const settings = modules.build('settings', () => createWorkspaceSettingsModule(dependencies, caches.workspaceSettings));
|
|
274
|
+
modules.build('releaseHealth', () => createReleaseHealthModule(dependencies));
|
|
275
|
+
modules.build('packageRegistries', () => createPackageRegistriesModule(dependencies));
|
|
276
|
+
modules.build('preview', () => createPreviewModule(dependencies));
|
|
277
|
+
modules.build('incidentEnrichmentSettings', () => createIncidentEnrichmentModule(dependencies));
|
|
278
|
+
modules.build('modelPresets', () => createModelPresetsModule(dependencies));
|
|
279
|
+
modules.build('serviceFragmentDefaults', () => createServiceFragmentDefaultsModule(dependencies));
|
|
1276
280
|
// Built before the execution engine so the planning pipeline's plan ingest + the
|
|
1277
281
|
// committer step's tracker mirror can run through it.
|
|
1278
282
|
const initiativeService = dependencies.initiativeRepository
|
|
@@ -1308,14 +312,14 @@ export function createCore(dependencies) {
|
|
|
1308
312
|
});
|
|
1309
313
|
// Built before the execution engine so the special `requirements-review` gate step can
|
|
1310
314
|
// drive the inline reviewer + the iterative answer → incorporate → re-review loop.
|
|
1311
|
-
const requirements = createRequirementsModule(dependencies, notifications?.service, fragmentLibrary);
|
|
315
|
+
const requirements = modules.build('requirements', () => createRequirementsModule(dependencies, notifications?.service, fragmentLibrary));
|
|
1312
316
|
const docInterview = createDocInterviewService(dependencies);
|
|
1313
317
|
const forkChat = createForkChatService(dependencies);
|
|
1314
|
-
const clarity = createClarityModule(dependencies, notifications?.service);
|
|
1315
|
-
const brainstorm = createBrainstormModule(dependencies, notifications?.service);
|
|
318
|
+
const clarity = modules.build('clarity', () => createClarityModule(dependencies, notifications?.service));
|
|
319
|
+
const brainstorm = modules.build('brainstorm', () => createBrainstormModule(dependencies, notifications?.service));
|
|
1316
320
|
// Built before the execution engine so the engine's terminal hook can schedule a
|
|
1317
321
|
// post-run Kaizen grading for each completed agent step.
|
|
1318
|
-
const kaizen = createKaizenModule(dependencies);
|
|
322
|
+
const kaizen = modules.build('kaizen', () => createKaizenModule(dependencies));
|
|
1319
323
|
// Late-bound so the engine's terminal hooks can poke the execution loop, which is built AFTER
|
|
1320
324
|
// the engine (the loop depends on `executionService.start`). Fire-and-forget; a null ref (the
|
|
1321
325
|
// loop unwired, or the settled block not part of an initiative) is a no-op.
|
|
@@ -1326,12 +330,13 @@ export function createCore(dependencies) {
|
|
|
1326
330
|
// Built before the execution engine so the engine's `bug-intake` step can drive the
|
|
1327
331
|
// read-and-claim intake helper (`tasks.bugIntakeService`). Also feeds the recurring module's
|
|
1328
332
|
// schedule intake-config validation below.
|
|
1329
|
-
const tasks = createTasksModule(dependencies, boardService);
|
|
333
|
+
const tasks = modules.build('tasks', () => createTasksModule(dependencies, boardService));
|
|
1330
334
|
const executionService = new ExecutionService({
|
|
1331
335
|
...dependencies,
|
|
1332
336
|
agentKindRegistry,
|
|
1333
337
|
gateRegistry,
|
|
1334
338
|
stepResolverRegistry,
|
|
339
|
+
providerRegistry,
|
|
1335
340
|
initiativePresetRegistry,
|
|
1336
341
|
workRunner,
|
|
1337
342
|
executionEventPublisher,
|
|
@@ -1394,13 +399,21 @@ export function createCore(dependencies) {
|
|
|
1394
399
|
? (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(dependencies.modelPresetRepository, workspaceId, agentKind, modelPresetId)
|
|
1395
400
|
: undefined,
|
|
1396
401
|
});
|
|
1397
|
-
|
|
1398
|
-
|
|
402
|
+
modules.build('github', () => createGitHubModule(dependencies, caches));
|
|
403
|
+
modules.build('runners', () => createRunnersModule(dependencies));
|
|
1399
404
|
// After a bootstrap succeeds, map the new repo into a blueprint + the board by
|
|
1400
405
|
// starting the blueprint-only pipeline against the service frame.
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
406
|
+
modules.build('bootstrap', () => createBootstrapModule(dependencies, executionEventPublisher, (ws, blockId) => executionService.start(ws, blockId, BLUEPRINT_PIPELINE_ID).then(() => undefined)));
|
|
407
|
+
modules.build('tracker', () => createTrackerModule(dependencies));
|
|
408
|
+
modules.build('recurring', () => createRecurringModule(dependencies, executionService, executionEventPublisher, tasks?.connectionService));
|
|
409
|
+
// The env-config-repair module is a sub-module of `environments` — surfaced as its own top-level
|
|
410
|
+
// `Core.envConfigRepair` key. Registered here (not in the `environments` build above) so it emits
|
|
411
|
+
// through the same registry rather than a bespoke return spread.
|
|
412
|
+
modules.build('envConfigRepair', () => environments?.envConfigRepair);
|
|
413
|
+
// Observability + per-account settings that a facade builds and passes through on the deps bag.
|
|
414
|
+
modules.build('agentContextObservability', () => dependencies.agentContextObservability);
|
|
415
|
+
modules.build('searchQueryObservability', () => dependencies.searchQueryObservability);
|
|
416
|
+
modules.build('accountSettings', () => dependencies.accountSettings ? { service: dependencies.accountSettings } : undefined);
|
|
1404
417
|
// The initiative EXECUTION LOOP (slice 3): built after the engine (it drives
|
|
1405
418
|
// `executionService.start` to spawn tasks), then late-bound into the terminal poke above so a
|
|
1406
419
|
// settling child run advances its owning initiative immediately. Present only when initiatives
|
|
@@ -1421,70 +434,27 @@ export function createCore(dependencies) {
|
|
|
1421
434
|
})
|
|
1422
435
|
: undefined;
|
|
1423
436
|
initiativeLoopRef = initiativeLoop;
|
|
1424
|
-
|
|
437
|
+
modules.build('initiatives', () => initiativeService && initiativeLoop
|
|
1425
438
|
? { service: initiativeService, loop: initiativeLoop }
|
|
1426
|
-
: undefined;
|
|
1427
|
-
|
|
439
|
+
: undefined);
|
|
440
|
+
modules.build('services', () => createServicesModule(dependencies));
|
|
441
|
+
// The always-present spine, plus every optional module the registry assembled in ONE place
|
|
442
|
+
// (unwired keys absent) — replacing the ~40 hand-written `...(x ? { x } : {})` return spreads.
|
|
1428
443
|
return {
|
|
1429
444
|
caches,
|
|
1430
445
|
workspaceService,
|
|
1431
|
-
workspaceMemberService,
|
|
1432
446
|
accountService,
|
|
1433
447
|
userService,
|
|
1434
|
-
...(invitations ? { invitations } : {}),
|
|
1435
|
-
...(passwordReset ? { passwordReset } : {}),
|
|
1436
|
-
...(email ? { email } : {}),
|
|
1437
448
|
boardService,
|
|
1438
449
|
pipelineService,
|
|
1439
450
|
executionService,
|
|
1440
451
|
spendService,
|
|
1441
452
|
agentKindRegistry,
|
|
1442
453
|
gateRegistry,
|
|
454
|
+
pipelineRegistry,
|
|
1443
455
|
initiativePresetRegistry,
|
|
1444
456
|
executionEventPublisher,
|
|
1445
|
-
...(
|
|
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 } : {}),
|
|
457
|
+
...modules.assemble(),
|
|
1488
458
|
};
|
|
1489
459
|
}
|
|
1490
460
|
//# sourceMappingURL=container.js.map
|