@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,295 @@
1
+ import { assertFound, ConflictError, requireWorkspace } from '@cat-factory/kernel';
2
+ import { computeNextRun } from './schedule.logic.js';
3
+ /** Default seed descriptions for the canned recurring templates. */
4
+ const TEMPLATE_DESCRIPTIONS = {
5
+ 'dep-update': 'Recurring dependency-update pass: bring this service’s dependencies up to the latest compatible versions, update lockfiles, and make sure the build and tests still pass.',
6
+ 'tech-debt': 'Recurring tech-debt remediation pass: analyse this service for the highest-value technical debt, file a tracking ticket, then implement the fix with tests.',
7
+ custom: '',
8
+ };
9
+ /**
10
+ * Manages a workspace's recurring pipelines. Each schedule owns one reused
11
+ * on-board block (a task leaf inside the chosen service frame); the cron sweeper
12
+ * calls {@link runDue} to fire every due schedule by starting its pipeline against
13
+ * that block (skipping any whose block already has an active run), recording each
14
+ * fire in the run-history table the inspector reads.
15
+ */
16
+ export class RecurringPipelineService {
17
+ schedules;
18
+ workspaceRepository;
19
+ pipelineRepository;
20
+ blockRepository;
21
+ executionRepository;
22
+ executionService;
23
+ idGenerator;
24
+ clock;
25
+ serviceRepository;
26
+ workspaceMountRepository;
27
+ constructor(deps) {
28
+ this.schedules = deps.pipelineScheduleRepository;
29
+ this.workspaceRepository = deps.workspaceRepository;
30
+ this.pipelineRepository = deps.pipelineRepository;
31
+ this.blockRepository = deps.blockRepository;
32
+ this.executionRepository = deps.executionRepository;
33
+ this.executionService = deps.executionService;
34
+ this.idGenerator = deps.idGenerator;
35
+ this.clock = deps.clock;
36
+ this.serviceRepository = deps.serviceRepository;
37
+ this.workspaceMountRepository = deps.workspaceMountRepository;
38
+ }
39
+ requireWorkspace(workspaceId) {
40
+ return requireWorkspace(this.workspaceRepository, workspaceId);
41
+ }
42
+ async list(workspaceId) {
43
+ await this.requireWorkspace(workspaceId);
44
+ // The workspace's own schedules (including legacy/seeded frames with no service) UNION
45
+ // the schedules of every service it mounts — so a shared service's schedules show on
46
+ // every board that mounts it. Dedup by id.
47
+ const seen = new Set();
48
+ const out = [];
49
+ const add = (schedule) => {
50
+ if (!seen.has(schedule.id)) {
51
+ seen.add(schedule.id);
52
+ out.push(schedule);
53
+ }
54
+ };
55
+ for (const schedule of await this.schedules.list(workspaceId))
56
+ add(schedule);
57
+ if (this.workspaceMountRepository) {
58
+ const mounts = await this.workspaceMountRepository.listByWorkspace(workspaceId);
59
+ // One batched query for every mounted service's schedules (not one round-trip per mount).
60
+ for (const schedule of await this.schedules.listByServices(mounts.map((m) => m.serviceId))) {
61
+ add(schedule);
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+ /**
67
+ * Create a recurring pipeline on a service frame. Materialises the reused on-board
68
+ * block (a task leaf inside the frame), computes the first `nextRunAt`, and
69
+ * persists the schedule.
70
+ */
71
+ async create(workspaceId, input) {
72
+ await this.requireWorkspace(workspaceId);
73
+ const frame = assertFound(await this.blockRepository.get(workspaceId, input.frameId), 'Block', input.frameId);
74
+ if (frame.level !== 'frame') {
75
+ throw new ConflictError('Recurring pipelines can only be attached to a service frame.');
76
+ }
77
+ assertFound(await this.pipelineRepository.get(workspaceId, input.pipelineId), 'Pipeline', input.pipelineId);
78
+ // The owning service (in-org sharing): the schedule + its reused block belong to the
79
+ // frame's service, so they render on — and are listed by — every workspace that mounts it.
80
+ const serviceId = this.serviceRepository
81
+ ? ((await this.serviceRepository.getByFrameBlock(frame.id))?.id ?? null)
82
+ : null;
83
+ const now = this.clock.now();
84
+ const block = {
85
+ id: this.idGenerator.next('blk'),
86
+ title: input.name,
87
+ type: 'service',
88
+ // The user's own prompt when given, else the canned template seed.
89
+ description: input.description?.trim() || TEMPLATE_DESCRIPTIONS[input.template] || '',
90
+ position: { x: 24, y: 96 },
91
+ status: 'planned',
92
+ progress: 0,
93
+ dependsOn: [],
94
+ executionId: null,
95
+ level: 'task',
96
+ parentId: frame.id,
97
+ // A recurring schedule's reused on-board block is a recurring-type task.
98
+ taskType: 'recurring',
99
+ };
100
+ await this.blockRepository.insert(workspaceId, block, serviceId);
101
+ const schedule = {
102
+ id: this.idGenerator.next('sch'),
103
+ serviceId,
104
+ blockId: block.id,
105
+ frameId: frame.id,
106
+ pipelineId: input.pipelineId,
107
+ template: input.template,
108
+ name: input.name,
109
+ recurrence: input.recurrence,
110
+ enabled: input.enabled,
111
+ lastRunAt: null,
112
+ // First fire is one interval out, rolled into the allowed window.
113
+ nextRunAt: computeNextRun(now, input.recurrence),
114
+ createdAt: now,
115
+ };
116
+ await this.schedules.upsert(workspaceId, schedule);
117
+ return schedule;
118
+ }
119
+ async update(workspaceId, id, patch) {
120
+ await this.requireWorkspace(workspaceId);
121
+ const existing = assertFound(await this.schedules.get(workspaceId, id), 'Schedule', id);
122
+ if (patch.pipelineId !== undefined) {
123
+ assertFound(await this.pipelineRepository.get(workspaceId, patch.pipelineId), 'Pipeline', patch.pipelineId);
124
+ }
125
+ const recurrence = patch.recurrence ?? existing.recurrence;
126
+ const updated = {
127
+ ...existing,
128
+ ...(patch.name !== undefined ? { name: patch.name } : {}),
129
+ ...(patch.pipelineId !== undefined ? { pipelineId: patch.pipelineId } : {}),
130
+ ...(patch.recurrence !== undefined ? { recurrence } : {}),
131
+ ...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
132
+ // Recomputing the next fire keeps a cadence change effective immediately.
133
+ ...(patch.recurrence !== undefined
134
+ ? { nextRunAt: computeNextRun(this.clock.now(), recurrence) }
135
+ : {}),
136
+ };
137
+ await this.schedules.upsert(workspaceId, updated);
138
+ if (patch.name !== undefined) {
139
+ await this.blockRepository.update(workspaceId, existing.blockId, { title: patch.name });
140
+ }
141
+ return updated;
142
+ }
143
+ /** Remove a schedule, its reused block, and its run history. */
144
+ async remove(workspaceId, id) {
145
+ await this.requireWorkspace(workspaceId);
146
+ const existing = assertFound(await this.schedules.get(workspaceId, id), 'Schedule', id);
147
+ await this.executionRepository.deleteByBlock(workspaceId, existing.blockId);
148
+ await this.blockRepository.deleteMany(workspaceId, [existing.blockId]);
149
+ await this.schedules.remove(workspaceId, id);
150
+ }
151
+ /** A schedule's run history (most recent first), with live status overlaid. */
152
+ async listRuns(workspaceId, id) {
153
+ await this.requireWorkspace(workspaceId);
154
+ const schedule = assertFound(await this.schedules.get(workspaceId, id), 'Schedule', id);
155
+ const runs = await this.schedules.listRuns(workspaceId, id);
156
+ // The most recent run's execution usually still exists (it is only replaced on
157
+ // the next fire); overlay its live status so the inspector reflects progress.
158
+ const live = await this.executionRepository.getByBlock(workspaceId, schedule.blockId);
159
+ if (!live)
160
+ return runs;
161
+ return runs.map((run) => run.executionId && run.executionId === live.id
162
+ ? { ...run, ...this.deriveRunOutcome(live) }
163
+ : run);
164
+ }
165
+ /** Fire a schedule immediately (ignoring its cadence), if its block is free. */
166
+ async runNow(workspaceId, id) {
167
+ await this.requireWorkspace(workspaceId);
168
+ const schedule = assertFound(await this.schedules.get(workspaceId, id), 'Schedule', id);
169
+ await this.fire(workspaceId, schedule, { force: true });
170
+ return assertFound(await this.schedules.get(workspaceId, id), 'Schedule', id);
171
+ }
172
+ /**
173
+ * Fire every due schedule across all workspaces. The cron/interval sweepers call
174
+ * this; it skips any schedule whose block already has an active run. Returns the
175
+ * number of runs started (for logging).
176
+ */
177
+ async runDue(now) {
178
+ const due = await this.schedules.listDue(now);
179
+ let fired = 0;
180
+ let skipped = 0;
181
+ for (const { workspaceId, schedule } of due) {
182
+ const started = await this.fire(workspaceId, schedule, { now });
183
+ if (started)
184
+ fired++;
185
+ else
186
+ skipped++;
187
+ }
188
+ return { fired, skipped };
189
+ }
190
+ /**
191
+ * Start a schedule's pipeline against its reused block. Finalises the prior run's
192
+ * history row (its execution is about to be replaced), records a new running row,
193
+ * and advances `lastRunAt`/`nextRunAt`. Returns false (without starting) when the
194
+ * block already has an active run.
195
+ */
196
+ async fire(workspaceId, schedule, opts = {}) {
197
+ const now = opts.now ?? this.clock.now();
198
+ // Individual-usage subscriptions (Claude) require their owner to be present to unlock
199
+ // them per run, so they can never run on an unattended schedule. Refuse to fire and
200
+ // record a clear failure (the user must switch the block to an API-key or pooled
201
+ // coding-plan model) rather than starting a run that would fault at dispatch. Resolve
202
+ // the vendor set with the SAME precedence dispatch uses (block pin → workspace per-kind
203
+ // default), via the engine, so a block with no pin but an individual-usage workspace
204
+ // default is caught here too — not just an explicitly pinned one.
205
+ const scheduledBlock = await this.blockRepository.get(workspaceId, schedule.blockId);
206
+ const individualVendor = scheduledBlock
207
+ ? ((await this.executionService.individualVendorsForBlock(workspaceId, schedule.blockId, schedule.pipelineId))[0] ?? null)
208
+ : null;
209
+ if (individualVendor) {
210
+ if (opts.force) {
211
+ throw new ConflictError(`This recurring pipeline targets an individual-usage ${individualVendor} model, which ` +
212
+ `cannot run on a schedule. Pick an API-key or coding-plan model.`);
213
+ }
214
+ await this.schedules.insertRun(workspaceId, {
215
+ id: this.idGenerator.next('schr'),
216
+ scheduleId: schedule.id,
217
+ executionId: null,
218
+ status: 'failed',
219
+ startedAt: now,
220
+ finishedAt: now,
221
+ outcome: `Individual-usage ${individualVendor} models cannot run on a recurring schedule.`,
222
+ });
223
+ await this.advanceCadence(workspaceId, schedule, now);
224
+ return false;
225
+ }
226
+ const prior = await this.executionRepository.getByBlock(workspaceId, schedule.blockId);
227
+ if (prior && (prior.status === 'running' || prior.status === 'paused')) {
228
+ if (opts.force) {
229
+ throw new ConflictError('This recurring pipeline already has a run in progress.');
230
+ }
231
+ // Don't overlap; leave nextRunAt so the sweeper retries next pass.
232
+ return false;
233
+ }
234
+ // Persist the prior (now terminal) run's outcome before start() deletes it.
235
+ if (prior) {
236
+ const runs = await this.schedules.listRuns(workspaceId, schedule.id);
237
+ const priorRun = runs.find((r) => r.executionId === prior.id);
238
+ if (priorRun) {
239
+ await this.schedules.updateRun(workspaceId, priorRun.id, this.deriveRunOutcome(prior));
240
+ }
241
+ }
242
+ let executionId = null;
243
+ try {
244
+ const instance = await this.executionService.start(workspaceId, schedule.blockId, schedule.pipelineId);
245
+ executionId = instance.id;
246
+ }
247
+ catch (error) {
248
+ // Record the failed fire so the history shows it, then advance the cadence.
249
+ await this.schedules.insertRun(workspaceId, {
250
+ id: this.idGenerator.next('schr'),
251
+ scheduleId: schedule.id,
252
+ executionId: null,
253
+ status: 'failed',
254
+ startedAt: now,
255
+ finishedAt: now,
256
+ outcome: error instanceof Error ? error.message : 'Failed to start run.',
257
+ });
258
+ await this.advanceCadence(workspaceId, schedule, now);
259
+ return false;
260
+ }
261
+ await this.schedules.insertRun(workspaceId, {
262
+ id: this.idGenerator.next('schr'),
263
+ scheduleId: schedule.id,
264
+ executionId,
265
+ status: 'running',
266
+ startedAt: now,
267
+ finishedAt: null,
268
+ outcome: null,
269
+ });
270
+ await this.advanceCadence(workspaceId, schedule, now);
271
+ return true;
272
+ }
273
+ async advanceCadence(workspaceId, schedule, now) {
274
+ await this.schedules.upsert(workspaceId, {
275
+ ...schedule,
276
+ lastRunAt: now,
277
+ nextRunAt: computeNextRun(now, schedule.recurrence),
278
+ });
279
+ }
280
+ /** Map an execution's state to a history-row status + short outcome. */
281
+ deriveRunOutcome(instance) {
282
+ if (instance.status === 'done') {
283
+ return { status: 'done', finishedAt: this.clock.now(), outcome: 'completed' };
284
+ }
285
+ if (instance.status === 'failed') {
286
+ return {
287
+ status: 'failed',
288
+ finishedAt: this.clock.now(),
289
+ outcome: instance.failure?.message ?? 'failed',
290
+ };
291
+ }
292
+ return { status: 'running', finishedAt: null, outcome: null };
293
+ }
294
+ }
295
+ //# sourceMappingURL=RecurringPipelineService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecurringPipelineService.js","sourceRoot":"","sources":["../../../src/modules/recurring/RecurringPipelineService.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAElF,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAqBpD,oEAAoE;AACpE,MAAM,qBAAqB,GAAqC;IAC9D,YAAY,EACV,2KAA2K;IAC7K,WAAW,EACT,6JAA6J;IAC/J,MAAM,EAAE,EAAE;CACX,CAAA;AAED;;;;;;GAMG;AACH,MAAM,OAAO,wBAAwB;IAClB,SAAS,CAA4B;IACrC,mBAAmB,CAAqB;IACxC,kBAAkB,CAAoB;IACtC,eAAe,CAAiB;IAChC,mBAAmB,CAAqB;IACxC,gBAAgB,CAAkB;IAClC,WAAW,CAAa;IACxB,KAAK,CAAO;IACZ,iBAAiB,CAAoB;IACrC,wBAAwB,CAA2B;IAEpE,YAAY,IAA0C;QACpD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,0BAA0B,CAAA;QAChD,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAA;QACnD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,CAAA;QACjD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAA;QAC3C,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAA;QACnD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAA;QAC7C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAA;QACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QACvB,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAA;QAC/C,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC,wBAAwB,CAAA;IAC/D,CAAC;IAEO,gBAAgB,CAAC,WAAmB;QAC1C,OAAO,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;IAChE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,WAAmB;QAC5B,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACxC,uFAAuF;QACvF,qFAAqF;QACrF,2CAA2C;QAC3C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;QAC9B,MAAM,GAAG,GAAuB,EAAE,CAAA;QAClC,MAAM,GAAG,GAAG,CAAC,QAA0B,EAAE,EAAE;YACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;gBACrB,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,CAAA;QACD,KAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC;YAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC5E,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,eAAe,CAAC,WAAW,CAAC,CAAA;YAC/E,0FAA0F;YAC1F,KAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;gBAC3F,GAAG,CAAC,QAAQ,CAAC,CAAA;YACf,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,WAAmB,EAAE,KAA0B;QAC1D,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACxC,MAAM,KAAK,GAAG,WAAW,CACvB,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,EAC1D,OAAO,EACP,KAAK,CAAC,OAAO,CACd,CAAA;QACD,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,8DAA8D,CAAC,CAAA;QACzF,CAAC;QACD,WAAW,CACT,MAAM,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,EAChE,UAAU,EACV,KAAK,CAAC,UAAU,CACjB,CAAA;QAED,qFAAqF;QACrF,2FAA2F;QAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB;YACtC,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;YACxE,CAAC,CAAC,IAAI,CAAA;QAER,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;QAC5B,MAAM,KAAK,GAAU;YACnB,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;YAChC,KAAK,EAAE,KAAK,CAAC,IAAI;YACjB,IAAI,EAAE,SAAS;YACf,mEAAmE;YACnE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE;YACrF,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;YAC1B,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,CAAC;YACX,SAAS,EAAE,EAAE;YACb,WAAW,EAAE,IAAI;YACjB,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,KAAK,CAAC,EAAE;YAClB,yEAAyE;YACzE,QAAQ,EAAE,WAAW;SACtB,CAAA;QACD,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,SAAS,CAAC,CAAA;QAEhE,MAAM,QAAQ,GAAqB;YACjC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;YAChC,SAAS;YACT,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,IAAI;YACf,kEAAkE;YAClE,SAAS,EAAE,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,UAAU,CAAC;YAChD,SAAS,EAAE,GAAG;SACf,CAAA;QACD,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAA;QAClD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,KAAK,CAAC,MAAM,CACV,WAAmB,EACnB,EAAU,EACV,KAA0B;QAE1B,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QACvF,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACnC,WAAW,CACT,MAAM,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,EAChE,UAAU,EACV,KAAK,CAAC,UAAU,CACjB,CAAA;QACH,CAAC;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU,CAAA;QAC1D,MAAM,OAAO,GAAqB;YAChC,GAAG,QAAQ;YACX,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,0EAA0E;YAC1E,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS;gBAChC,CAAC,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE;gBAC7D,CAAC,CAAC,EAAE,CAAC;SACR,CAAA;QACD,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;QACjD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QACzF,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,gEAAgE;IAChE,KAAK,CAAC,MAAM,CAAC,WAAmB,EAAE,EAAU;QAC1C,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QACvF,MAAM,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;QAC3E,MAAM,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;QACtE,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;IAC9C,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,QAAQ,CAAC,WAAmB,EAAE,EAAU;QAC5C,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QACvF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;QAC3D,+EAA+E;QAC/E,8EAA8E;QAC9E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;QACrF,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACtB,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,KAAK,IAAI,CAAC,EAAE;YAC5C,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE;YAC5C,CAAC,CAAC,GAAG,CACR,CAAA;IACH,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,MAAM,CAAC,WAAmB,EAAE,EAAU;QAC1C,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAA;QACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QACvF,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACvD,OAAO,WAAW,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;IAC/E,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC7C,IAAI,KAAK,GAAG,CAAC,CAAA;QACb,IAAI,OAAO,GAAG,CAAC,CAAA;QACf,KAAK,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,CAAC;YAC5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAA;YAC/D,IAAI,OAAO;gBAAE,KAAK,EAAE,CAAA;;gBACf,OAAO,EAAE,CAAA;QAChB,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IAC3B,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,IAAI,CAChB,WAAmB,EACnB,QAA0B,EAC1B,IAAI,GAAsC,EAAE;QAE5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;QAExC,sFAAsF;QACtF,oFAAoF;QACpF,iFAAiF;QACjF,sFAAsF;QACtF,wFAAwF;QACxF,qFAAqF;QACrF,kEAAkE;QAClE,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;QACpF,MAAM,gBAAgB,GAAG,cAAc;YACrC,CAAC,CAAC,CAAC,CACC,MAAM,IAAI,CAAC,gBAAgB,CAAC,yBAAyB,CACnD,WAAW,EACX,QAAQ,CAAC,OAAO,EAChB,QAAQ,CAAC,UAAU,CACpB,CACF,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;YACf,CAAC,CAAC,IAAI,CAAA;QACR,IAAI,gBAAgB,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,aAAa,CACrB,uDAAuD,gBAAgB,gBAAgB;oBACrF,iEAAiE,CACpE,CAAA;YACH,CAAC;YACD,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE;gBAC1C,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC;gBACjC,UAAU,EAAE,QAAQ,CAAC,EAAE;gBACvB,WAAW,EAAE,IAAI;gBACjB,MAAM,EAAE,QAAQ;gBAChB,SAAS,EAAE,GAAG;gBACd,UAAU,EAAE,GAAG;gBACf,OAAO,EAAE,oBAAoB,gBAAgB,6CAA6C;aAC3F,CAAC,CAAA;YACF,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAA;YACrD,OAAO,KAAK,CAAA;QACd,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;QACtF,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,EAAE,CAAC;YACvE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,aAAa,CAAC,wDAAwD,CAAC,CAAA;YACnF,CAAC;YACD,mEAAmE;YACnE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,4EAA4E;QAC5E,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;YACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,KAAK,CAAC,EAAE,CAAC,CAAA;YAC7D,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAA;YACxF,CAAC;QACH,CAAC;QAED,IAAI,WAAW,GAAkB,IAAI,CAAA;QACrC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAChD,WAAW,EACX,QAAQ,CAAC,OAAO,EAChB,QAAQ,CAAC,UAAU,CACpB,CAAA;YACD,WAAW,GAAG,QAAQ,CAAC,EAAE,CAAA;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,4EAA4E;YAC5E,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE;gBAC1C,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC;gBACjC,UAAU,EAAE,QAAQ,CAAC,EAAE;gBACvB,WAAW,EAAE,IAAI;gBACjB,MAAM,EAAE,QAAQ;gBAChB,SAAS,EAAE,GAAG;gBACd,UAAU,EAAE,GAAG;gBACf,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB;aACzE,CAAC,CAAA;YACF,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAA;YACrD,OAAO,KAAK,CAAA;QACd,CAAC;QAED,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE;YAC1C,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC;YACjC,UAAU,EAAE,QAAQ,CAAC,EAAE;YACvB,WAAW;YACX,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,GAAG;YACd,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,IAAI;SACd,CAAC,CAAA;QACF,MAAM,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAA;QACrD,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,KAAK,CAAC,cAAc,CAC1B,WAAmB,EACnB,QAA0B,EAC1B,GAAW;QAEX,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE;YACvC,GAAG,QAAQ;YACX,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,cAAc,CAAC,GAAG,EAAE,QAAQ,CAAC,UAAU,CAAC;SACpD,CAAC,CAAA;IACJ,CAAC;IAED,wEAAwE;IAChE,gBAAgB,CACtB,QAA2B;QAE3B,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAA;QAC/E,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO;gBACL,MAAM,EAAE,QAAQ;gBAChB,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC5B,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,IAAI,QAAQ;aAC/C,CAAA;QACH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC/D,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import type { Clock, PutTrackerSettingsInput, TrackerSettings, TrackerSettingsRepository, WorkspaceRepository } from '@cat-factory/kernel';
2
+ export interface TrackerSettingsServiceDependencies {
3
+ trackerSettingsRepository: TrackerSettingsRepository;
4
+ workspaceRepository: WorkspaceRepository;
5
+ clock: Clock;
6
+ }
7
+ /** Read/write a workspace's issue-tracker selection (one row per workspace). */
8
+ export declare class TrackerSettingsService {
9
+ private readonly repo;
10
+ private readonly workspaceRepository;
11
+ private readonly clock;
12
+ constructor(deps: TrackerSettingsServiceDependencies);
13
+ get(workspaceId: string): Promise<TrackerSettings>;
14
+ put(workspaceId: string, input: PutTrackerSettingsInput): Promise<TrackerSettings>;
15
+ }
16
+ //# sourceMappingURL=TrackerSettingsService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TrackerSettingsService.d.ts","sourceRoot":"","sources":["../../../src/modules/recurring/TrackerSettingsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,uBAAuB,EACvB,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAG5B,MAAM,WAAW,kCAAkC;IACjD,yBAAyB,EAAE,yBAAyB,CAAA;IACpD,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;CACb;AAKD,gFAAgF;AAChF,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA2B;IAChD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAE7B,YAAY,IAAI,EAAE,kCAAkC,EAInD;IAEK,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAGvD;IAEK,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CAUvF;CACF"}
@@ -0,0 +1,30 @@
1
+ import { requireWorkspace } from '@cat-factory/kernel';
2
+ /** The empty/unconfigured tracker settings returned before anything is set. */
3
+ const EMPTY = { tracker: null, jiraProjectKey: null };
4
+ /** Read/write a workspace's issue-tracker selection (one row per workspace). */
5
+ export class TrackerSettingsService {
6
+ repo;
7
+ workspaceRepository;
8
+ clock;
9
+ constructor(deps) {
10
+ this.repo = deps.trackerSettingsRepository;
11
+ this.workspaceRepository = deps.workspaceRepository;
12
+ this.clock = deps.clock;
13
+ }
14
+ async get(workspaceId) {
15
+ await requireWorkspace(this.workspaceRepository, workspaceId);
16
+ return (await this.repo.get(workspaceId)) ?? { ...EMPTY, updatedAt: 0 };
17
+ }
18
+ async put(workspaceId, input) {
19
+ await requireWorkspace(this.workspaceRepository, workspaceId);
20
+ const settings = {
21
+ tracker: input.tracker,
22
+ // Only keep a Jira project key when Jira is the selected tracker.
23
+ jiraProjectKey: input.tracker === 'jira' ? input.jiraProjectKey?.trim() || null : null,
24
+ updatedAt: this.clock.now(),
25
+ };
26
+ await this.repo.put(workspaceId, settings);
27
+ return settings;
28
+ }
29
+ }
30
+ //# sourceMappingURL=TrackerSettingsService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TrackerSettingsService.js","sourceRoot":"","sources":["../../../src/modules/recurring/TrackerSettingsService.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAQtD,+EAA+E;AAC/E,MAAM,KAAK,GAAuC,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAA;AAEzF,gFAAgF;AAChF,MAAM,OAAO,sBAAsB;IAChB,IAAI,CAA2B;IAC/B,mBAAmB,CAAqB;IACxC,KAAK,CAAO;IAE7B,YAAY,IAAwC;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,yBAAyB,CAAA;QAC1C,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAA;QACnD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,WAAmB;QAC3B,MAAM,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QAC7D,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;IACzE,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,WAAmB,EAAE,KAA8B;QAC3D,MAAM,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QAC7D,MAAM,QAAQ,GAAoB;YAChC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,kEAAkE;YAClE,cAAc,EAAE,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI;YACtF,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;SAC5B,CAAA;QACD,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAA;QAC1C,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import type { Recurrence } from '@cat-factory/kernel';
2
+ /** The local weekday (0=Sun..6=Sat) and hour (0..23) of `ms` in `timezone`. */
3
+ export declare function localParts(ms: number, timezone: string): {
4
+ weekday: number;
5
+ hour: number;
6
+ };
7
+ /** Whether `ms` is an instant the schedule is allowed to fire at. */
8
+ export declare function isWithinWindow(ms: number, recurrence: Recurrence): boolean;
9
+ /**
10
+ * The next fire time after `fromMs`: advance by `intervalHours`, then roll forward
11
+ * to the next instant inside the allowed weekday/hour window.
12
+ */
13
+ export declare function computeNextRun(fromMs: number, recurrence: Recurrence): number;
14
+ //# sourceMappingURL=schedule.logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/recurring/schedule.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAarD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAsB1F;AAuBD,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAG1E;AAkBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAG7E"}
@@ -0,0 +1,85 @@
1
+ // Pure cadence math for recurring pipelines. A schedule fires every
2
+ // `intervalHours`, but only at instants inside its allowed window — a set of
3
+ // weekdays plus an hour-of-day range, evaluated in the schedule's IANA timezone.
4
+ // `computeNextRun` advances by the interval and then rolls forward to the next
5
+ // eligible instant; `isWithinWindow` answers whether a given instant is eligible.
6
+ //
7
+ // Timezone handling uses `Intl.DateTimeFormat` (available in both workerd and
8
+ // Node), so we never depend on the host's local zone.
9
+ const HOUR_MS = 60 * 60 * 1000;
10
+ /** The local weekday (0=Sun..6=Sat) and hour (0..23) of `ms` in `timezone`. */
11
+ export function localParts(ms, timezone) {
12
+ const fmt = new Intl.DateTimeFormat('en-US', {
13
+ timeZone: timezone,
14
+ weekday: 'short',
15
+ hour: 'numeric',
16
+ hour12: false,
17
+ });
18
+ const parts = fmt.formatToParts(new Date(ms));
19
+ const weekdayName = parts.find((p) => p.type === 'weekday')?.value ?? 'Sun';
20
+ let hourStr = parts.find((p) => p.type === 'hour')?.value ?? '0';
21
+ // `hour12: false` can render midnight as "24"; normalize to 0.
22
+ if (hourStr === '24')
23
+ hourStr = '0';
24
+ const weekdays = {
25
+ Sun: 0,
26
+ Mon: 1,
27
+ Tue: 2,
28
+ Wed: 3,
29
+ Thu: 4,
30
+ Fri: 5,
31
+ Sat: 6,
32
+ };
33
+ return { weekday: weekdays[weekdayName] ?? 0, hour: Number(hourStr) };
34
+ }
35
+ /** Whether the allowed-day set permits `weekday` (empty set = every day). */
36
+ function weekdayAllowed(recurrence, weekday) {
37
+ return recurrence.weekdays.length === 0 || recurrence.weekdays.includes(weekday);
38
+ }
39
+ /**
40
+ * Whether `hour` falls in the allowed window. Both bounds null = any hour. The
41
+ * window is [start, end): a start without an end runs from `start` to midnight; an
42
+ * end without a start runs from midnight to `end`. A `start >= end` window wraps
43
+ * past midnight (e.g. 22→6 means 22,23,0..5).
44
+ */
45
+ function hourAllowed(recurrence, hour) {
46
+ const { windowStartHour: start, windowEndHour: end } = recurrence;
47
+ if (start === null && end === null)
48
+ return true;
49
+ const lo = start ?? 0;
50
+ const hi = end ?? 24;
51
+ if (lo < hi)
52
+ return hour >= lo && hour < hi;
53
+ // Wrapping window (lo >= hi): allowed outside the [hi, lo) gap.
54
+ return hour >= lo || hour < hi;
55
+ }
56
+ /** Whether `ms` is an instant the schedule is allowed to fire at. */
57
+ export function isWithinWindow(ms, recurrence) {
58
+ const { weekday, hour } = localParts(ms, recurrence.timezone);
59
+ return weekdayAllowed(recurrence, weekday) && hourAllowed(recurrence, hour);
60
+ }
61
+ /**
62
+ * The next instant at or after `fromMs` that satisfies the window, snapping to the
63
+ * top of the hour. Steps hour-by-hour (bounded) so DST transitions and wrapping
64
+ * windows are handled by the same `isWithinWindow` check rather than date math.
65
+ */
66
+ function nextEligible(fromMs, recurrence) {
67
+ // Snap up to the next whole hour boundary so fires land at :00.
68
+ let candidate = Math.ceil(fromMs / HOUR_MS) * HOUR_MS;
69
+ // At most a year of hourly steps — guards against an impossible window.
70
+ for (let i = 0; i < 24 * 366; i++) {
71
+ if (isWithinWindow(candidate, recurrence))
72
+ return candidate;
73
+ candidate += HOUR_MS;
74
+ }
75
+ return candidate;
76
+ }
77
+ /**
78
+ * The next fire time after `fromMs`: advance by `intervalHours`, then roll forward
79
+ * to the next instant inside the allowed weekday/hour window.
80
+ */
81
+ export function computeNextRun(fromMs, recurrence) {
82
+ const base = fromMs + recurrence.intervalHours * HOUR_MS;
83
+ return nextEligible(base, recurrence);
84
+ }
85
+ //# sourceMappingURL=schedule.logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.logic.js","sourceRoot":"","sources":["../../../src/modules/recurring/schedule.logic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { BlockRepository, Clock, DatadogConnectionRepository, ReleaseHealthConfigRepository, SecretCipher, WorkspaceRepository } from '@cat-factory/kernel';
2
+ import type { DatadogConnectionView, ReleaseHealthConfigWire, UpsertDatadogConnectionInput, UpsertReleaseHealthConfigInput } from '@cat-factory/contracts';
3
+ export interface ReleaseHealthServiceDependencies {
4
+ datadogConnectionRepository: DatadogConnectionRepository;
5
+ releaseHealthConfigRepository: ReleaseHealthConfigRepository;
6
+ /** Seals the Datadog API/app keys at rest (domain tag 'cat-factory:datadog'). */
7
+ datadogSecretCipher: SecretCipher;
8
+ workspaceRepository: WorkspaceRepository;
9
+ /** Validates a per-block config targets a block that exists in the workspace. */
10
+ blockRepository: BlockRepository;
11
+ clock: Clock;
12
+ }
13
+ /**
14
+ * Manages the post-release-health integration's settings for a workspace: the (single)
15
+ * Datadog connection — credentials sealed at rest, never read back — and the per-block
16
+ * monitor/SLO mappings the gate reads. Read paths return redacted views; the secrets
17
+ * only leave the cipher inside the `DatadogReleaseHealthProvider` at probe time.
18
+ */
19
+ export declare class ReleaseHealthService {
20
+ private readonly connections;
21
+ private readonly configs;
22
+ private readonly cipher;
23
+ private readonly workspaceRepository;
24
+ private readonly blocks;
25
+ private readonly clock;
26
+ constructor(deps: ReleaseHealthServiceDependencies);
27
+ /** The workspace's Datadog connection, redacted (never returns the secret keys). */
28
+ getConnection(workspaceId: string): Promise<DatadogConnectionView>;
29
+ /** Set/replace the workspace's Datadog connection, sealing the keys at rest. */
30
+ setConnection(workspaceId: string, input: UpsertDatadogConnectionInput): Promise<DatadogConnectionView>;
31
+ deleteConnection(workspaceId: string): Promise<void>;
32
+ /** The workspace's per-block monitor/SLO mappings. */
33
+ listConfigs(workspaceId: string): Promise<ReleaseHealthConfigWire[]>;
34
+ /** Create/replace a block's release-health config. */
35
+ upsertConfig(workspaceId: string, blockId: string, input: UpsertReleaseHealthConfigInput): Promise<ReleaseHealthConfigWire>;
36
+ deleteConfig(workspaceId: string, blockId: string): Promise<void>;
37
+ }
38
+ //# sourceMappingURL=ReleaseHealthService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReleaseHealthService.d.ts","sourceRoot":"","sources":["../../../src/modules/releaseHealth/ReleaseHealthService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,KAAK,EACL,2BAA2B,EAC3B,6BAA6B,EAC7B,YAAY,EACZ,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACvB,4BAA4B,EAC5B,8BAA8B,EAC/B,MAAM,wBAAwB,CAAA;AAE/B,MAAM,WAAW,gCAAgC;IAC/C,2BAA2B,EAAE,2BAA2B,CAAA;IACxD,6BAA6B,EAAE,6BAA6B,CAAA;IAC5D,iFAAiF;IACjF,mBAAmB,EAAE,YAAY,CAAA;IACjC,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,iFAAiF;IACjF,eAAe,EAAE,eAAe,CAAA;IAChC,KAAK,EAAE,KAAK,CAAA;CACb;AAED;;;;;GAKG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAE7B,YAAY,IAAI,EAAE,gCAAgC,EAOjD;IAED,oFAAoF;IAC9E,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAMvE;IAED,gFAAgF;IAC1E,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAiBhC;IAEK,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzD;IAED,sDAAsD;IAChD,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CASzE;IAED,sDAAsD;IAChD,YAAY,CAChB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,uBAAuB,CAAC,CAuBlC;IAEK,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;CACF"}
@@ -0,0 +1,96 @@
1
+ import { assertFound, requireWorkspace } from '@cat-factory/kernel';
2
+ /**
3
+ * Manages the post-release-health integration's settings for a workspace: the (single)
4
+ * Datadog connection — credentials sealed at rest, never read back — and the per-block
5
+ * monitor/SLO mappings the gate reads. Read paths return redacted views; the secrets
6
+ * only leave the cipher inside the `DatadogReleaseHealthProvider` at probe time.
7
+ */
8
+ export class ReleaseHealthService {
9
+ connections;
10
+ configs;
11
+ cipher;
12
+ workspaceRepository;
13
+ blocks;
14
+ clock;
15
+ constructor(deps) {
16
+ this.connections = deps.datadogConnectionRepository;
17
+ this.configs = deps.releaseHealthConfigRepository;
18
+ this.cipher = deps.datadogSecretCipher;
19
+ this.workspaceRepository = deps.workspaceRepository;
20
+ this.blocks = deps.blockRepository;
21
+ this.clock = deps.clock;
22
+ }
23
+ /** The workspace's Datadog connection, redacted (never returns the secret keys). */
24
+ async getConnection(workspaceId) {
25
+ await requireWorkspace(this.workspaceRepository, workspaceId);
26
+ const connection = await this.connections.get(workspaceId);
27
+ return connection
28
+ ? { connected: true, site: connection.site }
29
+ : { connected: false, site: null };
30
+ }
31
+ /** Set/replace the workspace's Datadog connection, sealing the keys at rest. */
32
+ async setConnection(workspaceId, input) {
33
+ await requireWorkspace(this.workspaceRepository, workspaceId);
34
+ const now = this.clock.now();
35
+ const [apiKey, appKey] = await Promise.all([
36
+ this.cipher.encrypt(input.apiKey),
37
+ this.cipher.encrypt(input.appKey),
38
+ ]);
39
+ const existing = await this.connections.get(workspaceId);
40
+ await this.connections.upsert({
41
+ workspaceId,
42
+ site: input.site,
43
+ apiKey,
44
+ appKey,
45
+ createdAt: existing?.createdAt ?? now,
46
+ updatedAt: now,
47
+ });
48
+ return { connected: true, site: input.site };
49
+ }
50
+ async deleteConnection(workspaceId) {
51
+ await requireWorkspace(this.workspaceRepository, workspaceId);
52
+ await this.connections.delete(workspaceId);
53
+ }
54
+ /** The workspace's per-block monitor/SLO mappings. */
55
+ async listConfigs(workspaceId) {
56
+ await requireWorkspace(this.workspaceRepository, workspaceId);
57
+ const rows = await this.configs.listByWorkspace(workspaceId);
58
+ return rows.map((r) => ({
59
+ blockId: r.blockId,
60
+ monitorIds: r.monitorIds,
61
+ sloIds: r.sloIds,
62
+ envTag: r.envTag,
63
+ }));
64
+ }
65
+ /** Create/replace a block's release-health config. */
66
+ async upsertConfig(workspaceId, blockId, input) {
67
+ await requireWorkspace(this.workspaceRepository, workspaceId);
68
+ // The config is keyed by (workspace, block); reject a block that isn't in this
69
+ // workspace so a config can't be planted against a foreign/non-existent block id.
70
+ assertFound(await this.blocks.get(workspaceId, blockId), 'Block', blockId);
71
+ const now = this.clock.now();
72
+ const existing = await this.configs.getByBlock(workspaceId, blockId);
73
+ const record = {
74
+ workspaceId,
75
+ blockId,
76
+ monitorIds: input.monitorIds ?? [],
77
+ sloIds: input.sloIds ?? [],
78
+ envTag: input.envTag ?? null,
79
+ createdAt: existing?.createdAt ?? now,
80
+ updatedAt: now,
81
+ };
82
+ await this.configs.upsert(record);
83
+ return {
84
+ blockId: record.blockId,
85
+ monitorIds: record.monitorIds,
86
+ sloIds: record.sloIds,
87
+ envTag: record.envTag,
88
+ };
89
+ }
90
+ async deleteConfig(workspaceId, blockId) {
91
+ await requireWorkspace(this.workspaceRepository, workspaceId);
92
+ assertFound(await this.blocks.get(workspaceId, blockId), 'Block', blockId);
93
+ await this.configs.delete(workspaceId, blockId);
94
+ }
95
+ }
96
+ //# sourceMappingURL=ReleaseHealthService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReleaseHealthService.js","sourceRoot":"","sources":["../../../src/modules/releaseHealth/ReleaseHealthService.ts"],"names":[],"mappings":"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"}