@substrat-run/kernel 0.114.0 → 0.116.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.
- package/dist/denial-query.d.ts +3 -1
- package/dist/denial-query.d.ts.map +1 -1
- package/dist/denial-query.js +5 -1
- package/dist/denial-query.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/job-run.d.ts +493 -0
- package/dist/job-run.d.ts.map +1 -0
- package/dist/job-run.js +655 -0
- package/dist/job-run.js.map +1 -0
- package/dist/platform-sweep.d.ts +57 -0
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +85 -0
- package/dist/platform-sweep.js.map +1 -1
- package/dist/scope-host.d.ts +234 -1
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js.map +1 -1
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +12 -1
- package/dist/timeline.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import { type ModuleId } from '@substrat-run/contracts';
|
|
2
|
+
import { type ExecutorRetryPolicy, type ScopeStub } from './scope-host.js';
|
|
3
|
+
/**
|
|
4
|
+
* The fourth driver (#1577): long, resumable, coalesced work.
|
|
5
|
+
*
|
|
6
|
+
* Three drivers already move work off a request, and each is correct for what it
|
|
7
|
+
* is. `registerExecutor` + `drainDue` retries ONE delivery, whole. A declared
|
|
8
|
+
* schedule + `runDueSchedules` fires ONE operation inside a cadence window, which
|
|
9
|
+
* must then finish. `runPlatformSweep` does a pass of per-unit maintenance and
|
|
10
|
+
* reports it. None of them covers the shape this file names:
|
|
11
|
+
*
|
|
12
|
+
* Walk 100 000 objects in an external system. It takes an hour. It must survive a
|
|
13
|
+
* worker eviction, a deploy and a transient upstream failure by CONTINUING WHERE IT
|
|
14
|
+
* STOPPED. Only one walk per source may run at a time, and asking for a second while
|
|
15
|
+
* one is in flight must join the first. When it ends the outcome has to be legible.
|
|
16
|
+
*
|
|
17
|
+
* ## The model, in four words: run, pass, step, cursor
|
|
18
|
+
*
|
|
19
|
+
* A **run** is the durable record — one `_substrat_job_runs` row, in the scope, with
|
|
20
|
+
* its status, its resume cursor, a counter bag, its start/end and its last error. It
|
|
21
|
+
* is what an operator reads afterwards, and it is the thing coalescing joins.
|
|
22
|
+
*
|
|
23
|
+
* A **pass** is one invocation of the job's handler. A pass does a BOUNDED chunk of
|
|
24
|
+
* the walk and hands a cursor forward; the next pass resumes from it. That is the
|
|
25
|
+
* whole of resume at the outer level, and it is why a 100 000-object walk is not a
|
|
26
|
+
* single hour-long call anybody has to keep alive.
|
|
27
|
+
*
|
|
28
|
+
* A **step** is a named unit inside a pass. Its result is committed as it completes,
|
|
29
|
+
* so a step that already succeeded is NOT re-run — not on a retry of the step after
|
|
30
|
+
* it, and not after the process was killed mid-pass. That is resume at the inner
|
|
31
|
+
* level, and it is what "no step before it repeated" means.
|
|
32
|
+
*
|
|
33
|
+
* A **cursor** is whatever the handler says the next pass should resume from — an id,
|
|
34
|
+
* a page token, an offset. Opaque to the driver, stored as JSON, held to the payload
|
|
35
|
+
* rule below because it has to survive the same trip.
|
|
36
|
+
*
|
|
37
|
+
* **The step ledger is per PASS, and that is deliberate.** When a pass commits, its
|
|
38
|
+
* step rows are dropped: the next pass is new work, named by the new cursor, and a
|
|
39
|
+
* ledger that accumulated across an hour-long walk would be the unbounded table this
|
|
40
|
+
* design exists to avoid. The cursor carries progress BETWEEN passes; the ledger
|
|
41
|
+
* carries it WITHIN one.
|
|
42
|
+
*
|
|
43
|
+
* ## The determinism rule, and the half of it that is mechanical
|
|
44
|
+
*
|
|
45
|
+
* Step names must be a pure function of the payload and prior results. If a name
|
|
46
|
+
* varies between passes — a timestamp in it, a random suffix — the memo never hits,
|
|
47
|
+
* every resume replays work that already happened, and resume is a lie told in a
|
|
48
|
+
* green test. That rule cannot be fully checked from here; what CAN be checked is
|
|
49
|
+
* the sharpest way to break it, and is: two `step()` calls under ONE name in ONE
|
|
50
|
+
* pass are refused (`JOB_STEP_REUSED`), because the second would read the first's
|
|
51
|
+
* memo and silently skip its own work.
|
|
52
|
+
*
|
|
53
|
+
* ## Coalescing is the DRIVER's, never a unique index
|
|
54
|
+
*
|
|
55
|
+
* One run in flight per `(module, job, instance)`. `startJobRun` looks for a live row
|
|
56
|
+
* and RETURNS it rather than inserting a second. It is not a `UNIQUE` constraint, and
|
|
57
|
+
* that is the point: a run whose process was killed is still `running`, and it must be
|
|
58
|
+
* restartable — a uniqueness constraint would be "one row ever", which would refuse
|
|
59
|
+
* the re-import that has to happen next year as loudly as it refuses the duplicate.
|
|
60
|
+
* `_substrat_sweep_runs` is a RECEIPT and carries such a constraint; a cursor is not a
|
|
61
|
+
* receipt, and the two must not be re-merged (#1571, #1572).
|
|
62
|
+
*
|
|
63
|
+
* ## One driver per scope at a time — a stated bound, not a mechanism
|
|
64
|
+
*
|
|
65
|
+
* Coalescing stops duplicate RUNS. It does not stop two concurrent callers of
|
|
66
|
+
* `runDueJobs` from picking the same run out of the due read and advancing it at the
|
|
67
|
+
* same time: there is no lease, and a lease is not smuggled in here. The topology the
|
|
68
|
+
* driver is built for has one tick per scope — `runPlatformSweep` enumerates scopes
|
|
69
|
+
* and does one call each, a scope DO's alarm fires for its own scope — so the bound
|
|
70
|
+
* is satisfied by construction rather than defended against.
|
|
71
|
+
*
|
|
72
|
+
* What the overlap would cost, if a deployment did drive one scope twice at once: the
|
|
73
|
+
* step ledger absorbs most of it (a step already committed returns its memo to both),
|
|
74
|
+
* so the exposure is a step neither pass has finished yet, which both would run. That
|
|
75
|
+
* is the same at-least-once residue an executor already has and which handlers already
|
|
76
|
+
* have to absorb — but it is NOT what "only one walk per source at a time" promises,
|
|
77
|
+
* so it is written down rather than implied. Adding a lease is a real design with a
|
|
78
|
+
* real expiry question behind it (a leaked lease is a run nothing will ever touch
|
|
79
|
+
* again), and it wants a consumer's numbers before it gets one.
|
|
80
|
+
*
|
|
81
|
+
* ## What this is NOT
|
|
82
|
+
*
|
|
83
|
+
* Not a workflow engine: no branching, no fan-out, no BPMN, no timers between steps.
|
|
84
|
+
* A linear, resumable, coalesced sequence of steps with a cursor — the smallest thing
|
|
85
|
+
* that makes an hour-long import survivable. A use case that needs branching is a
|
|
86
|
+
* different issue and probably a different answer.
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* The run table and its step ledger, as both adapters build them.
|
|
90
|
+
*
|
|
91
|
+
* Shared rather than spelled twice for the reason `IDEMPOTENCY_DDL` and
|
|
92
|
+
* `SCHEDULE_STATE_DDL` are: `lint:spine-ddl` compares what each adapter's
|
|
93
|
+
* `KERNEL_DDL` executes, and one definition is what keeps the self-hosted store
|
|
94
|
+
* and the hosted store the same shape rather than merely the same intention.
|
|
95
|
+
* Spine — kernel-written, `_substrat_*`, never a module migration.
|
|
96
|
+
*/
|
|
97
|
+
export declare const JOB_RUN_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_job_runs (\n id TEXT PRIMARY KEY,\n -- The coalescing key: one LIVE run per (module_id, job, instance). Not a UNIQUE\n -- index, deliberately -- see this file's header. instance names WHAT is being\n -- walked (a source id, a mapping version); a job with one walk per scope uses\n -- the 'default' the input schema fills in.\n module_id TEXT NOT NULL,\n job TEXT NOT NULL,\n instance TEXT NOT NULL,\n -- What start() was handed, as JSON. Held to the queue-safety rule\n -- (assertQueueSafe): ids and configuration, never bytes, class instances or\n -- functions, because this value has to survive a queue message unchanged.\n payload TEXT NOT NULL,\n -- 'running' | 'done' | 'failed'. A killed run stays 'running' and is picked up\n -- again by the next drive -- which is exactly what makes it restartable.\n status TEXT NOT NULL,\n -- What the last COMMITTED pass handed forward, as JSON. NULL = no pass has\n -- committed yet, which is a fact ('start from the beginning'), not missing data.\n cursor TEXT,\n -- The run's counter bag (JSON object of numbers), merged on each commit. An\n -- uncommitted pass's counts are discarded with the rest of the pass.\n counters TEXT NOT NULL DEFAULT '{}',\n -- CONSECUTIVE failed passes. Reset to 0 whenever a pass commits, so this reads\n -- as \"how stuck is it now\", not \"how much work has it done\".\n attempts INTEGER NOT NULL DEFAULT 0,\n -- The error the last failed pass left. Retained after the run goes 'failed' --\n -- the record is the evidence, so it must still say why.\n last_error TEXT,\n started_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n -- When the next pass may run, on the executor's own backoff curve. NULL = now\n -- (or terminal), which is why the due read tests IS NULL as well as <= now.\n next_attempt_at TEXT,\n -- When the run reached 'done' or 'failed'. NULL while it is still running.\n ended_at TEXT\n );\n -- The drive's read: WHERE status = 'running' AND (next_attempt_at IS NULL OR <= ?)\n -- ORDER BY id. Leading with status makes the live runs a seekable range over a\n -- table that RETAINS every finished run, so the cost tracks how much is in flight\n -- rather than how much the scope has ever imported.\n CREATE INDEX IF NOT EXISTS _substrat_job_runs_due ON _substrat_job_runs (status, next_attempt_at, id);\n -- Coalescing's read, and the operator read's filter.\n CREATE INDEX IF NOT EXISTS _substrat_job_runs_key ON _substrat_job_runs (module_id, job, instance, id);\n -- The step ledger of the pass currently in flight. Rows are written as each step\n -- COMPLETES and dropped when the pass COMMITS, so this holds one pass's worth of\n -- steps and never grows with the length of the walk.\n --\n -- A FAILED run keeps its last pass's rows, deliberately: they are the difference\n -- between \"it got nowhere\" and \"it got three quarters of the way and then the\n -- provider went down\", which the run row's single last_error cannot say. Still\n -- bounded -- one pass's worth per failed run -- because a restart is a NEW run\n -- with a new id and therefore its own ledger.\n CREATE TABLE IF NOT EXISTS _substrat_job_steps (\n run_id TEXT NOT NULL,\n step TEXT NOT NULL,\n -- The step's return value as JSON. NOT NULL is what MEANS completed: a step that\n -- threw leaves the row with a NULL result and a raised attempts count, so the\n -- next pass runs it again rather than reading a success that never happened. A\n -- step returning nothing stores the JSON text 'null', which is not SQL NULL.\n result TEXT,\n attempts INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n recorded_at TEXT NOT NULL,\n PRIMARY KEY (run_id, step)\n );\n";
|
|
98
|
+
/** Where a run is. A killed run is `running` — that is what makes it resumable. */
|
|
99
|
+
export type JobRunStatus = 'running' | 'done' | 'failed';
|
|
100
|
+
/** The coalescing key: one LIVE run per triple. */
|
|
101
|
+
export interface JobRunKey {
|
|
102
|
+
moduleId: ModuleId;
|
|
103
|
+
/** The job's name, as `registerJob` declared it. */
|
|
104
|
+
job: string;
|
|
105
|
+
/** What is being walked — a source id, a mapping version. `'default'` when there is one. */
|
|
106
|
+
instance: string;
|
|
107
|
+
}
|
|
108
|
+
/** The durable run record, as an operator reads it. */
|
|
109
|
+
export interface JobRun extends JobRunKey {
|
|
110
|
+
id: string;
|
|
111
|
+
status: JobRunStatus;
|
|
112
|
+
/** What `startJobRun` was handed. */
|
|
113
|
+
payload: unknown;
|
|
114
|
+
/** What the last COMMITTED pass handed forward; null before the first commit. */
|
|
115
|
+
cursor: unknown;
|
|
116
|
+
counters: Record<string, number>;
|
|
117
|
+
/** Consecutive failed passes; 0 after any commit. */
|
|
118
|
+
attempts: number;
|
|
119
|
+
lastError: string | null;
|
|
120
|
+
startedAt: string;
|
|
121
|
+
updatedAt: string;
|
|
122
|
+
nextAttemptAt: string | null;
|
|
123
|
+
endedAt: string | null;
|
|
124
|
+
/**
|
|
125
|
+
* Why this row could not be read whole, or null — which is the ordinary case and
|
|
126
|
+
* what every run written by this driver carries.
|
|
127
|
+
*
|
|
128
|
+
* Present because the read and the DRIVER want opposite things from a malformed
|
|
129
|
+
* row, and both are right. A pass cannot run on a payload it cannot decode, so the
|
|
130
|
+
* driver fails the run and records why. The READ exists so an operator can see
|
|
131
|
+
* exactly that — and a read that threw on the one row being investigated would
|
|
132
|
+
* take every other run on the scope with it, since this returns a list. So the
|
|
133
|
+
* decode here is tolerant and SAYS SO: the undecodable columns come back empty
|
|
134
|
+
* (`null` / `{}`) with the parse error named here, rather than a silent `null`
|
|
135
|
+
* that reads as "no cursor".
|
|
136
|
+
*
|
|
137
|
+
* Reachable without any forge: `importDump` replays a dump's rows verbatim, so a
|
|
138
|
+
* dump from another world or edited by hand is enough.
|
|
139
|
+
*/
|
|
140
|
+
decodeError: string | null;
|
|
141
|
+
}
|
|
142
|
+
/** What `startJobRun` is handed. */
|
|
143
|
+
export interface StartJobRunInput {
|
|
144
|
+
moduleId: ModuleId;
|
|
145
|
+
job: string;
|
|
146
|
+
/** Defaults to `'default'` — a job with one walk per scope needs no instance. */
|
|
147
|
+
instance?: string;
|
|
148
|
+
/** Ids and configuration. Refused at this boundary if it is anything else. */
|
|
149
|
+
payload?: unknown;
|
|
150
|
+
}
|
|
151
|
+
/** The operator read's filter. Every field narrows; none is required. */
|
|
152
|
+
export interface JobRunFilter {
|
|
153
|
+
moduleId?: ModuleId;
|
|
154
|
+
job?: string;
|
|
155
|
+
instance?: string;
|
|
156
|
+
status?: JobRunStatus;
|
|
157
|
+
/** Default `JOB_RUN_LIST_LIMIT`. */
|
|
158
|
+
limit?: number;
|
|
159
|
+
}
|
|
160
|
+
/** Rows one `jobRuns` read returns by default. */
|
|
161
|
+
export declare const JOB_RUN_LIST_LIMIT = 50;
|
|
162
|
+
/** The most rows one `jobRuns` read will ever return, whatever the caller asks for. */
|
|
163
|
+
export declare const JOB_RUN_LIST_MAX = 500;
|
|
164
|
+
/**
|
|
165
|
+
* The row budget for one operator read, normalised before it reaches SQL.
|
|
166
|
+
*
|
|
167
|
+
* `JobRunFilter.limit` comes from a caller and was bound straight to `LIMIT`, where
|
|
168
|
+
* SQLite reads a NEGATIVE value as unbounded, refuses a fractional one outright, and
|
|
169
|
+
* honours an oversized one. Since finished runs are retained, "unbounded" means the
|
|
170
|
+
* scope's entire history in one response — a read whose cost grows with retention,
|
|
171
|
+
* reachable by passing `-1`. Clamped here rather than in each adapter so the pure and
|
|
172
|
+
* the hosted read cannot answer the same filter differently.
|
|
173
|
+
*/
|
|
174
|
+
export declare function jobRunListLimit(limit: number | undefined): number;
|
|
175
|
+
/** Runs one `runDueJobs` call picks up by default. */
|
|
176
|
+
export declare const JOB_DRIVE_LIMIT = 50;
|
|
177
|
+
/**
|
|
178
|
+
* Rows one `runDueJobs` call will READ while looking for runnable ones.
|
|
179
|
+
*
|
|
180
|
+
* The drive skips runs whose job this host does not register, so "read `limit` rows"
|
|
181
|
+
* and "find `limit` runs to drive" are different numbers, and a scope can hold an
|
|
182
|
+
* arbitrary number of the unrunnable kind. This caps the difference: past it the call
|
|
183
|
+
* drives what it found and returns, rather than scanning a scope's whole history on a
|
|
184
|
+
* maintenance tick.
|
|
185
|
+
*/
|
|
186
|
+
export declare const JOB_DRIVE_SCAN_MAX = 500;
|
|
187
|
+
/** What a handler says at the end of a pass. */
|
|
188
|
+
export interface JobPassResult {
|
|
189
|
+
/**
|
|
190
|
+
* What the NEXT pass resumes from. Omitted keeps the cursor the last pass
|
|
191
|
+
* committed — which is how a pass that only did steps, and moved nothing on,
|
|
192
|
+
* says so. Held to the same queue-safety rule as the payload.
|
|
193
|
+
*/
|
|
194
|
+
cursor?: unknown;
|
|
195
|
+
/** True when the walk is finished: the run goes `done` and is never driven again. */
|
|
196
|
+
done?: boolean;
|
|
197
|
+
}
|
|
198
|
+
/** What one pass of a job is given. */
|
|
199
|
+
export interface JobPassContext {
|
|
200
|
+
/** This run's id and coalescing key. */
|
|
201
|
+
readonly run: JobRunKey & {
|
|
202
|
+
id: string;
|
|
203
|
+
};
|
|
204
|
+
/** What `startJobRun` was handed, decoded. */
|
|
205
|
+
readonly payload: unknown;
|
|
206
|
+
/** What the last committed pass handed forward; `null` on the first pass. */
|
|
207
|
+
readonly cursor: unknown;
|
|
208
|
+
/** The counter bag as of the last commit. `count()` adds to the pass's copy. */
|
|
209
|
+
readonly counters: Readonly<Record<string, number>>;
|
|
210
|
+
/**
|
|
211
|
+
* Run one NAMED step, at most once per run-pass.
|
|
212
|
+
*
|
|
213
|
+
* A step whose result is already committed returns it WITHOUT running `fn` —
|
|
214
|
+
* that is the memo, and it is what survives both a retry of a later step and a
|
|
215
|
+
* process kill mid-pass. A step that throws records its attempt and fails the
|
|
216
|
+
* pass; the next pass replays the handler, skips every committed step above,
|
|
217
|
+
* and runs this one again until its own `retry` is exhausted.
|
|
218
|
+
*
|
|
219
|
+
* `name` must be a pure function of the payload and prior results. Two calls
|
|
220
|
+
* under one name in one pass are refused rather than silently memo-aliased.
|
|
221
|
+
*
|
|
222
|
+
* **AT-LEAST-ONCE. `fn` must be idempotent, and this is not a formality.** The
|
|
223
|
+
* ledger row is written AFTER `fn` resolves, which is the only ordering that is
|
|
224
|
+
* safe — claiming the step first would make it at-most-once and lose the effect on
|
|
225
|
+
* any crash in between. The cost is the opposite window: a stop after `fn`'s effect
|
|
226
|
+
* lands and before `recordStep` commits leaves no memo, so the next pass runs that
|
|
227
|
+
* effect a second time. It is the same trade, made the same way and for the same
|
|
228
|
+
* reason, as `recordExecutorDelivery` in the executor journal — whose docblock says
|
|
229
|
+
* it plainly — and like an executor handler, a step body absorbs the residue.
|
|
230
|
+
*
|
|
231
|
+
* `value` is round-tripped through its stored JSON before being returned, so what a
|
|
232
|
+
* handler sees is identical whether the step just ran or was replayed from the memo.
|
|
233
|
+
*/
|
|
234
|
+
step<T>(name: string, fn: () => T | Promise<T>, retry?: ExecutorRetryPolicy): Promise<T>;
|
|
235
|
+
/** Add to a counter. Committed with the pass; discarded if the pass fails. */
|
|
236
|
+
count(name: string, by?: number): void;
|
|
237
|
+
/**
|
|
238
|
+
* The scope, through the SYSTEM door — the same `getSystemScope` a declared
|
|
239
|
+
* schedule invokes through, so a job's writes are attributed `{ system: moduleId }`
|
|
240
|
+
* and gated by an ordinary `ctx.check` against `system:<moduleId>` grants. Opened
|
|
241
|
+
* on first call, then reused for the rest of the pass.
|
|
242
|
+
*/
|
|
243
|
+
scope(): Promise<ScopeStub>;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* A job's body: one pass, given where the last one stopped.
|
|
247
|
+
*
|
|
248
|
+
* HOST code, never module code — a walk of an external system holds credentials and
|
|
249
|
+
* makes network calls, which is exactly what module code may not do. It reaches the
|
|
250
|
+
* scope the way a schedule does, through `pass.scope()`.
|
|
251
|
+
*/
|
|
252
|
+
export type JobHandler = (pass: JobPassContext) => JobPassResult | void | Promise<JobPassResult | void>;
|
|
253
|
+
/** What `runDueJobs` did in one call. */
|
|
254
|
+
export interface JobDriveReport {
|
|
255
|
+
/** Runs this call picked up — due, and `running`. */
|
|
256
|
+
attempted: number;
|
|
257
|
+
/** Passes that COMMITTED with the run still going. With `maxPasses > 1`, more than one per run. */
|
|
258
|
+
advanced: number;
|
|
259
|
+
/** Runs that reported `done` this call. */
|
|
260
|
+
completed: number;
|
|
261
|
+
/** Passes that failed with retries left — the run stays `running`, due after its backoff. */
|
|
262
|
+
retrying: number;
|
|
263
|
+
/** Runs whose step exhausted its retries this call. Terminal: `failed`, with the error on the record. */
|
|
264
|
+
failed: number;
|
|
265
|
+
/** Per-run failures, the same shape `ScheduleRunReport.errors` has: what failed and why. */
|
|
266
|
+
errors: {
|
|
267
|
+
runId: string;
|
|
268
|
+
error: string;
|
|
269
|
+
}[];
|
|
270
|
+
}
|
|
271
|
+
/** The `_substrat_job_runs` row, as both adapters' SQL returns it. */
|
|
272
|
+
export interface JobRunRow {
|
|
273
|
+
readonly id: string;
|
|
274
|
+
readonly module_id: string;
|
|
275
|
+
readonly job: string;
|
|
276
|
+
readonly instance: string;
|
|
277
|
+
readonly payload: string;
|
|
278
|
+
readonly status: string;
|
|
279
|
+
readonly cursor: string | null;
|
|
280
|
+
readonly counters: string;
|
|
281
|
+
readonly attempts: number;
|
|
282
|
+
readonly last_error: string | null;
|
|
283
|
+
readonly started_at: string;
|
|
284
|
+
readonly updated_at: string;
|
|
285
|
+
readonly next_attempt_at: string | null;
|
|
286
|
+
readonly ended_at: string | null;
|
|
287
|
+
}
|
|
288
|
+
/** The `_substrat_job_steps` row, as both adapters' SQL returns it. */
|
|
289
|
+
export interface JobStepRow {
|
|
290
|
+
readonly step: string;
|
|
291
|
+
readonly result: string | null;
|
|
292
|
+
readonly attempts: number;
|
|
293
|
+
readonly last_error: string | null;
|
|
294
|
+
}
|
|
295
|
+
/** Everything a pass writes onto the run row, in one statement — never a partial update. */
|
|
296
|
+
export interface JobRunPatch {
|
|
297
|
+
readonly status: JobRunStatus;
|
|
298
|
+
readonly cursor: string | null;
|
|
299
|
+
readonly counters: string;
|
|
300
|
+
readonly attempts: number;
|
|
301
|
+
readonly lastError: string | null;
|
|
302
|
+
readonly updatedAt: string;
|
|
303
|
+
readonly nextAttemptAt: string | null;
|
|
304
|
+
readonly endedAt: string | null;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The storage the driver runs against — the ONE thing each adapter supplies.
|
|
308
|
+
*
|
|
309
|
+
* D-14's two drivers are two implementations of this and nothing else: the pure
|
|
310
|
+
* adapter's is direct SQL on the scope db, the hosted adapter's is RPC to the scope
|
|
311
|
+
* DO. Every decision — what coalescing means, when a step is skipped, when a run
|
|
312
|
+
* fails — lives in the functions below, so the two drivers cannot disagree about
|
|
313
|
+
* any of it. What they may legitimately differ on is only how a row is read.
|
|
314
|
+
*
|
|
315
|
+
* **Each step commits on its own round trip**, and that is not an accident of the
|
|
316
|
+
* port's shape. Batching a pass's steps into one write at the end would lose exactly
|
|
317
|
+
* the work a mid-pass kill is supposed to keep.
|
|
318
|
+
*/
|
|
319
|
+
export interface JobRunStore {
|
|
320
|
+
/**
|
|
321
|
+
* The coalescing decision itself, as ONE operation: return the live (`running`)
|
|
322
|
+
* run for `row`'s key if there is one, otherwise insert `row` and return it.
|
|
323
|
+
*
|
|
324
|
+
* **Atomic, and it has to be.** Split into a lookup and an insert — which is how
|
|
325
|
+
* this was first written — two concurrent starts both see no live row and both
|
|
326
|
+
* insert, and the schema deliberately carries no constraint that could reject the
|
|
327
|
+
* second. The result is two runs walking one source: exactly what "a start against
|
|
328
|
+
* a live key joins it" promises not to happen, defeated by the promise's own
|
|
329
|
+
* mechanism. Nothing underneath made it safe: no unique index (by design), no
|
|
330
|
+
* transaction, and neither the pure host's actor queue nor a single DO RPC wrapped
|
|
331
|
+
* the pair.
|
|
332
|
+
*
|
|
333
|
+
* It is the port's job rather than the kernel's because atomicity is the one thing
|
|
334
|
+
* only the adapter can supply — a transaction on the pure side, a single RPC on the
|
|
335
|
+
* DO side, where the input gate makes one round trip indivisible.
|
|
336
|
+
*/
|
|
337
|
+
startOrJoin(key: JobRunKey, row: JobRunRow): Promise<JobRunRow>;
|
|
338
|
+
/** One run by id, whatever its status. */
|
|
339
|
+
get(id: string): Promise<JobRunRow | null>;
|
|
340
|
+
/**
|
|
341
|
+
* `running` runs whose `next_attempt_at` has passed (or is NULL), oldest first,
|
|
342
|
+
* starting strictly after `afterId` when one is given.
|
|
343
|
+
*
|
|
344
|
+
* The cursor exists for starvation, not for paging convenience: the driver skips
|
|
345
|
+
* runs whose job this host does not register, and without a cursor those rows sit
|
|
346
|
+
* at the head of every batch forever, so a scope holding `limit` of them never
|
|
347
|
+
* drives anything newer. See `runDueJobRuns`.
|
|
348
|
+
*/
|
|
349
|
+
due(now: string, limit: number, afterId?: string): Promise<JobRunRow[]>;
|
|
350
|
+
list(filter: JobRunFilter): Promise<JobRunRow[]>;
|
|
351
|
+
patch(id: string, patch: JobRunPatch): Promise<void>;
|
|
352
|
+
/**
|
|
353
|
+
* A COMMITTED pass: write the run's new state and drop its step ledger together,
|
|
354
|
+
* indivisibly.
|
|
355
|
+
*
|
|
356
|
+
* Two statements, one operation, for the reason `startOrJoin` is one: a stop
|
|
357
|
+
* between them leaves the advanced cursor beside the finished pass's memo rows,
|
|
358
|
+
* and the next pass — which is entitled to reuse a step name, since the
|
|
359
|
+
* determinism rule binds names to the payload and prior results, NOT to the
|
|
360
|
+
* cursor — reads that stale memo and skips work it never did. The first cut of
|
|
361
|
+
* this file ordered the two calls carefully and explained in a comment why the
|
|
362
|
+
* gap was harmless. The comment was wrong; only atomicity makes it true.
|
|
363
|
+
*/
|
|
364
|
+
commitPass(id: string, patch: JobRunPatch): Promise<void>;
|
|
365
|
+
step(runId: string, name: string): Promise<JobStepRow | null>;
|
|
366
|
+
recordStep(runId: string, name: string, result: string | null, attempts: number, lastError: string | null, at: string): Promise<void>;
|
|
367
|
+
}
|
|
368
|
+
/** `conflict` reason: two `step()` calls under one name in one pass. */
|
|
369
|
+
export declare const JOB_STEP_REUSED = "job_step_reused";
|
|
370
|
+
/**
|
|
371
|
+
* Refuse anything that cannot survive a queue message, naming the path.
|
|
372
|
+
*
|
|
373
|
+
* What may be handed to a run, and handed forward by one, is **ids and
|
|
374
|
+
* configuration**: JSON's own values, nothing else. Bytes, class instances and
|
|
375
|
+
* functions are refused — and so, less obviously, are `undefined` in a nested
|
|
376
|
+
* position, a non-finite number and a cycle, because JSON turns each of those into
|
|
377
|
+
* something else without saying so. A payload silently reshaped on its way to
|
|
378
|
+
* storage is the failure this rule exists to prevent: the run resumes against a
|
|
379
|
+
* value that is not the one it was started with, and nothing anywhere reports it.
|
|
380
|
+
*
|
|
381
|
+
* `validation_failed` with the offending path in `errors`, exactly as an operation's
|
|
382
|
+
* own input failure arrives, so a transport renders it with no special case.
|
|
383
|
+
*
|
|
384
|
+
* Held against the payload at `start` and against the cursor at every commit —
|
|
385
|
+
* the two values that cross the boundary and land on the record. NOT held against a
|
|
386
|
+
* step's result, deliberately: that value is the handler's own, round-tripped to
|
|
387
|
+
* itself on resume, and the common shape of a step done for its effect is to return
|
|
388
|
+
* nothing at all — which this would refuse.
|
|
389
|
+
*/
|
|
390
|
+
export declare function assertQueueSafe(value: unknown, root: string): void;
|
|
391
|
+
/**
|
|
392
|
+
* A row, decoded into the record an operator reads — TOLERANTLY, and saying so.
|
|
393
|
+
*
|
|
394
|
+
* The status, the attempts, the timestamps and the `last_error` are columns and
|
|
395
|
+
* always readable; only `payload`, `cursor` and `counters` are JSON, and a row whose
|
|
396
|
+
* JSON will not parse still has to be visible. It comes back with those three empty
|
|
397
|
+
* and `decodeError` naming the parse failure — never a bare `null` cursor, which a
|
|
398
|
+
* reader would take for "no pass has committed yet".
|
|
399
|
+
*
|
|
400
|
+
* This is deliberately NOT what the driver does with the same row: a pass cannot run
|
|
401
|
+
* on a payload it cannot decode, so `runJobPass` treats the parse failure as a failed
|
|
402
|
+
* pass and lets the run retry and then fail with the reason on its record. Strict
|
|
403
|
+
* where work happens, tolerant where evidence is read.
|
|
404
|
+
*/
|
|
405
|
+
export declare function jobRunOf(row: JobRunRow): JobRun;
|
|
406
|
+
/**
|
|
407
|
+
* Start a run, or JOIN the one already in flight (#1577's first acceptance).
|
|
408
|
+
*
|
|
409
|
+
* The payload is refused here, before a row exists, so a run is never recorded
|
|
410
|
+
* carrying a value it cannot resume from.
|
|
411
|
+
*
|
|
412
|
+
* The join returns the LIVE run unchanged — its cursor, its counters, its start
|
|
413
|
+
* time. A second caller therefore learns the id of the walk that is already
|
|
414
|
+
* happening and can watch it, which is what "joins the first" has to mean for the
|
|
415
|
+
* caller to be able to do anything with the answer.
|
|
416
|
+
*
|
|
417
|
+
* **The lookup and the insert are ONE store operation** (`startOrJoin`), not two.
|
|
418
|
+
* As two, concurrent starts both find no live run and both insert — and there is no
|
|
419
|
+
* unique index to catch the second, deliberately, because a crashed run must stay
|
|
420
|
+
* restartable. The coalescing guarantee would then be false exactly when it is load
|
|
421
|
+
* bearing: two callers asking at once, which is the case it exists for.
|
|
422
|
+
*/
|
|
423
|
+
export declare function startJobRun(store: JobRunStore, input: StartJobRunInput, mintId: () => string, now: () => string): Promise<JobRunRow>;
|
|
424
|
+
/** What one pass did, as the drive loop reads it. */
|
|
425
|
+
export interface JobPassOutcome {
|
|
426
|
+
status: 'advanced' | 'completed' | 'retrying' | 'failed';
|
|
427
|
+
/** The error left on the record. Present exactly on `retrying` and `failed`. */
|
|
428
|
+
error?: string;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Run ONE pass of one run, and write what happened.
|
|
432
|
+
*
|
|
433
|
+
* The whole of the contract is here, which is why both adapters call it rather than
|
|
434
|
+
* porting it:
|
|
435
|
+
*
|
|
436
|
+
* - A committed pass writes the new cursor and counters, clears `attempts` and
|
|
437
|
+
* `last_error`, and DROPS the step ledger — a new pass is new work.
|
|
438
|
+
* - A failed pass writes NOTHING the handler produced: the cursor and counters stay
|
|
439
|
+
* where the last commit left them, so a partially-walked chunk is never mistaken
|
|
440
|
+
* for a committed one. The step ledger SURVIVES, which is what stops the next pass
|
|
441
|
+
* repeating the steps that did succeed.
|
|
442
|
+
* - A step at its `maxAttempts` fails the RUN: status `failed`, the step's error on
|
|
443
|
+
* the record, `ended_at` stamped. It is reported, never thrown — a driver that
|
|
444
|
+
* let one run's failure escape would take down every run behind it, which is the
|
|
445
|
+
* failure `runDueSchedules` already refuses for schedules.
|
|
446
|
+
*/
|
|
447
|
+
export declare function runJobPass(options: {
|
|
448
|
+
store: JobRunStore;
|
|
449
|
+
run: JobRunRow;
|
|
450
|
+
handler: JobHandler;
|
|
451
|
+
/** The job's own retry policy — a step may narrow it, none may widen past its own. */
|
|
452
|
+
retry?: ExecutorRetryPolicy;
|
|
453
|
+
now: () => string;
|
|
454
|
+
/** Opens the system door for this run's module. Called at most once per pass. */
|
|
455
|
+
openScope: () => Promise<ScopeStub>;
|
|
456
|
+
}): Promise<JobPassOutcome>;
|
|
457
|
+
/**
|
|
458
|
+
* Advance every due run on one scope — the driver both adapters expose as
|
|
459
|
+
* `runDueJobs`.
|
|
460
|
+
*
|
|
461
|
+
* Bounded on both axes, because a maintenance tick has a budget: `limit` runs per
|
|
462
|
+
* call, `maxPasses` passes per run. A run that still has work left after its budget
|
|
463
|
+
* is simply left `running` and due, and the next call takes it — the same "reported
|
|
464
|
+
* rather than looped" shape the event drain's `incomplete` has. Default `maxPasses`
|
|
465
|
+
* is 1, so a caller gets one predictable unit of work unless it asks for more.
|
|
466
|
+
*
|
|
467
|
+
* **`limit` counts RUNNABLE runs, not rows read**, and that distinction is a fix
|
|
468
|
+
* rather than a nicety. Runs whose job this host does not register are skipped, and
|
|
469
|
+
* when the budget was applied to the query instead, a scope holding `limit` such rows
|
|
470
|
+
* at the head of the due order returned the same unrunnable batch on every call —
|
|
471
|
+
* nothing newer was ever reached, and the report said `attempted: 0` forever with no
|
|
472
|
+
* indication why. So the read pages past them, bounded by `JOB_DRIVE_SCAN_MAX` rows
|
|
473
|
+
* examined so one scope full of orphans cannot turn a tick into a table scan.
|
|
474
|
+
*
|
|
475
|
+
* The starvation was spotted while re-reading this file, judged unlikely and left
|
|
476
|
+
* alone — and then found independently by a reviewer. The judgement may even have
|
|
477
|
+
* been right; recording it only in the author's head was not, because a decision
|
|
478
|
+
* nobody can see is indistinguishable from an oversight. Hence the fix and hence
|
|
479
|
+
* this paragraph.
|
|
480
|
+
*/
|
|
481
|
+
export declare function runDueJobRuns(options: {
|
|
482
|
+
store: JobRunStore;
|
|
483
|
+
/** job name → its handler and policy, as `registerJob` recorded them. */
|
|
484
|
+
handlerFor: (run: JobRunRow) => {
|
|
485
|
+
handler: JobHandler;
|
|
486
|
+
retry?: ExecutorRetryPolicy;
|
|
487
|
+
} | undefined;
|
|
488
|
+
now: () => string;
|
|
489
|
+
openScope: (run: JobRunRow) => Promise<ScopeStub>;
|
|
490
|
+
maxPasses?: number;
|
|
491
|
+
limit?: number;
|
|
492
|
+
}): Promise<JobDriveReport>;
|
|
493
|
+
//# sourceMappingURL=job-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-run.d.ts","sourceRoot":"","sources":["../src/job-run.ts"],"names":[],"mappings":"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"}
|