@cat-factory/orchestration 0.136.0 → 0.137.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/container/engine-collaborators.d.ts +49 -0
  2. package/dist/container/engine-collaborators.d.ts.map +1 -0
  3. package/dist/container/engine-collaborators.js +93 -0
  4. package/dist/container/engine-collaborators.js.map +1 -0
  5. package/dist/container/engine-dependent-modules.d.ts +30 -0
  6. package/dist/container/engine-dependent-modules.d.ts.map +1 -0
  7. package/dist/container/engine-dependent-modules.js +54 -0
  8. package/dist/container/engine-dependent-modules.js.map +1 -0
  9. package/dist/container.d.ts +21 -3
  10. package/dist/container.d.ts.map +1 -1
  11. package/dist/container.js +31 -102
  12. package/dist/container.js.map +1 -1
  13. package/dist/modules/execution/ExecutionService.d.ts +27 -370
  14. package/dist/modules/execution/ExecutionService.d.ts.map +1 -1
  15. package/dist/modules/execution/ExecutionService.js +51 -139
  16. package/dist/modules/execution/ExecutionService.js.map +1 -1
  17. package/dist/modules/execution/ExecutionServiceDependencies.d.ts +381 -0
  18. package/dist/modules/execution/ExecutionServiceDependencies.d.ts.map +1 -0
  19. package/dist/modules/execution/ExecutionServiceDependencies.js +2 -0
  20. package/dist/modules/execution/ExecutionServiceDependencies.js.map +1 -0
  21. package/dist/modules/execution/PrVerificationReportController.d.ts +106 -0
  22. package/dist/modules/execution/PrVerificationReportController.d.ts.map +1 -0
  23. package/dist/modules/execution/PrVerificationReportController.js +134 -0
  24. package/dist/modules/execution/PrVerificationReportController.js.map +1 -0
  25. package/dist/modules/execution/RunDispatcher.d.ts +4 -0
  26. package/dist/modules/execution/RunDispatcher.d.ts.map +1 -1
  27. package/dist/modules/execution/RunDispatcher.js +13 -0
  28. package/dist/modules/execution/RunDispatcher.js.map +1 -1
  29. package/dist/modules/execution/gate-window-controllers.d.ts +209 -0
  30. package/dist/modules/execution/gate-window-controllers.d.ts.map +1 -0
  31. package/dist/modules/execution/gate-window-controllers.js +212 -0
  32. package/dist/modules/execution/gate-window-controllers.js.map +1 -0
  33. package/dist/modules/execution/prReport.logic.d.ts +25 -0
  34. package/dist/modules/execution/prReport.logic.d.ts.map +1 -0
  35. package/dist/modules/execution/prReport.logic.js +436 -0
  36. package/dist/modules/execution/prReport.logic.js.map +1 -0
  37. package/dist/modules/execution/prReportText.logic.d.ts +46 -0
  38. package/dist/modules/execution/prReportText.logic.d.ts.map +1 -0
  39. package/dist/modules/execution/prReportText.logic.js +188 -0
  40. package/dist/modules/execution/prReportText.logic.js.map +1 -0
  41. package/dist/modules/settings/WorkspaceSettingsService.d.ts.map +1 -1
  42. package/dist/modules/settings/WorkspaceSettingsService.js +1 -0
  43. package/dist/modules/settings/WorkspaceSettingsService.js.map +1 -1
  44. package/package.json +11 -11
@@ -0,0 +1,381 @@
1
+ import type { AccountRepository, AgentExecutor, BlockRepository, BlueprintService, BranchUpdater, BrainstormSessionRepository, BrainstormStage, ClarityReviewRepository, Clock, DocInterviewRepository, DocumentRepository, ExecutionEventPublisher, ExecutionRepository, GateRegistry, GroupCacheHandle, IdGenerator, InitiativePresetRegistry, InitiativeRepository, IssueWritebackProvider, ModelRef, PipelineRepository, PrVerificationReportPublisher, ProviderCapabilities, ProviderRegistry, PullRequestMerger, RequirementReviewRepository, ResolveBinaryArtifactStore, ResolveRunRepoContext, RiskPolicyCacheValue, RiskPolicyRepository, RunInitiatorScope, StepResolverRegistry, SubscriptionActivationRepository, TaskRepository, TestSecretRef, TicketTrackerProvider, WorkRunner, WorkspaceRepository, WorkspaceSettingsRepository } from '@cat-factory/kernel';
2
+ import type { AgentKindRegistry } from '@cat-factory/agents';
3
+ import type { BugIntakeService, EnvironmentProvisioningService, EnvironmentTeardownService } from '@cat-factory/integrations';
4
+ import type { SpendService } from '@cat-factory/spend';
5
+ import type { BoardService } from '../board/BoardService.js';
6
+ import type { BrainstormService } from '../brainstorm/BrainstormService.js';
7
+ import type { ClarityReviewService } from '../clarity/ClarityReviewService.js';
8
+ import type { DocInterviewService } from '../docInterview/DocInterviewService.js';
9
+ import type { InitiativeInterviewService } from '../initiative/InitiativeInterviewService.js';
10
+ import type { InitiativeRunHarvest } from '../initiative/initiative.logic.js';
11
+ import type { InitiativeService } from '../initiative/InitiativeService.js';
12
+ import type { LlmObservabilityService } from '../observability/LlmObservabilityService.js';
13
+ import type { NotificationService } from '../notifications/NotificationService.js';
14
+ import type { RequirementReviewService } from '../requirements/RequirementReviewService.js';
15
+ import type { WorkspaceSettingsService } from '../settings/WorkspaceSettingsService.js';
16
+ import type { DocumentUrlResolver, FragmentBodyResolver, SkillResolver } from './AgentContextBuilder.js';
17
+ import type { ForkChatService } from './ForkChatService.js';
18
+ import type { PrReportLogger } from './PrVerificationReportController.js';
19
+ import type { KaizenScheduler } from './RunStateMachine.js';
20
+ import type { TesterQualityReviewer } from './TesterQualityReviewService.js';
21
+ /** Reconciles a Blueprinter step's tree onto the board in place (BoardScanService). */
22
+ export interface BlueprintReconciler {
23
+ reconcileBlueprint(workspaceId: string, frameId: string | null, service: BlueprintService): Promise<unknown>;
24
+ }
25
+ export interface ExecutionServiceDependencies {
26
+ workspaceRepository: WorkspaceRepository;
27
+ blockRepository: BlockRepository;
28
+ pipelineRepository: PipelineRepository;
29
+ executionRepository: ExecutionRepository;
30
+ /**
31
+ * Resolves the owning account of a workspace so a service that pins no cloud
32
+ * provider falls back to the account's `defaultCloudProvider` at dispatch.
33
+ */
34
+ accountRepository: AccountRepository;
35
+ idGenerator: IdGenerator;
36
+ clock: Clock;
37
+ agentExecutor: AgentExecutor;
38
+ /**
39
+ * The app-owned agent-kind registry, threaded through to the trait/inline-surface checks
40
+ * and a registered kind's pre/post-op hooks. `createCore` defaults it to
41
+ * `defaultAgentKindRegistry()` when a facade doesn't inject the shared instance.
42
+ */
43
+ agentKindRegistry: AgentKindRegistry;
44
+ /**
45
+ * The app-owned polling-gate registry (the built-in `@cat-factory/gates` suite installed by
46
+ * the facade + any deployment-registered gates), threaded to the dispatcher's gate machine.
47
+ * `createCore` defaults it to `defaultGateRegistry()` (empty) when a facade doesn't inject one.
48
+ */
49
+ gateRegistry: GateRegistry;
50
+ /**
51
+ * The app-owned step-completion-resolver registry (deployment-registered resolvers),
52
+ * threaded to the dispatcher. `createCore` defaults it to `defaultStepResolverRegistry()`.
53
+ */
54
+ stepResolverRegistry: StepResolverRegistry;
55
+ /**
56
+ * The app-owned provider registry (gate data sources keyed by {@link ProviderToken}), threaded
57
+ * to the dispatcher's gate machine so its {@link GateContext} reads the wired providers.
58
+ * `createCore` defaults it to `defaultProviderRegistry()` (empty ⇒ every gate passes through).
59
+ */
60
+ providerRegistry: ProviderRegistry;
61
+ /**
62
+ * The app-owned initiative-preset registry, threaded into the context builder so a spawned /
63
+ * planning run resolves its preset steering. `createCore` defaults it to
64
+ * `defaultInitiativePresetRegistry()` when a facade doesn't inject the shared instance.
65
+ */
66
+ initiativePresetRegistry: InitiativePresetRegistry;
67
+ workRunner: WorkRunner;
68
+ executionEventPublisher: ExecutionEventPublisher;
69
+ boardService: BoardService;
70
+ spendService: SpendService;
71
+ /**
72
+ * Optional: when the document-source integration is configured, documents
73
+ * linked to a block are resolved here and fed to the agent as extra context.
74
+ */
75
+ documentRepository?: DocumentRepository;
76
+ /**
77
+ * Optional: canonicalises a URL named in a block's description to the document's stable
78
+ * `(source, externalId)` (via the document providers' `parseRef`) so a pasted design/doc
79
+ * link auto-matches its imported page even when the URL carries title/tracking noise.
80
+ * Forwarded to {@link AgentContextBuilder}; absent → url-string matching only.
81
+ */
82
+ documentUrlResolver?: DocumentUrlResolver;
83
+ /**
84
+ * Optional: when the task-source integration is configured, tracker issues
85
+ * linked to a block are resolved here and fed to the agent as extra context.
86
+ */
87
+ taskRepository?: TaskRepository;
88
+ /**
89
+ * Optional: when the requirements-review feature is configured, a block's
90
+ * reworked ("incorporated") requirements are read here. When present they REPLACE
91
+ * the block's description + linked docs/tasks as the agent context (for every
92
+ * step) and become the per-task input the spec-writer aggregates. Absent
93
+ * → the engine uses the original description + docs/tasks unchanged.
94
+ */
95
+ requirementReviewRepository?: RequirementReviewRepository;
96
+ /**
97
+ * Optional: when the interactive document-interview feature is configured (WS5), a block's
98
+ * synthesized authoring brief is read here and folded into the doc-writer's context. Absent
99
+ * → the writer runs off the raw outline/description unchanged.
100
+ */
101
+ docInterviewRepository?: DocInterviewRepository;
102
+ /**
103
+ * Optional: the requirements-review feature's service, present when the reviewer is
104
+ * wired. Drives the special `requirements-review` gate step (run reviewer inline, the
105
+ * iterative answer → incorporate → re-review loop). Absent → the gate step passes
106
+ * through so pipelines run unchanged without the feature.
107
+ */
108
+ requirementReviewService?: RequirementReviewService;
109
+ /**
110
+ * Optional: the interactive document-interview service (WS5). When wired, the
111
+ * `doc-interviewer` step converses with the human (park/answer/resume) to refine a
112
+ * document's scope/structure and synthesizes an authoring brief the writer starts from.
113
+ * Absent (or no model) → the interviewer step passes through so document pipelines run
114
+ * unchanged off the raw outline.
115
+ */
116
+ docInterviewService?: DocInterviewService;
117
+ /**
118
+ * Optional: the inline grounded-chat responder for the implementation-fork decision phase.
119
+ * When wired, a human chat turn about the surfaced forks is answered by an inline LLM in the
120
+ * durable driver; absent (no model) the chat degrades to a canned "chat unavailable" reply so
121
+ * pick / custom still work. Passed to the {@link ForkDecisionController}.
122
+ */
123
+ forkChatService?: ForkChatService;
124
+ /**
125
+ * Optional: the inline reviewer for the test quality-control companion. When wired (and a
126
+ * Tester step has the companion enabled), each Tester report is audited for coverage before
127
+ * the greenlight/fixer decision and an inadequate report loops the Tester. Passed straight
128
+ * to the {@link TesterController}. Absent → QC is a pass-through.
129
+ */
130
+ testerQualityReviewer?: TesterQualityReviewer;
131
+ /**
132
+ * Optional: the Kaizen agent's scheduler. When wired, a run reaching a terminal state
133
+ * schedules a post-run grading for each completed agent step (skipping verified combos).
134
+ * Structural so the engine doesn't depend on the concrete service. Absent → no grading.
135
+ */
136
+ kaizenScheduler?: KaizenScheduler;
137
+ /**
138
+ * Optional: persistence for the clarity-review (bug-report triage) feature. Read here
139
+ * to substitute a converged clarified report as the downstream agent context (the
140
+ * mirror of `requirementReviewRepository`). Absent → no substitution.
141
+ */
142
+ clarityReviewRepository?: ClarityReviewRepository;
143
+ /**
144
+ * Optional: the clarity-review feature's service, present when the reviewer is wired.
145
+ * Drives the special `clarity-review` gate step (inline reviewer + the iterative
146
+ * answer → incorporate → re-review loop). Absent → the gate step passes through.
147
+ */
148
+ clarityReviewService?: ClarityReviewService;
149
+ /**
150
+ * Optional: the brainstorm (structured-dialogue) feature's services, one per stage, present
151
+ * when the brainstorm module is wired. Drive the special `requirements-brainstorm` /
152
+ * `architecture-brainstorm` gate steps (inline option-generator + the iterative propose →
153
+ * pick → incorporate → re-run loop). Absent → the gate steps pass through.
154
+ */
155
+ brainstormServices?: Record<BrainstormStage, BrainstormService>;
156
+ /**
157
+ * Optional: persistence for the brainstorm feature. Read by the agent-context builder to
158
+ * surface a converged `architecture-brainstorm` direction to the architect (the mirror of
159
+ * `requirementReviewRepository`). Absent → no substitution.
160
+ */
161
+ brainstormSessionRepository?: BrainstormSessionRepository;
162
+ /**
163
+ * Optional: resolves fragment ids against the merged tenant catalog (managed +
164
+ * document-backed fragments), live-resolving linked Confluence/Notion/GitHub
165
+ * documents at run time. Wired only when the prompt-fragment library is
166
+ * configured; absent → the engine resolves against the static built-in pool.
167
+ */
168
+ fragmentResolver?: FragmentBodyResolver;
169
+ /**
170
+ * Optional: resolves a `skill` step's picked skill to its instructions + resource bodies for
171
+ * the run (see {@link SkillResolver}). Wired only when the repo-sourced Claude Skills library is
172
+ * configured; a skill step dispatched with this unwired fails loudly rather than running blank.
173
+ */
174
+ skillResolver?: SkillResolver;
175
+ /**
176
+ * Optional: when the individual-usage subscription store is configured, a finished
177
+ * run's per-run credential activation is deleted here the moment it reaches a terminal
178
+ * state, bounding standing exposure to the run's own lifetime (the TTL sweep is the
179
+ * backstop). Absent → activations are reclaimed by the TTL sweep alone.
180
+ */
181
+ subscriptionActivationRepository?: SubscriptionActivationRepository;
182
+ /**
183
+ * Optional: resolve a workspace's per-agent-kind default model id (the same resolver
184
+ * the container executor uses for dispatch). The personal-credential gate consults it
185
+ * so a run whose block has NO pinned model but whose workspace default resolves to an
186
+ * individual-usage vendor is still gated up-front — matching what dispatch will resolve,
187
+ * instead of starting and then failing on a missing activation. Absent → the gate sees
188
+ * only the block's pinned model (env-routing defaults are operator-level and not gated).
189
+ */
190
+ resolveWorkspaceModelDefault?: (workspaceId: string, agentKind: string, modelPresetId?: string) => Promise<string | undefined>;
191
+ /**
192
+ * Optional: resolve the provider capabilities (configured direct keys +
193
+ * subscription vendors + whether Cloudflare AI is enabled) for a workspace and the
194
+ * run initiator. The start guard uses it to block a pipeline whose steps' canonical
195
+ * models have no usable provider. Absent → the guard is skipped (tests / unconfigured
196
+ * facades), exactly like the existing optional engine deps.
197
+ */
198
+ resolveProviderCapabilities?: (workspaceId: string, initiatedBy?: string | null) => Promise<ProviderCapabilities>;
199
+ /**
200
+ * Optional: whether a container-only subscription harness ref (`claude-code` / `codex`)
201
+ * can run as an INLINE LLM call in this deployment (local mode's ambient CLI). The preset
202
+ * satisfiability guard uses it so an inline step pinned to a subscription model is
203
+ * satisfiable where the harness runs inline, and refused where it doesn't (Node/Worker).
204
+ * From `config.agents.inlineHarnessRef`; absent → no inline harness support.
205
+ */
206
+ inlineHarnessRef?: (ref: ModelRef) => boolean;
207
+ /**
208
+ * Optional: when the environment integration is configured, a `deployer` step
209
+ * provisions an ephemeral environment deterministically through this service
210
+ * (no LLM), and downstream steps discover the resulting env via it.
211
+ */
212
+ environmentProvisioning?: EnvironmentProvisioningService;
213
+ /**
214
+ * Optional: resolve the NON-secret refs (key + description) of the sensitive test credentials
215
+ * for a run block's service frame, folded into the tester prompt by the context builder.
216
+ * Wired from the facade's `TestSecretsService`; absent ⇒ no advertised secrets. NEVER values.
217
+ */
218
+ resolveTestSecretRefs?: (workspaceId: string, blockId: string) => Promise<TestSecretRef[]>;
219
+ /**
220
+ * Optional: resolves the binary-artifact store (UI screenshots + reference design images)
221
+ * for a workspace's account; the `visual-confirmation` gate reads it. Absent (or resolving
222
+ * to null — storage not configured) → the gate passes through (auto-advances), since there
223
+ * is nowhere to read screenshots from.
224
+ */
225
+ resolveBinaryArtifactStore?: ResolveBinaryArtifactStore;
226
+ /**
227
+ * Optional: tears down ephemeral environments. Wired alongside
228
+ * {@link environmentProvisioning}; the `human-test` gate uses it to destroy an env on
229
+ * confirm / recreate / on-demand. Absent → the gate's destroy/recreate is a no-op.
230
+ */
231
+ environmentTeardown?: EnvironmentTeardownService;
232
+ /**
233
+ * Optional: merges the repo default branch into a block's PR branch server-side. Wired
234
+ * when GitHub is configured; the `human-test` gate's "pull latest main" action uses it
235
+ * (a clean merge rebuilds the env; a conflict escalates to the conflict-resolver). Absent
236
+ * → pulling main is unavailable on the gate.
237
+ */
238
+ branchUpdater?: BranchUpdater;
239
+ /**
240
+ * Optional: when the board-scan module is configured, a `blueprints` step's
241
+ * decomposition tree is reconciled onto the board through this (BoardScanService).
242
+ * Absent → a blueprint step still runs and commits its in-repo files, but the
243
+ * board isn't auto-updated from it.
244
+ */
245
+ blueprintReconciler?: BlueprintReconciler;
246
+ /**
247
+ * Optional: when the initiatives module is wired, the `initiative-planner` step's
248
+ * plan draft is ingested into the block's initiative entity through this, and the
249
+ * `initiative-committer` step flips it to `executing` + mirrors the in-repo
250
+ * tracker. Absent → the initiative steps fail loudly (an initiative pipeline is
251
+ * meaningless without the module) while every other pipeline runs unchanged.
252
+ */
253
+ initiativeService?: InitiativeService;
254
+ /**
255
+ * Optional: the initiative store, wired into the agent-context builder so an
256
+ * `initiative`-level run carries the interview + analysis context into the analyst/planner
257
+ * prompts. Same repo the {@link initiativeService} wraps; absent → those steps run off the
258
+ * raw block description.
259
+ */
260
+ initiativeRepository?: InitiativeRepository;
261
+ /**
262
+ * Optional: the inline interviewer for the interactive-planning gate (slice 2). When
263
+ * wired, the `initiative-interviewer` step interviews the human (park/answer/resume) and
264
+ * synthesizes the goal/constraints brief onto the entity before the analyst/planner run.
265
+ * Absent (or no model) → the interviewer step passes through and planning runs off the
266
+ * raw block description. Requires {@link initiativeService} to persist the interview state.
267
+ */
268
+ initiativeInterviewService?: InitiativeInterviewService;
269
+ /**
270
+ * Best-effort poke of the initiative execution loop (slice 3): called after a spawned task's
271
+ * PR merges (`finalizeMerge`), so its owning initiative reconciles + advances immediately
272
+ * rather than on the next cron sweep. Threaded through to the {@link RunStateMachine} for the
273
+ * symmetric terminal-run poke. Fire-and-forget; a no-op when initiatives are unwired. The
274
+ * optional `harvest` (slice 4) carries the settling run's follow-ups + failure cause.
275
+ */
276
+ pokeInitiativeLoop?: (workspaceId: string, initiativeBlockId: string, harvest?: InitiativeRunHarvest) => void;
277
+ /**
278
+ * Optional: raises human-actionable notifications (a PR needs a merge decision,
279
+ * a no-merger pipeline finished, CI fixing gave up). Absent → those events still
280
+ * transition the block but no notification surfaces (tests).
281
+ */
282
+ notificationService?: NotificationService;
283
+ /**
284
+ * Optional: resolves a workspace's runtime settings so {@link ExecutionService.start}
285
+ * can enforce the per-service running-task limit. Absent → the limit is never enforced
286
+ * (tests / unconfigured facades start runs unbounded).
287
+ */
288
+ workspaceSettingsService?: WorkspaceSettingsService;
289
+ /**
290
+ * Optional: performs the real GitHub merge when a task should become `done`.
291
+ * Absent → `done` is a board-only flip (tests); when wired, `done` provably
292
+ * means the PR was merged on the remote.
293
+ */
294
+ pullRequestMerger?: PullRequestMerger;
295
+ /**
296
+ * Optional: resolves a task's merge threshold preset (auto-merge ceilings + the
297
+ * CI-fixer attempt budget). Absent → the built-in {@link DEFAULT_RISK_POLICY}.
298
+ */
299
+ riskPolicyRepository?: RiskPolicyRepository;
300
+ /**
301
+ * Optional: the {@link AppCaches.riskPolicy} slice — read-through for `resolveRiskPolicy`
302
+ * so the slow-moving merge-preset row isn't re-fetched on every gate evaluation. Absent →
303
+ * every resolve hits the repository (tests / no cache wired). Invalidated by
304
+ * `RiskPolicyService` on every preset write.
305
+ */
306
+ riskPolicyCache?: GroupCacheHandle<RiskPolicyCacheValue>;
307
+ /**
308
+ * Optional: runs the gate-probe / merge GitHub reads under the run initiator's
309
+ * ambient context, so a per-user PAT (when set) is preferred over the deployment's
310
+ * App/env token (see `PatPreferringAppRegistry`). Absent → a pass-through
311
+ * (`(_, fn) => fn()`), so tests/conformance run unchanged.
312
+ */
313
+ runInitiatorScope?: RunInitiatorScope;
314
+ /**
315
+ * Optional: files a GitHub issue / Jira ticket for the `tracker` step (the
316
+ * tech-debt recurring pipeline). Absent → the `tracker` step passes through
317
+ * without filing anything, so the engine works unchanged when no tracker is wired.
318
+ */
319
+ ticketTrackerProvider?: TicketTrackerProvider;
320
+ /**
321
+ * Optional: writes back to a task's linked tracker issue(s) as its PR progresses
322
+ * (comment on PR open; comment + close as resolved on merge). Gated by the
323
+ * workspace's writeback settings + the per-task override. Absent → no writeback,
324
+ * so the engine works unchanged when no tracker writeback is wired.
325
+ */
326
+ issueWriteback?: IssueWritebackProvider;
327
+ /**
328
+ * Optional: the recurring `bug-intake` step's read-and-claim helper. When wired, a `bug-intake`
329
+ * step pulls one matching open issue from the schedule's configured tracker board, claims it, and
330
+ * seeds the reused block from it; absent (no task sources wired) → the step is a no-op that
331
+ * completes the run without touching the block, so the engine works unchanged.
332
+ */
333
+ bugIntakeService?: BugIntakeService;
334
+ /**
335
+ * Optional: the LLM observability sink. When wired, each emit rolls the per-run
336
+ * model-call aggregates onto the matching pipeline steps (`step.metrics`) so the
337
+ * board shows tokens / output-limit headroom / transport-vs-execution latency
338
+ * live. Absent (tests / unconfigured) → steps carry no `metrics`.
339
+ */
340
+ llmObservability?: LlmObservabilityService;
341
+ /**
342
+ * Optional: resolve a block's run repo (installation + repo + default branch) bound to
343
+ * a checkout-free {@link RepoFiles} so a registered custom kind's pre/post-op hooks
344
+ * read/commit a targeted subset of the repo WITHOUT a checkout. A facade composes it
345
+ * from its wired `GitHubClient` + `resolveRepoTarget` (`makeResolveRunRepoContext`).
346
+ * Absent (tests / GitHub not connected) → pre/post-ops are skipped.
347
+ */
348
+ resolveRunRepoContext?: ResolveRunRepoContext;
349
+ /**
350
+ * Optional: assert the workspace has a usable container-agent backend before a run
351
+ * starts (local mode delegating agents to a runner pool that isn't registered throws a
352
+ * clean {@link ConflictError} here). Absent → no start-time check (Cloudflare/Node have
353
+ * a fixed backend; a missing local pool still fails loudly at dispatch).
354
+ */
355
+ assertAgentBackendConfigured?: (workspaceId: string) => Promise<void>;
356
+ /**
357
+ * Optional: writes the engine's verification report (CI verdict, tester report, ephemeral
358
+ * environment lifecycle, merge assessment, run metadata + an observability deep link) onto
359
+ * the run's pull request as a marker-delimited, idempotently-updated section. A facade
360
+ * composes it from its engine VCS client, so GitLab deployments publish too. Absent (tests,
361
+ * a no-VCS deployment) → the engine behaves exactly as it did before the feature.
362
+ */
363
+ prVerificationReportPublisher?: PrVerificationReportPublisher;
364
+ /**
365
+ * Optional: the deployment's public SPA base URL, used to build the verification report's
366
+ * observability deep link. Absent → the report carries no link rather than a dead one.
367
+ */
368
+ appBaseUrl?: string;
369
+ /**
370
+ * Optional: the per-workspace settings row, read by the verification-report hook for the
371
+ * `publishPrVerificationReport` opt-out. Absent ⇒ the default (on).
372
+ */
373
+ workspaceSettingsRepository?: WorkspaceSettingsRepository;
374
+ /**
375
+ * Optional structured logger (the facade's pino logger) for the engine's best-effort paths —
376
+ * today the PR verification report, whose whole contract is that it never fails a run. Absent
377
+ * ⇒ those failures are silent, which is why every facade wires it.
378
+ */
379
+ logger?: PrReportLogger;
380
+ }
381
+ //# sourceMappingURL=ExecutionServiceDependencies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExecutionServiceDependencies.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/ExecutionServiceDependencies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ExecutionServiceDependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExecutionServiceDependencies.js","sourceRoot":"","sources":["../../../src/modules/execution/ExecutionServiceDependencies.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import type { BlockRepository, Clock, ExecutionInstance, PrVerificationReportPublisher, TaskRepository, WorkspaceSettingsRepository } from '@cat-factory/kernel';
2
+ /** Minimal structured logger (pino-compatible); optional, like every other best-effort path. */
3
+ export interface PrReportLogger {
4
+ warn(obj: Record<string, unknown>, msg?: string): void;
5
+ }
6
+ /**
7
+ * The engine collaborator that keeps a run's **verification report** on its pull request.
8
+ *
9
+ * Shape decision (see `docs/initiatives/pr-verification-report.md`, D2): this is an engine
10
+ * HOOK on step settlement, not a pipeline step — so it is pipeline-shape agnostic (a
11
+ * deployment-authored pipeline gets a report without editing it) and a run that fails or
12
+ * parks part-way still leaves its evidence on the PR. `RunDispatcher.recordStepResult` calls
13
+ * {@link publishForRun} once per settled step, positioned AFTER the terminal step resolver
14
+ * (so the `merger` step's publish carries the resolved `MergeDecision`) and BEFORE
15
+ * `finalizeBlock` (so the `pipeline_complete` card a merger-less pipeline raises points at a
16
+ * PR that already carries the finished report). A passing polling gate settles through the
17
+ * same `recordStepResult`, so the CI verdict needs no hook of its own.
18
+ *
19
+ * Everything it reports is already in memory on the {@link ExecutionInstance}; the only reads
20
+ * are one block point-read and one batched `listByBlock` for the linked tracker issues — no
21
+ * N+1, and no re-probe of the CI/mergeability providers (which would cost a round trip and
22
+ * could disagree with the verdict the gate actually acted on).
23
+ *
24
+ * Every failure mode is a silent no-op: no publisher wired (tests, a no-VCS deployment), no
25
+ * PR yet, an unchanged report, or a transport error. Publishing a report must never fail a
26
+ * run that otherwise succeeded.
27
+ */
28
+ export interface PrVerificationReportControllerDeps {
29
+ blockRepository: BlockRepository;
30
+ clock: Clock;
31
+ /**
32
+ * Optional: writes the rendered section onto the block's PR. Absent (no VCS client wired)
33
+ * ⇒ every publish is a no-op, so the engine behaves exactly as it did before this feature.
34
+ */
35
+ publisher?: PrVerificationReportPublisher;
36
+ /** Optional: resolves the task's linked tracker issues in ONE batched read. */
37
+ taskRepository?: TaskRepository;
38
+ /**
39
+ * Optional: the per-workspace `publishPrVerificationReport` opt-out. Absent (or no saved
40
+ * settings row) ⇒ the default, which is ON — a deployment that wired a publisher wants the
41
+ * report.
42
+ */
43
+ workspaceSettingsRepository?: WorkspaceSettingsRepository;
44
+ /**
45
+ * Optional: the deployment's public SPA base URL, used to build the observability deep
46
+ * link. Absent ⇒ the report's `observability.runUrl` is null and no link is rendered
47
+ * (better than emitting a link to nowhere).
48
+ */
49
+ appBaseUrl?: string;
50
+ /**
51
+ * Optional structured logger for the best-effort failure path. Wire it: publishing is the
52
+ * one part of the run that is DESIGNED to fail silently, so without a log a revoked token or
53
+ * a rejected body leaves no trace anywhere — the report simply stops appearing.
54
+ */
55
+ logger?: PrReportLogger;
56
+ }
57
+ export declare class PrVerificationReportController {
58
+ private readonly deps;
59
+ /**
60
+ * The last section published per execution id, so settlements that change nothing a reader
61
+ * would see cost no PR edit at all.
62
+ *
63
+ * It does NOT collapse a run to one edit: the report carries a per-step state table, so most
64
+ * settlements genuinely do change it and the report tracks the run as it progresses — which
65
+ * is the intent ("rewritten in place as the run progresses"). What the cache removes is the
66
+ * repeat write from a replayed durable step, a re-poll, or a settlement whose evidence is
67
+ * identical to the last one.
68
+ *
69
+ * In-process only and deliberately so: it is a WRITE-AVOIDANCE cache, never a correctness
70
+ * mechanism — the marker splice is idempotent, so a cold process (or a peer replica) simply
71
+ * re-publishes the same section and the adapter's own unchanged-check suppresses the remote
72
+ * write.
73
+ */
74
+ private readonly lastPublished;
75
+ /**
76
+ * Hard cap on {@link lastPublished}. A long-lived Node replica serves an unbounded number of
77
+ * runs, and each entry holds a rendered section, so the map is bounded here rather than
78
+ * relying on a call site to evict finished runs (a coupling that would silently leak the
79
+ * moment a new terminal path forgot to call it). Oldest-first eviction: `Map` preserves
80
+ * insertion order, and evicting an entry only costs one redundant (idempotent) republish.
81
+ */
82
+ private static readonly MAX_TRACKED_RUNS;
83
+ constructor(deps: PrVerificationReportControllerDeps);
84
+ /**
85
+ * Compose the report for `instance` and upsert it onto the run's PR. Best-effort: returns
86
+ * silently on every skip/failure path.
87
+ */
88
+ publishForRun(workspaceId: string, instance: ExecutionInstance): Promise<void>;
89
+ /** Record the published fingerprint, evicting the oldest entry once the cap is reached. */
90
+ private remember;
91
+ /**
92
+ * The workspace's opt-out. Checked BEFORE anything is read or composed, so a workspace that
93
+ * turned the report off pays nothing for the hook. A workspace with no saved settings row
94
+ * reads as the default (on).
95
+ */
96
+ private publishingEnabled;
97
+ private linkedIssues;
98
+ /**
99
+ * The deep link into the run's observability panel (Model activity / Provided context).
100
+ * The SPA is a single canvas, so the target is the board with the run's view params — see
101
+ * `useRunDeepLink` in `@cat-factory/app`, and slice 4 of the global-search initiative,
102
+ * which will generalise the parser.
103
+ */
104
+ private runUrl;
105
+ }
106
+ //# sourceMappingURL=PrVerificationReportController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrVerificationReportController.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/PrVerificationReportController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,KAAK,EACL,iBAAiB,EAEjB,6BAA6B,EAC7B,cAAc,EACd,2BAA2B,EAC5B,MAAM,qBAAqB,CAAA;AAI5B,gGAAgG;AAChG,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,kCAAkC;IACjD,eAAe,EAAE,eAAe,CAAA;IAChC,KAAK,EAAE,KAAK,CAAA;IACZ;;;OAGG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAA;IACzC,+EAA+E;IAC/E,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,2BAA2B,CAAA;IACzD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB;AAED,qBAAa,8BAA8B;IA2B7B,OAAO,CAAC,QAAQ,CAAC,IAAI;IA1BjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4B;IAE1D;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAE9C,YAA6B,IAAI,EAAE,kCAAkC,EAAI;IAEzE;;;OAGG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCnF;IAED,2FAA2F;IAC3F,OAAO,CAAC,QAAQ;IAUhB;;;;OAIG;YACW,iBAAiB;YAOjB,YAAY;IAY1B;;;;;OAKG;IACH,OAAO,CAAC,MAAM;CAef"}
@@ -0,0 +1,134 @@
1
+ import { DEFAULT_WORKSPACE_SETTINGS } from '@cat-factory/kernel';
2
+ import { composePrVerificationReport, renderPrVerificationReport } from './prReport.logic.js';
3
+ export class PrVerificationReportController {
4
+ deps;
5
+ /**
6
+ * The last section published per execution id, so settlements that change nothing a reader
7
+ * would see cost no PR edit at all.
8
+ *
9
+ * It does NOT collapse a run to one edit: the report carries a per-step state table, so most
10
+ * settlements genuinely do change it and the report tracks the run as it progresses — which
11
+ * is the intent ("rewritten in place as the run progresses"). What the cache removes is the
12
+ * repeat write from a replayed durable step, a re-poll, or a settlement whose evidence is
13
+ * identical to the last one.
14
+ *
15
+ * In-process only and deliberately so: it is a WRITE-AVOIDANCE cache, never a correctness
16
+ * mechanism — the marker splice is idempotent, so a cold process (or a peer replica) simply
17
+ * re-publishes the same section and the adapter's own unchanged-check suppresses the remote
18
+ * write.
19
+ */
20
+ lastPublished = new Map();
21
+ /**
22
+ * Hard cap on {@link lastPublished}. A long-lived Node replica serves an unbounded number of
23
+ * runs, and each entry holds a rendered section, so the map is bounded here rather than
24
+ * relying on a call site to evict finished runs (a coupling that would silently leak the
25
+ * moment a new terminal path forgot to call it). Oldest-first eviction: `Map` preserves
26
+ * insertion order, and evicting an entry only costs one redundant (idempotent) republish.
27
+ */
28
+ static MAX_TRACKED_RUNS = 256;
29
+ constructor(deps) {
30
+ this.deps = deps;
31
+ }
32
+ /**
33
+ * Compose the report for `instance` and upsert it onto the run's PR. Best-effort: returns
34
+ * silently on every skip/failure path.
35
+ */
36
+ async publishForRun(workspaceId, instance) {
37
+ const publisher = this.deps.publisher;
38
+ if (!publisher)
39
+ return;
40
+ try {
41
+ if (!(await this.publishingEnabled(workspaceId)))
42
+ return;
43
+ // Ask the adapter WHERE this would go before composing anything: it both short-circuits a
44
+ // run that has no PR yet (most runs, for most of their life) and supplies the repo +
45
+ // provider the report states — the same resolution the write itself uses, so the report
46
+ // can never name a different repo from the one it lands on.
47
+ const target = await publisher.resolveTarget(workspaceId, instance.blockId);
48
+ if (!target)
49
+ return;
50
+ const block = await this.deps.blockRepository.get(workspaceId, instance.blockId);
51
+ // Only a task carries an implementation PR; a frame/module run has nothing to report on.
52
+ if (!block)
53
+ return;
54
+ const section = renderPrVerificationReport(composePrVerificationReport(instance, {
55
+ block,
56
+ issues: await this.linkedIssues(workspaceId, instance.blockId),
57
+ repo: target.repo,
58
+ provider: target.provider,
59
+ runUrl: this.runUrl(workspaceId, instance),
60
+ now: this.deps.clock.now(),
61
+ }));
62
+ // `generatedAt` changes on every compose, so compare the section with it masked out —
63
+ // otherwise the cache would never hit and every step would edit the PR.
64
+ const fingerprint = section.replaceAll(/"generatedAt": \d+/g, '"generatedAt": 0');
65
+ if (this.lastPublished.get(instance.id) === fingerprint)
66
+ return;
67
+ await publisher.publish(workspaceId, block.id, section);
68
+ this.remember(instance.id, fingerprint);
69
+ }
70
+ catch (error) {
71
+ // A PR-report write is bookkeeping. A provider outage, a revoked token, or a PR someone
72
+ // closed underneath the run must never turn a green run red.
73
+ this.deps.logger?.warn({ err: error, executionId: instance.id, blockId: instance.blockId, workspaceId }, 'Failed to publish the PR verification report');
74
+ }
75
+ }
76
+ /** Record the published fingerprint, evicting the oldest entry once the cap is reached. */
77
+ remember(executionId, fingerprint) {
78
+ this.lastPublished.delete(executionId);
79
+ this.lastPublished.set(executionId, fingerprint);
80
+ while (this.lastPublished.size > PrVerificationReportController.MAX_TRACKED_RUNS) {
81
+ const oldest = this.lastPublished.keys().next();
82
+ if (oldest.done)
83
+ break;
84
+ this.lastPublished.delete(oldest.value);
85
+ }
86
+ }
87
+ /**
88
+ * The workspace's opt-out. Checked BEFORE anything is read or composed, so a workspace that
89
+ * turned the report off pays nothing for the hook. A workspace with no saved settings row
90
+ * reads as the default (on).
91
+ */
92
+ async publishingEnabled(workspaceId) {
93
+ const repo = this.deps.workspaceSettingsRepository;
94
+ if (!repo)
95
+ return true;
96
+ const settings = (await repo.get(workspaceId)) ?? DEFAULT_WORKSPACE_SETTINGS;
97
+ return settings.publishPrVerificationReport;
98
+ }
99
+ async linkedIssues(workspaceId, blockId) {
100
+ const repo = this.deps.taskRepository;
101
+ if (!repo)
102
+ return [];
103
+ const records = await repo.listByBlock(workspaceId, blockId);
104
+ return records.map((record) => ({
105
+ source: record.source,
106
+ externalId: record.externalId,
107
+ title: record.title,
108
+ url: record.url,
109
+ }));
110
+ }
111
+ /**
112
+ * The deep link into the run's observability panel (Model activity / Provided context).
113
+ * The SPA is a single canvas, so the target is the board with the run's view params — see
114
+ * `useRunDeepLink` in `@cat-factory/app`, and slice 4 of the global-search initiative,
115
+ * which will generalise the parser.
116
+ */
117
+ runUrl(workspaceId, instance) {
118
+ const base = this.deps.appBaseUrl?.trim();
119
+ if (!base)
120
+ return null;
121
+ // Built by hand rather than with `URLSearchParams`: orchestration is runtime-neutral and
122
+ // compiles without the DOM lib, so the global isn't in its type surface.
123
+ const query = Object.entries({
124
+ ws: workspaceId,
125
+ block: instance.blockId,
126
+ run: instance.id,
127
+ view: 'observability',
128
+ })
129
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
130
+ .join('&');
131
+ return `${base.replace(/\/$/, '')}/?${query}`;
132
+ }
133
+ }
134
+ //# sourceMappingURL=PrVerificationReportController.js.map