@cat-factory/orchestration 0.6.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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/dist/container.d.ts +460 -0
  3. package/dist/container.d.ts.map +1 -0
  4. package/dist/container.js +657 -0
  5. package/dist/container.js.map +1 -0
  6. package/dist/index.d.ts +29 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +31 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/modules/board/BoardService.d.ts +125 -0
  11. package/dist/modules/board/BoardService.d.ts.map +1 -0
  12. package/dist/modules/board/BoardService.js +496 -0
  13. package/dist/modules/board/BoardService.js.map +1 -0
  14. package/dist/modules/board/board.logic.d.ts +17 -0
  15. package/dist/modules/board/board.logic.d.ts.map +1 -0
  16. package/dist/modules/board/board.logic.js +51 -0
  17. package/dist/modules/board/board.logic.js.map +1 -0
  18. package/dist/modules/boardScan/BoardScanService.d.ts +35 -0
  19. package/dist/modules/boardScan/BoardScanService.d.ts.map +1 -0
  20. package/dist/modules/boardScan/BoardScanService.js +91 -0
  21. package/dist/modules/boardScan/BoardScanService.js.map +1 -0
  22. package/dist/modules/boardScan/board-scan.logic.d.ts +10 -0
  23. package/dist/modules/boardScan/board-scan.logic.d.ts.map +1 -0
  24. package/dist/modules/boardScan/board-scan.logic.js +26 -0
  25. package/dist/modules/boardScan/board-scan.logic.js.map +1 -0
  26. package/dist/modules/bootstrap/BootstrapService.d.ts +114 -0
  27. package/dist/modules/bootstrap/BootstrapService.d.ts.map +1 -0
  28. package/dist/modules/bootstrap/BootstrapService.js +516 -0
  29. package/dist/modules/bootstrap/BootstrapService.js.map +1 -0
  30. package/dist/modules/clarity/ClarityReviewService.d.ts +48 -0
  31. package/dist/modules/clarity/ClarityReviewService.d.ts.map +1 -0
  32. package/dist/modules/clarity/ClarityReviewService.js +63 -0
  33. package/dist/modules/clarity/ClarityReviewService.js.map +1 -0
  34. package/dist/modules/clarity/clarity.logic.d.ts +36 -0
  35. package/dist/modules/clarity/clarity.logic.d.ts.map +1 -0
  36. package/dist/modules/clarity/clarity.logic.js +98 -0
  37. package/dist/modules/clarity/clarity.logic.js.map +1 -0
  38. package/dist/modules/estimation/estimate.logic.d.ts +11 -0
  39. package/dist/modules/estimation/estimate.logic.d.ts.map +1 -0
  40. package/dist/modules/estimation/estimate.logic.js +37 -0
  41. package/dist/modules/estimation/estimate.logic.js.map +1 -0
  42. package/dist/modules/execution/AgentContextBuilder.d.ts +114 -0
  43. package/dist/modules/execution/AgentContextBuilder.d.ts.map +1 -0
  44. package/dist/modules/execution/AgentContextBuilder.js +316 -0
  45. package/dist/modules/execution/AgentContextBuilder.js.map +1 -0
  46. package/dist/modules/execution/CompanionController.d.ts +60 -0
  47. package/dist/modules/execution/CompanionController.d.ts.map +1 -0
  48. package/dist/modules/execution/CompanionController.js +216 -0
  49. package/dist/modules/execution/CompanionController.js.map +1 -0
  50. package/dist/modules/execution/ExecutionService.d.ts +874 -0
  51. package/dist/modules/execution/ExecutionService.d.ts.map +1 -0
  52. package/dist/modules/execution/ExecutionService.js +2921 -0
  53. package/dist/modules/execution/ExecutionService.js.map +1 -0
  54. package/dist/modules/execution/MergeResolver.d.ts +34 -0
  55. package/dist/modules/execution/MergeResolver.d.ts.map +1 -0
  56. package/dist/modules/execution/MergeResolver.js +81 -0
  57. package/dist/modules/execution/MergeResolver.js.map +1 -0
  58. package/dist/modules/execution/ReviewGateController.d.ts +163 -0
  59. package/dist/modules/execution/ReviewGateController.d.ts.map +1 -0
  60. package/dist/modules/execution/ReviewGateController.js +251 -0
  61. package/dist/modules/execution/ReviewGateController.js.map +1 -0
  62. package/dist/modules/execution/TesterController.d.ts +61 -0
  63. package/dist/modules/execution/TesterController.d.ts.map +1 -0
  64. package/dist/modules/execution/TesterController.js +215 -0
  65. package/dist/modules/execution/TesterController.js.map +1 -0
  66. package/dist/modules/execution/advance.d.ts +84 -0
  67. package/dist/modules/execution/advance.d.ts.map +1 -0
  68. package/dist/modules/execution/advance.js +2 -0
  69. package/dist/modules/execution/advance.js.map +1 -0
  70. package/dist/modules/execution/artifact-review.logic.d.ts +25 -0
  71. package/dist/modules/execution/artifact-review.logic.d.ts.map +1 -0
  72. package/dist/modules/execution/artifact-review.logic.js +39 -0
  73. package/dist/modules/execution/artifact-review.logic.js.map +1 -0
  74. package/dist/modules/execution/ci.logic.d.ts +101 -0
  75. package/dist/modules/execution/ci.logic.d.ts.map +1 -0
  76. package/dist/modules/execution/ci.logic.js +117 -0
  77. package/dist/modules/execution/ci.logic.js.map +1 -0
  78. package/dist/modules/execution/drive.d.ts +47 -0
  79. package/dist/modules/execution/drive.d.ts.map +1 -0
  80. package/dist/modules/execution/drive.js +112 -0
  81. package/dist/modules/execution/drive.js.map +1 -0
  82. package/dist/modules/execution/gates.d.ts +97 -0
  83. package/dist/modules/execution/gates.d.ts.map +1 -0
  84. package/dist/modules/execution/gates.js +2 -0
  85. package/dist/modules/execution/gates.js.map +1 -0
  86. package/dist/modules/execution/individualVendors.logic.d.ts +22 -0
  87. package/dist/modules/execution/individualVendors.logic.d.ts.map +1 -0
  88. package/dist/modules/execution/individualVendors.logic.js +33 -0
  89. package/dist/modules/execution/individualVendors.logic.js.map +1 -0
  90. package/dist/modules/execution/job.logic.d.ts +52 -0
  91. package/dist/modules/execution/job.logic.d.ts.map +1 -0
  92. package/dist/modules/execution/job.logic.js +56 -0
  93. package/dist/modules/execution/job.logic.js.map +1 -0
  94. package/dist/modules/execution/release.logic.d.ts +43 -0
  95. package/dist/modules/execution/release.logic.d.ts.map +1 -0
  96. package/dist/modules/execution/release.logic.js +49 -0
  97. package/dist/modules/execution/release.logic.js.map +1 -0
  98. package/dist/modules/execution/retry.logic.d.ts +40 -0
  99. package/dist/modules/execution/retry.logic.d.ts.map +1 -0
  100. package/dist/modules/execution/retry.logic.js +83 -0
  101. package/dist/modules/execution/retry.logic.js.map +1 -0
  102. package/dist/modules/execution/stepGating.logic.d.ts +15 -0
  103. package/dist/modules/execution/stepGating.logic.d.ts.map +1 -0
  104. package/dist/modules/execution/stepGating.logic.js +29 -0
  105. package/dist/modules/execution/stepGating.logic.js.map +1 -0
  106. package/dist/modules/execution/stepResolvers.d.ts +41 -0
  107. package/dist/modules/execution/stepResolvers.d.ts.map +1 -0
  108. package/dist/modules/execution/stepResolvers.js +2 -0
  109. package/dist/modules/execution/stepResolvers.js.map +1 -0
  110. package/dist/modules/execution/tester-infra.logic.d.ts +42 -0
  111. package/dist/modules/execution/tester-infra.logic.d.ts.map +1 -0
  112. package/dist/modules/execution/tester-infra.logic.js +46 -0
  113. package/dist/modules/execution/tester-infra.logic.js.map +1 -0
  114. package/dist/modules/merge/MergePresetService.d.ts +32 -0
  115. package/dist/modules/merge/MergePresetService.d.ts.map +1 -0
  116. package/dist/modules/merge/MergePresetService.js +109 -0
  117. package/dist/modules/merge/MergePresetService.js.map +1 -0
  118. package/dist/modules/modelDefaults/ModelDefaultsService.d.ts +22 -0
  119. package/dist/modules/modelDefaults/ModelDefaultsService.d.ts.map +1 -0
  120. package/dist/modules/modelDefaults/ModelDefaultsService.js +28 -0
  121. package/dist/modules/modelDefaults/ModelDefaultsService.js.map +1 -0
  122. package/dist/modules/notifications/NotificationService.d.ts +74 -0
  123. package/dist/modules/notifications/NotificationService.d.ts.map +1 -0
  124. package/dist/modules/notifications/NotificationService.js +131 -0
  125. package/dist/modules/notifications/NotificationService.js.map +1 -0
  126. package/dist/modules/observability/LlmObservabilityService.d.ts +121 -0
  127. package/dist/modules/observability/LlmObservabilityService.d.ts.map +1 -0
  128. package/dist/modules/observability/LlmObservabilityService.js +140 -0
  129. package/dist/modules/observability/LlmObservabilityService.js.map +1 -0
  130. package/dist/modules/observability/observability.logic.d.ts +57 -0
  131. package/dist/modules/observability/observability.logic.d.ts.map +1 -0
  132. package/dist/modules/observability/observability.logic.js +186 -0
  133. package/dist/modules/observability/observability.logic.js.map +1 -0
  134. package/dist/modules/pipelines/PipelineService.d.ts +54 -0
  135. package/dist/modules/pipelines/PipelineService.d.ts.map +1 -0
  136. package/dist/modules/pipelines/PipelineService.js +226 -0
  137. package/dist/modules/pipelines/PipelineService.js.map +1 -0
  138. package/dist/modules/pipelines/pipelineShape.d.ts +53 -0
  139. package/dist/modules/pipelines/pipelineShape.d.ts.map +1 -0
  140. package/dist/modules/pipelines/pipelineShape.js +74 -0
  141. package/dist/modules/pipelines/pipelineShape.js.map +1 -0
  142. package/dist/modules/recurring/RecurringPipelineService.d.ts +76 -0
  143. package/dist/modules/recurring/RecurringPipelineService.d.ts.map +1 -0
  144. package/dist/modules/recurring/RecurringPipelineService.js +295 -0
  145. package/dist/modules/recurring/RecurringPipelineService.js.map +1 -0
  146. package/dist/modules/recurring/TrackerSettingsService.d.ts +16 -0
  147. package/dist/modules/recurring/TrackerSettingsService.d.ts.map +1 -0
  148. package/dist/modules/recurring/TrackerSettingsService.js +30 -0
  149. package/dist/modules/recurring/TrackerSettingsService.js.map +1 -0
  150. package/dist/modules/recurring/schedule.logic.d.ts +14 -0
  151. package/dist/modules/recurring/schedule.logic.d.ts.map +1 -0
  152. package/dist/modules/recurring/schedule.logic.js +85 -0
  153. package/dist/modules/recurring/schedule.logic.js.map +1 -0
  154. package/dist/modules/releaseHealth/ReleaseHealthService.d.ts +38 -0
  155. package/dist/modules/releaseHealth/ReleaseHealthService.d.ts.map +1 -0
  156. package/dist/modules/releaseHealth/ReleaseHealthService.js +96 -0
  157. package/dist/modules/releaseHealth/ReleaseHealthService.js.map +1 -0
  158. package/dist/modules/requirements/RequirementReviewService.d.ts +48 -0
  159. package/dist/modules/requirements/RequirementReviewService.d.ts.map +1 -0
  160. package/dist/modules/requirements/RequirementReviewService.js +83 -0
  161. package/dist/modules/requirements/RequirementReviewService.js.map +1 -0
  162. package/dist/modules/requirements/requirements.logic.d.ts +93 -0
  163. package/dist/modules/requirements/requirements.logic.d.ts.map +1 -0
  164. package/dist/modules/requirements/requirements.logic.js +203 -0
  165. package/dist/modules/requirements/requirements.logic.js.map +1 -0
  166. package/dist/modules/review/IterativeReviewService.d.ts +175 -0
  167. package/dist/modules/review/IterativeReviewService.d.ts.map +1 -0
  168. package/dist/modules/review/IterativeReviewService.js +327 -0
  169. package/dist/modules/review/IterativeReviewService.js.map +1 -0
  170. package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsService.d.ts +20 -0
  171. package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsService.d.ts.map +1 -0
  172. package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsService.js +26 -0
  173. package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsService.js.map +1 -0
  174. package/dist/modules/services/ServiceMountService.d.ts +48 -0
  175. package/dist/modules/services/ServiceMountService.d.ts.map +1 -0
  176. package/dist/modules/services/ServiceMountService.js +90 -0
  177. package/dist/modules/services/ServiceMountService.js.map +1 -0
  178. package/dist/modules/settings/WorkspaceSettingsService.d.ts +22 -0
  179. package/dist/modules/settings/WorkspaceSettingsService.d.ts.map +1 -0
  180. package/dist/modules/settings/WorkspaceSettingsService.js +50 -0
  181. package/dist/modules/settings/WorkspaceSettingsService.js.map +1 -0
  182. package/package.json +41 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stepResolvers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stepResolvers.js","sourceRoot":"","sources":["../../../src/modules/execution/stepResolvers.ts"],"names":[],"mappings":""}
@@ -0,0 +1,42 @@
1
+ /** The Tester's resolved environment choice (`ephemeral` is the default when unset). */
2
+ export type TesterEnvironment = 'local' | 'ephemeral';
3
+ export interface TesterInfraInput {
4
+ /** Whether the runtime can run local docker-compose infra via Docker-in-Docker. */
5
+ localTestInfraSupported: boolean;
6
+ /** The task's resolved Tester environment. */
7
+ environment: TesterEnvironment;
8
+ /** The service frame is marked as having no infra to stand up. */
9
+ noInfraDependencies: boolean;
10
+ /** The service frame has a docker-compose path to stand its infra up. */
11
+ hasComposePath: boolean;
12
+ /** An ephemeral-environment provider is wired (so a deployed URL can be provisioned). */
13
+ hasEnvironmentProvider: boolean;
14
+ }
15
+ export type TesterInfraDecision = {
16
+ ok: true;
17
+ } | {
18
+ ok: false;
19
+ reason: 'limited-local';
20
+ } | {
21
+ ok: false;
22
+ reason: 'limited-ephemeral-no-provider';
23
+ } | {
24
+ ok: false;
25
+ reason: 'local-unconfigured';
26
+ };
27
+ /**
28
+ * Decide whether a Tester pipeline may start.
29
+ *
30
+ * - **Limited mode** (runtime can't nest containers, e.g. Apple `container`): a `local`
31
+ * run is allowed only when the service stands nothing up (`noInfraDependencies`); an
32
+ * `ephemeral` run is allowed only when a provider is configured (else there's no URL,
33
+ * and no local fallback on this runtime).
34
+ * - **Capable runtime**: `ephemeral` always passes (zero-config default); `local`
35
+ * requires the service to declare a compose path or no infra.
36
+ */
37
+ export declare function decideTesterInfra(input: TesterInfraInput): TesterInfraDecision;
38
+ /** The actionable error message for each refusal reason. */
39
+ export declare const TESTER_INFRA_MESSAGES: Record<Exclude<TesterInfraDecision, {
40
+ ok: true;
41
+ }>['reason'], string>;
42
+ //# sourceMappingURL=tester-infra.logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tester-infra.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/execution/tester-infra.logic.ts"],"names":[],"mappings":"AAOA,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,WAAW,CAAA;AAErD,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,uBAAuB,EAAE,OAAO,CAAA;IAChC,8CAA8C;IAC9C,WAAW,EAAE,iBAAiB,CAAA;IAC9B,kEAAkE;IAClE,mBAAmB,EAAE,OAAO,CAAA;IAC5B,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAA;IACvB,yFAAyF;IACzF,sBAAsB,EAAE,OAAO,CAAA;CAChC;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAEZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAEtC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,+BAA+B,CAAA;CAAE,GAEtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAA;AAE/C;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,mBAAmB,CAc9E;AAED,4DAA4D;AAC5D,eAAO,MAAM,qBAAqB,EAAE,MAAM,CACxC,OAAO,CAAC,mBAAmB,EAAE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC,EACpD,MAAM,CAgBP,CAAA"}
@@ -0,0 +1,46 @@
1
+ // Pure decision for the Tester's start-time infra gate — no IO, no ports. Given the
2
+ // runtime's Docker-in-Docker capability and the task/service test-infra config, decide
3
+ // whether a Tester pipeline may start, and if not, why. ExecutionService resolves the
4
+ // inputs (service config, environment-provider presence) and translates the verdict
5
+ // into an actionable ConflictError; keeping the branching here makes the whole matrix
6
+ // (incl. the "limited mode" of a runtime without nesting) trivially testable.
7
+ /**
8
+ * Decide whether a Tester pipeline may start.
9
+ *
10
+ * - **Limited mode** (runtime can't nest containers, e.g. Apple `container`): a `local`
11
+ * run is allowed only when the service stands nothing up (`noInfraDependencies`); an
12
+ * `ephemeral` run is allowed only when a provider is configured (else there's no URL,
13
+ * and no local fallback on this runtime).
14
+ * - **Capable runtime**: `ephemeral` always passes (zero-config default); `local`
15
+ * requires the service to declare a compose path or no infra.
16
+ */
17
+ export function decideTesterInfra(input) {
18
+ if (!input.localTestInfraSupported) {
19
+ if (input.environment === 'local') {
20
+ return input.noInfraDependencies ? { ok: true } : { ok: false, reason: 'limited-local' };
21
+ }
22
+ return input.hasEnvironmentProvider
23
+ ? { ok: true }
24
+ : { ok: false, reason: 'limited-ephemeral-no-provider' };
25
+ }
26
+ if (input.environment !== 'local')
27
+ return { ok: true };
28
+ return input.noInfraDependencies || input.hasComposePath
29
+ ? { ok: true }
30
+ : { ok: false, reason: 'local-unconfigured' };
31
+ }
32
+ /** The actionable error message for each refusal reason. */
33
+ export const TESTER_INFRA_MESSAGES = {
34
+ 'limited-local': "This deployment's container runtime can't run the Tester's local docker-compose " +
35
+ 'infra (no Docker-in-Docker). Switch the Tester to the ephemeral environment (with an ' +
36
+ "environment provider configured), or mark the service 'No infra dependencies', before " +
37
+ 'starting.',
38
+ 'limited-ephemeral-no-provider': "This deployment's container runtime can't run the Tester's local infra, and no " +
39
+ 'ephemeral environment provider is configured, so the Tester has nothing to test ' +
40
+ 'against. Configure an environment provider (ENVIRONMENTS_ENABLED + a connection), or ' +
41
+ "mark the service 'No infra dependencies' and run the Tester locally.",
42
+ 'local-unconfigured': "This task's pipeline runs the Tester locally, but its service has no test infra " +
43
+ "configured. Set the service's docker-compose path, or mark it as having no infra " +
44
+ 'dependencies, before starting — or switch the Tester to the ephemeral environment.',
45
+ };
46
+ //# sourceMappingURL=tester-infra.logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tester-infra.logic.js","sourceRoot":"","sources":["../../../src/modules/execution/tester-infra.logic.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,uFAAuF;AACvF,sFAAsF;AACtF,oFAAoF;AACpF,sFAAsF;AACtF,8EAA8E;AA2B9E;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAuB;IACvD,IAAI,CAAC,KAAK,CAAC,uBAAuB,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAA;QAC1F,CAAC;QACD,OAAO,KAAK,CAAC,sBAAsB;YACjC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE;YACd,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,+BAA+B,EAAE,CAAA;IAC5D,CAAC;IAED,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;IACtD,OAAO,KAAK,CAAC,mBAAmB,IAAI,KAAK,CAAC,cAAc;QACtD,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE;QACd,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAA;AACjD,CAAC;AAED,4DAA4D;AAC5D,MAAM,CAAC,MAAM,qBAAqB,GAG9B;IACF,eAAe,EACb,kFAAkF;QAClF,uFAAuF;QACvF,wFAAwF;QACxF,WAAW;IACb,+BAA+B,EAC7B,iFAAiF;QACjF,kFAAkF;QAClF,uFAAuF;QACvF,sEAAsE;IACxE,oBAAoB,EAClB,kFAAkF;QAClF,mFAAmF;QACnF,oFAAoF;CACvF,CAAA"}
@@ -0,0 +1,32 @@
1
+ import type { Clock, CreateMergePresetInput, IdGenerator, MergePresetRepository, MergeThresholdPreset, UpdateMergePresetInput, WorkspaceRepository } from '@cat-factory/kernel';
2
+ export interface MergePresetServiceDependencies {
3
+ mergePresetRepository: MergePresetRepository;
4
+ workspaceRepository: WorkspaceRepository;
5
+ idGenerator: IdGenerator;
6
+ clock: Clock;
7
+ }
8
+ /**
9
+ * CRUD for a workspace's merge threshold presets (the library a task picks its
10
+ * auto-merge policy from). Maintains the invariant that a workspace always has at
11
+ * least one preset, exactly one of which is the default: {@link list} lazily seeds
12
+ * the built-in {@link DEFAULT_MERGE_PRESET} on first use, and the default cannot be
13
+ * deleted. The single-default promotion is enforced in the repository.
14
+ */
15
+ export declare class MergePresetService {
16
+ private readonly presets;
17
+ private readonly workspaceRepository;
18
+ private readonly idGenerator;
19
+ private readonly clock;
20
+ constructor(deps: MergePresetServiceDependencies);
21
+ /** List a workspace's presets, seeding the built-in default if none exist yet. */
22
+ list(workspaceId: string): Promise<MergeThresholdPreset[]>;
23
+ /** Create a new preset. The first one (or one flagged default) becomes the default. */
24
+ create(workspaceId: string, input: CreateMergePresetInput): Promise<MergeThresholdPreset>;
25
+ /** Patch a preset. Demoting the only default is rejected (one must remain). */
26
+ update(workspaceId: string, id: string, patch: UpdateMergePresetInput): Promise<MergeThresholdPreset>;
27
+ /** Remove a preset. The default preset cannot be removed. */
28
+ remove(workspaceId: string, id: string): Promise<void>;
29
+ /** Seed the built-in default preset for a workspace that has none yet. Idempotent. */
30
+ private ensureDefault;
31
+ }
32
+ //# sourceMappingURL=MergePresetService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MergePresetService.d.ts","sourceRoot":"","sources":["../../../src/modules/merge/MergePresetService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,sBAAsB,EACtB,WAAW,EACX,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAQ5B,MAAM,WAAW,8BAA8B;IAC7C,qBAAqB,EAAE,qBAAqB,CAAA;IAC5C,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;CACb;AAED;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAE7B,YAAY,IAAI,EAAE,8BAA8B,EAK/C;IAED,kFAAkF;IAC5E,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAI/D;IAED,uFAAuF;IACjF,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAoB9F;IAED,+EAA+E;IACzE,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CA6B/B;IAED,6DAA6D;IACvD,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO3D;IAED,sFAAsF;YACxE,aAAa;CAkB5B"}
@@ -0,0 +1,109 @@
1
+ import { assertFound, ConflictError, DEFAULT_MERGE_PRESET, requireWorkspace, } from '@cat-factory/kernel';
2
+ /**
3
+ * CRUD for a workspace's merge threshold presets (the library a task picks its
4
+ * auto-merge policy from). Maintains the invariant that a workspace always has at
5
+ * least one preset, exactly one of which is the default: {@link list} lazily seeds
6
+ * the built-in {@link DEFAULT_MERGE_PRESET} on first use, and the default cannot be
7
+ * deleted. The single-default promotion is enforced in the repository.
8
+ */
9
+ export class MergePresetService {
10
+ presets;
11
+ workspaceRepository;
12
+ idGenerator;
13
+ clock;
14
+ constructor(deps) {
15
+ this.presets = deps.mergePresetRepository;
16
+ this.workspaceRepository = deps.workspaceRepository;
17
+ this.idGenerator = deps.idGenerator;
18
+ this.clock = deps.clock;
19
+ }
20
+ /** List a workspace's presets, seeding the built-in default if none exist yet. */
21
+ async list(workspaceId) {
22
+ await requireWorkspace(this.workspaceRepository, workspaceId);
23
+ await this.ensureDefault(workspaceId);
24
+ return this.presets.list(workspaceId);
25
+ }
26
+ /** Create a new preset. The first one (or one flagged default) becomes the default. */
27
+ async create(workspaceId, input) {
28
+ await requireWorkspace(this.workspaceRepository, workspaceId);
29
+ const existing = await this.presets.list(workspaceId);
30
+ const preset = {
31
+ id: this.idGenerator.next('mp'),
32
+ name: input.name,
33
+ maxComplexity: input.maxComplexity,
34
+ maxRisk: input.maxRisk,
35
+ maxImpact: input.maxImpact,
36
+ ciMaxAttempts: input.ciMaxAttempts,
37
+ maxRequirementIterations: input.maxRequirementIterations,
38
+ maxRequirementConcernAllowed: input.maxRequirementConcernAllowed,
39
+ releaseWatchWindowMinutes: input.releaseWatchWindowMinutes,
40
+ releaseMaxAttempts: input.releaseMaxAttempts,
41
+ // The very first preset must be the default; otherwise honour the request.
42
+ isDefault: existing.length === 0 ? true : input.isDefault,
43
+ createdAt: this.clock.now(),
44
+ };
45
+ await this.presets.upsert(workspaceId, preset);
46
+ return preset;
47
+ }
48
+ /** Patch a preset. Demoting the only default is rejected (one must remain). */
49
+ async update(workspaceId, id, patch) {
50
+ await requireWorkspace(this.workspaceRepository, workspaceId);
51
+ const existing = assertFound(await this.presets.get(workspaceId, id), 'MergePreset', id);
52
+ if (existing.isDefault && patch.isDefault === false) {
53
+ throw new ConflictError('Cannot unset the default preset; promote another preset instead.');
54
+ }
55
+ const updated = {
56
+ ...existing,
57
+ ...(patch.name !== undefined ? { name: patch.name } : {}),
58
+ ...(patch.maxComplexity !== undefined ? { maxComplexity: patch.maxComplexity } : {}),
59
+ ...(patch.maxRisk !== undefined ? { maxRisk: patch.maxRisk } : {}),
60
+ ...(patch.maxImpact !== undefined ? { maxImpact: patch.maxImpact } : {}),
61
+ ...(patch.ciMaxAttempts !== undefined ? { ciMaxAttempts: patch.ciMaxAttempts } : {}),
62
+ ...(patch.maxRequirementIterations !== undefined
63
+ ? { maxRequirementIterations: patch.maxRequirementIterations }
64
+ : {}),
65
+ ...(patch.maxRequirementConcernAllowed !== undefined
66
+ ? { maxRequirementConcernAllowed: patch.maxRequirementConcernAllowed }
67
+ : {}),
68
+ ...(patch.releaseWatchWindowMinutes !== undefined
69
+ ? { releaseWatchWindowMinutes: patch.releaseWatchWindowMinutes }
70
+ : {}),
71
+ ...(patch.releaseMaxAttempts !== undefined
72
+ ? { releaseMaxAttempts: patch.releaseMaxAttempts }
73
+ : {}),
74
+ ...(patch.isDefault !== undefined ? { isDefault: patch.isDefault } : {}),
75
+ };
76
+ await this.presets.upsert(workspaceId, updated);
77
+ return updated;
78
+ }
79
+ /** Remove a preset. The default preset cannot be removed. */
80
+ async remove(workspaceId, id) {
81
+ await requireWorkspace(this.workspaceRepository, workspaceId);
82
+ const existing = await this.presets.get(workspaceId, id);
83
+ if (existing?.isDefault) {
84
+ throw new ConflictError('Cannot delete the default preset; promote another preset first.');
85
+ }
86
+ await this.presets.remove(workspaceId, id);
87
+ }
88
+ /** Seed the built-in default preset for a workspace that has none yet. Idempotent. */
89
+ async ensureDefault(workspaceId) {
90
+ const current = await this.presets.list(workspaceId);
91
+ if (current.length > 0)
92
+ return;
93
+ await this.presets.upsert(workspaceId, {
94
+ id: this.idGenerator.next('mp'),
95
+ name: DEFAULT_MERGE_PRESET.name,
96
+ maxComplexity: DEFAULT_MERGE_PRESET.maxComplexity,
97
+ maxRisk: DEFAULT_MERGE_PRESET.maxRisk,
98
+ maxImpact: DEFAULT_MERGE_PRESET.maxImpact,
99
+ ciMaxAttempts: DEFAULT_MERGE_PRESET.ciMaxAttempts,
100
+ maxRequirementIterations: DEFAULT_MERGE_PRESET.maxRequirementIterations,
101
+ maxRequirementConcernAllowed: DEFAULT_MERGE_PRESET.maxRequirementConcernAllowed,
102
+ releaseWatchWindowMinutes: DEFAULT_MERGE_PRESET.releaseWatchWindowMinutes,
103
+ releaseMaxAttempts: DEFAULT_MERGE_PRESET.releaseMaxAttempts,
104
+ isDefault: true,
105
+ createdAt: this.clock.now(),
106
+ });
107
+ }
108
+ }
109
+ //# sourceMappingURL=MergePresetService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MergePresetService.js","sourceRoot":"","sources":["../../../src/modules/merge/MergePresetService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { ModelDefaults, ModelDefaultsRepository, SetModelDefaultsInput, WorkspaceRepository } from '@cat-factory/kernel';
2
+ export interface ModelDefaultsServiceDependencies {
3
+ modelDefaultsRepository: ModelDefaultsRepository;
4
+ workspaceRepository: WorkspaceRepository;
5
+ }
6
+ /**
7
+ * Read/replace a workspace's per-agent-kind default models (the model each agent
8
+ * kind defaults to, overriding the env routing for that workspace). The map is
9
+ * keyed by agent kind and valued by a model catalog id; sending the full map
10
+ * replaces it wholesale (a kind omitted is cleared). A kind absent from the map
11
+ * falls back to the env routing for that kind at run time.
12
+ */
13
+ export declare class ModelDefaultsService {
14
+ private readonly defaults;
15
+ private readonly workspaceRepository;
16
+ constructor(deps: ModelDefaultsServiceDependencies);
17
+ /** The workspace's per-kind default map (empty when none set). */
18
+ get(workspaceId: string): Promise<ModelDefaults>;
19
+ /** Replace the workspace's per-kind default map and return the stored result. */
20
+ set(workspaceId: string, input: SetModelDefaultsInput): Promise<ModelDefaults>;
21
+ }
22
+ //# sourceMappingURL=ModelDefaultsService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelDefaultsService.d.ts","sourceRoot":"","sources":["../../../src/modules/modelDefaults/ModelDefaultsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAG5B,MAAM,WAAW,gCAAgC;IAC/C,uBAAuB,EAAE,uBAAuB,CAAA;IAChD,mBAAmB,EAAE,mBAAmB,CAAA;CACzC;AAED;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,YAAY,IAAI,EAAE,gCAAgC,EAGjD;IAED,kEAAkE;IAC5D,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAGrD;IAED,iFAAiF;IAC3E,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,aAAa,CAAC,CAInF;CACF"}
@@ -0,0 +1,28 @@
1
+ import { requireWorkspace } from '@cat-factory/kernel';
2
+ /**
3
+ * Read/replace a workspace's per-agent-kind default models (the model each agent
4
+ * kind defaults to, overriding the env routing for that workspace). The map is
5
+ * keyed by agent kind and valued by a model catalog id; sending the full map
6
+ * replaces it wholesale (a kind omitted is cleared). A kind absent from the map
7
+ * falls back to the env routing for that kind at run time.
8
+ */
9
+ export class ModelDefaultsService {
10
+ defaults;
11
+ workspaceRepository;
12
+ constructor(deps) {
13
+ this.defaults = deps.modelDefaultsRepository;
14
+ this.workspaceRepository = deps.workspaceRepository;
15
+ }
16
+ /** The workspace's per-kind default map (empty when none set). */
17
+ async get(workspaceId) {
18
+ await requireWorkspace(this.workspaceRepository, workspaceId);
19
+ return { defaults: await this.defaults.get(workspaceId) };
20
+ }
21
+ /** Replace the workspace's per-kind default map and return the stored result. */
22
+ async set(workspaceId, input) {
23
+ await requireWorkspace(this.workspaceRepository, workspaceId);
24
+ await this.defaults.replace(workspaceId, input.defaults);
25
+ return { defaults: input.defaults };
26
+ }
27
+ }
28
+ //# sourceMappingURL=ModelDefaultsService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelDefaultsService.js","sourceRoot":"","sources":["../../../src/modules/modelDefaults/ModelDefaultsService.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAOtD;;;;;;GAMG;AACH,MAAM,OAAO,oBAAoB;IACd,QAAQ,CAAyB;IACjC,mBAAmB,CAAqB;IAEzD,YAAY,IAAsC;QAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,uBAAuB,CAAA;QAC5C,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAA;IACrD,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,GAAG,CAAC,WAAmB;QAC3B,MAAM,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAA;IAC3D,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,GAAG,CAAC,WAAmB,EAAE,KAA4B;QACzD,MAAM,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QAC7D,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;QACxD,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAA;IACrC,CAAC;CACF"}
@@ -0,0 +1,74 @@
1
+ import type { Clock, IdGenerator, Notification, NotificationChannel, NotificationPayload, NotificationRepository, NotificationType, ResolveNotificationAction } from '@cat-factory/kernel';
2
+ import type { WorkspaceRepository } from '@cat-factory/kernel';
3
+ export interface NotificationServiceDependencies {
4
+ notificationRepository: NotificationRepository;
5
+ workspaceRepository: WorkspaceRepository;
6
+ idGenerator: IdGenerator;
7
+ clock: Clock;
8
+ /**
9
+ * How humans are told. Defaults to in-app delivery (the `notification`
10
+ * WorkspaceEvent) wired by the worker; email / Slack channels compose in via
11
+ * {@link CompositeNotificationChannel} with no change here. Optional so tests
12
+ * can omit it (the repository remains the canonical store either way).
13
+ */
14
+ channel?: NotificationChannel;
15
+ }
16
+ /** What a caller (the execution engine) supplies to raise a notification. */
17
+ export interface RaiseNotificationInput {
18
+ type: NotificationType;
19
+ blockId: string | null;
20
+ executionId: string | null;
21
+ title: string;
22
+ body: string;
23
+ payload?: NotificationPayload | null;
24
+ }
25
+ /**
26
+ * Owns the lifecycle of human-actionable notifications: the canonical D1-backed
27
+ * store (so the inbox + snapshot can render them) plus delivery to the configured
28
+ * channel(s). The *action* a notification triggers (merge a PR, confirm a
29
+ * pipeline, retry a run) is performed by the worker's controller; this service
30
+ * only raises, lists and resolves — keeping it free of execution/GitHub concerns.
31
+ */
32
+ export declare class NotificationService {
33
+ private readonly notifications;
34
+ private readonly workspaceRepository;
35
+ private readonly idGenerator;
36
+ private readonly clock;
37
+ private readonly channel?;
38
+ constructor(deps: NotificationServiceDependencies);
39
+ /**
40
+ * Raise (or refresh) a notification. To avoid stacking identical cards when a
41
+ * run is re-driven, an existing OPEN notification of the same `type` on the same
42
+ * block is replaced in place (its id is reused) rather than duplicated.
43
+ */
44
+ raise(workspaceId: string, input: RaiseNotificationInput): Promise<Notification>;
45
+ /** Resolve a notification (the human acted on it or dismissed it). Idempotent. */
46
+ resolve(workspaceId: string, id: string, action: ResolveNotificationAction): Promise<Notification>;
47
+ /** All open notifications for the workspace (for the inbox + snapshot). */
48
+ listOpen(workspaceId: string): Promise<Notification[]>;
49
+ /**
50
+ * Resolve the auto-raised "waiting for a human decision" card on a block once its run
51
+ * has advanced past the decision (the human responded, or it auto-passed). Only the
52
+ * `decision_required` type is dismissed — the human-actionable cards a stopped run
53
+ * leaves behind (`merge_review`, `pipeline_complete`, `requirement_review`, …) are
54
+ * resolved by the human acting on them, not here. Without this the card would linger
55
+ * open and the escalation sweep would later flip it red ("Overdue") for a decision that
56
+ * was already made. Idempotent + best-effort: a no-op when no such card is open.
57
+ */
58
+ clearWaitingDecision(workspaceId: string, blockId: string): Promise<void>;
59
+ /**
60
+ * Escalate long-waiting open notifications from `normal` (yellow) to `urgent` (red).
61
+ * Called by the periodic sweep with the workspace's `waitingEscalationMinutes`
62
+ * threshold (as ms). Any open notification older than `thresholdMs` that is still
63
+ * `normal` is flipped to `urgent`, persisted, and re-delivered so the inbox re-renders
64
+ * it red in real time. This is the signal that replaced the old hard decision timeout:
65
+ * runs wait indefinitely, the notification colour conveys that a human is overdue.
66
+ * Returns the number escalated.
67
+ */
68
+ escalateStale(workspaceId: string, thresholdMs: number, now: number): Promise<number>;
69
+ /** A single notification by id, or null. */
70
+ get(workspaceId: string, id: string): Promise<Notification | null>;
71
+ /** Best-effort delivery to the channel — a failure must never break the caller. */
72
+ private deliver;
73
+ }
74
+ //# sourceMappingURL=NotificationService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationService.d.ts","sourceRoot":"","sources":["../../../src/modules/notifications/NotificationService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAE9D,MAAM,WAAW,+BAA+B;IAC9C,sBAAsB,EAAE,sBAAsB,CAAA;IAC9C,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ;;;;;OAKG;IACH,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAC9B;AAED,6EAA6E;AAC7E,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACrC;AAED;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwB;IACtD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAqB;IAE9C,YAAY,IAAI,EAAE,+BAA+B,EAMhD;IAED;;;;OAIG;IACG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuBrF;IAED,kFAAkF;IAC5E,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,YAAY,CAAC,CAYvB;IAED,2EAA2E;IACrE,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAE3D;IAED;;;;;;;;OAQG;IACG,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc9E;IAED;;;;;;;;OAQG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY1F;IAED,4CAA4C;IACtC,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAEvE;IAED,mFAAmF;YACrE,OAAO;CAQtB"}
@@ -0,0 +1,131 @@
1
+ import { assertFound, requireWorkspace } from '@cat-factory/kernel';
2
+ /**
3
+ * Owns the lifecycle of human-actionable notifications: the canonical D1-backed
4
+ * store (so the inbox + snapshot can render them) plus delivery to the configured
5
+ * channel(s). The *action* a notification triggers (merge a PR, confirm a
6
+ * pipeline, retry a run) is performed by the worker's controller; this service
7
+ * only raises, lists and resolves — keeping it free of execution/GitHub concerns.
8
+ */
9
+ export class NotificationService {
10
+ notifications;
11
+ workspaceRepository;
12
+ idGenerator;
13
+ clock;
14
+ channel;
15
+ constructor(deps) {
16
+ this.notifications = deps.notificationRepository;
17
+ this.workspaceRepository = deps.workspaceRepository;
18
+ this.idGenerator = deps.idGenerator;
19
+ this.clock = deps.clock;
20
+ this.channel = deps.channel;
21
+ }
22
+ /**
23
+ * Raise (or refresh) a notification. To avoid stacking identical cards when a
24
+ * run is re-driven, an existing OPEN notification of the same `type` on the same
25
+ * block is replaced in place (its id is reused) rather than duplicated.
26
+ */
27
+ async raise(workspaceId, input) {
28
+ const existing = input.blockId
29
+ ? await this.notifications.findOpenByBlock(workspaceId, input.blockId, input.type)
30
+ : null;
31
+ const now = this.clock.now();
32
+ const notification = {
33
+ id: existing?.id ?? this.idGenerator.next('ntf'),
34
+ type: input.type,
35
+ status: 'open',
36
+ // Preserve an already-escalated severity across a re-raise (createdAt is preserved
37
+ // too, so the card keeps its "overdue" red rather than resetting to yellow).
38
+ severity: existing?.severity ?? 'normal',
39
+ blockId: input.blockId,
40
+ executionId: input.executionId,
41
+ title: input.title,
42
+ body: input.body,
43
+ payload: input.payload ?? null,
44
+ createdAt: existing?.createdAt ?? now,
45
+ resolvedAt: null,
46
+ };
47
+ await this.notifications.upsert(workspaceId, notification);
48
+ await this.deliver(workspaceId, notification);
49
+ return notification;
50
+ }
51
+ /** Resolve a notification (the human acted on it or dismissed it). Idempotent. */
52
+ async resolve(workspaceId, id, action) {
53
+ await requireWorkspace(this.workspaceRepository, workspaceId);
54
+ const existing = assertFound(await this.notifications.get(workspaceId, id), 'Notification', id);
55
+ if (existing.status !== 'open')
56
+ return existing;
57
+ const resolved = {
58
+ ...existing,
59
+ status: action === 'act' ? 'acted' : 'dismissed',
60
+ resolvedAt: this.clock.now(),
61
+ };
62
+ await this.notifications.upsert(workspaceId, resolved);
63
+ await this.deliver(workspaceId, resolved);
64
+ return resolved;
65
+ }
66
+ /** All open notifications for the workspace (for the inbox + snapshot). */
67
+ async listOpen(workspaceId) {
68
+ return this.notifications.listOpen(workspaceId);
69
+ }
70
+ /**
71
+ * Resolve the auto-raised "waiting for a human decision" card on a block once its run
72
+ * has advanced past the decision (the human responded, or it auto-passed). Only the
73
+ * `decision_required` type is dismissed — the human-actionable cards a stopped run
74
+ * leaves behind (`merge_review`, `pipeline_complete`, `requirement_review`, …) are
75
+ * resolved by the human acting on them, not here. Without this the card would linger
76
+ * open and the escalation sweep would later flip it red ("Overdue") for a decision that
77
+ * was already made. Idempotent + best-effort: a no-op when no such card is open.
78
+ */
79
+ async clearWaitingDecision(workspaceId, blockId) {
80
+ const existing = await this.notifications.findOpenByBlock(workspaceId, blockId, 'decision_required');
81
+ if (!existing)
82
+ return;
83
+ const resolved = {
84
+ ...existing,
85
+ status: 'dismissed',
86
+ resolvedAt: this.clock.now(),
87
+ };
88
+ await this.notifications.upsert(workspaceId, resolved);
89
+ await this.deliver(workspaceId, resolved);
90
+ }
91
+ /**
92
+ * Escalate long-waiting open notifications from `normal` (yellow) to `urgent` (red).
93
+ * Called by the periodic sweep with the workspace's `waitingEscalationMinutes`
94
+ * threshold (as ms). Any open notification older than `thresholdMs` that is still
95
+ * `normal` is flipped to `urgent`, persisted, and re-delivered so the inbox re-renders
96
+ * it red in real time. This is the signal that replaced the old hard decision timeout:
97
+ * runs wait indefinitely, the notification colour conveys that a human is overdue.
98
+ * Returns the number escalated.
99
+ */
100
+ async escalateStale(workspaceId, thresholdMs, now) {
101
+ const open = await this.notifications.listOpen(workspaceId);
102
+ let escalated = 0;
103
+ for (const n of open) {
104
+ if ((n.severity ?? 'normal') !== 'normal')
105
+ continue;
106
+ if (now - n.createdAt < thresholdMs)
107
+ continue;
108
+ const updated = { ...n, severity: 'urgent' };
109
+ await this.notifications.upsert(workspaceId, updated);
110
+ await this.deliver(workspaceId, updated);
111
+ escalated++;
112
+ }
113
+ return escalated;
114
+ }
115
+ /** A single notification by id, or null. */
116
+ async get(workspaceId, id) {
117
+ return this.notifications.get(workspaceId, id);
118
+ }
119
+ /** Best-effort delivery to the channel — a failure must never break the caller. */
120
+ async deliver(workspaceId, notification) {
121
+ if (!this.channel)
122
+ return;
123
+ try {
124
+ await this.channel.deliver(workspaceId, notification);
125
+ }
126
+ catch {
127
+ // The row is persisted; delivery is an optimisation. Swallow channel errors.
128
+ }
129
+ }
130
+ }
131
+ //# sourceMappingURL=NotificationService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationService.js","sourceRoot":"","sources":["../../../src/modules/notifications/NotificationService.ts"],"names":[],"mappings":"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"}