@capybara-hq/kernel 0.1.0-rc.24 → 0.1.0-rc.26

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 (198) hide show
  1. package/dist/admin/admin.module.js +2 -0
  2. package/dist/admin/health.service.d.ts +2 -2
  3. package/dist/admin/health.service.js +6 -2
  4. package/dist/admin/outbox-dead.service.d.ts +7 -3
  5. package/dist/admin/outbox-dead.service.js +17 -6
  6. package/dist/admin/overview.service.d.ts +6 -6
  7. package/dist/admin/overview.service.js +7 -7
  8. package/dist/agents/agents.module.js +3 -0
  9. package/dist/agents/agents.service.d.ts +6 -1
  10. package/dist/agents/agents.service.js +11 -2
  11. package/dist/background/wake-rules.d.ts +5 -0
  12. package/dist/background/wake-rules.js +16 -5
  13. package/dist/catalog/catalog-import.module.js +0 -4
  14. package/dist/catalog/catalog-scope.service.d.ts +63 -26
  15. package/dist/catalog/catalog-scope.service.js +146 -71
  16. package/dist/catalog/catalog.module.js +4 -3
  17. package/dist/catalog/catalog.service.d.ts +11 -4
  18. package/dist/catalog/catalog.service.js +21 -5
  19. package/dist/catalog/cli.d.ts +4 -3
  20. package/dist/catalog/cli.js +8 -13
  21. package/dist/catalog/job-ids.d.ts +8 -20
  22. package/dist/catalog/job-ids.js +11 -16
  23. package/dist/catalog/marketplaces.service.d.ts +1 -1
  24. package/dist/catalog/org-plugin-uploads.d.ts +11 -4
  25. package/dist/catalog/org-plugin-uploads.js +21 -6
  26. package/dist/catalog/owned-items.d.ts +6 -13
  27. package/dist/catalog/owned-items.js +11 -29
  28. package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
  29. package/dist/catalog/owned-plugin-cleanup.js +12 -66
  30. package/dist/catalog/platform-components.d.ts +56 -34
  31. package/dist/catalog/platform-components.js +68 -78
  32. package/dist/catalog/plugin-import.d.ts +28 -25
  33. package/dist/catalog/plugin-import.js +90 -76
  34. package/dist/catalog/plugin-upgrade.d.ts +4 -4
  35. package/dist/catalog/plugin-upgrade.js +7 -19
  36. package/dist/catalog/scope-jobs.d.ts +59 -0
  37. package/dist/catalog/scope-jobs.js +65 -0
  38. package/dist/catalog/scope-sync-workers.d.ts +22 -0
  39. package/dist/catalog/scope-sync-workers.js +58 -2
  40. package/dist/catalog/scope-sync.d.ts +40 -65
  41. package/dist/catalog/scope-sync.js +114 -122
  42. package/dist/channels/channels.module.d.ts +9 -8
  43. package/dist/channels/channels.module.js +11 -10
  44. package/dist/channels/connections.service.d.ts +3 -3
  45. package/dist/channels/connections.service.js +6 -6
  46. package/dist/channels/deliver-job.d.ts +10 -3
  47. package/dist/channels/deliver-job.js +7 -3
  48. package/dist/channels/inbound.js +2 -2
  49. package/dist/channels/jobs.d.ts +11 -4
  50. package/dist/channels/jobs.js +18 -3
  51. package/dist/channels/outbound.d.ts +5 -2
  52. package/dist/channels/outbound.js +9 -2
  53. package/dist/channels/threads.d.ts +4 -3
  54. package/dist/channels/threads.js +4 -3
  55. package/dist/conversations/conversations.controller.d.ts +4 -2
  56. package/dist/conversations/conversations.controller.js +8 -4
  57. package/dist/conversations/conversations.service.d.ts +54 -9
  58. package/dist/conversations/conversations.service.js +94 -25
  59. package/dist/conversations/events.d.ts +27 -0
  60. package/dist/conversations/events.js +30 -1
  61. package/dist/conversations/source.d.ts +10 -0
  62. package/dist/conversations/source.js +17 -0
  63. package/dist/conversations/tree.d.ts +10 -0
  64. package/dist/conversations/tree.js +35 -0
  65. package/dist/db/kernel-schema.d.ts +19 -15
  66. package/dist/devices/devices.service.d.ts +25 -14
  67. package/dist/devices/devices.service.js +36 -30
  68. package/dist/evals/evals.module.js +5 -1
  69. package/dist/evals/jobs.d.ts +14 -6
  70. package/dist/evals/jobs.js +21 -19
  71. package/dist/evals/run-replay.d.ts +13 -2
  72. package/dist/evals/run-replay.js +28 -1
  73. package/dist/evals/runs.service.d.ts +5 -6
  74. package/dist/evals/runs.service.js +11 -33
  75. package/dist/evals/snapshot.d.ts +7 -1
  76. package/dist/evals/snapshot.js +2 -1
  77. package/dist/extensions/hooks.d.ts +22 -3
  78. package/dist/extensions/hooks.js +15 -1
  79. package/dist/extensions/lifecycle-hooks.d.ts +99 -0
  80. package/dist/extensions/lifecycle-hooks.js +273 -0
  81. package/dist/extensions/org-config.d.ts +13 -1
  82. package/dist/extensions/org-config.js +19 -2
  83. package/dist/extensions/permission-view.d.ts +9 -5
  84. package/dist/extensions/permission-view.js +9 -4
  85. package/dist/extensions/point-defaults.d.ts +28 -1
  86. package/dist/extensions/point-defaults.js +44 -13
  87. package/dist/inbox/inbox.service.d.ts +6 -20
  88. package/dist/inbox/inbox.service.js +0 -3
  89. package/dist/inbox/org-notices.d.ts +1 -1
  90. package/dist/inbox/platform-inbox.service.d.ts +3 -32
  91. package/dist/inbox/platform-inbox.service.js +5 -54
  92. package/dist/infra/outbox/outbox.service.d.ts +6 -0
  93. package/dist/infra/outbox/outbox.service.js +51 -17
  94. package/dist/infra/outbox/registry.d.ts +176 -0
  95. package/dist/infra/outbox/registry.js +183 -0
  96. package/dist/infra/queue/queue-names.d.ts +1 -1
  97. package/dist/infra/queue/queue-names.js +1 -0
  98. package/dist/local-devices/device-policy.service.d.ts +131 -26
  99. package/dist/local-devices/device-policy.service.js +341 -145
  100. package/dist/local-devices/machines.d.ts +17 -2
  101. package/dist/local-devices/machines.js +66 -5
  102. package/dist/local-devices/policy-op-jobs.d.ts +15 -0
  103. package/dist/local-devices/policy-op-jobs.js +12 -0
  104. package/dist/local-devices/policy-summary.d.ts +36 -3
  105. package/dist/local-devices/policy-summary.js +47 -1
  106. package/dist/orgs/directory-sync.js +1 -1
  107. package/dist/orgs/members.service.js +1 -1
  108. package/dist/orgs/offboarding.d.ts +50 -57
  109. package/dist/orgs/offboarding.js +117 -162
  110. package/dist/orgs/orgs.module.js +3 -0
  111. package/dist/orgs/orgs.service.d.ts +11 -5
  112. package/dist/orgs/orgs.service.js +19 -7
  113. package/dist/overrides.d.ts +7 -0
  114. package/dist/overrides.js +4 -0
  115. package/dist/platform/ops/conversations.ops.js +1 -1
  116. package/dist/platform/ops/inbox.ops.js +4 -20
  117. package/dist/platform/ops/platform.ops.d.ts +1 -0
  118. package/dist/platform/ops/platform.ops.js +6 -3
  119. package/dist/roles/worker-role.d.ts +10 -5
  120. package/dist/roles/worker-role.js +19 -9
  121. package/dist/roles/worker.module.js +2 -4
  122. package/dist/runtime/context/build.d.ts +3 -0
  123. package/dist/runtime/context/build.js +15 -0
  124. package/dist/runtime/context/plugin-context.d.ts +20 -0
  125. package/dist/runtime/context/plugin-context.js +30 -0
  126. package/dist/runtime/langgraph-loop.js +4 -0
  127. package/dist/runtime/loop.d.ts +8 -0
  128. package/dist/runtime/maintenance.d.ts +9 -4
  129. package/dist/runtime/maintenance.js +52 -13
  130. package/dist/runtime/model-call.d.ts +6 -1
  131. package/dist/runtime/model-call.js +1 -0
  132. package/dist/runtime/plugin-context.service.d.ts +75 -0
  133. package/dist/runtime/plugin-context.service.js +220 -0
  134. package/dist/runtime/run-context.service.d.ts +7 -3
  135. package/dist/runtime/run-context.service.js +12 -2
  136. package/dist/runtime/run-control.d.ts +6 -1
  137. package/dist/runtime/run-control.js +13 -0
  138. package/dist/runtime/run-finish.d.ts +2 -2
  139. package/dist/runtime/run-finish.js +11 -6
  140. package/dist/runtime/run-jobs.d.ts +21 -4
  141. package/dist/runtime/run-jobs.js +29 -6
  142. package/dist/runtime/run-orphan-check.job.d.ts +14 -11
  143. package/dist/runtime/run-orphan-check.job.js +16 -11
  144. package/dist/runtime/run-queue.d.ts +29 -24
  145. package/dist/runtime/run-queue.js +118 -57
  146. package/dist/runtime/run-status.d.ts +12 -0
  147. package/dist/runtime/run-status.js +21 -0
  148. package/dist/runtime/run-worker.d.ts +32 -3
  149. package/dist/runtime/run-worker.js +143 -21
  150. package/dist/runtime/runtime.module.js +2 -0
  151. package/dist/runtime/step-log.d.ts +6 -5
  152. package/dist/runtime/step-log.js +6 -5
  153. package/dist/runtime/subagents/dispatch.js +1 -0
  154. package/dist/runtime/subagents/skill-fork.js +3 -0
  155. package/dist/runtime/usage-report.d.ts +3 -1
  156. package/dist/runtime/usage-report.js +4 -2
  157. package/dist/runtime/waits.d.ts +16 -6
  158. package/dist/runtime/waits.js +37 -15
  159. package/dist/sandbox/jobs.d.ts +20 -10
  160. package/dist/sandbox/jobs.js +23 -17
  161. package/dist/schedules/fire.d.ts +25 -4
  162. package/dist/schedules/fire.js +87 -13
  163. package/dist/schedules/job-ids.d.ts +66 -17
  164. package/dist/schedules/job-ids.js +81 -13
  165. package/dist/schedules/run-tracker.js +2 -1
  166. package/dist/schedules/scan.job.d.ts +16 -35
  167. package/dist/schedules/scan.job.js +38 -147
  168. package/dist/schedules/scheduled-tasks.service.d.ts +6 -6
  169. package/dist/schedules/scheduled-tasks.service.js +17 -14
  170. package/dist/schedules/schedules.module.js +0 -4
  171. package/dist/skills/catalog.d.ts +7 -3
  172. package/dist/skills/catalog.js +12 -2
  173. package/dist/skills/changes.controller.d.ts +1 -1
  174. package/dist/skills/changes.controller.js +1 -1
  175. package/dist/tools/approvals.service.d.ts +111 -23
  176. package/dist/tools/approvals.service.js +326 -77
  177. package/dist/tools/builtins/sandbox/task-output.js +21 -6
  178. package/dist/tools/device-op-resolver.d.ts +40 -0
  179. package/dist/tools/device-op-resolver.js +121 -0
  180. package/dist/tools/network-asks.d.ts +1 -1
  181. package/dist/tools/network-asks.js +4 -3
  182. package/dist/tools/pipeline.d.ts +11 -2
  183. package/dist/tools/pipeline.js +5 -0
  184. package/dist/tools/questions.service.d.ts +15 -4
  185. package/dist/tools/questions.service.js +21 -19
  186. package/dist/tools/registry.d.ts +8 -1
  187. package/dist/tools/registry.js +9 -0
  188. package/dist/tools/tools.module.js +4 -0
  189. package/package.json +3 -3
  190. package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
  191. package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
  192. package/src/migrations/0123_pgvector.sql +27 -0
  193. package/src/migrations/0124_conversation_source.sql +36 -0
  194. package/src/migrations/0125_conversation_tree_stopped_at.sql +5 -0
  195. package/dist/catalog/platform-migration.d.ts +0 -115
  196. package/dist/catalog/platform-migration.js +0 -583
  197. package/dist/infra/queue/add-job-once.d.ts +0 -14
  198. package/dist/infra/queue/add-job-once.js +0 -16
@@ -19,10 +19,29 @@ export const SANDBOX_SUSPEND_JOB = "sandbox-suspend-idle";
19
19
  /** The inactivity destruction sweep (hourly, once per tick across replicas). */
20
20
  export const SANDBOX_DESTROY_JOB = "sandbox-destroy-inactive";
21
21
  /**
22
- * One-off job: destroy a batch of devices now, instance and file system (device
23
- * deletion, offboarding; data `{ deviceIds }`).
22
+ * One-off job: destroy devices now, instance and file system (data `{ deviceIds }`). Owed through
23
+ * the outbox by the transaction that soft-deletes the device (a device's deletion, offboarding),
24
+ * one device per job under {@link deviceDestroyJobId} ({@link oweDeviceDestroy}).
24
25
  */
25
26
  export const SANDBOX_DESTROY_FOR_JOB = "sandbox-destroy-for";
27
+ /**
28
+ * The deterministic id of a deleted device's destroy job (no `:`): a device is deleted once, so
29
+ * its destruction is owed once.
30
+ */
31
+ export const deviceDestroyJobId = (deviceId) => `device-destroy-${deviceId}`;
32
+ /**
33
+ * Owes the destruction of a device in the caller's transaction, the one that soft-deleted it
34
+ * (transactional outbox spec §8.1; throws outside a transaction).
35
+ */
36
+ export function oweDeviceDestroy(outbox, deviceId, orgId) {
37
+ return outbox.add({
38
+ queue: MAINTENANCE_QUEUE_NAME,
39
+ name: SANDBOX_DESTROY_FOR_JOB,
40
+ jobId: deviceDestroyJobId(deviceId),
41
+ data: { deviceIds: [deviceId] },
42
+ orgId,
43
+ });
44
+ }
26
45
  /**
27
46
  * One-off job: start a device over with empty files (`resetFileSystem`; data
28
47
  * `{ deviceId }`, job id {@link resetFilesJobId}).
@@ -34,27 +53,14 @@ export const SANDBOX_RESET_FILES_JOB = "sandbox-reset-files";
34
53
  */
35
54
  export const resetFilesJobId = (deviceId) => `${SANDBOX_RESET_FILES_JOB}-${deviceId}`;
36
55
  /**
37
- * The producer of the sandbox maintenance jobs: {@link SANDBOX_DESTROY_FOR_JOB} (BullMQ's own job
38
- * ids, as before: every enqueue is a new job, also after a batch failed all its attempts) and
39
- * {@link SANDBOX_RESET_FILES_JOB}; `MaintenanceProcessor` consumes them.
56
+ * The producer of {@link SANDBOX_RESET_FILES_JOB} (destroys are owed through the outbox,
57
+ * {@link oweDeviceDestroy}); `MaintenanceProcessor` consumes both.
40
58
  */
41
59
  let SandboxJobs = class SandboxJobs {
42
60
  queue;
43
61
  constructor(queue) {
44
62
  this.queue = queue;
45
63
  }
46
- /**
47
- * Enqueues the destruction of these devices: a failed batch is retried whole (destroying
48
- * one already destroyed does nothing).
49
- */
50
- async destroy(deviceIds) {
51
- await this.queue.add(SANDBOX_DESTROY_FOR_JOB, { deviceIds }, {
52
- attempts: 5,
53
- backoff: { type: "exponential", delay: 30_000 },
54
- removeOnComplete: true,
55
- removeOnFail: 100,
56
- });
57
- }
58
64
  /**
59
65
  * Enqueues the reset of a device's files (see {@link resetFilesJobId}): retried on failure
60
66
  * (the exclusive lease not obtained in time, the provider failing); a job that failed all its
@@ -7,8 +7,10 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
7
7
  import { KernelDb } from "../infra/database/kernel-db.js";
8
8
  import { TransactionHost } from "../infra/database/transaction-host.js";
9
9
  import { LockService } from "../infra/lock/lock.service.js";
10
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
10
11
  import type { KernelLogger } from "../log.js";
11
12
  import { OrgLock } from "../orgs/org-lock.js";
13
+ import { type SchedSettleJobData } from "./job-ids.js";
12
14
  import { ScheduledTaskRunTracker } from "./run-tracker.js";
13
15
  import { ScheduledTaskStore } from "./store.js";
14
16
  /** A task run's message client id in a conversation's queue (never read back as "the task's"). */
@@ -21,10 +23,12 @@ export declare function localTimeOf(at: Date, timeZone: string): string;
21
23
  *
22
24
  * 1. The claim, one transaction under the org row lock (which serializes the org's rate limit
23
25
  * count), then the task run and the task rows: the checks in order (task paused since the
24
- * scan, unless a person's "run now"; org disabled, creator
25
- * inactive or not a member, agent gone or not visible, conversation gone, connection inactive,
26
+ * scan, unless a person's "run now"; a tick fired more than 10 minutes past the org's catch-up
27
+ * window, `missed`, unless a "run now"; org disabled, creator inactive or not a member, agent
28
+ * gone or not visible, conversation gone, connection inactive,
26
29
  * overlap, rate limit, device offline, queue paused), each a `skipped` run with its reason, the
27
- * lasting ones also pausing the task; then `fired_at` is written, after the rate limit count.
30
+ * lasting ones also pausing the task; then `fired_at` is written, after the rate limit count,
31
+ * with the run's delayed settle check owed (`sched-settle`, {@link settle}).
28
32
  * A run whose claim committed before (a retried job) goes straight to the start.
29
33
  * 2. The start, idempotent: `new` creates the conversation with its first message keyed by the
30
34
  * task run id (`create_request_id`); `conversation` queues the prompt in the target
@@ -51,12 +55,29 @@ export declare class ScheduledTaskFirer {
51
55
  /** Reads a local device's connection lease (online or not). */
52
56
  private readonly locks;
53
57
  private readonly log;
58
+ /** The claim owes the run's settle check (ruling R26). */
59
+ private readonly outbox;
54
60
  constructor(kdb: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, store: ScheduledTaskStore, tracker: Pick<ScheduledTaskRunTracker, "inFlightIn" | "failStartIn">, orgLock: Pick<OrgLock, "tryLock">, agents: Pick<AgentLookup, "get">, access: Pick<ConversationAccess, "isAccessible">, conversations: Pick<ConversationsService, "createWithMessage" | "create">, queue: Pick<ConversationQueue, "add">, connections: Pick<ChannelConnections, "row" | "channel">,
55
61
  /** Reads a local device's connection lease (online or not). */
56
- locks: Pick<LockService, "holderOf">, log: Pick<KernelLogger, "warn" | "info">);
62
+ locks: Pick<LockService, "holderOf">, log: Pick<KernelLogger, "warn" | "info">,
63
+ /** The claim owes the run's settle check (ruling R26). */
64
+ outbox: Pick<OutboxService, "add">);
57
65
  private get db();
58
66
  /** Fires task run `taskRunId` (see the class); a run not `pending` is left as it is. */
59
67
  fire(taskRunId: string): Promise<void>;
68
+ /**
69
+ * The `sched-settle` job (ruling R26; `job-ids.ts`). The claimed check: a task run claimed
70
+ * (`fired_at`) and still `pending` goes through the run tracker's check, which links it to its
71
+ * run, settles it, or releases a run that left no trace (`skipped`, `removed`), so a `once`
72
+ * task's last run never stays pending; one still in flight (its item queued, its message posted)
73
+ * owes the next check an hour on. The unfired check: a run still `pending` that no claim took
74
+ * (its fire job failed for good before the claim) is skipped, `missed` for a tick, `removed` for
75
+ * a "run now", so the overlap check never waits on it. A run claimed, started, ended or skipped
76
+ * since is left as it is (the claimed check follows a claimed run).
77
+ */
78
+ settle(data: SchedSettleJobData): Promise<void>;
79
+ /** The unfired check (see {@link settle}), task row first (the one lock order). */
80
+ private settleUnfired;
60
81
  /** Step 1 (see the class): a skip, the plan, or null when there is nothing to fire. */
61
82
  private claim;
62
83
  /** Step 2 (see the class). */
@@ -16,19 +16,20 @@ import { ConversationAccess, ConversationAccessModule } from "../conversations/a
16
16
  import { ConversationsModule } from "../conversations/conversations.module.js";
17
17
  import { ConversationsService } from "../conversations/conversations.service.js";
18
18
  import { ConversationQueue } from "../conversations/queue.service.js";
19
+ import { channelSource } from "../conversations/source.js";
19
20
  import { ApiError } from "../errors.js";
20
21
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
21
22
  import { KernelDb } from "../infra/database/kernel-db.js";
22
23
  import { TransactionHost } from "../infra/database/transaction-host.js";
23
24
  import { Transactional } from "../infra/database/transactional.decorator.js";
24
25
  import { LockService } from "../infra/lock/lock.service.js";
25
- import { QueueModule } from "../infra/queue/queue.module.js";
26
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
26
27
  import { KERNEL_LOGGER } from "../infra/tokens.js";
27
28
  import { deviceLeaseKey } from "../local-devices/keys.js";
28
29
  import { OrgLock, OrgLockModule } from "../orgs/org-lock.js";
29
- import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
30
30
  import { RuntimeModule } from "../runtime/runtime.module.js";
31
31
  import { readScheduleSettings } from "./effective-settings.js";
32
+ import { oweSchedSettle, SCHED_LATE_FIRE_SLACK_MS, SCHED_SETTLE_AFTER_MS, SCHED_SETTLE_LONG_CHECKS, SCHED_SETTLE_RECHECK_MS, } from "./job-ids.js";
32
33
  import { pauseTaskIn } from "./pause.js";
33
34
  import { lockTaskRow, ScheduledTaskRunTracker, ScheduledTaskTrackerModule } from "./run-tracker.js";
34
35
  import { ScheduledTaskScanJob } from "./scan.job.js";
@@ -69,10 +70,12 @@ const DEVICE_GONE = new Set(["device.not_found", "device.unavailable", "sandbox.
69
70
  *
70
71
  * 1. The claim, one transaction under the org row lock (which serializes the org's rate limit
71
72
  * count), then the task run and the task rows: the checks in order (task paused since the
72
- * scan, unless a person's "run now"; org disabled, creator
73
- * inactive or not a member, agent gone or not visible, conversation gone, connection inactive,
73
+ * scan, unless a person's "run now"; a tick fired more than 10 minutes past the org's catch-up
74
+ * window, `missed`, unless a "run now"; org disabled, creator inactive or not a member, agent
75
+ * gone or not visible, conversation gone, connection inactive,
74
76
  * overlap, rate limit, device offline, queue paused), each a `skipped` run with its reason, the
75
- * lasting ones also pausing the task; then `fired_at` is written, after the rate limit count.
77
+ * lasting ones also pausing the task; then `fired_at` is written, after the rate limit count,
78
+ * with the run's delayed settle check owed (`sched-settle`, {@link settle}).
76
79
  * A run whose claim committed before (a retried job) goes straight to the start.
77
80
  * 2. The start, idempotent: `new` creates the conversation with its first message keyed by the
78
81
  * task run id (`create_request_id`); `conversation` queues the prompt in the target
@@ -98,9 +101,12 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
98
101
  connections;
99
102
  locks;
100
103
  log;
104
+ outbox;
101
105
  constructor(kdb, txHost, config, store, tracker, orgLock, agents, access, conversations, queue, connections,
102
106
  /** Reads a local device's connection lease (online or not). */
103
- locks, log) {
107
+ locks, log,
108
+ /** The claim owes the run's settle check (ruling R26). */
109
+ outbox) {
104
110
  this.kdb = kdb;
105
111
  this.txHost = txHost;
106
112
  this.config = config;
@@ -114,6 +120,7 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
114
120
  this.connections = connections;
115
121
  this.locks = locks;
116
122
  this.log = log;
123
+ this.outbox = outbox;
117
124
  }
118
125
  get db() {
119
126
  return this.kdb.db;
@@ -137,6 +144,61 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
137
144
  return;
138
145
  await this.start(claimed.plan);
139
146
  }
147
+ /**
148
+ * The `sched-settle` job (ruling R26; `job-ids.ts`). The claimed check: a task run claimed
149
+ * (`fired_at`) and still `pending` goes through the run tracker's check, which links it to its
150
+ * run, settles it, or releases a run that left no trace (`skipped`, `removed`), so a `once`
151
+ * task's last run never stays pending; one still in flight (its item queued, its message posted)
152
+ * owes the next check an hour on. The unfired check: a run still `pending` that no claim took
153
+ * (its fire job failed for good before the claim) is skipped, `missed` for a tick, `removed` for
154
+ * a "run now", so the overlap check never waits on it. A run claimed, started, ended or skipped
155
+ * since is left as it is (the claimed check follows a claimed run).
156
+ */
157
+ async settle(data) {
158
+ const { taskRunId } = data;
159
+ if ("unfired" in data) {
160
+ await this.settleUnfired(taskRunId);
161
+ return;
162
+ }
163
+ const run = await this.store.updateRun(this.db, taskRunId, {});
164
+ if (run?.status !== "pending" || run.firedAt === null)
165
+ return;
166
+ // inFlightIn takes the task row before the run (the one lock order)
167
+ if (!(await this.tracker.inFlightIn(run)))
168
+ return;
169
+ const now = await this.store.updateRun(this.db, taskRunId, {});
170
+ if (now?.status !== "pending" || now.firedAt === null)
171
+ return;
172
+ const next = data.check + 1;
173
+ if (next === SCHED_SETTLE_LONG_CHECKS) {
174
+ this.log.warn("scheduled task run still in flight a week after its claim", {
175
+ taskId: run.taskId,
176
+ taskRunId,
177
+ });
178
+ }
179
+ await oweSchedSettle(this.outbox, taskRunId, run.orgId, next, SCHED_SETTLE_RECHECK_MS);
180
+ }
181
+ /** The unfired check (see {@link settle}), task row first (the one lock order). */
182
+ async settleUnfired(taskRunId) {
183
+ const t = this.db;
184
+ const owner = await t
185
+ .selectFrom("scheduled_task_runs")
186
+ .select("task_id")
187
+ .where("id", "=", taskRunId)
188
+ .executeTakeFirst();
189
+ if (!owner)
190
+ return;
191
+ await lockTaskRow(t, owner.task_id);
192
+ const run = await t
193
+ .selectFrom("scheduled_task_runs")
194
+ .select(["status", "fired_at", "manual"])
195
+ .where("id", "=", taskRunId)
196
+ .forUpdate()
197
+ .executeTakeFirst();
198
+ if (run?.status !== "pending" || run.fired_at !== null)
199
+ return;
200
+ await this.skipIn(t, taskRunId, owner.task_id, run.manual ? "removed" : "missed");
201
+ }
140
202
  /** Step 1 (see the class): a skip, the plan, or null when there is nothing to fire. */
141
203
  async claim(taskRunId, orgId, deviceOffline) {
142
204
  const t = this.db;
@@ -179,6 +241,12 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
179
241
  if (!resumed && !run.manual && task.status === "paused")
180
242
  return skip("task_paused");
181
243
  const settings = await readScheduleSettings(t, this.config, orgId);
244
+ // a tick whose fire comes this late (workers down for long) is missed, as the scan records a
245
+ // tick it finds outside the window; a person's "run now" still runs
246
+ const lateAfter = settings.catchUpWindowMinutes * 60_000 + SCHED_LATE_FIRE_SLACK_MS;
247
+ if (!resumed && !run.manual && Date.now() - run.scheduledFor.getTime() > lateAfter) {
248
+ return skip("missed");
249
+ }
182
250
  if (!resumed && !settings.enabled)
183
251
  return skip("org_disabled");
184
252
  if (!resumed && !(await this.creatorActive(orgId, task.accountId))) {
@@ -260,6 +328,8 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
260
328
  firedAt: new Date(),
261
329
  ...(plan.mode === "conversation" && { conversationId: plan.conversationId }),
262
330
  });
331
+ // a start that fails for good leaves the run claimed: its settle check releases or ends it
332
+ await oweSchedSettle(this.outbox, run.id, orgId, 0, SCHED_SETTLE_AFTER_MS);
263
333
  return { plan: { ...plan, run: claimed ?? run } };
264
334
  }
265
335
  /** Step 2 (see the class). */
@@ -282,7 +352,7 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
282
352
  ...(task.model !== null && { model: task.model }),
283
353
  ...(task.effort !== null && { effort: task.effort }),
284
354
  };
285
- const out = await this.conversations.createWithMessage(task.orgId, task.accountId, task.agentId, { text: task.prompt, clientMessageId: run.id, ...pick }, task.deviceId === null ? {} : { deviceId: task.deviceId }, internal);
355
+ const out = await this.conversations.createWithMessage(task.orgId, task.accountId, task.agentId, { text: task.prompt, clientMessageId: run.id, ...pick }, task.deviceId === null ? {} : { deviceId: task.deviceId }, { ...internal, source: "web" });
286
356
  // a retried start found the conversation made before: `createWithMessage` already asked
287
357
  // for its run (and the first start's message owes the ensure-run backstop)
288
358
  await this.setConversation(run.id, out.conversation.id);
@@ -345,7 +415,7 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
345
415
  const { conversationId, created } = await findOrBindThread(t, plan.thread, plan.target, async () => {
346
416
  const conv = await this.conversations.create(task.orgId, task.accountId, task.agentId,
347
417
  // a chat task takes no device (new mode only): an IM conversation's default
348
- { im: true });
418
+ { im: true }, { source: channelSource(plan.target.chatType) });
349
419
  await t
350
420
  .updateTable("conversations")
351
421
  .set({ scheduled_task_id: task.id })
@@ -382,15 +452,17 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
382
452
  return null;
383
453
  if (locked.conversation_id !== null)
384
454
  return locked.conversation_id;
455
+ const target = { chatId: plan.chatId, chatType: "group" };
385
456
  const conv = await this.conversations.create(task.orgId, task.accountId, task.agentId,
386
457
  // a chat task takes no device (new mode only): an IM conversation's default
387
- { im: true });
458
+ { im: true },
459
+ // stored now: the thread to derive it from binds only at the run's first post
460
+ { source: channelSource(target.chatType) });
388
461
  await t
389
462
  .updateTable("conversations")
390
463
  .set({ scheduled_task_id: task.id })
391
464
  .where("id", "=", conv.id)
392
465
  .execute();
393
- const target = { chatId: plan.chatId, chatType: "group" };
394
466
  await t
395
467
  .insertInto("channel_deliveries")
396
468
  .values({
@@ -558,6 +630,9 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
558
630
  return (await this.locks.holderOf(deviceLeaseKey(deviceId))) === null;
559
631
  }
560
632
  };
633
+ __decorate([
634
+ Transactional()
635
+ ], ScheduledTaskFirer.prototype, "settle", null);
561
636
  __decorate([
562
637
  Transactional()
563
638
  ], ScheduledTaskFirer.prototype, "claim", null);
@@ -590,7 +665,8 @@ ScheduledTaskFirer = __decorate([
590
665
  __param(9, Inject(ConversationQueue)),
591
666
  __param(10, Inject(ChannelConnections)),
592
667
  __param(11, Inject(LockService)),
593
- __param(12, Inject(KERNEL_LOGGER))
668
+ __param(12, Inject(KERNEL_LOGGER)),
669
+ __param(13, Inject(OutboxService))
594
670
  ], ScheduledTaskFirer);
595
671
  export { ScheduledTaskFirer };
596
672
  /**
@@ -610,8 +686,6 @@ ScheduledTaskFireModule = __decorate([
610
686
  OrgLockModule,
611
687
  ScheduledTaskStoreModule,
612
688
  ScheduledTaskTrackerModule,
613
- // the scanner enqueues the fire jobs
614
- QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
615
689
  ],
616
690
  providers: [ScheduledTaskFirer, ScheduledTaskScanJob],
617
691
  exports: [ScheduledTaskFirer],
@@ -1,4 +1,5 @@
1
- /** The scheduled tasks' jobs (a leaf module: no imports). */
1
+ import type { OutboxService } from "../infra/outbox/outbox.service.js";
2
+ /** The scheduled tasks' jobs (a leaf module: type imports only). */
2
3
  /**
3
4
  * The once-a-minute `@LockedCron` that turns due tasks into task runs (spec §6); its name enqueued
4
5
  * on the maintenance queue runs one scan (`ScheduledRuns.runNow`).
@@ -13,20 +14,68 @@ export interface SchedFireJobData {
13
14
  /** The fire job's id: `sched-fire-<taskRunId>` (no `:`, task run ids are UUIDs). */
14
15
  export declare const schedFireJobId: (taskRunId: string) => string;
15
16
  /**
16
- * The fire job of one task run. Removed when done, so the scanner's repair can enqueue it again
17
- * after a lost job; the fire is idempotent on the task run's status, so retries are safe.
18
- */
19
- export declare function schedFireJob(taskRunId: string): {
20
- name: string;
21
- data: SchedFireJobData;
22
- opts: {
23
- jobId: string;
24
- attempts: number;
25
- backoff: {
26
- type: "exponential";
27
- delay: number;
28
- };
29
- removeOnComplete: true;
30
- removeOnFail: true;
31
- };
17
+ * Owes the fire job of task run `taskRunId` in the transaction in context (transactional outbox
18
+ * spec §8.1): the transaction that writes a pending task run (the scan's tick, a person's "run
19
+ * now") owes its fire, so a tick is never left unfired. The job's options are its outbox registry
20
+ * entry's; the fire is idempotent on the task run's status.
21
+ */
22
+ export declare function oweSchedFire(outbox: Pick<OutboxService, "add">, taskRunId: string, orgId: string): Promise<boolean>;
23
+ /**
24
+ * The delayed job that settles a task run left pending (ruling R26, fix round 1), on
25
+ * `cap-kernel-maintenance`. Two kinds:
26
+ * - the claimed check, owed by the fire job's claim: the run tracker links, settles or releases a
27
+ * run claimed but never started (`sched-settle-<taskRunId>`, then `-<check>`);
28
+ * - the unfired check, owed with the task run itself (the scan's tick, "run now"): a run its fire
29
+ * job never claimed (the job failed for good before its claim) is skipped, `missed` for a tick,
30
+ * `removed` for a "run now", so it never blocks the task's later ticks as an overlap
31
+ * (`sched-settle-<taskRunId>-unfired`).
32
+ */
33
+ export declare const SCHED_SETTLE_JOB = "sched-settle";
34
+ /**
35
+ * The data of a {@link SCHED_SETTLE_JOB}: the claimed check (`check` counts the checks before this
36
+ * one) or the unfired check.
37
+ */
38
+ export type SchedSettleJobData = {
39
+ taskRunId: string;
40
+ check: number;
41
+ } | {
42
+ taskRunId: string;
43
+ unfired: true;
32
44
  };
45
+ /**
46
+ * The first claimed check: 10 minutes after the claim, when the start's retries (seconds) are long
47
+ * over and the run tracker's start grace (`START_GRACE_MS`, 5 minutes) has passed, so a run that
48
+ * left no trace is released at once. Also the unfired check's delay after a "run now".
49
+ */
50
+ export declare const SCHED_SETTLE_AFTER_MS: number;
51
+ /**
52
+ * A claimed run still in flight at a check (its item queued, its message posted and not yet taken
53
+ * by a run) is checked again hourly. The chain ends with the first check that finds the run
54
+ * started, ended or released: the queued item is consumed or removed, the message taken by a run.
55
+ * A chain past {@link SCHED_SETTLE_LONG_CHECKS} checks (a week) is logged as a warning, once.
56
+ */
57
+ export declare const SCHED_SETTLE_RECHECK_MS: number;
58
+ /** A week of hourly checks: the claimed check warns at this one (a run in flight that long). */
59
+ export declare const SCHED_SETTLE_LONG_CHECKS = 168;
60
+ /**
61
+ * How long past the org's catch-up window a tick's fire may come before the tick is missed (the
62
+ * fire job's own rule, and when a tick's unfired check runs): the scan fires a tick still inside
63
+ * the window, and its fire job follows within seconds.
64
+ */
65
+ export declare const SCHED_LATE_FIRE_SLACK_MS: number;
66
+ /** The claimed check's id: `sched-settle-<taskRunId>`, then `-<check>` for the later checks. */
67
+ export declare const schedSettleJobId: (taskRunId: string, check: number) => string;
68
+ /** The unfired check's id: `sched-settle-<taskRunId>-unfired`. */
69
+ export declare const schedSettleUnfiredJobId: (taskRunId: string) => string;
70
+ /**
71
+ * Owes claimed check number `check` of task run `taskRunId` in the transaction in context, due
72
+ * `after` milliseconds from now.
73
+ */
74
+ export declare function oweSchedSettle(outbox: Pick<OutboxService, "add">, taskRunId: string, orgId: string, check: number, after: number): Promise<boolean>;
75
+ /**
76
+ * Owes the unfired check of task run `taskRunId` in the transaction that writes the run: for a
77
+ * tick of the schedule (`catchUpWindowMinutes` given) when the fire job would itself call the
78
+ * tick missed (its tick plus the window plus {@link SCHED_LATE_FIRE_SLACK_MS}, at least
79
+ * {@link SCHED_SETTLE_AFTER_MS} on); for a "run now" (null) {@link SCHED_SETTLE_AFTER_MS} on.
80
+ */
81
+ export declare function oweSchedSettleUnfired(outbox: Pick<OutboxService, "add">, taskRunId: string, orgId: string, scheduledFor: Date, catchUpWindowMinutes: number | null): Promise<boolean>;
@@ -1,4 +1,4 @@
1
- /** The scheduled tasks' jobs (a leaf module: no imports). */
1
+ /** The scheduled tasks' jobs (a leaf module: type imports only). */
2
2
  /**
3
3
  * The once-a-minute `@LockedCron` that turns due tasks into task runs (spec §6); its name enqueued
4
4
  * on the maintenance queue runs one scan (`ScheduledRuns.runNow`).
@@ -9,19 +9,87 @@ export const SCHED_FIRE_JOB = "sched-fire";
9
9
  /** The fire job's id: `sched-fire-<taskRunId>` (no `:`, task run ids are UUIDs). */
10
10
  export const schedFireJobId = (taskRunId) => `${SCHED_FIRE_JOB}-${taskRunId}`;
11
11
  /**
12
- * The fire job of one task run. Removed when done, so the scanner's repair can enqueue it again
13
- * after a lost job; the fire is idempotent on the task run's status, so retries are safe.
12
+ * Owes the fire job of task run `taskRunId` in the transaction in context (transactional outbox
13
+ * spec §8.1): the transaction that writes a pending task run (the scan's tick, a person's "run
14
+ * now") owes its fire, so a tick is never left unfired. The job's options are its outbox registry
15
+ * entry's; the fire is idempotent on the task run's status.
14
16
  */
15
- export function schedFireJob(taskRunId) {
16
- return {
17
+ export function oweSchedFire(outbox, taskRunId, orgId) {
18
+ return outbox.add({
19
+ queue: "cap-kernel-maintenance",
17
20
  name: SCHED_FIRE_JOB,
21
+ jobId: schedFireJobId(taskRunId),
18
22
  data: { taskRunId },
19
- opts: {
20
- jobId: schedFireJobId(taskRunId),
21
- attempts: 3,
22
- backoff: { type: "exponential", delay: 2_000 },
23
- removeOnComplete: true,
24
- removeOnFail: true,
25
- },
26
- };
23
+ orgId,
24
+ });
25
+ }
26
+ /**
27
+ * The delayed job that settles a task run left pending (ruling R26, fix round 1), on
28
+ * `cap-kernel-maintenance`. Two kinds:
29
+ * - the claimed check, owed by the fire job's claim: the run tracker links, settles or releases a
30
+ * run claimed but never started (`sched-settle-<taskRunId>`, then `-<check>`);
31
+ * - the unfired check, owed with the task run itself (the scan's tick, "run now"): a run its fire
32
+ * job never claimed (the job failed for good before its claim) is skipped, `missed` for a tick,
33
+ * `removed` for a "run now", so it never blocks the task's later ticks as an overlap
34
+ * (`sched-settle-<taskRunId>-unfired`).
35
+ */
36
+ export const SCHED_SETTLE_JOB = "sched-settle";
37
+ /**
38
+ * The first claimed check: 10 minutes after the claim, when the start's retries (seconds) are long
39
+ * over and the run tracker's start grace (`START_GRACE_MS`, 5 minutes) has passed, so a run that
40
+ * left no trace is released at once. Also the unfired check's delay after a "run now".
41
+ */
42
+ export const SCHED_SETTLE_AFTER_MS = 10 * 60_000;
43
+ /**
44
+ * A claimed run still in flight at a check (its item queued, its message posted and not yet taken
45
+ * by a run) is checked again hourly. The chain ends with the first check that finds the run
46
+ * started, ended or released: the queued item is consumed or removed, the message taken by a run.
47
+ * A chain past {@link SCHED_SETTLE_LONG_CHECKS} checks (a week) is logged as a warning, once.
48
+ */
49
+ export const SCHED_SETTLE_RECHECK_MS = 60 * 60_000;
50
+ /** A week of hourly checks: the claimed check warns at this one (a run in flight that long). */
51
+ export const SCHED_SETTLE_LONG_CHECKS = 168;
52
+ /**
53
+ * How long past the org's catch-up window a tick's fire may come before the tick is missed (the
54
+ * fire job's own rule, and when a tick's unfired check runs): the scan fires a tick still inside
55
+ * the window, and its fire job follows within seconds.
56
+ */
57
+ export const SCHED_LATE_FIRE_SLACK_MS = 10 * 60_000;
58
+ /** The claimed check's id: `sched-settle-<taskRunId>`, then `-<check>` for the later checks. */
59
+ export const schedSettleJobId = (taskRunId, check) => check === 0 ? `${SCHED_SETTLE_JOB}-${taskRunId}` : `${SCHED_SETTLE_JOB}-${taskRunId}-${check}`;
60
+ /** The unfired check's id: `sched-settle-<taskRunId>-unfired`. */
61
+ export const schedSettleUnfiredJobId = (taskRunId) => `${SCHED_SETTLE_JOB}-${taskRunId}-unfired`;
62
+ /**
63
+ * Owes claimed check number `check` of task run `taskRunId` in the transaction in context, due
64
+ * `after` milliseconds from now.
65
+ */
66
+ export function oweSchedSettle(outbox, taskRunId, orgId, check, after) {
67
+ return outbox.add({
68
+ queue: "cap-kernel-maintenance",
69
+ name: SCHED_SETTLE_JOB,
70
+ jobId: schedSettleJobId(taskRunId, check),
71
+ data: { taskRunId, check },
72
+ runAt: new Date(Date.now() + after),
73
+ orgId,
74
+ });
75
+ }
76
+ /**
77
+ * Owes the unfired check of task run `taskRunId` in the transaction that writes the run: for a
78
+ * tick of the schedule (`catchUpWindowMinutes` given) when the fire job would itself call the
79
+ * tick missed (its tick plus the window plus {@link SCHED_LATE_FIRE_SLACK_MS}, at least
80
+ * {@link SCHED_SETTLE_AFTER_MS} on); for a "run now" (null) {@link SCHED_SETTLE_AFTER_MS} on.
81
+ */
82
+ export function oweSchedSettleUnfired(outbox, taskRunId, orgId, scheduledFor, catchUpWindowMinutes) {
83
+ const soonest = Date.now() + SCHED_SETTLE_AFTER_MS;
84
+ const at = catchUpWindowMinutes === null
85
+ ? soonest
86
+ : Math.max(soonest, scheduledFor.getTime() + catchUpWindowMinutes * 60_000 + SCHED_LATE_FIRE_SLACK_MS);
87
+ return outbox.add({
88
+ queue: "cap-kernel-maintenance",
89
+ name: SCHED_SETTLE_JOB,
90
+ jobId: schedSettleUnfiredJobId(taskRunId),
91
+ data: { taskRunId, unfired: true },
92
+ runAt: new Date(at),
93
+ orgId,
94
+ });
27
95
  }
@@ -114,7 +114,8 @@ let ScheduledTaskRunTracker = class ScheduledTaskRunTracker {
114
114
  await this.settle(tr, run.status);
115
115
  return false;
116
116
  }
117
- // not started yet: its fire job is still to come (the scanner repairs a lost one)
117
+ // not claimed yet: its fire job is still to come (the run's unfired settle check skips it
118
+ // when the job failed for good before its claim)
118
119
  if (tr.fired_at === null)
119
120
  return true;
120
121
  // only a run with a target conversation is ever queued (`conversation`, `chat`): no scan of
@@ -1,10 +1,9 @@
1
- import type { Queue } from "bullmq";
2
1
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
3
2
  import { KernelDb } from "../infra/database/kernel-db.js";
4
3
  import { TransactionHost } from "../infra/database/transaction-host.js";
5
4
  import { LockService } from "../infra/lock/lock.service.js";
5
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
6
6
  import type { KernelLogger } from "../log.js";
7
- import { ScheduledTaskRunTracker } from "./run-tracker.js";
8
7
  import { ScheduledTaskStore } from "./store.js";
9
8
  /**
10
9
  * The scheduled task scanner (spec §6), once a minute across replicas. Each due task in its own
@@ -12,53 +11,35 @@ import { ScheduledTaskStore } from "./store.js";
12
11
  * same task): the ticks missed outside the org's `catchUpWindowMinutes` become one `skipped` run
13
12
  * (`missed`, at the first missed tick), the latest tick in the window gets its run (the unique
14
13
  * `(task_id, scheduled_for)` key makes a second insert a no-op), `next_run_at` moves to the next
15
- * tick plus the task's jitter (or a `once` task ends, or one past its `expires_at`), and after the
16
- * commit `sched-fire-<taskRunId>` is enqueued. A task whose stored trigger the schedule math cannot
17
- * evaluate is paused (`invalid_schedule`) instead of failing the scan; a database error fails the
18
- * pass (the next minute retries) and pauses nothing.
19
- *
20
- * Then the repair: a run still `pending` and unfired 10 minutes after its tick has its fire job
21
- * enqueued again, and is given up (`skipped`, `missed`) after an hour; a run fired an hour ago and
22
- * still `pending` (its start failed for good) goes through the run tracker's check, which links,
23
- * settles or releases it, so a `once` task's last run never stays pending.
14
+ * tick plus the task's jitter (or a `once` task ends, or one past its `expires_at`), and the same
15
+ * transaction owes `sched-fire-<taskRunId>` (transactional outbox spec §8.1), so a written tick is
16
+ * never left unfired and the scan needs no repair of lost fire jobs (a fire delivered too late
17
+ * marks its tick missed itself, `ScheduledTaskFirer`), and the run's unfired settle check (a fire
18
+ * job that failed for good before its claim: the tick is missed, `job-ids.ts`). A task whose stored
19
+ * trigger the schedule math cannot evaluate is paused (`invalid_schedule`) instead of failing the
20
+ * scan; a database error fails the pass (the next minute retries) and pauses nothing.
24
21
  */
25
22
  export declare class ScheduledTaskScanJob {
26
23
  private readonly kdb;
27
24
  readonly txHost: TransactionHost;
28
25
  private readonly config;
29
26
  private readonly store;
30
- private readonly tracker;
31
- private readonly maintenance;
27
+ private readonly outbox;
32
28
  readonly lockService: LockService;
33
29
  readonly log: KernelLogger;
34
- constructor(kdb: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, store: ScheduledTaskStore, tracker: Pick<ScheduledTaskRunTracker, "inFlightIn">, maintenance: Pick<Queue, "add">, lockService: LockService, log: KernelLogger);
30
+ constructor(kdb: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, store: ScheduledTaskStore, outbox: Pick<OutboxService, "add">, lockService: LockService, log: KernelLogger);
35
31
  private get db();
36
32
  scan(): Promise<void>;
33
+ /** One scan as of `now` (tests pass their own); returns how many due tasks it handled. */
34
+ scanAt(now: Date): Promise<number>;
37
35
  /**
38
- * One scan as of `now` (tests pass their own); returns how many due tasks it handled. The repair
39
- * runs even when the scan loop failed (its error is rethrown afterwards). `repairBatch`: how many
40
- * rows each repair step takes (tests pass a small one).
41
- */
42
- scanAt(now: Date, repairBatch?: number): Promise<number>;
43
- /**
44
- * One due task in its own transaction (a task whose trigger cannot be evaluated rolls back
45
- * alone and is paused, so it cannot block the others): locked with `SKIP LOCKED`, so replicas take different tasks. False
46
- * when no task is due.
36
+ * One due task in its own transaction (a task whose trigger cannot be evaluated rolls back alone
37
+ * and is paused, so it cannot block the others): locked with `SKIP LOCKED`, so replicas take
38
+ * different tasks. False when no task is due.
47
39
  */
48
40
  private scanOne;
49
41
  /** A task the scan cannot advance stops being due: paused with `invalid_schedule`. */
50
42
  private quarantine;
51
- /** One due task (see the class); returns the id of the run to fire, if any. */
43
+ /** One due task (see the class); returns the run to fire, if any. */
52
44
  private advance;
53
- /** Enqueues the fire jobs (after the commit; a failure is repaired by a later scan). */
54
- private enqueue;
55
- /** Lost fire jobs (see the class). */
56
- private repair;
57
- /** A run never fired an hour after its tick: given up, task row first (the one lock order). */
58
- private giveUp;
59
- /**
60
- * A started run still pending long after: the tracker links, settles or releases it; one still
61
- * in flight is stamped (`rechecked_at`) and goes to the back of the next rechecks.
62
- */
63
- private recheck;
64
45
  }