@usine/engine 0.3.0 → 0.5.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 (201) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +112 -38
  3. package/dist/board-moves/board-moves.d.ts +20 -0
  4. package/dist/board-moves/board-moves.d.ts.map +1 -0
  5. package/dist/board-moves/board-moves.js +74 -0
  6. package/dist/board-moves/board-moves.js.map +1 -0
  7. package/dist/board-moves/model.d.ts +21 -0
  8. package/dist/board-moves/model.d.ts.map +1 -0
  9. package/dist/board-moves/model.js +22 -0
  10. package/dist/board-moves/model.js.map +1 -0
  11. package/dist/cursor/cursor.d.ts +2 -6
  12. package/dist/cursor/cursor.d.ts.map +1 -1
  13. package/dist/cursor/cursor.js +2 -6
  14. package/dist/cursor/cursor.js.map +1 -1
  15. package/dist/cycle-budget/cycle-budget.d.ts +14 -8
  16. package/dist/cycle-budget/cycle-budget.d.ts.map +1 -1
  17. package/dist/cycle-budget/cycle-budget.js +51 -10
  18. package/dist/cycle-budget/cycle-budget.js.map +1 -1
  19. package/dist/cycle-budget/model.d.ts +6 -2
  20. package/dist/cycle-budget/model.d.ts.map +1 -1
  21. package/dist/cycle-budget/model.js +5 -2
  22. package/dist/cycle-budget/model.js.map +1 -1
  23. package/dist/cycle-budget/queries.d.ts +1 -1
  24. package/dist/cycle-budget/queries.d.ts.map +1 -1
  25. package/dist/cycle-budget/queries.js +3 -3
  26. package/dist/cycle-budget/queries.js.map +1 -1
  27. package/dist/database/database.d.ts +1 -1
  28. package/dist/database/database.d.ts.map +1 -1
  29. package/dist/database/local-sqlite.d.ts +13 -10
  30. package/dist/database/local-sqlite.d.ts.map +1 -1
  31. package/dist/database/local-sqlite.js +22 -17
  32. package/dist/database/local-sqlite.js.map +1 -1
  33. package/dist/database/schema.d.ts +668 -36
  34. package/dist/database/schema.d.ts.map +1 -1
  35. package/dist/database/schema.js +123 -23
  36. package/dist/database/schema.js.map +1 -1
  37. package/dist/{workflow → engine}/admission.d.ts +2 -2
  38. package/dist/engine/admission.d.ts.map +1 -0
  39. package/dist/{workflow → engine}/admission.js +4 -4
  40. package/dist/engine/admission.js.map +1 -0
  41. package/dist/engine/checkout-recovery.d.ts +79 -0
  42. package/dist/engine/checkout-recovery.d.ts.map +1 -0
  43. package/dist/engine/checkout-recovery.js +238 -0
  44. package/dist/engine/checkout-recovery.js.map +1 -0
  45. package/dist/engine/engine.d.ts +67 -26
  46. package/dist/engine/engine.d.ts.map +1 -1
  47. package/dist/engine/engine.js +136 -129
  48. package/dist/engine/engine.js.map +1 -1
  49. package/dist/engine/error.d.ts +29 -10
  50. package/dist/engine/error.d.ts.map +1 -1
  51. package/dist/engine/error.js +28 -8
  52. package/dist/engine/error.js.map +1 -1
  53. package/dist/engine/lifecycle.d.ts +83 -28
  54. package/dist/engine/lifecycle.d.ts.map +1 -1
  55. package/dist/engine/lifecycle.js +94 -45
  56. package/dist/engine/lifecycle.js.map +1 -1
  57. package/dist/engine/retry.d.ts +16 -0
  58. package/dist/engine/retry.d.ts.map +1 -0
  59. package/dist/engine/retry.js +18 -0
  60. package/dist/engine/retry.js.map +1 -0
  61. package/dist/engine/run-again.d.ts +45 -0
  62. package/dist/engine/run-again.d.ts.map +1 -0
  63. package/dist/engine/run-again.js +56 -0
  64. package/dist/engine/run-again.js.map +1 -0
  65. package/dist/engine/run-driver.d.ts +68 -0
  66. package/dist/engine/run-driver.d.ts.map +1 -0
  67. package/dist/engine/run-driver.js +448 -0
  68. package/dist/engine/run-driver.js.map +1 -0
  69. package/dist/engine/shutdown.d.ts +3 -4
  70. package/dist/engine/shutdown.d.ts.map +1 -1
  71. package/dist/engine/shutdown.js +3 -4
  72. package/dist/engine/shutdown.js.map +1 -1
  73. package/dist/engine/submission.d.ts +31 -11
  74. package/dist/engine/submission.d.ts.map +1 -1
  75. package/dist/engine/submission.js +43 -12
  76. package/dist/engine/submission.js.map +1 -1
  77. package/dist/engine/unconfirmed-stops.d.ts +27 -0
  78. package/dist/engine/unconfirmed-stops.d.ts.map +1 -0
  79. package/dist/engine/unconfirmed-stops.js +26 -0
  80. package/dist/engine/unconfirmed-stops.js.map +1 -0
  81. package/dist/item-projection/item-projection.d.ts +14 -4
  82. package/dist/item-projection/item-projection.d.ts.map +1 -1
  83. package/dist/item-projection/item-projection.js +60 -14
  84. package/dist/item-projection/item-projection.js.map +1 -1
  85. package/dist/item-projection/model.d.ts +5 -1
  86. package/dist/item-projection/model.d.ts.map +1 -1
  87. package/dist/item-projection/model.js +6 -2
  88. package/dist/item-projection/model.js.map +1 -1
  89. package/dist/loop-control/loop-control.d.ts +5 -5
  90. package/dist/loop-control/loop-control.d.ts.map +1 -1
  91. package/dist/loop-control/loop-control.js +4 -4
  92. package/dist/loop-control/loop-control.js.map +1 -1
  93. package/dist/loop-control/model.d.ts +1 -1
  94. package/dist/loop-control/model.d.ts.map +1 -1
  95. package/dist/loop-control/model.js +1 -1
  96. package/dist/loop-control/model.js.map +1 -1
  97. package/dist/repository-label-projection/model.d.ts +2 -2
  98. package/dist/repository-label-projection/model.d.ts.map +1 -1
  99. package/dist/repository-label-projection/model.js +2 -2
  100. package/dist/repository-label-projection/model.js.map +1 -1
  101. package/dist/repository-label-projection/repository-label-projection.d.ts +1 -1
  102. package/dist/repository-label-projection/repository-label-projection.d.ts.map +1 -1
  103. package/dist/repository-label-projection/repository-label-projection.js +1 -1
  104. package/dist/repository-label-projection/repository-label-projection.js.map +1 -1
  105. package/dist/run-reader/model.d.ts +62 -4
  106. package/dist/run-reader/model.d.ts.map +1 -1
  107. package/dist/run-reader/model.js +66 -7
  108. package/dist/run-reader/model.js.map +1 -1
  109. package/dist/run-reader/run-reader.d.ts +25 -10
  110. package/dist/run-reader/run-reader.d.ts.map +1 -1
  111. package/dist/run-reader/run-reader.js +25 -7
  112. package/dist/run-reader/run-reader.js.map +1 -1
  113. package/dist/step-executor.d.ts +86 -13
  114. package/dist/step-executor.d.ts.map +1 -1
  115. package/dist/step-executor.js +10 -2
  116. package/dist/step-executor.js.map +1 -1
  117. package/dist/store/attempt-windows.d.ts +8 -0
  118. package/dist/store/attempt-windows.d.ts.map +1 -0
  119. package/dist/store/attempt-windows.js +53 -0
  120. package/dist/store/attempt-windows.js.map +1 -0
  121. package/dist/store/codec.d.ts +242 -43
  122. package/dist/store/codec.d.ts.map +1 -1
  123. package/dist/store/codec.js +61 -16
  124. package/dist/store/codec.js.map +1 -1
  125. package/dist/store/leftovers.d.ts +10 -0
  126. package/dist/store/leftovers.d.ts.map +1 -0
  127. package/dist/store/leftovers.js +43 -0
  128. package/dist/store/leftovers.js.map +1 -0
  129. package/dist/store/loop-items.d.ts +16 -0
  130. package/dist/store/loop-items.d.ts.map +1 -0
  131. package/dist/store/loop-items.js +185 -0
  132. package/dist/store/loop-items.js.map +1 -0
  133. package/dist/store/queries.d.ts +385 -36
  134. package/dist/store/queries.d.ts.map +1 -1
  135. package/dist/store/queries.js +355 -107
  136. package/dist/store/queries.js.map +1 -1
  137. package/dist/store/store.d.ts +106 -54
  138. package/dist/store/store.d.ts.map +1 -1
  139. package/dist/store/store.js +497 -190
  140. package/dist/store/store.js.map +1 -1
  141. package/dist/store/transcript.d.ts +3 -3
  142. package/dist/store/transcript.d.ts.map +1 -1
  143. package/dist/store/transcript.js +25 -6
  144. package/dist/store/transcript.js.map +1 -1
  145. package/dist/transcript.d.ts +19 -20
  146. package/dist/transcript.d.ts.map +1 -1
  147. package/dist/transcript.js +23 -21
  148. package/dist/transcript.js.map +1 -1
  149. package/dist/trigger-condition.d.ts +23 -0
  150. package/dist/trigger-condition.d.ts.map +1 -0
  151. package/dist/trigger-condition.js +8 -0
  152. package/dist/trigger-condition.js.map +1 -0
  153. package/drizzle/20260917071139_resolve_run_failure/migration.sql +1 -0
  154. package/drizzle/20260917071139_resolve_run_failure/snapshot.json +1471 -0
  155. package/drizzle/20260918032643_attempt_salvage/migration.sql +1 -0
  156. package/drizzle/20260918032643_attempt_salvage/snapshot.json +1481 -0
  157. package/drizzle/20261011153856_drop_workflow_tables/migration.sql +4 -0
  158. package/drizzle/20261011153856_drop_workflow_tables/snapshot.json +1433 -0
  159. package/drizzle/20261017155043_attempt_salvage_check/migration.sql +41 -0
  160. package/drizzle/20261017155043_attempt_salvage_check/snapshot.json +1421 -0
  161. package/drizzle/20261018162700_discard_attempt_leftovers/migration.sql +1 -0
  162. package/drizzle/20261018162700_discard_attempt_leftovers/snapshot.json +1431 -0
  163. package/drizzle/20261018172605_loop_items_and_reopenings/migration.sql +15 -0
  164. package/drizzle/20261018172605_loop_items_and_reopenings/snapshot.json +1574 -0
  165. package/drizzle/20261018180339_attempt_ended_at/migration.sql +1 -0
  166. package/drizzle/20261018180339_attempt_ended_at/snapshot.json +1584 -0
  167. package/drizzle/20261019104043_run_approval_source/migration.sql +15 -0
  168. package/drizzle/20261019104043_run_approval_source/snapshot.json +1610 -0
  169. package/drizzle/20261019133538_board_moves/migration.sql +14 -0
  170. package/drizzle/20261019133538_board_moves/snapshot.json +1731 -0
  171. package/drizzle/20261019170538_item_projection_gone/migration.sql +1 -0
  172. package/drizzle/20261019170538_item_projection_gone/snapshot.json +1741 -0
  173. package/drizzle/20261019180649_run_again_events/migration.sql +7 -0
  174. package/drizzle/20261019180649_run_again_events/snapshot.json +1804 -0
  175. package/drizzle/20261019185008_trigger_event_permissions/migration.sql +1 -0
  176. package/drizzle/20261019185008_trigger_event_permissions/snapshot.json +1814 -0
  177. package/drizzle/20261020103746_attempt_start/migration.sql +5 -0
  178. package/drizzle/20261020103746_attempt_start/snapshot.json +1844 -0
  179. package/drizzle/20261020125211_attempt_comments/migration.sql +1 -0
  180. package/drizzle/20261020125211_attempt_comments/snapshot.json +1854 -0
  181. package/drizzle/20261020191320_run_closed_at/migration.sql +6 -0
  182. package/drizzle/20261020191320_run_closed_at/snapshot.json +1864 -0
  183. package/drizzle/20261023091428_rename_loop_item_hold_to_suppression/migration.sql +2 -0
  184. package/drizzle/20261023091428_rename_loop_item_hold_to_suppression/snapshot.json +1867 -0
  185. package/drizzle/20261024162954_cycle_ask_closures/migration.sql +10 -0
  186. package/drizzle/20261024162954_cycle_ask_closures/snapshot.json +1948 -0
  187. package/drizzle/20261027104206_attempt_out_of_memory/migration.sql +1 -0
  188. package/drizzle/20261027104206_attempt_out_of_memory/snapshot.json +1958 -0
  189. package/drizzle/20261027191944_failure_resolution/migration.sql +1 -0
  190. package/drizzle/20261027191944_failure_resolution/snapshot.json +1968 -0
  191. package/package.json +22 -12
  192. package/dist/workflow/admission.d.ts.map +0 -1
  193. package/dist/workflow/admission.js.map +0 -1
  194. package/dist/workflow/command.d.ts +0 -13
  195. package/dist/workflow/command.d.ts.map +0 -1
  196. package/dist/workflow/command.js +0 -17
  197. package/dist/workflow/command.js.map +0 -1
  198. package/dist/workflow/workflow.d.ts +0 -18
  199. package/dist/workflow/workflow.d.ts.map +0 -1
  200. package/dist/workflow/workflow.js +0 -108
  201. package/dist/workflow/workflow.js.map +0 -1
@@ -1,22 +1,25 @@
1
- import { Attempt, AttemptId, AttemptContext } from "@usine/core/attempt";
1
+ import { RepositoryName } from "@usine/core/repository";
2
+ import { WorkerName } from "@usine/core/worker";
3
+ import { Attempt, AttemptId, AttemptContext, AttemptNumber, totalOutOfMemoryKills, } from "@usine/core/attempt";
2
4
  import { AttemptOutcome } from "@usine/core/attempt/outcome";
5
+ import { AttemptSalvage, salvageRefFor } from "@usine/core/attempt/salvage";
3
6
  import { Event, DedupKey, EventId } from "@usine/core/event";
4
7
  import { ItemKey } from "@usine/core/item";
5
- import { LoopName } from "@usine/core/loop";
8
+ import { LoopName, LoopPermissions } from "@usine/core/loop";
6
9
  import { RunId } from "@usine/core/run";
7
10
  import { StepName } from "@usine/core/step";
8
11
  import * as RunState from "@usine/core/run/state";
9
12
  import { StepResult } from "@usine/core/step/result";
10
- import { and, asc, desc, eq, inArray, isNull, ne, notInArray, sql } from "drizzle-orm";
13
+ import { and, asc, desc, eq, gte, inArray, isNull, ne, notInArray, sql, } from "drizzle-orm";
11
14
  import { alias } from "drizzle-orm/sqlite-core";
12
15
  import { Array as EffectArray, DateTime, Effect, Option, Schema } from "effect";
13
- import { approvals, attempts, events, ignoredEvents, projectionRevision, runs, steps, triggerEvents, workflowCommands, } from "../database/schema.js";
16
+ import { approvals, attempts, events, ignoredEvents, itemCycleBudgets, loopItems, projectionRevision, runAgainEvents, runs, steps, triggerEvents, } from "../database/schema.js";
14
17
  import { RunNotFoundError } from "../engine/error.js";
15
18
  import { RunSnapshot, RunSummary } from "../run-reader/model.js";
16
19
  import { Submission } from "../engine/submission.js";
17
- import { WORKFLOW_COMMAND_PRIORITY } from "../workflow/command.js";
18
20
  import { cycleQueries } from "../cycle-budget/queries.js";
19
- import { attemptFromRow, decodeAttemptRows, decodeDiscards, decodeEventRoutes, decodeRecoverableRuns, decodeProjectionRevision, decodeRunIds, decodeRunRows, decodeWorkflowCommands, encodeAgent, encodeBrainSpec, encodeDateTime, encodeOutcome, encodePayload, encodeRunState, encodeStepResult, eventFromRow, runFromRow, runSummaryFromRow, stepFromRow, submissionFromRow, } from "./codec.js";
21
+ import { approvalFromRow, attemptFromRow, decodeAttemptRows, decodeDiscards, decodeNextStarts, decodeOutOfMemory, decodeEventRoutes, decodeProjectionRevision, decodeReopenedEvents, decodeReopenings, decodeRunEventRows, decodeRunIds, decodeRunRows, encodeAgent, encodeAttemptStart, encodeFailureResolution, encodeBrainSpec, encodeDateTime, encodeOutcome, encodeComments, encodeOutOfMemory, encodeSalvage, encodePayload, encodeRunState, encodeStepResult, eventFromRow, runAgainFromRow, runEventFromRow, runFromRow, runSummaryFromRow, stepFromRow, submissionFromRow, } from "./codec.js";
22
+ import { loopItemQueries } from "./loop-items.js";
20
23
  import { transcriptQueries } from "./transcript.js";
21
24
  const newestRunFirst = (left, right) => DateTime.toEpochMillis(right.openedAt) - DateTime.toEpochMillis(left.openedAt) ||
22
25
  right.runId.localeCompare(left.runId);
@@ -29,6 +32,7 @@ const eventRouteSelection = {
29
32
  loop: triggerEvents.loopName,
30
33
  step: triggerEvents.stepName,
31
34
  approval: triggerEvents.approval,
35
+ permissions: triggerEvents.permissions,
32
36
  cycleLimit: triggerEvents.cycleLimit,
33
37
  cycleGeneration: triggerEvents.cycleGeneration,
34
38
  repository: triggerEvents.repository,
@@ -37,13 +41,16 @@ const eventRouteSelection = {
37
41
  agent: triggerEvents.agentJson,
38
42
  parentModel: triggerEvents.parentModel,
39
43
  };
44
+ const approvalEvents = alias(events, "approval_event");
40
45
  const runSelection = {
41
46
  itemKey: triggerEvents.itemKey,
42
47
  runId: runs.runId,
43
48
  eventId: runs.eventId,
44
49
  loop: runs.loopName,
45
50
  state: runs.stateJson,
51
+ resolvedFailureAttemptId: runs.resolvedFailureAttemptId,
46
52
  openedAt: runs.openedAt,
53
+ closedAt: runs.closedAt,
47
54
  repository: runs.repository,
48
55
  revision: runs.revision,
49
56
  task: runs.task,
@@ -51,8 +58,13 @@ const runSelection = {
51
58
  parentModel: runs.parentModel,
52
59
  stepName: steps.stepName,
53
60
  result: steps.result,
61
+ approvedAt: approvalEvents.receivedAt,
62
+ approvalChannel: approvals.channel,
63
+ approver: approvals.approver,
64
+ againFrom: runAgainEvents.fromRunId,
65
+ againRequestedBy: runAgainEvents.requestedBy,
54
66
  };
55
- const attemptSelection = {
67
+ export const attemptSelection = {
56
68
  attemptId: attempts.attemptId,
57
69
  runId: attempts.runId,
58
70
  stepName: attempts.stepName,
@@ -65,8 +77,14 @@ const attemptSelection = {
65
77
  parentModel: attempts.parentModel,
66
78
  startedAt: attempts.startedAt,
67
79
  outcome: attempts.outcomeJson,
80
+ endedAt: attempts.endedAt,
81
+ salvage: attempts.salvageJson,
82
+ start: attempts.startJson,
83
+ failureResolution: attempts.failureResolutionJson,
84
+ reattachedAt: attempts.reattachedAt,
85
+ comments: attempts.commentsJson,
86
+ outOfMemory: attempts.outOfMemoryJson,
68
87
  };
69
- // The state partitions are owned by @usine/core; these predicates only borrow their tags.
70
88
  const storedStateTag = sql `json_extract(${runs.stateJson}, '$._tag')`;
71
89
  const openState = inArray(storedStateTag, [...RunState.OPEN_STATE_TAGS]);
72
90
  const unstartedState = inArray(storedStateTag, [...RunState.UNSTARTED_STATE_TAGS]);
@@ -74,8 +92,7 @@ const unstartedState = inArray(storedStateTag, [...RunState.UNSTARTED_STATE_TAGS
74
92
  // cannot emit the declaration (TS2883).
75
93
  const storedAgent = (agent) => Schema.decodeUnknownSync(Schema.Json)(encodeAgent(agent));
76
94
  export const queriesFor = (database) => {
77
- // Keep `.execute()` explicit: the yieldable wrapper can cross an Effect
78
- // Workflow suspension before its final mutation is observable (ADR 0020).
95
+ // `.execute()` stays explicit (ADR 0020).
79
96
  const eventRouteByDedup = Effect.fn("RunStoreQueries.eventRouteByDedup")(function* (dedupKey) {
80
97
  const rows = yield* database
81
98
  .select(eventRouteSelection)
@@ -95,15 +112,64 @@ export const queriesFor = (database) => {
95
112
  .execute();
96
113
  return yield* decodeEventRoutes(rows);
97
114
  });
115
+ const selectRuns = () => database
116
+ .select(runSelection)
117
+ .from(runs)
118
+ .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
119
+ .innerJoin(steps, eq(steps.runId, runs.runId))
120
+ .leftJoin(approvals, eq(approvals.runId, runs.runId))
121
+ .leftJoin(approvalEvents, eq(approvalEvents.eventId, approvals.eventId))
122
+ .leftJoin(runAgainEvents, eq(runAgainEvents.eventId, runs.eventId));
98
123
  const runRows = Effect.fn("RunStoreQueries.runRows")(function* (runId) {
124
+ const rows = yield* selectRuns().where(eq(runs.runId, runId)).execute();
125
+ return yield* decodeRunRows(rows);
126
+ });
127
+ /** Returns the Attempts that match `filters` and whose leftovers weren't discarded. */
128
+ const recoveries = (...filters) => Effect.gen(function* () {
99
129
  const rows = yield* database
100
- .select(runSelection)
101
- .from(runs)
130
+ .select({
131
+ attemptId: attempts.attemptId,
132
+ worker: attempts.workerName,
133
+ repository: runs.repository,
134
+ runId: attempts.runId,
135
+ loop: triggerEvents.loopName,
136
+ itemKey: triggerEvents.itemKey,
137
+ number: attempts.attemptNumber,
138
+ permissions: triggerEvents.permissions,
139
+ outcome: sql `coalesce(json_extract(${attempts.outcomeJson}, '$._tag'), '')`,
140
+ salvage: attempts.salvageJson,
141
+ })
142
+ .from(attempts)
143
+ .innerJoin(runs, eq(runs.runId, attempts.runId))
102
144
  .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
103
- .innerJoin(steps, eq(steps.runId, runs.runId))
104
- .where(eq(runs.runId, runId))
145
+ .where(and(isNull(attempts.leftoversDiscardedAt), ...filters))
105
146
  .execute();
106
- return yield* decodeRunRows(rows);
147
+ const decoded = yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({
148
+ attemptId: AttemptId,
149
+ worker: WorkerName,
150
+ repository: RepositoryName,
151
+ runId: RunId,
152
+ loop: LoopName,
153
+ itemKey: ItemKey,
154
+ number: AttemptNumber,
155
+ permissions: LoopPermissions,
156
+ outcome: Schema.String,
157
+ salvage: Schema.NullOr(AttemptSalvage),
158
+ })))(rows);
159
+ return decoded.map(({ number, outcome, salvage, ...attempt }) => ({
160
+ ...attempt,
161
+ ref: salvageRefFor(attempt.runId, number),
162
+ succeeded: outcome === "Succeeded",
163
+ open: outcome === "",
164
+ rejected: salvage?._tag === "Rejected" ? Option.some(salvage) : Option.none(),
165
+ }));
166
+ });
167
+ const salvageCandidates = Effect.fn("RunStoreQueries.salvageCandidates")(function* () {
168
+ return yield* recoveries();
169
+ });
170
+ const salvageCandidate = Effect.fn("RunStoreQueries.salvageCandidate")(function* (attemptId) {
171
+ yield* Effect.annotateCurrentSpan({ attemptId });
172
+ return EffectArray.head(yield* recoveries(eq(attempts.attemptId, attemptId)));
107
173
  });
108
174
  const attemptRows = Effect.fn("RunStoreQueries.attemptRows")(function* (runId) {
109
175
  const rows = yield* database
@@ -131,17 +197,21 @@ export const queriesFor = (database) => {
131
197
  run: runFromRow(storedRun),
132
198
  step: stepFromRow(storedRun),
133
199
  attempts: storedAttempts.map(attemptFromRow),
200
+ resolvedFailureAttemptId: Option.fromNullishOr(storedRun.resolvedFailureAttemptId),
201
+ closedAt: Option.fromNullishOr(storedRun.closedAt),
202
+ approval: approvalFromRow(storedRun),
203
+ runAgain: runAgainFromRow(storedRun),
134
204
  repository: storedRun.repository,
135
205
  revision: storedRun.revision,
136
206
  task: storedRun.task,
137
207
  agent: storedRun.agent,
138
208
  parentModel: Option.fromNullishOr(storedRun.parentModel),
209
+ permissions: route.permissions,
139
210
  });
140
211
  });
141
212
  const summariesWithAttempts = Effect.fn("RunStoreQueries.summariesWithAttempts")(function* (storedRuns) {
142
213
  const runIds = storedRuns.map(({ runId }) => runId);
143
- // Keep history queries below adapter bind-parameter limits, even for a
144
- // long-lived Task with many Runs.
214
+ // Chunks keep each read below the driver's bind-parameter limit.
145
215
  const storedAttempts = (yield* Effect.forEach(EffectArray.chunksOf(runIds, 100), (ids) => database
146
216
  .select(attemptSelection)
147
217
  .from(attempts)
@@ -155,11 +225,7 @@ export const queriesFor = (database) => {
155
225
  const itemRunSummaries = Effect.fn("RunStoreQueries.itemRunSummaries")(function* (itemKeys) {
156
226
  if (itemKeys.length === 0)
157
227
  return [];
158
- const storedRuns = (yield* Effect.forEach(EffectArray.chunksOf([...new Set(itemKeys)], 100), (keys) => database
159
- .select(runSelection)
160
- .from(runs)
161
- .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
162
- .innerJoin(steps, eq(steps.runId, runs.runId))
228
+ const storedRuns = (yield* Effect.forEach(EffectArray.chunksOf([...new Set(itemKeys)], 100), (keys) => selectRuns()
163
229
  .where(inArray(triggerEvents.itemKey, keys))
164
230
  .execute()
165
231
  .pipe(Effect.flatMap(decodeRunRows))))
@@ -167,24 +233,15 @@ export const queriesFor = (database) => {
167
233
  .sort(newestRunFirst);
168
234
  return yield* summariesWithAttempts(storedRuns);
169
235
  });
170
- // Every open Run plus the newest `closedLimit` settled ones, so an old
171
- // Waiting run never falls out of the operator's view while history stays bounded.
236
+ // Every open Run plus the newest `closedLimit` closed ones, so an old waiting Run stays listed.
172
237
  const runSummaries = Effect.fn("RunStoreQueries.runSummaries")(function* (closedLimit) {
173
238
  const [openRuns, closedRuns] = yield* Effect.all([
174
- database
175
- .select(runSelection)
176
- .from(runs)
177
- .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
178
- .innerJoin(steps, eq(steps.runId, runs.runId))
239
+ selectRuns()
179
240
  .where(openState)
180
241
  .orderBy(desc(runs.openedAt), desc(runs.runId))
181
242
  .execute()
182
243
  .pipe(Effect.flatMap(decodeRunRows)),
183
- database
184
- .select(runSelection)
185
- .from(runs)
186
- .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
187
- .innerJoin(steps, eq(steps.runId, runs.runId))
244
+ selectRuns()
188
245
  .where(notInArray(storedStateTag, [...RunState.OPEN_STATE_TAGS]))
189
246
  .orderBy(desc(runs.openedAt), desc(runs.runId))
190
247
  .limit(closedLimit)
@@ -194,6 +251,45 @@ export const queriesFor = (database) => {
194
251
  const storedRuns = [...openRuns, ...closedRuns].sort(newestRunFirst);
195
252
  return yield* summariesWithAttempts(storedRuns);
196
253
  });
254
+ const runEvents = Effect.fn("RunStoreQueries.runEvents")(function* (runIds) {
255
+ // Chunks keep each read below the driver's bind-parameter limit.
256
+ const rows = yield* Effect.forEach(EffectArray.chunksOf([...new Set(runIds)], 100), (ids) => database
257
+ .select({
258
+ runId: runs.runId,
259
+ eventId: events.eventId,
260
+ dedupKey: events.dedupKey,
261
+ payload: events.payloadJson,
262
+ receivedAt: events.receivedAt,
263
+ })
264
+ .from(runs)
265
+ .innerJoin(events, eq(events.eventId, runs.eventId))
266
+ .where(inArray(runs.runId, ids))
267
+ .execute()
268
+ .pipe(Effect.flatMap(decodeRunEventRows)));
269
+ return rows.flat().map(runEventFromRow);
270
+ });
271
+ const closedRunSummaries = Effect.fn("RunStoreQueries.closedRunSummaries")(function* (limit) {
272
+ const closedRuns = yield* selectRuns()
273
+ .where(notInArray(storedStateTag, [...RunState.OPEN_STATE_TAGS]))
274
+ .orderBy(desc(runs.closedAt), desc(runs.runId))
275
+ .limit(limit)
276
+ .execute()
277
+ .pipe(Effect.flatMap(decodeRunRows));
278
+ return yield* summariesWithAttempts(closedRuns);
279
+ });
280
+ // Every closed Run with `closedAt` at or after `since`, or every closed Run for `None`.
281
+ const closedRunSummariesSince = Effect.fn("RunStoreQueries.closedRunSummariesSince")(function* (since) {
282
+ const closed = notInArray(storedStateTag, [...RunState.OPEN_STATE_TAGS]);
283
+ const closedRuns = yield* selectRuns()
284
+ .where(Option.match(since, {
285
+ onNone: () => closed,
286
+ onSome: (from) => and(closed, gte(runs.closedAt, encodeDateTime(from))),
287
+ }))
288
+ .orderBy(desc(runs.closedAt), desc(runs.runId))
289
+ .execute()
290
+ .pipe(Effect.flatMap(decodeRunRows));
291
+ return yield* summariesWithAttempts(closedRuns);
292
+ });
197
293
  const revision = Effect.fn("RunStoreQueries.projectionRevision")(function* () {
198
294
  const rows = yield* decodeProjectionRevision(yield* database
199
295
  .select({ revision: projectionRevision.revision })
@@ -203,10 +299,12 @@ export const queriesFor = (database) => {
203
299
  .execute());
204
300
  return Option.fromNullishOr(rows[0]?.revision);
205
301
  });
206
- const updateState = Effect.fn("RunStoreQueries.updateState")(function* (runId, state) {
302
+ // A closed state sets `closedAt` to `at` or now; an open state clears it.
303
+ const updateState = Effect.fn("RunStoreQueries.updateState")(function* (runId, state, at) {
304
+ const closedAt = RunState.isOpen(state) ? null : encodeDateTime(at ?? (yield* DateTime.now));
207
305
  const updated = yield* database
208
306
  .update(runs)
209
- .set({ stateJson: encodeRunState(state) })
307
+ .set({ stateJson: encodeRunState(state), closedAt })
210
308
  .where(eq(runs.runId, runId))
211
309
  .returning({ runId: runs.runId })
212
310
  .execute();
@@ -214,6 +312,19 @@ export const queriesFor = (database) => {
214
312
  return yield* new RunNotFoundError({ runId });
215
313
  }
216
314
  });
315
+ const resolveFailure = Effect.fn("RunStoreQueries.resolveFailure")(function* (runId, attemptId, resolution) {
316
+ yield* Effect.annotateCurrentSpan({ runId, attemptId });
317
+ yield* database
318
+ .update(attempts)
319
+ .set({ failureResolutionJson: encodeFailureResolution(resolution) })
320
+ .where(eq(attempts.attemptId, attemptId))
321
+ .execute();
322
+ yield* database
323
+ .update(runs)
324
+ .set({ resolvedFailureAttemptId: attemptId })
325
+ .where(eq(runs.runId, runId))
326
+ .execute();
327
+ });
217
328
  const addAttempt = Effect.fn("RunStoreQueries.addAttempt")(function* (attempt) {
218
329
  yield* database
219
330
  .insert(attempts)
@@ -230,13 +341,82 @@ export const queriesFor = (database) => {
230
341
  parentModel: Option.getOrNull(attempt.parentModel),
231
342
  startedAt: encodeDateTime(attempt.startedAt),
232
343
  outcomeJson: null,
344
+ endedAt: null,
345
+ salvageJson: null,
346
+ startJson: Option.match(attempt.start, { onNone: () => null, onSome: encodeAttemptStart }),
233
347
  })
234
348
  .execute();
235
349
  });
236
- const closeAttempt = Effect.fn("RunStoreQueries.closeAttempt")(function* (attemptId, outcome) {
350
+ const recordReattach = Effect.fn("RunStoreQueries.recordReattach")(function* (attemptId, reattachedAt) {
351
+ yield* Effect.annotateCurrentSpan({ attemptId });
237
352
  yield* database
238
353
  .update(attempts)
239
- .set({ outcomeJson: encodeOutcome(outcome) })
354
+ .set({ reattachedAt: encodeDateTime(reattachedAt) })
355
+ .where(eq(attempts.attemptId, attemptId))
356
+ .execute();
357
+ });
358
+ /** Returns the operator retry for the Run's next Attempt, or `None`. */
359
+ const nextStart = Effect.fn("RunStoreQueries.nextStart")(function* (runId) {
360
+ const rows = yield* database
361
+ .select({ nextStart: runs.nextStartJson })
362
+ .from(runs)
363
+ .where(eq(runs.runId, runId))
364
+ .execute();
365
+ return Option.fromNullishOr((yield* decodeNextStarts(rows))[0]?.nextStart);
366
+ });
367
+ const setNextStart = Effect.fn("RunStoreQueries.setNextStart")(function* (runId, start) {
368
+ yield* database
369
+ .update(runs)
370
+ .set({
371
+ nextStartJson: Option.match(start, { onNone: () => null, onSome: encodeAttemptStart }),
372
+ })
373
+ .where(eq(runs.runId, runId))
374
+ .execute();
375
+ });
376
+ const recordSalvage = Effect.fn("RunStoreQueries.recordSalvage")(function* (attemptId, salvage) {
377
+ yield* Effect.annotateCurrentSpan({ attemptId, salvage: salvage._tag });
378
+ yield* database
379
+ .update(attempts)
380
+ .set({ salvageJson: encodeSalvage(salvage) })
381
+ .where(eq(attempts.attemptId, attemptId))
382
+ .execute();
383
+ });
384
+ const recordComments = Effect.fn("RunStoreQueries.recordComments")(function* (attemptId, comments) {
385
+ yield* Effect.annotateCurrentSpan({ attemptId, comments: comments.length });
386
+ yield* database
387
+ .update(attempts)
388
+ .set({ commentsJson: encodeComments(comments) })
389
+ .where(eq(attempts.attemptId, attemptId))
390
+ .execute();
391
+ });
392
+ /**
393
+ * Records the kills of one of the Attempt's containers, replacing an earlier record of the same
394
+ * container, and returns the kills of all its containers.
395
+ */
396
+ const recordOutOfMemory = Effect.fn("RunStoreQueries.recordOutOfMemory")(function* (attemptId, kills) {
397
+ yield* Effect.annotateCurrentSpan({
398
+ attemptId,
399
+ container: kills.container,
400
+ kills: kills.kills,
401
+ });
402
+ const rows = yield* database
403
+ .select({ outOfMemory: attempts.outOfMemoryJson })
404
+ .from(attempts)
405
+ .where(eq(attempts.attemptId, attemptId))
406
+ .execute();
407
+ const recorded = (yield* decodeOutOfMemory(rows))[0]?.outOfMemory ?? [];
408
+ const containers = EffectArray.append(recorded.filter(({ container }) => container !== kills.container), kills);
409
+ yield* database
410
+ .update(attempts)
411
+ .set({ outOfMemoryJson: encodeOutOfMemory(containers) })
412
+ .where(eq(attempts.attemptId, attemptId))
413
+ .execute();
414
+ return totalOutOfMemoryKills(containers);
415
+ });
416
+ const closeAttempt = Effect.fn("RunStoreQueries.closeAttempt")(function* (attemptId, outcome, endedAt) {
417
+ yield* database
418
+ .update(attempts)
419
+ .set({ outcomeJson: encodeOutcome(outcome), endedAt: encodeDateTime(endedAt) })
240
420
  .where(and(eq(attempts.attemptId, attemptId), isNull(attempts.outcomeJson)))
241
421
  .execute();
242
422
  });
@@ -277,7 +457,7 @@ export const queriesFor = (database) => {
277
457
  const approvalExists = Effect.fn("RunStoreQueries.approvalExists")(function* (runId) {
278
458
  return (yield* database.$count(approvals, eq(approvals.runId, runId)).execute()) > 0;
279
459
  });
280
- const recordApproval = Effect.fn("RunStoreQueries.recordApproval")(function* (runId, approval) {
460
+ const recordApproval = Effect.fn("RunStoreQueries.recordApproval")(function* (runId, approval, source) {
281
461
  yield* database
282
462
  .insert(events)
283
463
  .values({
@@ -288,30 +468,15 @@ export const queriesFor = (database) => {
288
468
  })
289
469
  .onConflictDoNothing()
290
470
  .execute();
291
- yield* database.insert(approvals).values({ runId, eventId: approval.id }).execute();
292
- });
293
- const requestWorkflowCommand = Effect.fn("RunStoreQueries.requestWorkflowCommand")(function* (runId, command) {
294
- yield* database
295
- .insert(workflowCommands)
296
- .values({ runId, command })
297
- .onConflictDoNothing()
298
- .execute();
299
- });
300
- const requestInterrupt = Effect.fn("RunStoreQueries.requestInterrupt")(function* (runId) {
301
471
  yield* database
302
- .delete(workflowCommands)
303
- .where(and(eq(workflowCommands.runId, runId), inArray(workflowCommands.command, ["Approve", "Resume"])))
304
- .execute();
305
- yield* requestWorkflowCommand(runId, "Interrupt");
306
- });
307
- const workflowCommandsFor = Effect.fn("RunStoreQueries.workflowCommandsFor")(function* (runId) {
308
- yield* Effect.annotateCurrentSpan({ runId });
309
- const rows = yield* database
310
- .select({ runId: workflowCommands.runId, command: workflowCommands.command })
311
- .from(workflowCommands)
312
- .where(eq(workflowCommands.runId, runId))
472
+ .insert(approvals)
473
+ .values({
474
+ runId,
475
+ eventId: approval.id,
476
+ channel: source.channel,
477
+ approver: Option.getOrNull(source.approver),
478
+ })
313
479
  .execute();
314
- return (yield* decodeWorkflowCommands(rows)).map(({ command }) => command);
315
480
  });
316
481
  const createRun = Effect.fn("RunStoreQueries.createRun")(function* (submission, eventId, runId) {
317
482
  const candidate = alias(triggerEvents, "candidate");
@@ -376,7 +541,48 @@ export const queriesFor = (database) => {
376
541
  .limit(1)
377
542
  .execute());
378
543
  });
379
- const recordTriggerEvent = Effect.fn("RunStoreQueries.recordTriggerEvent")(function* (submission) {
544
+ /** Returns the newest reopening of `eventId` (#267), with its reopening number. */
545
+ const newestReopening = Effect.fn("RunStoreQueries.newestReopening")(function* (eventId) {
546
+ const rows = yield* decodeReopenings(yield* database
547
+ .select({ ...eventRouteSelection, reopening: triggerEvents.reopening })
548
+ .from(events)
549
+ .innerJoin(triggerEvents, eq(triggerEvents.eventId, events.eventId))
550
+ .where(eq(triggerEvents.reopensEventId, eventId))
551
+ .orderBy(desc(triggerEvents.reopening))
552
+ .limit(1)
553
+ .execute());
554
+ return Option.map(Option.fromNullishOr(rows[0]), (row) => ({
555
+ submission: submissionFromRow(row),
556
+ reopening: row.reopening,
557
+ }));
558
+ });
559
+ /** Returns the trigger Event that `eventId` reopens or runs again, or `None` for a trigger's own Event. */
560
+ const reopenedEvent = Effect.fn("RunStoreQueries.reopenedEvent")(function* (eventId) {
561
+ const reopened = alias(events, "reopened_event");
562
+ const rows = yield* decodeReopenedEvents(yield* database
563
+ .select({ eventId: reopened.eventId, dedupKey: reopened.dedupKey })
564
+ .from(triggerEvents)
565
+ .innerJoin(reopened, eq(reopened.eventId, triggerEvents.reopensEventId))
566
+ .where(eq(triggerEvents.eventId, eventId))
567
+ .execute());
568
+ return Option.fromNullishOr(rows[0]);
569
+ });
570
+ /** Records that `eventId` came from running a closed Run again, and the requesting login. */
571
+ const recordRunAgain = Effect.fn("RunStoreQueries.recordRunAgain")(function* (eventId, again) {
572
+ yield* database
573
+ .insert(runAgainEvents)
574
+ .values({
575
+ eventId,
576
+ fromRunId: again.from,
577
+ requestedBy: Option.getOrNull(again.requestedBy),
578
+ })
579
+ .execute();
580
+ });
581
+ /**
582
+ * Records a trigger Event and its route once, and returns the route as first recorded.
583
+ * `reopens` is the earlier Event that this one reopens.
584
+ */
585
+ const recordTriggerEvent = Effect.fn("RunStoreQueries.recordTriggerEvent")(function* (submission, reopens = Option.none()) {
380
586
  yield* database
381
587
  .insert(events)
382
588
  .values({
@@ -398,6 +604,7 @@ export const queriesFor = (database) => {
398
604
  loopName: submission.loop,
399
605
  stepName: submission.step,
400
606
  approval: submission.approval,
607
+ permissions: submission.permissions,
401
608
  cycleLimit: Option.getOrNull(submission.cycleLimit),
402
609
  cycleGeneration: submission.cycleGeneration,
403
610
  repository: submission.repository,
@@ -405,6 +612,11 @@ export const queriesFor = (database) => {
405
612
  task: submission.task,
406
613
  agentJson: storedAgent(submission.agent),
407
614
  parentModel: Option.getOrNull(submission.parentModel),
615
+ reopensEventId: Option.getOrNull(Option.map(reopens, ({ eventId }) => eventId)),
616
+ reopening: Option.match(reopens, {
617
+ onNone: () => 0,
618
+ onSome: ({ reopening }) => reopening,
619
+ }),
408
620
  })
409
621
  .execute();
410
622
  return submission;
@@ -416,6 +628,16 @@ export const queriesFor = (database) => {
416
628
  const current = routes[0];
417
629
  if (current === undefined)
418
630
  return Option.none();
631
+ const suppression = yield* loopItemQueries(database).suppression(current.loop, current.itemKey);
632
+ if (Option.isSome(suppression)) {
633
+ yield* Effect.logInfo("Queued Events wait while a suppression keeps the Item", {
634
+ runId,
635
+ suppressedBy: suppression.value.runId,
636
+ loop: current.loop,
637
+ itemKey: current.itemKey,
638
+ });
639
+ return Option.none();
640
+ }
419
641
  const queuedRows = yield* database
420
642
  .select(eventRouteSelection)
421
643
  .from(events)
@@ -424,55 +646,53 @@ export const queriesFor = (database) => {
424
646
  .leftJoin(ignoredEvents, eq(ignoredEvents.eventId, events.eventId))
425
647
  .where(and(eq(triggerEvents.loopName, current.loop), eq(triggerEvents.itemKey, current.itemKey), cycleQueries(database).currentEvent, isNull(runs.runId), isNull(ignoredEvents.eventId)))
426
648
  .orderBy(desc(events.receivedAt))
427
- .limit(1)
428
649
  .execute();
429
650
  const queued = yield* decodeEventRoutes(queuedRows);
430
- return Option.fromNullishOr(queued[0]).pipe(Option.map(submissionFromRow));
651
+ return Option.fromNullishOr(queued[0]).pipe(Option.map((newest) => ({
652
+ submission: submissionFromRow(newest),
653
+ eventIds: queued.map((row) => row.eventId),
654
+ })));
431
655
  });
432
- const recoverable = Effect.fn("RunStoreQueries.recoverable")(function* () {
656
+ const runIdsToDrive = Effect.fn("RunStoreQueries.runIdsToDrive")(function* () {
657
+ const waiting = "Waiting";
433
658
  const rows = yield* database
434
- .select({
435
- ...eventRouteSelection,
436
- runId: runs.runId,
437
- state: runs.stateJson,
438
- })
659
+ .select({ runId: runs.runId })
439
660
  .from(runs)
440
- .innerJoin(events, eq(events.eventId, runs.eventId))
441
- .innerJoin(triggerEvents, eq(triggerEvents.eventId, events.eventId))
442
- .where(openState)
661
+ .where(and(openState, ne(storedStateTag, waiting)))
443
662
  .execute();
444
- return yield* decodeRecoverableRuns(rows);
663
+ return (yield* decodeRunIds(rows)).map(({ runId }) => runId);
445
664
  });
446
- const pendingWorkflowCommands = Effect.fn("RunStoreQueries.pendingWorkflowCommands")(function* () {
447
- const rows = yield* database
448
- .select({ runId: workflowCommands.runId, command: workflowCommands.command })
449
- .from(workflowCommands)
450
- .execute();
451
- const decoded = yield* decodeWorkflowCommands(rows);
452
- // Delivery order per run is a replay invariant; WORKFLOW_COMMAND_PRIORITY names it.
453
- const priority = (command) => WORKFLOW_COMMAND_PRIORITY.indexOf(command);
454
- return [...decoded].sort((left, right) => left.runId === right.runId
455
- ? priority(left.command) - priority(right.command)
456
- : left.runId < right.runId
457
- ? -1
458
- : 1);
459
- });
460
- const completeWorkflowCommand = Effect.fn("RunStoreQueries.completeWorkflowCommand")(function* (runId, command) {
461
- yield* database
462
- .delete(workflowCommands)
463
- .where(and(eq(workflowCommands.runId, runId), eq(workflowCommands.command, command)))
665
+ // The newest closed Run of each Item that has a current queued Event and no open Run, excluding
666
+ // Items waiting for a cycle-budget reset or under a suppression.
667
+ const pendingContinuations = Effect.fn("RunStoreQueries.pendingContinuations")(function* () {
668
+ const items = yield* database
669
+ .selectDistinct({ loop: triggerEvents.loopName, itemKey: triggerEvents.itemKey })
670
+ .from(triggerEvents)
671
+ .leftJoin(runs, eq(runs.eventId, triggerEvents.eventId))
672
+ .leftJoin(ignoredEvents, eq(ignoredEvents.eventId, triggerEvents.eventId))
673
+ .leftJoin(itemCycleBudgets, eq(itemCycleBudgets.itemKey, triggerEvents.itemKey))
674
+ .leftJoin(loopItems, and(eq(loopItems.loopName, triggerEvents.loopName), eq(loopItems.itemKey, triggerEvents.itemKey)))
675
+ .where(and(isNull(runs.runId), isNull(ignoredEvents.eventId), isNull(itemCycleBudgets.askJson), isNull(loopItems.suppressedByRunId), cycleQueries(database).currentEvent))
464
676
  .execute();
677
+ const decoded = yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ loop: LoopName, itemKey: ItemKey })))(items);
678
+ const closed = yield* Effect.forEach(decoded, ({ loop, itemKey }) => Effect.gen(function* () {
679
+ if ((yield* blockingRun(loop, itemKey)).length > 0)
680
+ return [];
681
+ return yield* decodeRunIds(yield* database
682
+ .select({ runId: runs.runId })
683
+ .from(runs)
684
+ .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
685
+ .where(and(eq(triggerEvents.loopName, loop), eq(triggerEvents.itemKey, itemKey)))
686
+ .orderBy(desc(runs.openedAt), desc(runs.runId))
687
+ .limit(1)
688
+ .execute());
689
+ }));
690
+ return closed.flat().map(({ runId }) => runId);
465
691
  });
466
- const revokeUnstarted = Effect.fn("RunStoreQueries.revokeUnstarted")(function* (loop, itemKey) {
467
- const rows = yield* decodeRunIds(yield* database
468
- .select({ runId: runs.runId })
469
- .from(runs)
470
- .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
471
- .where(and(eq(triggerEvents.loopName, loop), eq(triggerEvents.itemKey, itemKey), unstartedState))
472
- .execute());
692
+ const revokeQueued = Effect.fn("RunStoreQueries.revokeQueued")(function* (loop, itemKey, eventIds) {
473
693
  const candidate = alias(triggerEvents, "candidate");
474
694
  const candidateRun = alias(runs, "candidate_run");
475
- yield* database
695
+ const ignored = yield* database
476
696
  .insert(ignoredEvents)
477
697
  .select(database
478
698
  .select({
@@ -481,41 +701,69 @@ export const queriesFor = (database) => {
481
701
  })
482
702
  .from(candidate)
483
703
  .leftJoin(candidateRun, eq(candidateRun.eventId, candidate.eventId))
484
- .where(and(eq(candidate.loopName, loop), eq(candidate.itemKey, itemKey), isNull(candidateRun.runId))))
704
+ .where(and(eq(candidate.loopName, loop), eq(candidate.itemKey, itemKey), inArray(candidate.eventId, eventIds), isNull(candidateRun.runId))))
485
705
  .onConflictDoNothing()
706
+ .returning({ eventId: ignoredEvents.eventId })
486
707
  .execute();
708
+ return ignored.length;
709
+ });
710
+ const revokeUnstarted = Effect.fn("RunStoreQueries.revokeUnstarted")(function* (loop, itemKey) {
711
+ const rows = yield* decodeRunIds(yield* database
712
+ .select({ runId: runs.runId })
713
+ .from(runs)
714
+ .innerJoin(triggerEvents, eq(triggerEvents.eventId, runs.eventId))
715
+ .where(and(eq(triggerEvents.loopName, loop), eq(triggerEvents.itemKey, itemKey), unstartedState))
716
+ .execute());
717
+ const queued = database
718
+ .select({ eventId: triggerEvents.eventId })
719
+ .from(triggerEvents)
720
+ .where(and(eq(triggerEvents.loopName, loop), eq(triggerEvents.itemKey, itemKey)));
721
+ yield* revokeQueued(loop, itemKey, queued);
487
722
  return rows;
488
723
  });
489
724
  return {
725
+ salvageCandidates,
726
+ salvageCandidate,
490
727
  cycles: cycleQueries(database),
728
+ loopItems: loopItemQueries(database),
491
729
  ...transcriptQueries(database),
492
730
  addAttempt,
731
+ recordReattach,
732
+ nextStart,
733
+ setNextStart,
734
+ recordSalvage,
735
+ recordComments,
736
+ recordOutOfMemory,
493
737
  amend,
494
738
  markIntervened,
495
739
  approvalExists,
496
740
  blockingRun,
497
741
  closeAttempt,
498
- completeWorkflowCommand,
499
742
  createRun,
500
743
  eventRouteByRun,
501
- workflowCommandsFor,
502
744
  ignoredEvent,
745
+ newestReopening,
503
746
  nextQueued,
504
- pendingWorkflowCommands,
747
+ reopenedEvent,
748
+ recordRunAgain,
749
+ pendingContinuations,
505
750
  recordTriggerEvent,
506
- recoverable,
751
+ runIdsToDrive,
507
752
  recordApproval,
508
- requestInterrupt,
509
- requestWorkflowCommand,
510
753
  revision,
754
+ revokeQueued,
511
755
  revokeUnstarted,
512
756
  runSummaries,
757
+ closedRunSummaries,
758
+ closedRunSummariesSince,
513
759
  itemRunSummaries,
760
+ runEvents,
514
761
  runForEvent,
515
762
  snapshot,
516
763
  updateStepResult,
517
764
  updateAttemptParentModel,
518
765
  updateState,
766
+ resolveFailure,
519
767
  };
520
768
  };
521
769
  //# sourceMappingURL=queries.js.map