@fungi.computer/watchdog 0.1.0 → 0.1.1

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.
@@ -0,0 +1,20 @@
1
+ /** Decide one unexpected disappearance from its durable count and window. */
2
+ export function disappearanceRecovery(policy, recoveryCount, windowStartedAt, now) {
3
+ if (policy === undefined)
4
+ return { kind: "interrupted" };
5
+ const expired = policy.windowMs !== undefined &&
6
+ windowStartedAt !== null &&
7
+ now - windowStartedAt >= policy.windowMs;
8
+ const count = expired ? 0 : recoveryCount;
9
+ if (count >= policy.maxRecoveries)
10
+ return { kind: "interrupted" };
11
+ return {
12
+ kind: "requeue",
13
+ recoveryCount: count + 1,
14
+ windowStartedAt: policy.windowMs === undefined
15
+ ? null
16
+ : expired || windowStartedAt === null
17
+ ? now
18
+ : windowStartedAt,
19
+ };
20
+ }
@@ -0,0 +1,19 @@
1
+ import type { WatchdogRetryPolicy } from "../effect.js";
2
+ export declare const parseRetryPolicy: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => {
3
+ readonly baseMs: number;
4
+ readonly maxMs: number;
5
+ readonly limit: {
6
+ readonly maxAttempts: number;
7
+ } | {
8
+ readonly deadlineAt: number;
9
+ };
10
+ };
11
+ export declare function retryDue(count: number, now: number, policy: WatchdogRetryPolicy): number;
12
+ export declare function reserveRetry(count: number, now: number, policy: WatchdogRetryPolicy): {
13
+ attempt: {
14
+ count: number;
15
+ status: "exhausted" | "admitted";
16
+ final: boolean;
17
+ };
18
+ notBefore: number;
19
+ };
@@ -0,0 +1,32 @@
1
+ import { backoffDelay } from "@fungi.computer/almanac";
2
+ import * as Schema from "effect/Schema";
3
+ const Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
4
+ const Policy = Schema.Struct({
5
+ baseMs: Count,
6
+ maxMs: Count,
7
+ limit: Schema.Union([
8
+ Schema.Struct({ maxAttempts: Count }),
9
+ Schema.Struct({ deadlineAt: Count }),
10
+ ]),
11
+ });
12
+ export const parseRetryPolicy = Schema.decodeUnknownSync(Policy);
13
+ export function retryDue(count, now, policy) {
14
+ return Math.min(now + backoffDelay(count, policy), "deadlineAt" in policy.limit
15
+ ? policy.limit.deadlineAt
16
+ : Number.MAX_SAFE_INTEGER);
17
+ }
18
+ export function reserveRetry(count, now, policy) {
19
+ const exhausted = "maxAttempts" in policy.limit
20
+ ? count >= policy.limit.maxAttempts
21
+ : now >= policy.limit.deadlineAt;
22
+ const charged = exhausted ? count : count + 1;
23
+ return {
24
+ attempt: {
25
+ count: charged,
26
+ status: exhausted ? "exhausted" : "admitted",
27
+ final: exhausted ||
28
+ ("maxAttempts" in policy.limit && charged >= policy.limit.maxAttempts),
29
+ },
30
+ notBefore: retryDue(charged, now, policy),
31
+ };
32
+ }
@@ -1,3 +1,4 @@
1
+ import { parseRetryPolicy } from "./retry.js";
1
2
  import * as Cause from "effect/Cause";
2
3
  import * as Deferred from "effect/Deferred";
3
4
  import * as Effect from "effect/Effect";
@@ -18,6 +19,30 @@ function failureReasonOf(cause) {
18
19
  export const bindWatchdogEngine = (options, store) => {
19
20
  let tickState = "idle";
20
21
  let activeExecution;
22
+ let lifecycle = "open";
23
+ let currentTick;
24
+ let closing;
25
+ const close = Effect.suspend(() => {
26
+ if (closing !== undefined)
27
+ return Deferred.await(closing);
28
+ const done = Deferred.makeUnsafe();
29
+ closing = done;
30
+ lifecycle = "stopping";
31
+ const pendingTick = currentTick;
32
+ activeExecution?.fiber.interruptUnsafe();
33
+ return (pendingTick === undefined
34
+ ? Effect.succeed(undefined)
35
+ : Deferred.await(pendingTick)).pipe(Effect.onExit((exit) => {
36
+ lifecycle = "stopped";
37
+ return Deferred.done(done, Exit.map(exit, () => undefined));
38
+ }), Effect.uninterruptible);
39
+ });
40
+ const policyFor = (job) => (options.executor.retryPolicy?.(job) ?? Effect.succeed(undefined)).pipe(Effect.flatMap((policy) => policy === undefined
41
+ ? Effect.succeed(undefined)
42
+ : Effect.try({
43
+ try: () => parseRetryPolicy(policy),
44
+ catch: (error) => new WatchdogError("store_invalid", error),
45
+ })));
21
46
  const ingest = (job) => store.ingest(job);
22
47
  const read = (jobId) => store.read(jobId).pipe(Effect.map((result) => result.status === "missing"
23
48
  ? result
@@ -27,7 +52,7 @@ export const bindWatchdogEngine = (options, store) => {
27
52
  cancellation: result.cancellation,
28
53
  }));
29
54
  const readQueueHead = (queue) => store.readQueueHead(queue);
30
- const readSettled = (limit, afterSequence) => store.readSettled(limit, afterSequence);
55
+ const readSettled = (limit, afterSequence, lane) => store.readSettled(limit, afterSequence, lane);
31
56
  const cancel = (input) => store.recordCancellation(input).pipe(Effect.tap((result) => {
32
57
  if (result.status !== "recorded" && result.status !== "replayed")
33
58
  return Effect.void;
@@ -70,13 +95,11 @@ export const bindWatchdogEngine = (options, store) => {
70
95
  };
71
96
  if (probe.success)
72
97
  return { status: "none" };
73
- const disposition = claim.job.recovery !== undefined &&
74
- claim.recoveryCount < claim.job.recovery.maxRecoveries
75
- ? "requeue"
76
- : "interrupted";
98
+ const policy = yield* policyFor(claim.job);
77
99
  const result = yield* store.reconcileDisappearedClaim({
78
100
  ...claim,
79
- disposition,
101
+ requeue: policy !== undefined ||
102
+ options.executor.replaySafe?.(claim.job) === true,
80
103
  });
81
104
  switch (result.status) {
82
105
  case "requeued":
@@ -100,93 +123,172 @@ export const bindWatchdogEngine = (options, store) => {
100
123
  }
101
124
  });
102
125
  };
126
+ const finishCompletion = (claim) => store.finishCompletingClaim(claim).pipe(Effect.flatMap((result) => result.status === "settled" || result.status === "replayed"
127
+ ? options.wake.recompute().pipe(Effect.as({
128
+ status: "settled",
129
+ job: result.job,
130
+ }))
131
+ : Effect.fail(new WatchdogError("settlement_rejected"))));
103
132
  const tick = () => Effect.suspend(() => {
133
+ if (lifecycle !== "open")
134
+ return Effect.succeed({ status: "stopped" });
104
135
  if (tickState !== "idle")
105
136
  return Effect.succeed({ status: "busy" });
106
137
  tickState = "running";
107
- return inspectDisappearedClaim().pipe(Effect.flatMap((inspection) => {
108
- if (inspection.status === "recovered")
109
- return Effect.succeed({
110
- status: "recovered",
111
- job: inspection.job,
112
- });
113
- // Other work still proceeds; only an otherwise idle tick has
114
- // nothing to say but the unanswered probe.
115
- return claimNext().pipe(Effect.map((selection) => selection.status === "no_ready" &&
116
- inspection.status === "liveness_unknown"
117
- ? inspection
118
- : selection));
119
- }), Effect.flatMap((selection) => {
120
- if (selection.status === "no_ready")
121
- return Effect.succeed({ status: "no_ready" });
122
- if (selection.status === "liveness_unknown")
123
- return Effect.succeed(selection);
124
- if (selection.status === "claim_unavailable")
125
- return Effect.fail(new WatchdogError("claim_unavailable"));
126
- if (selection.status === "cancelled")
127
- return options.wake.recompute().pipe(Effect.as({
128
- status: "settled",
129
- job: selection.job,
130
- }));
131
- if (selection.status === "recovered")
132
- return Effect.succeed({
133
- status: "settled",
134
- job: selection.job,
135
- });
136
- return Effect.gen(function* () {
137
- const start = yield* Deferred.make();
138
- const fiber = yield* Effect.forkChild(Deferred.await(start).pipe(Effect.andThen(options.executor.execute(selection.job))));
139
- const owned = {
140
- jobId: selection.job.jobId,
141
- fiber,
142
- };
143
- activeExecution = owned;
144
- const current = yield* store.read(selection.job.jobId);
145
- if (current.status !== "found" ||
146
- current.job.state !== "running" ||
147
- current.cancellation.status === "present") {
148
- if (current.status === "found" &&
149
- current.job.state === "settled" &&
150
- current.job.outcome === "failed")
151
- owned.failed = current.job;
152
- yield* Fiber.interrupt(fiber);
153
- }
154
- else
155
- yield* Deferred.succeed(start, undefined);
156
- const execution = yield* Fiber.await(fiber).pipe(Effect.ensuring(Effect.sync(() => {
157
- if (activeExecution?.fiber === fiber)
158
- activeExecution = undefined;
159
- })));
160
- if (owned.failed !== undefined)
161
- return yield* options.wake
162
- .recompute()
163
- .pipe(Effect.as({ status: "settled", job: owned.failed }));
164
- const decision = Exit.isSuccess(execution)
165
- ? execution.value
166
- : { kind: "failed", reason: failureReasonOf(execution.cause) };
167
- const settled = yield* store.settleExactClaim({
168
- jobId: selection.job.jobId,
169
- claimToken: selection.claimToken,
170
- decision,
138
+ const done = Deferred.makeUnsafe();
139
+ currentTick = done;
140
+ return Effect.gen(function* () {
141
+ // An execution decision survives projection failure. Retry only its
142
+ // transaction, once per tick; it is never a disappeared execution.
143
+ const completing = yield* store.nextCompletingClaim();
144
+ if (completing !== undefined)
145
+ return yield* finishCompletion({
146
+ jobId: completing.job.jobId,
147
+ claimToken: completing.claimToken,
171
148
  });
172
- switch (settled.status) {
173
- case "settled":
174
- case "replayed":
149
+ return yield* inspectDisappearedClaim().pipe(Effect.flatMap((inspection) => {
150
+ if (inspection.status === "recovered")
151
+ return Effect.succeed({
152
+ status: "recovered",
153
+ job: inspection.job,
154
+ });
155
+ // Other work still proceeds; only an otherwise idle tick has
156
+ // nothing to say but the unanswered probe.
157
+ return claimNext().pipe(Effect.map((selection) => selection.status === "no_ready" &&
158
+ inspection.status === "liveness_unknown"
159
+ ? inspection
160
+ : selection));
161
+ }), Effect.flatMap((selection) => {
162
+ if (selection.status === "no_ready")
163
+ return Effect.succeed({ status: "no_ready" });
164
+ if (selection.status === "liveness_unknown")
165
+ return Effect.succeed(selection);
166
+ if (selection.status === "claim_unavailable")
167
+ return Effect.fail(new WatchdogError("claim_unavailable"));
168
+ if (selection.status === "cancelled")
169
+ return options.wake.recompute().pipe(Effect.as({
170
+ status: "settled",
171
+ job: selection.job,
172
+ }));
173
+ if (selection.status === "recovered")
174
+ return Effect.succeed({
175
+ status: "settled",
176
+ job: selection.job,
177
+ });
178
+ return Effect.gen(function* () {
179
+ const policy = yield* policyFor(selection.job);
180
+ const replaySafe = options.executor.replaySafe?.(selection.job) === true;
181
+ const start = yield* Deferred.make();
182
+ const dispatch = { started: false };
183
+ const fiber = yield* Effect.forkChild(Deferred.await(start).pipe(Effect.andThen(Effect.sync(() => {
184
+ dispatch.started = true;
185
+ })), Effect.andThen(policy === undefined
186
+ ? Effect.succeed(undefined)
187
+ : store.reserveAttempt({
188
+ jobId: selection.job.jobId,
189
+ claimToken: selection.claimToken,
190
+ policy,
191
+ })), Effect.flatMap((attempt) => options.executor.execute(selection.job, attempt))));
192
+ const owned = {
193
+ jobId: selection.job.jobId,
194
+ fiber,
195
+ };
196
+ activeExecution = owned;
197
+ const current = yield* store.read(selection.job.jobId);
198
+ if (lifecycle !== "open" ||
199
+ current.status !== "found" ||
200
+ current.job.state !== "running" ||
201
+ current.cancellation.status === "present") {
202
+ if (current.status === "found" &&
203
+ current.job.state === "settled" &&
204
+ current.job.outcome === "failed")
205
+ owned.failed = current.job;
206
+ yield* Fiber.interrupt(fiber);
207
+ }
208
+ else
209
+ yield* Deferred.succeed(start, undefined);
210
+ const execution = yield* Fiber.await(fiber).pipe(Effect.ensuring(Effect.sync(() => {
211
+ if (activeExecution?.fiber === fiber)
212
+ activeExecution = undefined;
213
+ })));
214
+ if (owned.failed !== undefined)
175
215
  return yield* options.wake.recompute().pipe(Effect.as({
176
216
  status: "settled",
177
- job: settled.job,
217
+ job: owned.failed,
178
218
  }));
179
- case "conflict":
180
- case "missing":
181
- return yield* Effect.fail(new WatchdogError("settlement_rejected"));
182
- }
183
- });
184
- }), Effect.ensuring(Effect.sync(() => {
219
+ if (lifecycle !== "open" && Exit.isFailure(execution)) {
220
+ const drained = Cause.hasInterruptsOnly(execution.cause) &&
221
+ (!dispatch.started ||
222
+ (yield* options.executor.isDrained?.(selection.job) ??
223
+ Effect.succeed(false)));
224
+ if (!drained)
225
+ return yield* Effect.fail(new WatchdogError("drain_failed"));
226
+ const handoff = yield* store.handoffExactClaim({
227
+ jobId: selection.job.jobId,
228
+ claimToken: selection.claimToken,
229
+ });
230
+ if (handoff.status === "requeued")
231
+ return yield* options.wake
232
+ .recompute()
233
+ .pipe(Effect.as({ status: "stopped" }));
234
+ if (handoff.status !== "cancelled")
235
+ return yield* Effect.fail(new WatchdogError("settlement_rejected"));
236
+ }
237
+ const decision = Exit.isSuccess(execution)
238
+ ? execution.value
239
+ : { kind: "failed", reason: failureReasonOf(execution.cause) };
240
+ if ((policy !== undefined &&
241
+ (decision.kind === "retry" || Exit.isFailure(execution))) ||
242
+ (replaySafe && Exit.isFailure(execution))) {
243
+ const retryInput = {
244
+ jobId: selection.job.jobId,
245
+ claimToken: selection.claimToken,
246
+ };
247
+ if (policy !== undefined)
248
+ Object.assign(retryInput, { policy });
249
+ const retried = yield* store.retryExactClaim(retryInput);
250
+ if (retried.status === "requeued" &&
251
+ replaySafe &&
252
+ Exit.isFailure(execution))
253
+ return yield* Effect.failCause(execution.cause);
254
+ if (retried.status === "requeued")
255
+ return yield* options.wake.recompute().pipe(Effect.as({
256
+ status: "requeued",
257
+ jobId: selection.job.jobId,
258
+ }));
259
+ if (retried.status === "conflict")
260
+ return yield* Effect.fail(new WatchdogError("settlement_rejected"));
261
+ // A recorded cancellation wins over retry and follows normal terminal settlement.
262
+ }
263
+ const settled = yield* store.recordExecutionDecision({
264
+ jobId: selection.job.jobId,
265
+ claimToken: selection.claimToken,
266
+ decision: decision.kind === "retry"
267
+ ? { kind: "failed", reason: "retry_policy_missing" }
268
+ : decision,
269
+ });
270
+ switch (settled.status) {
271
+ case "recorded":
272
+ case "replayed":
273
+ return yield* finishCompletion({
274
+ jobId: selection.job.jobId,
275
+ claimToken: selection.claimToken,
276
+ });
277
+ case "conflict":
278
+ case "missing":
279
+ return yield* Effect.fail(new WatchdogError("settlement_rejected"));
280
+ }
281
+ });
282
+ }));
283
+ }).pipe(Effect.onExit((exit) => {
185
284
  tickState = "idle";
186
- })));
285
+ currentTick = undefined;
286
+ return Deferred.done(done, Exit.map(exit, () => undefined));
287
+ }));
187
288
  });
188
289
  return Object.freeze({
189
- hasPendingWork: store.hasPendingWork,
290
+ close,
291
+ wakeState: store.wakeState,
190
292
  ingest,
191
293
  read,
192
294
  readQueueHead,
@@ -1,6 +1,6 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
- import type { WatchdogLaneFailure, WatchdogCancellationIntent, WatchdogCancellationResult, WatchdogExecutionDecision, WatchdogIngestResult, WatchdogQueuedJob, WatchdogQueueHead, WatchdogQueueProjector, WatchdogReadResult, WatchdogRunningJob, WatchdogSettledJob, WatchdogSettledRecord, WatchdogSqliteOwner, WatchdogSqliteRow, WatchdogStoredJob, WatchdogTransactionalProjection } from "../effect.js";
3
+ import type { WatchdogCompletingJob, WatchdogLaneFailure, WatchdogCancellationIntent, WatchdogCancellationResult, WatchdogExecutionDecision, WatchdogIngestResult, WatchdogQueuedJob, WatchdogQueueHead, WatchdogQueueProjector, WatchdogReadResult, WatchdogRunningJob, WatchdogSettledJob, WatchdogSettledRecord, WatchdogSqliteOwner, WatchdogSqliteRow, WatchdogStoredJob, WatchdogTransactionalProjection, WatchdogWakeState, WatchdogRetryPolicy, WatchdogAttempt } from "../effect.js";
4
4
  import { WatchdogError } from "./watchdog-error.js";
5
5
  export type { WatchdogSqliteRow } from "../effect.js";
6
6
  export declare const laneFailureBatchSize = 256;
@@ -13,8 +13,12 @@ declare const JobRowSchema: Schema.Struct<{
13
13
  readonly lane: Schema.String;
14
14
  readonly priority: Schema.Int;
15
15
  readonly payload_json: Schema.String;
16
+ readonly not_before: Schema.Int;
17
+ readonly attempt_count: Schema.Int;
16
18
  readonly recovery_max: Schema.NullOr<Schema.Int>;
17
- readonly state: Schema.Literals<readonly ["queued", "running", "settled"]>;
19
+ readonly recovery_window_ms: Schema.NullOr<Schema.Int>;
20
+ readonly recovery_window_started_at: Schema.NullOr<Schema.Int>;
21
+ readonly state: Schema.Literals<readonly ["queued", "running", "completing", "settled"]>;
18
22
  readonly outcome: Schema.NullOr<Schema.Literals<readonly ["completed", "failed", "cancelled", "outcome_unknown", "interrupted"]>>;
19
23
  readonly claim_token: Schema.NullOr<Schema.String>;
20
24
  readonly recovery_count: Schema.Int;
@@ -48,7 +52,7 @@ export type LanePartition = Readonly<{
48
52
  }>;
49
53
  /** Package-private storage operations consumed by Watchdog's Effect engine. */
50
54
  export type WatchdogStore = Readonly<{
51
- hasPendingWork: Effect.Effect<boolean, WatchdogError>;
55
+ wakeState: (now: number) => Effect.Effect<WatchdogWakeState, WatchdogError>;
52
56
  ingest: (job: WatchdogQueuedJob) => Effect.Effect<WatchdogIngestResult, WatchdogError>;
53
57
  read: (jobId: string) => Effect.Effect<(Extract<WatchdogReadResult, {
54
58
  status: "found";
@@ -59,24 +63,57 @@ export type WatchdogStore = Readonly<{
59
63
  status: "missing";
60
64
  }>, WatchdogError>;
61
65
  readQueueHead: (queue: string) => Effect.Effect<WatchdogQueueHead, WatchdogError>;
62
- readSettled: (limit: number, afterSequence?: number) => Effect.Effect<readonly WatchdogSettledRecord[], WatchdogError>;
66
+ readSettled: (limit: number, afterSequence?: number, lane?: string) => Effect.Effect<readonly WatchdogSettledRecord[], WatchdogError>;
63
67
  selectAndClaim: (claimToken: string) => Effect.Effect<WatchdogSelection, WatchdogError>;
64
- settleExactClaim: (input: Readonly<{
68
+ reserveAttempt: (input: {
65
69
  jobId: string;
66
70
  claimToken: string;
67
- decision: WatchdogExecutionDecision;
71
+ policy: WatchdogRetryPolicy;
72
+ }) => Effect.Effect<WatchdogAttempt, WatchdogError>;
73
+ retryExactClaim: (input: {
74
+ jobId: string;
75
+ claimToken: string;
76
+ policy?: WatchdogRetryPolicy;
77
+ }) => Effect.Effect<{
78
+ status: "requeued" | "cancelled" | "conflict";
79
+ }, WatchdogError>;
80
+ recordExecutionDecision: (input: Readonly<{
81
+ jobId: string;
82
+ claimToken: string;
83
+ decision: Exclude<WatchdogExecutionDecision, {
84
+ kind: "retry";
85
+ }>;
68
86
  }>) => Effect.Effect<Readonly<{
69
- status: "settled" | "replayed";
70
- job: WatchdogSettledJob;
87
+ status: "recorded" | "replayed";
88
+ job: WatchdogCompletingJob | WatchdogSettledJob;
71
89
  }> | Readonly<{
72
90
  status: "conflict" | "missing";
73
91
  }>, WatchdogError>;
92
+ nextCompletingClaim: () => Effect.Effect<{
93
+ job: WatchdogCompletingJob;
94
+ claimToken: string;
95
+ } | undefined, WatchdogError>;
96
+ finishCompletingClaim: (input: {
97
+ jobId: string;
98
+ claimToken: string;
99
+ }) => Effect.Effect<{
100
+ status: "settled" | "replayed";
101
+ job: WatchdogSettledJob;
102
+ } | {
103
+ status: "conflict" | "missing";
104
+ }, WatchdogError>;
74
105
  recordCancellation: (input: WatchdogCancellationIntent) => Effect.Effect<WatchdogCancellationResult, WatchdogError>;
75
106
  failLane: (input: WatchdogLaneFailure) => Effect.Effect<Readonly<{
76
107
  jobs: readonly WatchdogSettledJob[];
77
108
  more: boolean;
78
109
  }>, WatchdogError>;
79
110
  purgeQueued: (queue: string) => Effect.Effect<void, WatchdogError>;
111
+ handoffExactClaim: (input: {
112
+ jobId: string;
113
+ claimToken: string;
114
+ }) => Effect.Effect<{
115
+ status: "requeued" | "cancelled" | "conflict" | "missing";
116
+ }, WatchdogError>;
80
117
  nextRunningClaim: () => Effect.Effect<Readonly<{
81
118
  job: WatchdogRunningJob;
82
119
  claimToken: string;
@@ -86,7 +123,7 @@ export type WatchdogStore = Readonly<{
86
123
  job: WatchdogRunningJob;
87
124
  claimToken: string;
88
125
  recoveryCount: number;
89
- disposition: "requeue" | "interrupted";
126
+ requeue?: boolean;
90
127
  }>) => Effect.Effect<Readonly<{
91
128
  status: "requeued";
92
129
  }> | Readonly<{
@@ -102,6 +139,10 @@ export type WatchdogSqliteState = Readonly<{
102
139
  partition: (partition: LanePartition) => WatchdogStore;
103
140
  transactional: WatchdogTransactionalProjection;
104
141
  }>;
142
+ /** Queue order is decided before readiness; a sleeping head still owns its queue. */
143
+ export declare const queuedHeadPredicate = "(cancellation_id IS NOT NULL OR NOT EXISTS (\n SELECT 1 FROM watchdog_jobs predecessor\n WHERE predecessor.queue_name = watchdog_jobs.queue_name\n AND predecessor.state IN ('queued', 'running', 'completing')\n AND (predecessor.state IN ('running', 'completing') OR\n (predecessor.state = 'queued' AND\n (predecessor.priority > watchdog_jobs.priority OR\n (predecessor.priority = watchdog_jobs.priority AND\n predecessor.sequence < watchdog_jobs.sequence))))))";
144
+ export declare const parseWakeTime: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => number;
145
+ export declare function parseWakeState(raw: WatchdogSqliteRow | undefined): WatchdogWakeState;
105
146
  export declare function parseJobRow(raw: WatchdogSqliteRow): ParsedWatchdogSqliteJob;
106
147
  /** Construct Watchdog's private owner-local SQLite state. */
107
148
  export declare function openSqliteState(owner: WatchdogSqliteOwner, project?: WatchdogQueueProjector, partition?: LanePartition): WatchdogSqliteState;