patchrelay 0.2.0 → 0.4.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.
@@ -1,346 +1,90 @@
1
1
  import { isoNow } from "./shared.js";
2
2
  export class IssueWorkflowStore {
3
- connection;
4
- constructor(connection) {
5
- this.connection = connection;
6
- }
7
- upsertTrackedIssue(params) {
8
- this.upsertIssueProjection({
9
- projectId: params.projectId,
10
- linearIssueId: params.linearIssueId,
11
- ...(params.issueKey ? { issueKey: params.issueKey } : {}),
12
- ...(params.title ? { title: params.title } : {}),
13
- ...(params.issueUrl ? { issueUrl: params.issueUrl } : {}),
14
- ...(params.currentLinearState ? { currentLinearState: params.currentLinearState } : {}),
15
- ...(params.lastWebhookAt ? { lastWebhookAt: params.lastWebhookAt } : {}),
16
- });
17
- const desiredReceiptId = params.desiredWebhookId === undefined
18
- ? undefined
19
- : params.desiredWebhookId === null
20
- ? null
21
- : this.ensureDesiredReceipt(params.projectId, params.linearIssueId, params.desiredWebhookId);
22
- this.upsertIssueControl({
23
- projectId: params.projectId,
24
- linearIssueId: params.linearIssueId,
25
- ...(params.desiredStage !== undefined ? { desiredStage: params.desiredStage } : {}),
26
- ...(desiredReceiptId !== undefined ? { desiredReceiptId } : {}),
27
- ...(params.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: params.activeWorkspaceId } : {}),
28
- ...(params.activeStageRunId !== undefined ? { activeRunLeaseId: params.activeStageRunId } : {}),
29
- ...(params.statusCommentId !== undefined ? { serviceOwnedCommentId: params.statusCommentId } : {}),
30
- ...(params.activeAgentSessionId !== undefined ? { activeAgentSessionId: params.activeAgentSessionId } : {}),
31
- lifecycleStatus: params.lifecycleStatus,
32
- });
33
- return this.getTrackedIssue(params.projectId, params.linearIssueId);
3
+ authoritativeLedger;
4
+ issueProjections;
5
+ runReports;
6
+ constructor(dependencies) {
7
+ this.authoritativeLedger = dependencies.authoritativeLedger;
8
+ this.issueProjections = dependencies.issueProjections;
9
+ this.runReports = dependencies.runReports;
34
10
  }
35
11
  getTrackedIssue(projectId, linearIssueId) {
36
- const issueControl = this.getIssueControlRow(projectId, linearIssueId);
37
- const projection = this.getIssueProjectionRow(projectId, linearIssueId);
12
+ const issueControl = this.authoritativeLedger.getIssueControl(projectId, linearIssueId);
13
+ const projection = this.issueProjections.getIssueProjection(projectId, linearIssueId);
38
14
  if (!issueControl && !projection) {
39
15
  return undefined;
40
16
  }
41
17
  return this.buildTrackedIssue(issueControl, projection);
42
18
  }
43
19
  getTrackedIssueByKey(issueKey) {
44
- const projection = this.connection
45
- .prepare("SELECT * FROM issue_projection WHERE issue_key = ? ORDER BY updated_at DESC LIMIT 1")
46
- .get(issueKey);
20
+ const projection = this.issueProjections.getIssueProjectionByKey(issueKey);
47
21
  if (!projection) {
48
22
  return undefined;
49
23
  }
50
- return this.getTrackedIssue(String(projection.project_id), String(projection.linear_issue_id));
24
+ return this.getTrackedIssue(projection.projectId, projection.linearIssueId);
51
25
  }
52
26
  getTrackedIssueByLinearIssueId(linearIssueId) {
53
- const projection = this.connection
54
- .prepare("SELECT * FROM issue_projection WHERE linear_issue_id = ? ORDER BY updated_at DESC LIMIT 1")
55
- .get(linearIssueId);
27
+ const projection = this.issueProjections.getIssueProjectionByLinearIssueId(linearIssueId);
56
28
  if (!projection) {
57
29
  return undefined;
58
30
  }
59
- return this.getTrackedIssue(String(projection.project_id), linearIssueId);
60
- }
61
- recordDesiredStage(params) {
62
- const existing = this.getTrackedIssue(params.projectId, params.linearIssueId);
63
- this.upsertIssueProjection({
64
- projectId: params.projectId,
65
- linearIssueId: params.linearIssueId,
66
- ...(params.issueKey ? { issueKey: params.issueKey } : existing?.issueKey ? { issueKey: existing.issueKey } : {}),
67
- ...(params.title ? { title: params.title } : existing?.title ? { title: existing.title } : {}),
68
- ...(params.issueUrl ? { issueUrl: params.issueUrl } : existing?.issueUrl ? { issueUrl: existing.issueUrl } : {}),
69
- ...(params.currentLinearState
70
- ? { currentLinearState: params.currentLinearState }
71
- : existing?.currentLinearState
72
- ? { currentLinearState: existing.currentLinearState }
73
- : {}),
74
- lastWebhookAt: params.lastWebhookAt,
75
- });
76
- const existingIssueControl = this.getIssueControlRow(params.projectId, params.linearIssueId);
77
- const lifecycleStatus = existingIssueControl?.active_run_lease_id || params.desiredStage
78
- ? existing?.lifecycleStatus ?? "queued"
79
- : existing?.lifecycleStatus ?? "idle";
80
- const desiredReceiptId = params.desiredWebhookId === undefined ? undefined : this.ensureDesiredReceipt(params.projectId, params.linearIssueId, params.desiredWebhookId);
81
- this.upsertIssueControl({
82
- projectId: params.projectId,
83
- linearIssueId: params.linearIssueId,
84
- ...(params.desiredStage !== undefined ? { desiredStage: params.desiredStage } : {}),
85
- ...(desiredReceiptId !== undefined ? { desiredReceiptId } : {}),
86
- lifecycleStatus,
87
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
88
- ...(existing?.activeAgentSessionId ? { activeAgentSessionId: existing.activeAgentSessionId } : {}),
89
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
90
- ...(existingIssueControl?.active_run_lease_id !== undefined && existingIssueControl.active_run_lease_id !== null
91
- ? { activeRunLeaseId: Number(existingIssueControl.active_run_lease_id) }
92
- : {}),
93
- });
94
- return this.getTrackedIssue(params.projectId, params.linearIssueId);
95
- }
96
- listIssuesReadyForExecution() {
97
- const rows = this.connection
98
- .prepare("SELECT project_id, linear_issue_id FROM issue_control WHERE desired_stage IS NOT NULL AND active_run_lease_id IS NULL ORDER BY id")
99
- .all();
100
- return rows.map((row) => ({
101
- projectId: String(row.project_id),
102
- linearIssueId: String(row.linear_issue_id),
103
- }));
31
+ return this.getTrackedIssue(projection.projectId, linearIssueId);
104
32
  }
105
33
  listActiveStageRuns() {
106
- return this.listRunLeaseRows({ status: "running" }).map((row) => this.buildStageRun(row));
107
- }
108
- claimStageRun(params) {
109
- const transaction = this.connection.transaction(() => {
110
- const issue = this.getTrackedIssue(params.projectId, params.linearIssueId);
111
- const issueControlRow = this.getIssueControlRow(params.projectId, params.linearIssueId);
112
- const issueControl = issueControlRow ? mapIssueControl(issueControlRow) : undefined;
113
- if (!issue || !issueControl || issueControl.activeRunLeaseId !== undefined || issue.desiredStage !== params.stage || issue.desiredWebhookId !== params.triggerWebhookId) {
114
- return undefined;
115
- }
116
- const workspaceOwnership = this.upsertWorkspaceOwnership({
117
- projectId: params.projectId,
118
- linearIssueId: params.linearIssueId,
119
- branchName: params.branchName,
120
- worktreePath: params.worktreePath,
121
- status: "active",
122
- });
123
- const runLease = this.insertRunLease({
124
- issueControlId: issueControl.id,
125
- projectId: params.projectId,
126
- linearIssueId: params.linearIssueId,
127
- workspaceOwnershipId: workspaceOwnership.id,
128
- stage: params.stage,
129
- status: "running",
130
- workflowFile: params.workflowFile,
131
- promptText: params.promptText,
132
- triggerReceiptId: issueControl.desiredReceiptId ?? null,
133
- });
134
- this.upsertWorkspaceOwnership({
135
- projectId: params.projectId,
136
- linearIssueId: params.linearIssueId,
137
- branchName: params.branchName,
138
- worktreePath: params.worktreePath,
139
- status: "active",
140
- currentRunLeaseId: runLease.id,
141
- });
142
- this.upsertIssueControl({
143
- projectId: params.projectId,
144
- linearIssueId: params.linearIssueId,
145
- desiredStage: null,
146
- desiredReceiptId: null,
147
- activeWorkspaceOwnershipId: workspaceOwnership.id,
148
- activeRunLeaseId: runLease.id,
149
- lifecycleStatus: "running",
150
- ...(issue.statusCommentId ? { serviceOwnedCommentId: issue.statusCommentId } : {}),
151
- ...(issue.activeAgentSessionId ? { activeAgentSessionId: issue.activeAgentSessionId } : {}),
152
- });
153
- const refreshedIssue = this.getTrackedIssue(params.projectId, params.linearIssueId);
154
- const workspace = this.getWorkspace(workspaceOwnership.id);
155
- const stageRun = this.getStageRun(runLease.id);
156
- const pipeline = this.getPipelineRun(runLease.id);
157
- return { issue: refreshedIssue, workspace, pipeline, stageRun };
158
- });
159
- return transaction();
34
+ return this.authoritativeLedger
35
+ .listActiveRunLeases()
36
+ .filter((runLease) => runLease.status === "running")
37
+ .map((runLease) => this.buildStageRun(runLease));
160
38
  }
161
39
  getWorkspace(id) {
162
- const row = this.connection.prepare("SELECT * FROM workspace_ownership WHERE id = ?").get(id);
163
- if (!row) {
164
- return undefined;
165
- }
166
- return this.buildWorkspace(mapWorkspaceOwnership(row));
40
+ const workspaceOwnership = this.authoritativeLedger.getWorkspaceOwnership(id);
41
+ return workspaceOwnership ? this.buildWorkspace(workspaceOwnership) : undefined;
167
42
  }
168
43
  getActiveWorkspaceForIssue(projectId, linearIssueId) {
169
- const row = this.connection
170
- .prepare("SELECT * FROM workspace_ownership WHERE project_id = ? AND linear_issue_id = ?")
171
- .get(projectId, linearIssueId);
172
- return row ? this.buildWorkspace(mapWorkspaceOwnership(row)) : undefined;
44
+ const workspaceOwnership = this.authoritativeLedger.getWorkspaceOwnershipForIssue(projectId, linearIssueId);
45
+ return workspaceOwnership ? this.buildWorkspace(workspaceOwnership) : undefined;
173
46
  }
174
47
  getPipelineRun(id) {
175
- const runLease = this.getRunLeaseRowById(id);
48
+ const runLease = this.authoritativeLedger.getRunLease(id);
176
49
  if (!runLease) {
177
50
  return undefined;
178
51
  }
179
- const issueControl = this.getIssueControlRow(String(runLease.project_id), String(runLease.linear_issue_id));
180
- const status = resolvePipelineStatus(runLease.status, issueControl?.lifecycle_status);
52
+ const issueControl = this.authoritativeLedger.getIssueControl(runLease.projectId, runLease.linearIssueId);
53
+ const status = resolvePipelineStatus(runLease.status, issueControl?.lifecycleStatus);
181
54
  return {
182
- id: Number(runLease.id),
183
- projectId: String(runLease.project_id),
184
- linearIssueId: String(runLease.linear_issue_id),
185
- workspaceId: Number(runLease.workspace_ownership_id),
55
+ id: runLease.id,
56
+ projectId: runLease.projectId,
57
+ linearIssueId: runLease.linearIssueId,
58
+ workspaceId: runLease.workspaceOwnershipId,
186
59
  status,
187
60
  currentStage: runLease.stage,
188
- startedAt: String(runLease.started_at),
189
- ...(runLease.ended_at === null ? {} : { endedAt: String(runLease.ended_at) }),
61
+ startedAt: runLease.startedAt,
62
+ ...(runLease.endedAt ? { endedAt: runLease.endedAt } : {}),
190
63
  };
191
64
  }
192
65
  getStageRun(id) {
193
- const row = this.getRunLeaseRowById(id);
194
- return row ? this.buildStageRun(row) : undefined;
66
+ const runLease = this.authoritativeLedger.getRunLease(id);
67
+ return runLease ? this.buildStageRun(runLease) : undefined;
195
68
  }
196
69
  getStageRunByThreadId(threadId) {
197
- const row = this.connection
198
- .prepare("SELECT * FROM run_leases WHERE thread_id = ? ORDER BY id DESC LIMIT 1")
199
- .get(threadId);
200
- return row ? this.buildStageRun(row) : undefined;
70
+ const runLease = this.authoritativeLedger.getRunLeaseByThreadId(threadId);
71
+ return runLease ? this.buildStageRun(runLease) : undefined;
201
72
  }
202
73
  listStageRunsForIssue(projectId, linearIssueId) {
203
- return this.listRunLeaseRows({ projectId, linearIssueId }).map((row) => this.buildStageRun(row));
204
- }
205
- updateStageRunThread(params) {
206
- this.connection
207
- .prepare(`
208
- UPDATE run_leases
209
- SET thread_id = ?,
210
- parent_thread_id = COALESCE(?, parent_thread_id),
211
- turn_id = COALESCE(?, turn_id)
212
- WHERE id = ?
213
- `)
214
- .run(params.threadId, params.parentThreadId ?? null, params.turnId ?? null, params.stageRunId);
215
- }
216
- finishStageRun(params) {
217
- const stageRun = this.getStageRun(params.stageRunId);
218
- if (!stageRun) {
219
- return;
220
- }
221
- const now = isoNow();
222
- this.connection
223
- .prepare(`
224
- INSERT INTO run_reports (run_lease_id, summary_json, report_json, created_at, updated_at)
225
- VALUES (?, ?, ?, ?, ?)
226
- ON CONFLICT(run_lease_id) DO UPDATE SET
227
- summary_json = excluded.summary_json,
228
- report_json = excluded.report_json,
229
- updated_at = excluded.updated_at
230
- `)
231
- .run(params.stageRunId, params.summaryJson ?? null, params.reportJson ?? null, now, now);
232
- this.connection
233
- .prepare(`
234
- UPDATE run_leases
235
- SET status = ?,
236
- thread_id = ?,
237
- turn_id = COALESCE(?, turn_id),
238
- ended_at = CASE WHEN ? IN ('completed', 'failed') THEN ? ELSE ended_at END
239
- WHERE id = ?
240
- `)
241
- .run(params.status === "failed" ? "failed" : "completed", params.threadId, params.turnId ?? null, params.status === "failed" ? "failed" : "completed", now, params.stageRunId);
242
- const workspace = this.getWorkspaceOwnershipRowById(stageRun.workspaceId);
243
- if (workspace) {
244
- this.upsertWorkspaceOwnership({
245
- projectId: stageRun.projectId,
246
- linearIssueId: stageRun.linearIssueId,
247
- branchName: String(workspace.branch_name),
248
- worktreePath: String(workspace.worktree_path),
249
- status: params.status === "completed" ? "active" : "paused",
250
- currentRunLeaseId: null,
251
- });
252
- }
253
- }
254
- setIssueDesiredStage(projectId, linearIssueId, desiredStage, desiredWebhookId) {
255
- const existing = this.getTrackedIssue(projectId, linearIssueId);
256
- const existingIssueControl = this.getIssueControlRow(projectId, linearIssueId);
257
- this.upsertIssueControl({
258
- projectId,
259
- linearIssueId,
260
- ...(desiredStage !== undefined ? { desiredStage } : { desiredStage: null }),
261
- ...(desiredWebhookId !== undefined
262
- ? { desiredReceiptId: this.ensureDesiredReceipt(projectId, linearIssueId, desiredWebhookId) }
263
- : desiredStage === undefined
264
- ? { desiredReceiptId: null }
265
- : {}),
266
- lifecycleStatus: desiredStage ? "queued" : existingIssueControl?.active_run_lease_id ? (existing?.lifecycleStatus ?? "idle") : "idle",
267
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
268
- ...(existing?.activeAgentSessionId ? { activeAgentSessionId: existing.activeAgentSessionId } : {}),
269
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
270
- ...(existingIssueControl?.active_run_lease_id !== undefined && existingIssueControl.active_run_lease_id !== null
271
- ? { activeRunLeaseId: Number(existingIssueControl.active_run_lease_id) }
272
- : {}),
273
- });
274
- }
275
- setIssueLifecycleStatus(projectId, linearIssueId, lifecycleStatus) {
276
- const existing = this.getTrackedIssue(projectId, linearIssueId);
277
- const existingIssueControl = this.getIssueControlRow(projectId, linearIssueId);
278
- this.upsertIssueControl({
279
- projectId,
280
- linearIssueId,
281
- lifecycleStatus,
282
- ...(existing?.desiredStage ? { desiredStage: existing.desiredStage } : {}),
283
- ...(existing?.desiredWebhookId ? { desiredReceiptId: this.ensureDesiredReceipt(projectId, linearIssueId, existing.desiredWebhookId) } : {}),
284
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
285
- ...(existingIssueControl?.active_run_lease_id !== undefined && existingIssueControl.active_run_lease_id !== null
286
- ? { activeRunLeaseId: Number(existingIssueControl.active_run_lease_id) }
287
- : {}),
288
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
289
- ...(existing?.activeAgentSessionId ? { activeAgentSessionId: existing.activeAgentSessionId } : {}),
290
- });
291
- }
292
- setIssueStatusComment(projectId, linearIssueId, statusCommentId) {
293
- const existing = this.getTrackedIssue(projectId, linearIssueId);
294
- const existingIssueControl = this.getIssueControlRow(projectId, linearIssueId);
295
- this.upsertIssueControl({
296
- projectId,
297
- linearIssueId,
298
- lifecycleStatus: existing?.lifecycleStatus ?? "idle",
299
- ...(existing?.desiredStage ? { desiredStage: existing.desiredStage } : {}),
300
- ...(existing?.desiredWebhookId ? { desiredReceiptId: this.ensureDesiredReceipt(projectId, linearIssueId, existing.desiredWebhookId) } : {}),
301
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
302
- ...(existingIssueControl?.active_run_lease_id !== undefined && existingIssueControl.active_run_lease_id !== null
303
- ? { activeRunLeaseId: Number(existingIssueControl.active_run_lease_id) }
304
- : {}),
305
- serviceOwnedCommentId: statusCommentId ?? null,
306
- ...(existing?.activeAgentSessionId ? { activeAgentSessionId: existing.activeAgentSessionId } : {}),
307
- });
308
- }
309
- setIssueActiveAgentSession(projectId, linearIssueId, agentSessionId) {
310
- const existing = this.getTrackedIssue(projectId, linearIssueId);
311
- const existingIssueControl = this.getIssueControlRow(projectId, linearIssueId);
312
- this.upsertIssueControl({
313
- projectId,
314
- linearIssueId,
315
- lifecycleStatus: existing?.lifecycleStatus ?? "idle",
316
- ...(existing?.desiredStage ? { desiredStage: existing.desiredStage } : {}),
317
- ...(existing?.desiredWebhookId ? { desiredReceiptId: this.ensureDesiredReceipt(projectId, linearIssueId, existing.desiredWebhookId) } : {}),
318
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
319
- ...(existingIssueControl?.active_run_lease_id !== undefined && existingIssueControl.active_run_lease_id !== null
320
- ? { activeRunLeaseId: Number(existingIssueControl.active_run_lease_id) }
321
- : {}),
322
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
323
- activeAgentSessionId: agentSessionId ?? null,
324
- });
74
+ return this.authoritativeLedger.listRunLeasesForIssue(projectId, linearIssueId).map((runLease) => this.buildStageRun(runLease));
325
75
  }
326
76
  getLatestStageRunForIssue(projectId, linearIssueId) {
327
- const row = this.connection
328
- .prepare("SELECT * FROM run_leases WHERE project_id = ? AND linear_issue_id = ? ORDER BY id DESC LIMIT 1")
329
- .get(projectId, linearIssueId);
330
- return row ? this.buildStageRun(row) : undefined;
77
+ const latestRunLease = this.getLatestRunLeaseForIssue(projectId, linearIssueId);
78
+ return latestRunLease ? this.buildStageRun(latestRunLease) : undefined;
331
79
  }
332
80
  getIssueOverview(issueKey) {
333
81
  const issue = this.getTrackedIssueByKey(issueKey);
334
82
  if (!issue) {
335
83
  return undefined;
336
84
  }
337
- const issueControl = this.getIssueControlRow(issue.projectId, issue.linearIssueId);
338
- const activeWorkspaceOwnershipId = issueControl?.active_workspace_ownership_id === null || issueControl?.active_workspace_ownership_id === undefined
339
- ? undefined
340
- : Number(issueControl.active_workspace_ownership_id);
341
- const activeRunLeaseId = issueControl?.active_run_lease_id === null || issueControl?.active_run_lease_id === undefined
342
- ? undefined
343
- : Number(issueControl.active_run_lease_id);
85
+ const issueControl = this.authoritativeLedger.getIssueControl(issue.projectId, issue.linearIssueId);
86
+ const activeWorkspaceOwnershipId = issueControl?.activeWorkspaceOwnershipId;
87
+ const activeRunLeaseId = issueControl?.activeRunLeaseId;
344
88
  const workspace = activeWorkspaceOwnershipId ? this.getWorkspace(activeWorkspaceOwnershipId) : this.getActiveWorkspaceForIssue(issue.projectId, issue.linearIssueId);
345
89
  const pipeline = activeRunLeaseId ? this.getPipelineRun(activeRunLeaseId) : issue.activePipelineRunId ? this.getPipelineRun(issue.activePipelineRunId) : undefined;
346
90
  const activeStageRun = activeRunLeaseId === undefined ? undefined : this.getStageRun(activeRunLeaseId);
@@ -351,107 +95,7 @@ export class IssueWorkflowStore {
351
95
  ...(activeStageRun ? { activeStageRun } : {}),
352
96
  };
353
97
  }
354
- upsertIssueProjection(params) {
355
- this.connection
356
- .prepare(`
357
- INSERT INTO issue_projection (
358
- project_id, linear_issue_id, issue_key, title, issue_url, current_linear_state, last_webhook_at, updated_at
359
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
360
- ON CONFLICT(project_id, linear_issue_id) DO UPDATE SET
361
- issue_key = COALESCE(excluded.issue_key, issue_projection.issue_key),
362
- title = COALESCE(excluded.title, issue_projection.title),
363
- issue_url = COALESCE(excluded.issue_url, issue_projection.issue_url),
364
- current_linear_state = COALESCE(excluded.current_linear_state, issue_projection.current_linear_state),
365
- last_webhook_at = COALESCE(excluded.last_webhook_at, issue_projection.last_webhook_at),
366
- updated_at = excluded.updated_at
367
- `)
368
- .run(params.projectId, params.linearIssueId, params.issueKey ?? null, params.title ?? null, params.issueUrl ?? null, params.currentLinearState ?? null, params.lastWebhookAt ?? null, isoNow());
369
- }
370
- upsertIssueControl(params) {
371
- const now = isoNow();
372
- this.connection
373
- .prepare(`
374
- INSERT INTO issue_control (
375
- project_id, linear_issue_id, desired_stage, desired_receipt_id, active_workspace_ownership_id,
376
- active_run_lease_id, service_owned_comment_id, active_agent_session_id, lifecycle_status, updated_at
377
- ) VALUES (
378
- @projectId, @linearIssueId, @desiredStage, @desiredReceiptId, @activeWorkspaceOwnershipId,
379
- @activeRunLeaseId, @serviceOwnedCommentId, @activeAgentSessionId, @lifecycleStatus, @updatedAt
380
- )
381
- ON CONFLICT(project_id, linear_issue_id) DO UPDATE SET
382
- desired_stage = CASE WHEN @setDesiredStage = 1 THEN @desiredStage ELSE issue_control.desired_stage END,
383
- desired_receipt_id = CASE WHEN @setDesiredReceiptId = 1 THEN @desiredReceiptId ELSE issue_control.desired_receipt_id END,
384
- active_workspace_ownership_id = CASE WHEN @setActiveWorkspaceOwnershipId = 1 THEN @activeWorkspaceOwnershipId ELSE issue_control.active_workspace_ownership_id END,
385
- active_run_lease_id = CASE WHEN @setActiveRunLeaseId = 1 THEN @activeRunLeaseId ELSE issue_control.active_run_lease_id END,
386
- service_owned_comment_id = CASE WHEN @setServiceOwnedCommentId = 1 THEN @serviceOwnedCommentId ELSE issue_control.service_owned_comment_id END,
387
- active_agent_session_id = CASE WHEN @setActiveAgentSessionId = 1 THEN @activeAgentSessionId ELSE issue_control.active_agent_session_id END,
388
- lifecycle_status = @lifecycleStatus,
389
- updated_at = @updatedAt
390
- `)
391
- .run({
392
- projectId: params.projectId,
393
- linearIssueId: params.linearIssueId,
394
- desiredStage: params.desiredStage ?? null,
395
- desiredReceiptId: params.desiredReceiptId ?? null,
396
- activeWorkspaceOwnershipId: params.activeWorkspaceOwnershipId ?? null,
397
- activeRunLeaseId: params.activeRunLeaseId ?? null,
398
- serviceOwnedCommentId: params.serviceOwnedCommentId ?? null,
399
- activeAgentSessionId: params.activeAgentSessionId ?? null,
400
- lifecycleStatus: params.lifecycleStatus,
401
- updatedAt: now,
402
- setDesiredStage: Number("desiredStage" in params),
403
- setDesiredReceiptId: Number("desiredReceiptId" in params),
404
- setActiveWorkspaceOwnershipId: Number("activeWorkspaceOwnershipId" in params),
405
- setActiveRunLeaseId: Number("activeRunLeaseId" in params),
406
- setServiceOwnedCommentId: Number("serviceOwnedCommentId" in params),
407
- setActiveAgentSessionId: Number("activeAgentSessionId" in params),
408
- });
409
- return mapIssueControl(this.connection
410
- .prepare("SELECT * FROM issue_control WHERE project_id = ? AND linear_issue_id = ?")
411
- .get(params.projectId, params.linearIssueId));
412
- }
413
- upsertWorkspaceOwnership(params) {
414
- const now = isoNow();
415
- this.connection
416
- .prepare(`
417
- INSERT INTO workspace_ownership (
418
- project_id, linear_issue_id, branch_name, worktree_path, status, current_run_lease_id, created_at, updated_at
419
- ) VALUES (@projectId, @linearIssueId, @branchName, @worktreePath, @status, @currentRunLeaseId, @createdAt, @updatedAt)
420
- ON CONFLICT(project_id, linear_issue_id) DO UPDATE SET
421
- branch_name = @branchName,
422
- worktree_path = @worktreePath,
423
- status = @status,
424
- current_run_lease_id = CASE WHEN @setCurrentRunLeaseId = 1 THEN @currentRunLeaseId ELSE workspace_ownership.current_run_lease_id END,
425
- updated_at = @updatedAt
426
- `)
427
- .run({
428
- projectId: params.projectId,
429
- linearIssueId: params.linearIssueId,
430
- branchName: params.branchName,
431
- worktreePath: params.worktreePath,
432
- status: params.status,
433
- currentRunLeaseId: params.currentRunLeaseId ?? null,
434
- createdAt: now,
435
- updatedAt: now,
436
- setCurrentRunLeaseId: Number("currentRunLeaseId" in params),
437
- });
438
- return mapWorkspaceOwnership(this.connection
439
- .prepare("SELECT * FROM workspace_ownership WHERE project_id = ? AND linear_issue_id = ?")
440
- .get(params.projectId, params.linearIssueId));
441
- }
442
- insertRunLease(params) {
443
- const result = this.connection
444
- .prepare(`
445
- INSERT INTO run_leases (
446
- issue_control_id, project_id, linear_issue_id, workspace_ownership_id, stage, status, trigger_receipt_id, workflow_file, prompt_text, started_at
447
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
448
- `)
449
- .run(params.issueControlId, params.projectId, params.linearIssueId, params.workspaceOwnershipId, params.stage, params.status, params.triggerReceiptId ?? null, params.workflowFile, params.promptText, isoNow());
450
- return this.buildRunLease(this.connection.prepare("SELECT * FROM run_leases WHERE id = ?").get(Number(result.lastInsertRowid)));
451
- }
452
- buildTrackedIssue(issueControlRow, projectionRow) {
453
- const issueControl = issueControlRow ? mapIssueControl(issueControlRow) : undefined;
454
- const projection = projectionRow ? mapIssueProjection(projectionRow) : undefined;
98
+ buildTrackedIssue(issueControl, projection) {
455
99
  const projectId = issueControl?.projectId ?? projection?.projectId;
456
100
  const linearIssueId = issueControl?.linearIssueId ?? projection?.linearIssueId;
457
101
  if (!projectId || !linearIssueId) {
@@ -472,7 +116,7 @@ export class IssueWorkflowStore {
472
116
  if (!issueControl?.desiredReceiptId) {
473
117
  return {};
474
118
  }
475
- const receipt = this.getEventReceiptById(issueControl.desiredReceiptId);
119
+ const receipt = this.authoritativeLedger.getEventReceipt(issueControl.desiredReceiptId);
476
120
  return receipt?.externalId ? { desiredWebhookId: receipt.externalId } : {};
477
121
  })(),
478
122
  ...(issueControl?.activeWorkspaceOwnershipId !== undefined ? { activeWorkspaceId: issueControl.activeWorkspaceOwnershipId } : {}),
@@ -502,10 +146,11 @@ export class IssueWorkflowStore {
502
146
  updatedAt: workspaceOwnership.updatedAt,
503
147
  };
504
148
  }
505
- buildStageRun(runLeaseRow) {
506
- const runLease = this.buildRunLease(runLeaseRow);
507
- const report = this.getRunReport(runLease.id);
508
- const triggerWebhookId = runLease.triggerReceiptId ? this.getEventReceiptById(runLease.triggerReceiptId)?.externalId ?? `run-lease:${runLease.id}` : `run-lease:${runLease.id}`;
149
+ buildStageRun(runLease) {
150
+ const report = this.runReports.getRunReport(runLease.id);
151
+ const triggerWebhookId = runLease.triggerReceiptId
152
+ ? this.authoritativeLedger.getEventReceipt(runLease.triggerReceiptId)?.externalId ?? `run-lease:${runLease.id}`
153
+ : `run-lease:${runLease.id}`;
509
154
  return {
510
155
  id: runLease.id,
511
156
  pipelineRunId: runLease.id,
@@ -513,7 +158,11 @@ export class IssueWorkflowStore {
513
158
  linearIssueId: runLease.linearIssueId,
514
159
  workspaceId: runLease.workspaceOwnershipId,
515
160
  stage: runLease.stage,
516
- status: runLease.status === "failed" ? "failed" : runLease.status === "completed" || runLease.status === "released" || runLease.status === "paused" ? "completed" : "running",
161
+ status: runLease.status === "failed"
162
+ ? "failed"
163
+ : runLease.status === "completed" || runLease.status === "released" || runLease.status === "paused"
164
+ ? "completed"
165
+ : "running",
517
166
  triggerWebhookId,
518
167
  workflowFile: runLease.workflowFile,
519
168
  promptText: runLease.promptText,
@@ -526,89 +175,8 @@ export class IssueWorkflowStore {
526
175
  ...(runLease.endedAt ? { endedAt: runLease.endedAt } : {}),
527
176
  };
528
177
  }
529
- buildRunLease(row) {
530
- return {
531
- id: Number(row.id),
532
- issueControlId: Number(row.issue_control_id),
533
- projectId: String(row.project_id),
534
- linearIssueId: String(row.linear_issue_id),
535
- workspaceOwnershipId: Number(row.workspace_ownership_id),
536
- stage: row.stage,
537
- status: row.status,
538
- ...(row.trigger_receipt_id === null ? {} : { triggerReceiptId: Number(row.trigger_receipt_id) }),
539
- workflowFile: String(row.workflow_file ?? ""),
540
- promptText: String(row.prompt_text ?? ""),
541
- ...(row.thread_id === null ? {} : { threadId: String(row.thread_id) }),
542
- ...(row.parent_thread_id === null ? {} : { parentThreadId: String(row.parent_thread_id) }),
543
- ...(row.turn_id === null ? {} : { turnId: String(row.turn_id) }),
544
- startedAt: String(row.started_at),
545
- ...(row.ended_at === null ? {} : { endedAt: String(row.ended_at) }),
546
- ...(row.failure_reason === null ? {} : { failureReason: String(row.failure_reason) }),
547
- };
548
- }
549
- getIssueProjectionRow(projectId, linearIssueId) {
550
- return this.connection
551
- .prepare("SELECT * FROM issue_projection WHERE project_id = ? AND linear_issue_id = ?")
552
- .get(projectId, linearIssueId);
553
- }
554
- getIssueControlRow(projectId, linearIssueId) {
555
- return this.connection
556
- .prepare("SELECT * FROM issue_control WHERE project_id = ? AND linear_issue_id = ?")
557
- .get(projectId, linearIssueId);
558
- }
559
- getWorkspaceOwnershipRowById(id) {
560
- return this.connection.prepare("SELECT * FROM workspace_ownership WHERE id = ?").get(id);
561
- }
562
- getRunLeaseRowById(id) {
563
- return this.connection.prepare("SELECT * FROM run_leases WHERE id = ?").get(id);
564
- }
565
- listRunLeaseRows(params) {
566
- const clauses = [];
567
- const values = [];
568
- if (params?.projectId) {
569
- clauses.push("project_id = ?");
570
- values.push(params.projectId);
571
- }
572
- if (params?.linearIssueId) {
573
- clauses.push("linear_issue_id = ?");
574
- values.push(params.linearIssueId);
575
- }
576
- if (params?.status) {
577
- clauses.push("status = ?");
578
- values.push(params.status);
579
- }
580
- const sql = `SELECT * FROM run_leases${clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY id`;
581
- return this.connection.prepare(sql).all(...values);
582
- }
583
178
  getLatestRunLeaseForIssue(projectId, linearIssueId) {
584
- const row = this.connection
585
- .prepare("SELECT * FROM run_leases WHERE project_id = ? AND linear_issue_id = ? ORDER BY id DESC LIMIT 1")
586
- .get(projectId, linearIssueId);
587
- return row ? this.buildRunLease(row) : undefined;
588
- }
589
- getRunReport(runLeaseId) {
590
- const row = this.connection.prepare("SELECT * FROM run_reports WHERE run_lease_id = ?").get(runLeaseId);
591
- return row ? mapRunReport(row) : undefined;
592
- }
593
- getEventReceiptById(id) {
594
- const row = this.connection.prepare("SELECT * FROM event_receipts WHERE id = ?").get(id);
595
- return row ? mapEventReceipt(row) : undefined;
596
- }
597
- ensureDesiredReceipt(projectId, linearIssueId, webhookId) {
598
- const existing = this.connection
599
- .prepare("SELECT * FROM event_receipts WHERE external_id = ? ORDER BY id DESC LIMIT 1")
600
- .get(webhookId);
601
- if (existing) {
602
- return Number(existing.id);
603
- }
604
- const result = this.connection
605
- .prepare(`
606
- INSERT INTO event_receipts (
607
- source, external_id, event_type, received_at, acceptance_status, processing_status, project_id, linear_issue_id
608
- ) VALUES (?, ?, ?, ?, 'accepted', 'processed', ?, ?)
609
- `)
610
- .run("patchrelay-desired-stage", webhookId, "desired_stage", isoNow(), projectId, linearIssueId);
611
- return Number(result.lastInsertRowid);
179
+ return this.authoritativeLedger.listRunLeasesForIssue(projectId, linearIssueId).at(-1);
612
180
  }
613
181
  }
614
182
  function resolvePipelineStatus(runStatus, lifecycleStatus) {
@@ -623,68 +191,3 @@ function resolvePipelineStatus(runStatus, lifecycleStatus) {
623
191
  }
624
192
  return "active";
625
193
  }
626
- function mapIssueProjection(row) {
627
- return {
628
- id: Number(row.id),
629
- projectId: String(row.project_id),
630
- linearIssueId: String(row.linear_issue_id),
631
- ...(row.issue_key === null ? {} : { issueKey: String(row.issue_key) }),
632
- ...(row.title === null ? {} : { title: String(row.title) }),
633
- ...(row.issue_url === null ? {} : { issueUrl: String(row.issue_url) }),
634
- ...(row.current_linear_state === null ? {} : { currentLinearState: String(row.current_linear_state) }),
635
- ...(row.last_webhook_at === null ? {} : { lastWebhookAt: String(row.last_webhook_at) }),
636
- updatedAt: String(row.updated_at),
637
- };
638
- }
639
- function mapIssueControl(row) {
640
- return {
641
- id: Number(row.id),
642
- projectId: String(row.project_id),
643
- linearIssueId: String(row.linear_issue_id),
644
- ...(row.desired_stage === null ? {} : { desiredStage: row.desired_stage }),
645
- ...(row.desired_receipt_id === null ? {} : { desiredReceiptId: Number(row.desired_receipt_id) }),
646
- ...(row.active_run_lease_id === null ? {} : { activeRunLeaseId: Number(row.active_run_lease_id) }),
647
- ...(row.active_workspace_ownership_id === null ? {} : { activeWorkspaceOwnershipId: Number(row.active_workspace_ownership_id) }),
648
- ...(row.service_owned_comment_id === null ? {} : { serviceOwnedCommentId: String(row.service_owned_comment_id) }),
649
- ...(row.active_agent_session_id === null ? {} : { activeAgentSessionId: String(row.active_agent_session_id) }),
650
- lifecycleStatus: row.lifecycle_status,
651
- updatedAt: String(row.updated_at),
652
- };
653
- }
654
- function mapWorkspaceOwnership(row) {
655
- return {
656
- id: Number(row.id),
657
- projectId: String(row.project_id),
658
- linearIssueId: String(row.linear_issue_id),
659
- branchName: String(row.branch_name),
660
- worktreePath: String(row.worktree_path),
661
- status: row.status,
662
- ...(row.current_run_lease_id === null ? {} : { currentRunLeaseId: Number(row.current_run_lease_id) }),
663
- createdAt: String(row.created_at),
664
- updatedAt: String(row.updated_at),
665
- };
666
- }
667
- function mapRunReport(row) {
668
- return {
669
- runLeaseId: Number(row.run_lease_id),
670
- ...(row.summary_json === null ? {} : { summaryJson: String(row.summary_json) }),
671
- ...(row.report_json === null ? {} : { reportJson: String(row.report_json) }),
672
- createdAt: String(row.created_at),
673
- updatedAt: String(row.updated_at),
674
- };
675
- }
676
- function mapEventReceipt(row) {
677
- return {
678
- id: Number(row.id),
679
- source: String(row.source),
680
- externalId: String(row.external_id),
681
- eventType: String(row.event_type),
682
- receivedAt: String(row.received_at),
683
- acceptanceStatus: row.acceptance_status,
684
- processingStatus: row.processing_status,
685
- ...(row.project_id === null ? {} : { projectId: String(row.project_id) }),
686
- ...(row.linear_issue_id === null ? {} : { linearIssueId: String(row.linear_issue_id) }),
687
- ...(row.headers_json === null ? {} : { headersJson: String(row.headers_json) }),
688
- ...(row.payload_json === null ? {} : { payloadJson: String(row.payload_json) }),
689
- };
690
- }