patchrelay 0.36.7 → 0.36.9

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.
@@ -29,7 +29,7 @@ export class IssueQueryService {
29
29
  return await this.codex.readThread(run.threadId, true).catch(() => undefined);
30
30
  }
31
31
  buildRuns(projectId, linearIssueId) {
32
- return this.db.listRunsForIssue(projectId, linearIssueId).map((run) => ({
32
+ return this.db.runs.listRunsForIssue(projectId, linearIssueId).map((run) => ({
33
33
  id: run.id,
34
34
  runType: run.runType,
35
35
  status: run.status,
@@ -41,7 +41,7 @@ export class IssueQueryService {
41
41
  return report ? { report } : {};
42
42
  })(),
43
43
  ...(() => {
44
- const events = this.db.listThreadEvents(run.id).flatMap((event) => {
44
+ const events = this.db.runs.listThreadEvents(run.id).flatMap((event) => {
45
45
  try {
46
46
  const parsed = JSON.parse(event.eventJson);
47
47
  return [{
@@ -66,16 +66,16 @@ export class IssueQueryService {
66
66
  }));
67
67
  }
68
68
  async getIssueOverview(issueKey) {
69
- const session = this.db.getIssueSessionByKey(issueKey);
69
+ const session = this.db.issueSessions.getIssueSessionByKey(issueKey);
70
70
  if (!session) {
71
71
  const legacy = this.db.getIssueOverview(issueKey);
72
72
  if (!legacy)
73
73
  return undefined;
74
- const issueRecord = this.db.getIssueByKey(issueKey);
74
+ const issueRecord = this.db.issues.getIssueByKey(issueKey);
75
75
  const activeStatus = await this.runStatusProvider.getActiveRunStatus(issueKey);
76
76
  const activeRun = activeStatus?.run ?? legacy.activeRun;
77
- const latestRun = this.db.getLatestRunForIssue(legacy.issue.projectId, legacy.issue.linearIssueId);
78
- const latestEvent = this.db.listIssueSessionEvents(legacy.issue.projectId, legacy.issue.linearIssueId, { limit: 1 }).at(-1);
77
+ const latestRun = this.db.runs.getLatestRunForIssue(legacy.issue.projectId, legacy.issue.linearIssueId);
78
+ const latestEvent = this.db.issueSessions.listIssueSessionEvents(legacy.issue.projectId, legacy.issue.linearIssueId, { limit: 1 }).at(-1);
79
79
  const runs = this.buildRuns(legacy.issue.projectId, legacy.issue.linearIssueId);
80
80
  const runCount = runs.length;
81
81
  const liveThread = await this.readLiveThread(activeRun);
@@ -123,16 +123,16 @@ export class IssueQueryService {
123
123
  : {}),
124
124
  };
125
125
  }
126
- const issueRecord = this.db.getIssueByKey(issueKey);
127
- const blockedBy = this.db.listIssueDependencies(session.projectId, session.linearIssueId);
126
+ const issueRecord = this.db.issues.getIssueByKey(issueKey);
127
+ const blockedBy = this.db.issues.listIssueDependencies(session.projectId, session.linearIssueId);
128
128
  const unresolvedBlockedBy = blockedBy.filter((entry) => (entry.blockerCurrentLinearStateType !== "completed"
129
129
  && entry.blockerCurrentLinearState?.trim().toLowerCase() !== "done"));
130
130
  const blockedByKeys = unresolvedBlockedBy.map((entry) => entry.blockerIssueKey ?? entry.blockerLinearIssueId);
131
131
  const activeStatus = await this.runStatusProvider.getActiveRunStatus(issueKey);
132
132
  const activeRun = activeStatus?.run
133
- ?? (session.activeRunId !== undefined ? this.db.getRun(session.activeRunId) : undefined);
134
- const latestRun = this.db.getLatestRunForIssue(session.projectId, session.linearIssueId);
135
- const latestEvent = this.db.listIssueSessionEvents(session.projectId, session.linearIssueId, { limit: 1 }).at(-1);
133
+ ?? (session.activeRunId !== undefined ? this.db.runs.getRunById(session.activeRunId) : undefined);
134
+ const latestRun = this.db.runs.getLatestRunForIssue(session.projectId, session.linearIssueId);
135
+ const latestEvent = this.db.issueSessions.listIssueSessionEvents(session.projectId, session.linearIssueId, { limit: 1 }).at(-1);
136
136
  const runs = this.buildRuns(session.projectId, session.linearIssueId);
137
137
  const runCount = runs.length;
138
138
  const liveThread = await this.readLiveThread(activeRun);
@@ -166,7 +166,7 @@ export class IssueQueryService {
166
166
  factoryState: issueRecord?.factoryState ?? "delegated",
167
167
  ...(activeRun ? { activeRunId: activeRun.id } : {}),
168
168
  blockedByCount: unresolvedBlockedBy.length,
169
- hasPendingWake: this.db.peekIssueSessionWake(session.projectId, session.linearIssueId) !== undefined,
169
+ hasPendingWake: this.db.issueSessions.peekIssueSessionWake(session.projectId, session.linearIssueId) !== undefined,
170
170
  hasLegacyPendingRun: issueRecord?.pendingRunType !== undefined,
171
171
  ...(session.prNumber !== undefined ? { prNumber: session.prNumber } : {}),
172
172
  ...(issueRecord?.prState ? { prState: issueRecord.prState } : {}),
@@ -231,7 +231,7 @@ export class IssueQueryService {
231
231
  const overview = await this.getIssueOverview(issueKey);
232
232
  if (!overview)
233
233
  return undefined;
234
- const issueRecord = this.db.getIssueByKey(issueKey);
234
+ const issueRecord = this.db.issues.getIssueByKey(issueKey);
235
235
  const latestRunReport = parseStageReport(overview.latestRun?.reportJson, overview.latestRun?.status ?? "unknown");
236
236
  const runs = (overview.runs ?? this.buildRuns(overview.issue.projectId, overview.issue.linearIssueId)).map((run) => ({
237
237
  run: {
@@ -0,0 +1,143 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { getThreadTurns } from "./codex-thread-utils.js";
3
+ const ISSUE_SESSION_LEASE_MS = 10 * 60_000;
4
+ export class IssueSessionLeaseService {
5
+ db;
6
+ logger;
7
+ workerId;
8
+ readThreadWithRetry;
9
+ activeSessionLeases = new Map();
10
+ constructor(db, logger, workerId, readThreadWithRetry) {
11
+ this.db = db;
12
+ this.logger = logger;
13
+ this.workerId = workerId;
14
+ this.readThreadWithRetry = readThreadWithRetry;
15
+ }
16
+ hasLocalLease(projectId, linearIssueId) {
17
+ return this.activeSessionLeases.has(this.issueSessionLeaseKey(projectId, linearIssueId));
18
+ }
19
+ getHeldLease(projectId, linearIssueId) {
20
+ const leaseId = this.activeSessionLeases.get(this.issueSessionLeaseKey(projectId, linearIssueId));
21
+ if (!leaseId)
22
+ return undefined;
23
+ return { projectId, linearIssueId, leaseId };
24
+ }
25
+ withHeldLease(projectId, linearIssueId, fn) {
26
+ const lease = this.getHeldLease(projectId, linearIssueId);
27
+ if (!lease)
28
+ return undefined;
29
+ return this.db.issueSessions.withIssueSessionLease(projectId, linearIssueId, lease.leaseId, () => fn(lease));
30
+ }
31
+ acquire(projectId, linearIssueId) {
32
+ const leaseId = randomUUID();
33
+ const leasedUntil = new Date(Date.now() + ISSUE_SESSION_LEASE_MS).toISOString();
34
+ const acquired = this.db.issueSessions.acquireIssueSessionLease({
35
+ projectId,
36
+ linearIssueId,
37
+ leaseId,
38
+ workerId: this.workerId,
39
+ leasedUntil,
40
+ });
41
+ if (!acquired)
42
+ return undefined;
43
+ this.activeSessionLeases.set(this.issueSessionLeaseKey(projectId, linearIssueId), leaseId);
44
+ return leaseId;
45
+ }
46
+ forceAcquire(projectId, linearIssueId) {
47
+ const leaseId = randomUUID();
48
+ const leasedUntil = new Date(Date.now() + ISSUE_SESSION_LEASE_MS).toISOString();
49
+ const acquired = this.db.issueSessions.forceAcquireIssueSessionLease({
50
+ projectId,
51
+ linearIssueId,
52
+ leaseId,
53
+ workerId: this.workerId,
54
+ leasedUntil,
55
+ });
56
+ if (!acquired)
57
+ return undefined;
58
+ this.activeSessionLeases.set(this.issueSessionLeaseKey(projectId, linearIssueId), leaseId);
59
+ return leaseId;
60
+ }
61
+ claimForReconciliation(projectId, linearIssueId) {
62
+ const key = this.issueSessionLeaseKey(projectId, linearIssueId);
63
+ if (this.activeSessionLeases.has(key)) {
64
+ return "owned";
65
+ }
66
+ const session = this.db.issueSessions.getIssueSession(projectId, linearIssueId);
67
+ if (!session)
68
+ return "skip";
69
+ const leasedUntilMs = session.leasedUntil ? Date.parse(session.leasedUntil) : undefined;
70
+ if (leasedUntilMs !== undefined && Number.isFinite(leasedUntilMs) && leasedUntilMs > Date.now()) {
71
+ return "skip";
72
+ }
73
+ return this.acquire(projectId, linearIssueId) ? true : "skip";
74
+ }
75
+ async reclaimForeignRecoveryLeaseIfSafe(run, issue) {
76
+ const key = this.issueSessionLeaseKey(run.projectId, run.linearIssueId);
77
+ if (this.activeSessionLeases.has(key)) {
78
+ return false;
79
+ }
80
+ const session = this.db.issueSessions.getIssueSession(run.projectId, run.linearIssueId);
81
+ if (!session?.leaseId || !session.workerId || session.workerId === this.workerId) {
82
+ return false;
83
+ }
84
+ if (issue.activeRunId !== run.id) {
85
+ return false;
86
+ }
87
+ let safeToReclaim = !run.threadId;
88
+ if (!safeToReclaim && run.threadId) {
89
+ try {
90
+ const thread = await this.readThreadWithRetry(run.threadId, 1);
91
+ const latestTurn = getThreadTurns(thread).at(-1);
92
+ safeToReclaim = thread.status === "notLoaded"
93
+ || latestTurn?.status === "interrupted"
94
+ || latestTurn?.status === "completed";
95
+ }
96
+ catch {
97
+ safeToReclaim = true;
98
+ }
99
+ }
100
+ if (!safeToReclaim) {
101
+ return false;
102
+ }
103
+ const leaseId = this.forceAcquire(run.projectId, run.linearIssueId);
104
+ if (!leaseId) {
105
+ return false;
106
+ }
107
+ this.logger.info({
108
+ issueKey: issue.issueKey,
109
+ runId: run.id,
110
+ previousWorkerId: session.workerId,
111
+ previousLeaseId: session.leaseId,
112
+ reclaimedLeaseId: leaseId,
113
+ }, "Reclaimed foreign issue-session lease for active-run recovery");
114
+ return true;
115
+ }
116
+ heartbeat(projectId, linearIssueId) {
117
+ const key = this.issueSessionLeaseKey(projectId, linearIssueId);
118
+ const leaseId = this.activeSessionLeases.get(key) ?? this.db.issueSessions.getIssueSession(projectId, linearIssueId)?.leaseId;
119
+ if (!leaseId)
120
+ return false;
121
+ const renewed = this.db.issueSessions.renewIssueSessionLease({
122
+ projectId,
123
+ linearIssueId,
124
+ leaseId,
125
+ leasedUntil: new Date(Date.now() + ISSUE_SESSION_LEASE_MS).toISOString(),
126
+ });
127
+ if (renewed) {
128
+ this.activeSessionLeases.set(key, leaseId);
129
+ return true;
130
+ }
131
+ this.activeSessionLeases.delete(key);
132
+ return false;
133
+ }
134
+ release(projectId, linearIssueId) {
135
+ const key = this.issueSessionLeaseKey(projectId, linearIssueId);
136
+ const leaseId = this.activeSessionLeases.get(key);
137
+ this.db.issueSessions.releaseIssueSessionLease(projectId, linearIssueId, leaseId);
138
+ this.activeSessionLeases.delete(key);
139
+ }
140
+ issueSessionLeaseKey(projectId, linearIssueId) {
141
+ return `${projectId}:${linearIssueId}`;
142
+ }
143
+ }
@@ -0,0 +1,114 @@
1
+ import { isoNow } from "./db/shared.js";
2
+ import { buildTrackedIssueRecord } from "./tracked-issue-projector.js";
3
+ import { extractLatestAssistantSummary, } from "./issue-session-events.js";
4
+ import { deriveIssueSessionState, deriveIssueSessionWakeReason, } from "./issue-session.js";
5
+ export function syncIssueSessionFromIssue(params) {
6
+ const { connection, issues, issueSessions, runs, issue, options } = params;
7
+ const existing = issueSessions.getIssueSession(issue.projectId, issue.linearIssueId);
8
+ const latestRun = runs.getLatestRunForIssue(issue.projectId, issue.linearIssueId);
9
+ const latestRunType = options?.lastRunType ?? latestRun?.runType ?? existing?.lastRunType;
10
+ const summaryText = resolveIssueSessionSummary(issue, runs, latestRun, existing?.summaryText, options?.summaryText);
11
+ const activeThreadId = issue.threadId ?? existing?.activeThreadId;
12
+ const threadGeneration = activeThreadId && activeThreadId !== existing?.activeThreadId
13
+ ? (existing?.threadGeneration ?? 0) + 1
14
+ : (existing?.threadGeneration ?? (activeThreadId ? 1 : 0));
15
+ const sessionState = deriveIssueSessionState({
16
+ ...(issue.activeRunId !== undefined ? { activeRunId: issue.activeRunId } : {}),
17
+ factoryState: issue.factoryState,
18
+ });
19
+ const tracked = buildTrackedIssueRecord({
20
+ issue,
21
+ session: existing,
22
+ blockedBy: issues.listIssueDependencies(issue.projectId, issue.linearIssueId),
23
+ hasPendingWake: issueSessions.peekIssueSessionWake(issue.projectId, issue.linearIssueId) !== undefined,
24
+ latestRun,
25
+ latestEvent: issueSessions.listIssueSessionEvents(issue.projectId, issue.linearIssueId, { limit: 1 }).at(-1),
26
+ });
27
+ const lastWakeReason = options?.lastWakeReason
28
+ ?? deriveIssueSessionWakeReason({
29
+ pendingRunType: issue.pendingRunType,
30
+ factoryState: issue.factoryState,
31
+ prNumber: issue.prNumber,
32
+ prState: issue.prState,
33
+ prReviewState: issue.prReviewState,
34
+ prCheckStatus: issue.prCheckStatus,
35
+ latestFailureSource: issue.lastGitHubFailureSource,
36
+ })
37
+ ?? existing?.lastWakeReason;
38
+ const now = isoNow();
39
+ if (existing) {
40
+ connection.prepare(`
41
+ UPDATE issue_sessions SET
42
+ issue_key = ?,
43
+ repo_id = ?,
44
+ branch_name = ?,
45
+ worktree_path = ?,
46
+ pr_number = ?,
47
+ pr_head_sha = ?,
48
+ pr_author_login = ?,
49
+ session_state = ?,
50
+ waiting_reason = ?,
51
+ summary_text = ?,
52
+ active_thread_id = ?,
53
+ thread_generation = ?,
54
+ active_run_id = ?,
55
+ last_run_type = ?,
56
+ last_wake_reason = ?,
57
+ ci_repair_attempts = ?,
58
+ queue_repair_attempts = ?,
59
+ review_fix_attempts = ?,
60
+ updated_at = ?
61
+ WHERE project_id = ? AND linear_issue_id = ?
62
+ `).run(issue.issueKey ?? null, issue.projectId, issue.branchName ?? null, issue.worktreePath ?? null, issue.prNumber ?? null, issue.prHeadSha ?? null, issue.prAuthorLogin ?? null, sessionState, tracked.waitingReason ?? null, summaryText ?? null, activeThreadId ?? null, threadGeneration, issue.activeRunId ?? null, latestRunType ?? null, lastWakeReason ?? null, issue.ciRepairAttempts, issue.queueRepairAttempts, issue.reviewFixAttempts, now, issue.projectId, issue.linearIssueId);
63
+ return;
64
+ }
65
+ connection.prepare(`
66
+ INSERT INTO issue_sessions (
67
+ project_id, linear_issue_id, issue_key, repo_id, branch_name, worktree_path,
68
+ pr_number, pr_head_sha, pr_author_login, session_state, waiting_reason, summary_text,
69
+ active_thread_id, thread_generation, active_run_id, last_run_type, last_wake_reason,
70
+ ci_repair_attempts, queue_repair_attempts, review_fix_attempts,
71
+ created_at, updated_at
72
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
73
+ `).run(issue.projectId, issue.linearIssueId, issue.issueKey ?? null, issue.projectId, issue.branchName ?? null, issue.worktreePath ?? null, issue.prNumber ?? null, issue.prHeadSha ?? null, issue.prAuthorLogin ?? null, sessionState, tracked.waitingReason ?? null, summaryText ?? null, activeThreadId ?? null, threadGeneration, issue.activeRunId ?? null, latestRunType ?? null, lastWakeReason ?? null, issue.ciRepairAttempts, issue.queueRepairAttempts, issue.reviewFixAttempts, now, now);
74
+ }
75
+ function resolveIssueSessionSummary(issue, runs, latestRun, existingSummaryText, explicitSummaryText) {
76
+ if (explicitSummaryText?.trim()) {
77
+ return explicitSummaryText;
78
+ }
79
+ const latestSummary = extractLatestAssistantSummary(latestRun);
80
+ if (latestRun && (latestRun.status === "queued" || latestRun.status === "running")) {
81
+ return latestSummary;
82
+ }
83
+ if (shouldKeepPreviousIssueSummary(issue, latestRun)) {
84
+ return findLatestCompletedRunSummary(runs, issue.projectId, issue.linearIssueId)
85
+ ?? existingSummaryText
86
+ ?? latestSummary;
87
+ }
88
+ return latestSummary ?? existingSummaryText;
89
+ }
90
+ function shouldKeepPreviousIssueSummary(issue, latestRun) {
91
+ if (!latestRun || latestRun.status !== "failed") {
92
+ return false;
93
+ }
94
+ if (latestRun.summaryJson || latestRun.reportJson) {
95
+ return false;
96
+ }
97
+ return issue.factoryState === "pr_open"
98
+ || issue.factoryState === "awaiting_queue"
99
+ || issue.factoryState === "done";
100
+ }
101
+ function findLatestCompletedRunSummary(runs, projectId, linearIssueId) {
102
+ const issueRuns = runs.listRunsForIssue(projectId, linearIssueId);
103
+ for (let index = issueRuns.length - 1; index >= 0; index -= 1) {
104
+ const run = issueRuns[index];
105
+ if (!run || run.status !== "completed") {
106
+ continue;
107
+ }
108
+ const summary = extractLatestAssistantSummary(run);
109
+ if (summary?.trim()) {
110
+ return summary;
111
+ }
112
+ }
113
+ return undefined;
114
+ }
@@ -22,11 +22,11 @@ export class LinearSessionSync {
22
22
  if (issue.agentSessionId) {
23
23
  return issue;
24
24
  }
25
- const recoveredAgentSessionId = this.db.findLatestAgentSessionIdForIssue(issue.linearIssueId);
25
+ const recoveredAgentSessionId = this.db.webhookEvents.findLatestAgentSessionIdForIssue(issue.linearIssueId);
26
26
  if (!recoveredAgentSessionId)
27
27
  return issue;
28
28
  this.logger.info({ issueKey: issue.issueKey, agentSessionId: recoveredAgentSessionId }, "Recovered missing Linear agent session id from webhook history");
29
- return this.db.upsertIssue({
29
+ return this.db.issues.upsertIssue({
30
30
  projectId: issue.projectId,
31
31
  linearIssueId: issue.linearIssueId,
32
32
  agentSessionId: recoveredAgentSessionId,
@@ -99,7 +99,7 @@ export class LinearSessionSync {
99
99
  currentLinearState: liveIssue.stateName,
100
100
  currentLinearStateType: liveIssue.stateType,
101
101
  })) {
102
- this.db.upsertIssue({
102
+ this.db.issues.upsertIssue({
103
103
  projectId: issue.projectId,
104
104
  linearIssueId: issue.linearIssueId,
105
105
  ...(liveIssue.stateName ? { currentLinearState: liveIssue.stateName } : {}),
@@ -112,7 +112,7 @@ export class LinearSessionSync {
112
112
  return;
113
113
  const normalizedCurrent = liveIssue.stateName?.trim().toLowerCase();
114
114
  if (normalizedCurrent === targetState.trim().toLowerCase()) {
115
- this.db.upsertIssue({
115
+ this.db.issues.upsertIssue({
116
116
  projectId: issue.projectId,
117
117
  linearIssueId: issue.linearIssueId,
118
118
  ...(liveIssue.stateName ? { currentLinearState: liveIssue.stateName } : {}),
@@ -121,7 +121,7 @@ export class LinearSessionSync {
121
121
  return;
122
122
  }
123
123
  const updated = await linear.setIssueState(issue.linearIssueId, targetState);
124
- this.db.upsertIssue({
124
+ this.db.issues.upsertIssue({
125
125
  projectId: issue.projectId,
126
126
  linearIssueId: issue.linearIssueId,
127
127
  ...(updated.stateName ? { currentLinearState: updated.stateName } : {}),
@@ -174,7 +174,7 @@ export class LinearSessionSync {
174
174
  if (now - lastEmit < PROGRESS_THROTTLE_MS)
175
175
  return;
176
176
  this.progressThrottle.set(run.id, now);
177
- const issue = this.db.getIssue(run.projectId, run.linearIssueId);
177
+ const issue = this.db.issues.getIssue(run.projectId, run.linearIssueId);
178
178
  if (issue) {
179
179
  void this.emitActivity(issue, activity, { ephemeral: true });
180
180
  }
@@ -192,7 +192,7 @@ export class LinearSessionSync {
192
192
  body,
193
193
  });
194
194
  if (result.id !== issue.statusCommentId) {
195
- this.db.upsertIssue({
195
+ this.db.issues.upsertIssue({
196
196
  projectId: issue.projectId,
197
197
  linearIssueId: issue.linearIssueId,
198
198
  statusCommentId: result.id,
@@ -229,9 +229,9 @@ function resolveProgressActivity(notification) {
229
229
  return undefined;
230
230
  }
231
231
  function renderStatusComment(db, issue, trackedIssue, options) {
232
- const activeRun = issue.activeRunId ? db.getRun(issue.activeRunId) : undefined;
233
- const latestRun = db.getLatestRunForIssue(issue.projectId, issue.linearIssueId);
234
- const latestEvent = db.listIssueSessionEvents(issue.projectId, issue.linearIssueId, { limit: 1 }).at(-1);
232
+ const activeRun = issue.activeRunId ? db.runs.getRunById(issue.activeRunId) : undefined;
233
+ const latestRun = db.runs.getLatestRunForIssue(issue.projectId, issue.linearIssueId);
234
+ const latestEvent = db.issueSessions.listIssueSessionEvents(issue.projectId, issue.linearIssueId, { limit: 1 }).at(-1);
235
235
  const activeRunType = issue.activeRunId !== undefined
236
236
  ? (options?.activeRunType ?? activeRun?.runType)
237
237
  : undefined;
@@ -25,7 +25,7 @@ export class QueueHealthMonitor {
25
25
  this.feed = feed;
26
26
  }
27
27
  async reconcile() {
28
- for (const issue of this.db.listAwaitingQueueIssues()) {
28
+ for (const issue of this.db.issues.listAwaitingQueueIssues()) {
29
29
  await this.probeQueuedIssue(issue);
30
30
  }
31
31
  }
@@ -68,7 +68,7 @@ export class QueueHealthMonitor {
68
68
  }
69
69
  this.probeFailureFeedTimes.delete(`${issue.projectId}::${issue.linearIssueId}`);
70
70
  if (pr.state === "MERGED") {
71
- this.db.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, prState: "merged" });
71
+ this.db.issues.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, prState: "merged" });
72
72
  this.advancer.advanceIdleIssue(issue, "done", { clearFailureProvenance: true });
73
73
  return;
74
74
  }
@@ -101,13 +101,13 @@ export class QueueHealthMonitor {
101
101
  if (isDuplicateProbe(issue, pendingRunContext)) {
102
102
  return;
103
103
  }
104
- this.db.upsertIssue({
104
+ this.db.issues.upsertIssue({
105
105
  projectId: issue.projectId,
106
106
  linearIssueId: issue.linearIssueId,
107
107
  lastAttemptedFailureHeadSha: headRefOid,
108
108
  lastAttemptedFailureSignature: signature,
109
109
  });
110
- this.db.appendIssueSessionEventRespectingActiveLease(issue.projectId, issue.linearIssueId, {
110
+ this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(issue.projectId, issue.linearIssueId, {
111
111
  projectId: issue.projectId,
112
112
  linearIssueId: issue.linearIssueId,
113
113
  eventType: "merge_steward_incident",
@@ -115,7 +115,7 @@ export class QueueHealthMonitor {
115
115
  dedupeKey: `queue_health:queue_repair:${issue.linearIssueId}:${signature}`,
116
116
  });
117
117
  this.advancer.advanceIdleIssue(issue, "repairing_queue");
118
- if (this.db.peekIssueSessionWake(issue.projectId, issue.linearIssueId)) {
118
+ if (this.db.issueSessions.peekIssueSessionWake(issue.projectId, issue.linearIssueId)) {
119
119
  this.advancer.enqueueIssue(issue.projectId, issue.linearIssueId);
120
120
  }
121
121
  this.logger.info({ issueKey: issue.issueKey, prNumber: issue.prNumber, headRefOid, reason }, "Queue health: queue issue detected, dispatching repair");