@semiont/jobs 0.6.7 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -64
- package/dist/index.d.ts +233 -534
- package/dist/index.js +9263 -986
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +66 -124
- package/dist/worker-main.js.map +1 -1
- package/package.json +9 -10
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { JobId, UserId, UnitCursor, ResourceId, EntityType, JobReferenceAnnotationResult, GenerationJobParams, JobHighlightAnnotationResult, JobAssessmentAnnotationResult, JobCommentAnnotationResult, TagSchema, JobTagAnnotationResult, Logger,
|
|
2
|
-
import { SemiontState } from '@semiont/core/node';
|
|
1
|
+
import { JobId, components, UserId, UnitCursor, ResourceId, EntityType, JobReferenceAnnotationResult, GenerationJobParams, JobHighlightAnnotationResult, JobAssessmentAnnotationResult, JobCommentAnnotationResult, TagSchema, JobTagAnnotationResult, Annotation, Logger, SupportedMediaType, GatheredContext, JobType as JobType$1, EventMap, BusRequestErrorCode, BusRequestPrimitive } from '@semiont/core';
|
|
3
2
|
import { InferenceClient } from '@semiont/inference';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Job Queue Type Definitions - Discriminated Union Design
|
|
@@ -15,8 +15,8 @@ import { InferenceClient } from '@semiont/inference';
|
|
|
15
15
|
* - State machine is explicit and type-safe
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
type JobType = '
|
|
19
|
-
type JobStatus = '
|
|
18
|
+
type JobType = components['schemas']['JobType'];
|
|
19
|
+
type JobStatus = components['schemas']['Job']['status'];
|
|
20
20
|
/**
|
|
21
21
|
* Job metadata - common to all states
|
|
22
22
|
*/
|
|
@@ -80,7 +80,7 @@ interface JobMetadata {
|
|
|
80
80
|
/**
|
|
81
81
|
* Detection job parameters
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
type DetectionParams = {
|
|
84
84
|
resourceId: ResourceId;
|
|
85
85
|
entityTypes: EntityType[];
|
|
86
86
|
includeDescriptiveReferences?: boolean;
|
|
@@ -88,21 +88,21 @@ interface DetectionParams {
|
|
|
88
88
|
language?: string;
|
|
89
89
|
/** Source-resource locale — see locale conventions above. */
|
|
90
90
|
sourceLanguage?: string;
|
|
91
|
-
}
|
|
91
|
+
};
|
|
92
92
|
/**
|
|
93
93
|
* Highlight detection job parameters
|
|
94
94
|
*/
|
|
95
|
-
|
|
95
|
+
type HighlightDetectionParams = {
|
|
96
96
|
resourceId: ResourceId;
|
|
97
97
|
instructions?: string;
|
|
98
98
|
density?: number;
|
|
99
99
|
/** Source-resource locale — see locale conventions above. */
|
|
100
100
|
sourceLanguage?: string;
|
|
101
|
-
}
|
|
101
|
+
};
|
|
102
102
|
/**
|
|
103
103
|
* Assessment detection job parameters
|
|
104
104
|
*/
|
|
105
|
-
|
|
105
|
+
type AssessmentDetectionParams = {
|
|
106
106
|
resourceId: ResourceId;
|
|
107
107
|
instructions?: string;
|
|
108
108
|
tone?: 'analytical' | 'critical' | 'balanced' | 'constructive';
|
|
@@ -111,11 +111,11 @@ interface AssessmentDetectionParams {
|
|
|
111
111
|
language?: string;
|
|
112
112
|
/** Source-resource locale — see locale conventions above. */
|
|
113
113
|
sourceLanguage?: string;
|
|
114
|
-
}
|
|
114
|
+
};
|
|
115
115
|
/**
|
|
116
116
|
* Comment detection job parameters
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
type CommentDetectionParams = {
|
|
119
119
|
resourceId: ResourceId;
|
|
120
120
|
instructions?: string;
|
|
121
121
|
tone?: 'scholarly' | 'explanatory' | 'conversational' | 'technical';
|
|
@@ -124,7 +124,7 @@ interface CommentDetectionParams {
|
|
|
124
124
|
language?: string;
|
|
125
125
|
/** Source-resource locale — see locale conventions above. */
|
|
126
126
|
sourceLanguage?: string;
|
|
127
|
-
}
|
|
127
|
+
};
|
|
128
128
|
/**
|
|
129
129
|
* Tag detection job parameters.
|
|
130
130
|
*
|
|
@@ -133,7 +133,7 @@ interface CommentDetectionParams {
|
|
|
133
133
|
* at job-creation time and embeds the resolved schema here, keeping the
|
|
134
134
|
* worker independent of the registry.
|
|
135
135
|
*/
|
|
136
|
-
|
|
136
|
+
type TagDetectionParams = {
|
|
137
137
|
resourceId: ResourceId;
|
|
138
138
|
schema: TagSchema;
|
|
139
139
|
categories: string[];
|
|
@@ -141,70 +141,13 @@ interface TagDetectionParams {
|
|
|
141
141
|
language?: string;
|
|
142
142
|
/** Source-resource locale — see locale conventions above. */
|
|
143
143
|
sourceLanguage?: string;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Detection job progress
|
|
147
|
-
*/
|
|
148
|
-
interface DetectionProgress {
|
|
149
|
-
totalEntityTypes: number;
|
|
150
|
-
processedEntityTypes: number;
|
|
151
|
-
entitiesFound: number;
|
|
152
|
-
entitiesEmitted: number;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Generation job progress
|
|
156
|
-
*/
|
|
157
|
-
interface YieldProgress {
|
|
158
|
-
/** The two real generation transitions — LLM call running, then persisting. */
|
|
159
|
-
stage: 'generating' | 'creating';
|
|
160
|
-
percentage: number;
|
|
161
|
-
message?: string;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Generation job result
|
|
165
|
-
*/
|
|
166
|
-
interface GenerationResult {
|
|
167
|
-
kind: 'generation';
|
|
168
|
-
resourceId: ResourceId;
|
|
169
|
-
resourceName: string;
|
|
170
|
-
/** True when the model stopped at the maxTokens ceiling — the artifact is cut off, not complete (GENERATE-FROM-RESOURCE D6). */
|
|
171
|
-
truncated: boolean;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Highlight detection job progress
|
|
175
|
-
*/
|
|
176
|
-
interface HighlightDetectionProgress {
|
|
177
|
-
stage: 'analyzing' | 'creating';
|
|
178
|
-
percentage: number;
|
|
179
|
-
message?: string;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Assessment detection job progress
|
|
183
|
-
*/
|
|
184
|
-
interface AssessmentDetectionProgress {
|
|
185
|
-
stage: 'analyzing' | 'creating';
|
|
186
|
-
percentage: number;
|
|
187
|
-
message?: string;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Comment detection job progress
|
|
191
|
-
*/
|
|
192
|
-
interface CommentDetectionProgress {
|
|
193
|
-
stage: 'analyzing' | 'creating';
|
|
194
|
-
percentage: number;
|
|
195
|
-
message?: string;
|
|
196
|
-
}
|
|
144
|
+
};
|
|
197
145
|
/**
|
|
198
|
-
*
|
|
146
|
+
* The progress a running job carries: the last its worker reported with
|
|
147
|
+
* `job:report-progress`, or an empty object before the first report. One shape
|
|
148
|
+
* for every job type, as the spec states it (JobRunning).
|
|
199
149
|
*/
|
|
200
|
-
|
|
201
|
-
stage: 'analyzing' | 'creating';
|
|
202
|
-
percentage: number;
|
|
203
|
-
currentCategory?: string;
|
|
204
|
-
processedCategories: number;
|
|
205
|
-
totalCategories: number;
|
|
206
|
-
message?: string;
|
|
207
|
-
}
|
|
150
|
+
type StoredProgress = components['schemas']['JobStoredProgress'];
|
|
208
151
|
/**
|
|
209
152
|
* Pending job - just created, waiting to be picked up
|
|
210
153
|
*/
|
|
@@ -216,12 +159,12 @@ interface PendingJob<P> {
|
|
|
216
159
|
/**
|
|
217
160
|
* Running job - actively being processed
|
|
218
161
|
*/
|
|
219
|
-
interface RunningJob<P
|
|
162
|
+
interface RunningJob<P> {
|
|
220
163
|
status: 'running';
|
|
221
164
|
metadata: JobMetadata;
|
|
222
165
|
params: P;
|
|
223
166
|
startedAt: string;
|
|
224
|
-
progress:
|
|
167
|
+
progress: StoredProgress;
|
|
225
168
|
}
|
|
226
169
|
/**
|
|
227
170
|
* Complete job - successfully finished
|
|
@@ -258,464 +201,29 @@ interface CancelledJob<P> {
|
|
|
258
201
|
/**
|
|
259
202
|
* Generic job - discriminated union of all states
|
|
260
203
|
*/
|
|
261
|
-
type Job<P,
|
|
262
|
-
type DetectionJob = Job<DetectionParams,
|
|
263
|
-
|
|
264
|
-
type
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
type
|
|
204
|
+
type Job<P, R> = PendingJob<P> | RunningJob<P> | CompleteJob<P, R> | FailedJob<P> | CancelledJob<P>;
|
|
205
|
+
type DetectionJob = Job<DetectionParams, JobReferenceAnnotationResult>;
|
|
206
|
+
/** A generation job's params carry the resource the dispatcher derived from the context's focus. */
|
|
207
|
+
type GenerationJob = Job<GenerationJobParams & {
|
|
208
|
+
resourceId: ResourceId;
|
|
209
|
+
}, components['schemas']['JobGenerationResult']>;
|
|
210
|
+
type HighlightDetectionJob = Job<HighlightDetectionParams, JobHighlightAnnotationResult>;
|
|
211
|
+
type AssessmentDetectionJob = Job<AssessmentDetectionParams, JobAssessmentAnnotationResult>;
|
|
212
|
+
type CommentDetectionJob = Job<CommentDetectionParams, JobCommentAnnotationResult>;
|
|
213
|
+
type TagDetectionJob = Job<TagDetectionParams, JobTagAnnotationResult>;
|
|
268
214
|
/**
|
|
269
215
|
* Discriminated union of all job types
|
|
270
216
|
*/
|
|
271
217
|
type AnyJob = DetectionJob | GenerationJob | HighlightDetectionJob | AssessmentDetectionJob | CommentDetectionJob | TagDetectionJob;
|
|
218
|
+
/** A job of any type, running: what a claim returns. */
|
|
219
|
+
type RunningAnyJob = Extract<AnyJob, {
|
|
220
|
+
status: 'running';
|
|
221
|
+
}>;
|
|
272
222
|
declare function isPendingJob(job: AnyJob): job is PendingJob<any>;
|
|
273
|
-
declare function isRunningJob(job: AnyJob): job is RunningJob<any
|
|
223
|
+
declare function isRunningJob(job: AnyJob): job is RunningJob<any>;
|
|
274
224
|
declare function isCompleteJob(job: AnyJob): job is CompleteJob<any, any>;
|
|
275
225
|
declare function isFailedJob(job: AnyJob): job is FailedJob<any>;
|
|
276
226
|
declare function isCancelledJob(job: AnyJob): job is CancelledJob<any>;
|
|
277
|
-
interface JobQueryFilters {
|
|
278
|
-
status?: JobStatus;
|
|
279
|
-
type?: JobType;
|
|
280
|
-
userId?: UserId;
|
|
281
|
-
limit?: number;
|
|
282
|
-
offset?: number;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
interface JobQueue {
|
|
286
|
-
initialize(): Promise<void>;
|
|
287
|
-
destroy(): void;
|
|
288
|
-
createJob(job: AnyJob): Promise<void>;
|
|
289
|
-
getJob(jobId: JobId): Promise<AnyJob | null>;
|
|
290
|
-
/**
|
|
291
|
-
* Atomically claim the NEXT pending job matching one of `types` — the ONE
|
|
292
|
-
* transition that makes this a queue rather than a state store
|
|
293
|
-
* (JOB-QUEUE-DRIVER P0; reshaped claim-by-TYPE in P2, while every worker
|
|
294
|
-
* is still ours). pending → running, `startedAt` stamped, progress empty.
|
|
295
|
-
* An announcement is a WAKE-UP, not a reservation: the claimed job may
|
|
296
|
-
* differ from any announced one, no ordering among matching pending jobs
|
|
297
|
-
* is promised, and an empty `types` accepts any type. Simultaneous claims
|
|
298
|
-
* admit one winner PER pending job; a claim that finds nothing is
|
|
299
|
-
* DECLINED (`none-available`), never an error.
|
|
300
|
-
*/
|
|
301
|
-
claimNextJob(types: string[]): Promise<{
|
|
302
|
-
job: AnyJob;
|
|
303
|
-
} | {
|
|
304
|
-
declined: 'none-available';
|
|
305
|
-
}>;
|
|
306
|
-
/** Move a running job to `complete`. Returns false if the job isn't running. */
|
|
307
|
-
completeJob(jobId: JobId, result: Record<string, unknown>): Promise<boolean>;
|
|
308
|
-
/**
|
|
309
|
-
* Move a running job back to `pending` (retry, re-announced) while
|
|
310
|
-
* `retryCount < maxRetries`, else to `failed`. Returns what happened,
|
|
311
|
-
* or null if the job isn't running. `completedUnits` — the units the
|
|
312
|
-
* failing attempt fully emitted — are unioned into the record's
|
|
313
|
-
* checkpoint (ABANDONED-INFERENCE P2) so a retry skips them. A
|
|
314
|
-
* `failureClass` of 'deterministic' goes straight to `failed` with any
|
|
315
|
-
* budget remaining — a second identical attempt cannot succeed (P3).
|
|
316
|
-
*/
|
|
317
|
-
failJob(jobId: JobId, error: string, completedUnits?: string[], failureClass?: 'transient' | 'deterministic', unitCursors?: Record<string, UnitCursor>): Promise<'retried' | 'failed' | null>;
|
|
318
|
-
/**
|
|
319
|
-
* Persist a running job's completed-unit checkpoint AT unit completion —
|
|
320
|
-
* not only when a job fails (JOB-RESTART-SAFETY P2). `failJob` carries the
|
|
321
|
-
* checkpoint on a clean failure, but a worker that DIES (crash/OOM/kill)
|
|
322
|
-
* never emits `job:fail`, so its finished units would be lost and the
|
|
323
|
-
* janitor's recovery would redo them. This writes them into the running
|
|
324
|
-
* file's metadata as each unit lands, unioned with any existing
|
|
325
|
-
* checkpoint, so recovery resumes rather than restarts. Unthrottled (a
|
|
326
|
-
* unit completion must never be dropped); a no-op for non-running jobs.
|
|
327
|
-
*
|
|
328
|
-
* `unitCursors` is the finer grain `completedUnits` cannot express
|
|
329
|
-
* (CHUNK-GRAIN-RESUME P2): how far an UNFINISHED unit got, so a job that
|
|
330
|
-
* dies mid-unit resumes there instead of at the top. It is written per
|
|
331
|
-
* committed chunk, not per unit.
|
|
332
|
-
*
|
|
333
|
-
* **The two merge differently, and the difference is the contract.**
|
|
334
|
-
* `completedUnits` is a set, so a union converges under concurrent snapshots
|
|
335
|
-
* — a set only grows. A cursor converges only if the merge is **monotone per
|
|
336
|
-
* unit**: a stale snapshot must never move one backward. `next` and `size`
|
|
337
|
-
* move TOGETHER as one observation; taking `size` from one snapshot and
|
|
338
|
-
* `next` from another would describe a chunk that never existed. A unit that
|
|
339
|
-
* reaches `completedUnits` drops its cursor, so "in progress with a cursor"
|
|
340
|
-
* and "complete" stay structurally exclusive rather than by convention.
|
|
341
|
-
*
|
|
342
|
-
* The rule belongs here rather than to any one driver because it is what
|
|
343
|
-
* makes a cursor safe to merge at all.
|
|
344
|
-
*/
|
|
345
|
-
checkpointUnits(jobId: JobId, completedUnits: string[], unitCursors?: Record<string, UnitCursor>): Promise<void>;
|
|
346
|
-
/** Write progress into a running job's file (throttled, best-effort). */
|
|
347
|
-
recordProgress(jobId: JobId, progress: Record<string, unknown>): Promise<void>;
|
|
348
|
-
/**
|
|
349
|
-
* Cancel all pending jobs in a category — 'generation' is the
|
|
350
|
-
* `generation` type; 'annotation' is every `*-annotation` type.
|
|
351
|
-
* Running jobs are left to finish. Returns the number cancelled.
|
|
352
|
-
*/
|
|
353
|
-
cancelPendingJobs(category: 'annotation' | 'generation'): Promise<number>;
|
|
354
|
-
cancelJob(jobId: JobId): Promise<boolean>;
|
|
355
|
-
getStats(): Promise<{
|
|
356
|
-
pending: number;
|
|
357
|
-
running: number;
|
|
358
|
-
complete: number;
|
|
359
|
-
failed: number;
|
|
360
|
-
cancelled: number;
|
|
361
|
-
}>;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Every bus channel a queue DRIVER emits, maintained beside the interface
|
|
365
|
-
* both drivers implement and censused against their sources
|
|
366
|
-
* (queue-emits-census.test.ts). The gateway's signal bridge forwards these
|
|
367
|
-
* from its local bus onto the plane — a queue announcement that stays on
|
|
368
|
-
* the raw bus reaches no worker under a remote driver (the job:queued
|
|
369
|
-
* starvation, 2026-09-15).
|
|
370
|
-
*/
|
|
371
|
-
declare const JOB_QUEUE_EMITS: readonly ["job:queued"];
|
|
372
|
-
/**
|
|
373
|
-
* How long a job's record survives after it reaches a terminal state
|
|
374
|
-
* (complete/failed/cancelled), and how often a driver enforces that.
|
|
375
|
-
*
|
|
376
|
-
* ONE pair for every driver. The window is a CONTRACT fact — a caller that
|
|
377
|
-
* reads a finished job's result gets the same day whichever backing store the
|
|
378
|
-
* stack runs — and the cadence is what turns the window from an aspiration
|
|
379
|
-
* into a promise. Restated per driver the two numbers drift, and the drift is
|
|
380
|
-
* invisible until someone compares two deployments.
|
|
381
|
-
*
|
|
382
|
-
* The METHOD that enforces retention is not on the interface and cannot be:
|
|
383
|
-
* it is an unlink in one driver and a stream purge in another. The NUMBER is
|
|
384
|
-
* here because it is the same number.
|
|
385
|
-
*/
|
|
386
|
-
declare const TERMINAL_JOB_RETENTION_MS: number;
|
|
387
|
-
/** How often a driver sweeps for records past `TERMINAL_JOB_RETENTION_MS`. */
|
|
388
|
-
declare const TERMINAL_JOB_SWEEP_INTERVAL_MS: number;
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Job Queue Manager
|
|
392
|
-
*
|
|
393
|
-
* Filesystem-based job queue with atomic operations.
|
|
394
|
-
* Jobs are stored in directories by status; status transitions are
|
|
395
|
-
* atomic delete + write across directories.
|
|
396
|
-
*/
|
|
397
|
-
|
|
398
|
-
declare class FsJobQueue implements JobQueue {
|
|
399
|
-
private eventBus?;
|
|
400
|
-
private jobsDir;
|
|
401
|
-
private logger;
|
|
402
|
-
private reannounceTimer;
|
|
403
|
-
private cleanupTimer;
|
|
404
|
-
/** Per-job timestamp of the last progress write, for throttling. */
|
|
405
|
-
private lastProgressWrite;
|
|
406
|
-
constructor(
|
|
407
|
-
/** `SemiontState` and not `SemiontProject`: the queue reads ONE path,
|
|
408
|
-
* and the gateway that owns it mounts no KB tree (SINGLE-KB-MOUNT P5). */
|
|
409
|
-
state: SemiontState, logger: Logger, eventBus?: EventBus | undefined);
|
|
410
|
-
/**
|
|
411
|
-
* Initialize job queue directories, announce any pending backlog,
|
|
412
|
-
* and start the re-announce interval. Idempotent.
|
|
413
|
-
*/
|
|
414
|
-
initialize(): Promise<void>;
|
|
415
|
-
/**
|
|
416
|
-
* Stop the re-announce and retention intervals
|
|
417
|
-
*/
|
|
418
|
-
destroy(): void;
|
|
419
|
-
/**
|
|
420
|
-
* Emit `job:queued` for a pending job, if an EventBus is wired and
|
|
421
|
-
* the job carries a `resourceId` (every current job type does).
|
|
422
|
-
*/
|
|
423
|
-
private announce;
|
|
424
|
-
/**
|
|
425
|
-
* Announce every job currently in `pending/`. Files that vanish or
|
|
426
|
-
* fail to parse mid-scan (claimed, cancelled, partially written)
|
|
427
|
-
* are skipped — they're either gone for a good reason or picked up
|
|
428
|
-
* on the next tick.
|
|
429
|
-
*/
|
|
430
|
-
private announcePendingJobs;
|
|
431
|
-
/**
|
|
432
|
-
* Create a new job
|
|
433
|
-
*/
|
|
434
|
-
createJob(job: AnyJob): Promise<void>;
|
|
435
|
-
/**
|
|
436
|
-
* Get a job by ID (searches all status directories)
|
|
437
|
-
*/
|
|
438
|
-
getJob(jobId: JobId): Promise<AnyJob | null>;
|
|
439
|
-
/**
|
|
440
|
-
* Serializes claims. This driver's atomicity is single-process by design,
|
|
441
|
-
* but `claimJob`'s contract must hold even against interleaved awaits in
|
|
442
|
-
* that one process — a read-check-write with await points in it can admit
|
|
443
|
-
* two winners without this chain (the TOCTOU the operation exists to kill).
|
|
444
|
-
*/
|
|
445
|
-
private claimChain;
|
|
446
|
-
claimNextJob(types: string[]): Promise<{
|
|
447
|
-
job: AnyJob;
|
|
448
|
-
} | {
|
|
449
|
-
declined: 'none-available';
|
|
450
|
-
}>;
|
|
451
|
-
private doClaimNext;
|
|
452
|
-
/**
|
|
453
|
-
* Update a job (atomic: delete old, write new)
|
|
454
|
-
*/
|
|
455
|
-
/**
|
|
456
|
-
* Cross-status move (or in-place rewrite): delete old slot, write new.
|
|
457
|
-
* Private — the interface exposes named transitions only (JOB-QUEUE-DRIVER
|
|
458
|
-
* P0); a generic public patch is trivial on files and impossible on an
|
|
459
|
-
* in-flight message, so no driver may offer one. Re-entering `pending`
|
|
460
|
-
* announces, which is how failJob's retry reaches a listening worker.
|
|
461
|
-
*/
|
|
462
|
-
private transition;
|
|
463
|
-
/**
|
|
464
|
-
* Move a running job to `complete`. Returns false (and changes
|
|
465
|
-
* nothing) if the job is missing or not running — which also makes
|
|
466
|
-
* duplicate `job:complete` events harmless.
|
|
467
|
-
*/
|
|
468
|
-
completeJob(jobId: JobId, result: Record<string, unknown>): Promise<boolean>;
|
|
469
|
-
/**
|
|
470
|
-
* Retry-or-fail a running job. While `retryCount < maxRetries` the
|
|
471
|
-
* job goes back to `pending` with the count bumped (and is
|
|
472
|
-
* re-announced); after that it lands in `failed` with the error.
|
|
473
|
-
* Returns null (and changes nothing) if the job isn't running.
|
|
474
|
-
*/
|
|
475
|
-
failJob(jobId: JobId, error: string, completedUnits?: string[], failureClass?: 'transient' | 'deterministic', unitCursors?: Record<string, UnitCursor>): Promise<'retried' | 'failed' | null>;
|
|
476
|
-
/**
|
|
477
|
-
* Persist a running job's completed-unit checkpoint at unit completion
|
|
478
|
-
* (JOB-RESTART-SAFETY P2). `failJob` writes this checkpoint on a clean
|
|
479
|
-
* failure, but a worker that dies without emitting `job:fail` would lose
|
|
480
|
-
* its finished units — so the janitor's recovery (which re-queues with
|
|
481
|
-
* whatever `metadata.completedUnits` the running file holds) would redo
|
|
482
|
-
* them. Writing it HERE, as each unit lands, makes a crash lose at most
|
|
483
|
-
* the in-flight unit. Unioned with any existing checkpoint, unthrottled
|
|
484
|
-
* (a unit completion is rare and must never be dropped), a no-op for
|
|
485
|
-
* non-running jobs. Written directly, like `recordProgress`, so it also
|
|
486
|
-
* refreshes the mtime heartbeat.
|
|
487
|
-
*/
|
|
488
|
-
checkpointUnits(jobId: JobId, completedUnits: string[], unitCursors?: Record<string, UnitCursor>): Promise<void>;
|
|
489
|
-
/**
|
|
490
|
-
* Write progress into a running job's file. Throttled per job, and
|
|
491
|
-
* a no-op for jobs that aren't running. Beyond surfacing live
|
|
492
|
-
* progress to `job:status-requested`, each write refreshes the
|
|
493
|
-
* file's mtime — the heartbeat `recoverStaleRunningJobs` watches.
|
|
494
|
-
*/
|
|
495
|
-
recordProgress(jobId: JobId, progress: Record<string, unknown>): Promise<void>;
|
|
496
|
-
/**
|
|
497
|
-
* List jobs with filters
|
|
498
|
-
*/
|
|
499
|
-
listJobs(filters?: JobQueryFilters): Promise<AnyJob[]>;
|
|
500
|
-
/**
|
|
501
|
-
* Cancel a job
|
|
502
|
-
*/
|
|
503
|
-
cancelJob(jobId: JobId): Promise<boolean>;
|
|
504
|
-
/**
|
|
505
|
-
* Cancel all pending jobs in a category — the granularity of the
|
|
506
|
-
* `job:cancel-requested` UI signal. Running jobs are left to finish:
|
|
507
|
-
* interrupting a worker mid-inference would need a worker-side kill
|
|
508
|
-
* channel that doesn't exist.
|
|
509
|
-
*/
|
|
510
|
-
cancelPendingJobs(category: 'annotation' | 'generation'): Promise<number>;
|
|
511
|
-
/**
|
|
512
|
-
* Recover running jobs orphaned by a dead worker: any `running/`
|
|
513
|
-
* file whose mtime is older than the stale window is fed through
|
|
514
|
-
* the same retry-or-fail path as `job:fail`. Progress writes
|
|
515
|
-
* refresh the mtime, so a live worker is never recovered out from
|
|
516
|
-
* under itself as long as it reports within the window.
|
|
517
|
-
*/
|
|
518
|
-
recoverStaleRunningJobs(): Promise<number>;
|
|
519
|
-
/**
|
|
520
|
-
* Delete terminal jobs whose `completedAt` is older than `retentionMs`.
|
|
521
|
-
* The window is a parameter with no default: the ONE value the queue runs
|
|
522
|
-
* on is `TERMINAL_JOB_RETENTION_MS`, and a default here would be a second
|
|
523
|
-
* copy of it that nobody notices going stale.
|
|
524
|
-
*/
|
|
525
|
-
cleanupOldJobs(retentionMs: number): Promise<number>;
|
|
526
|
-
/**
|
|
527
|
-
* Get job file path
|
|
528
|
-
*/
|
|
529
|
-
private getJobPath;
|
|
530
|
-
/**
|
|
531
|
-
* Get statistics about the queue
|
|
532
|
-
*/
|
|
533
|
-
getStats(): Promise<{
|
|
534
|
-
pending: number;
|
|
535
|
-
running: number;
|
|
536
|
-
complete: number;
|
|
537
|
-
failed: number;
|
|
538
|
-
cancelled: number;
|
|
539
|
-
}>;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* JetStreamJobQueue — the JetStream driver behind the `JobQueue` interface
|
|
544
|
-
* (JOB-QUEUE-DRIVER P1, topology ruling M: broker-mediated — the queue's own
|
|
545
|
-
* process holds the broker connection and the leases; workers never touch the
|
|
546
|
-
* broker. That process was the gateway; it is the dispatcher now.)
|
|
547
|
-
*
|
|
548
|
-
* Two primitives, one authority each:
|
|
549
|
-
*
|
|
550
|
-
* - **KV bucket `jobs`** is the AUTHORITATIVE operational state: one entry
|
|
551
|
-
* per job (`{ job, lastProgressAt }`), every transition a revision-CAS —
|
|
552
|
-
* which is what makes `claimJob` atomic: simultaneous claims race on one
|
|
553
|
-
* revision and exactly one update wins. It holds LIVE jobs plus a day of
|
|
554
|
-
* concluded ones, not a history: `pruneTerminalJobs` is the retention the
|
|
555
|
-
* fs driver's janitor holds, and without it every scan over the bucket
|
|
556
|
-
* — `getStats` is one, on the metrics interval — grows forever.
|
|
557
|
-
* - **Stream `JOBS`** (subjects `jobs.<category>.<type>`, work-queue
|
|
558
|
-
* retention) is the DELIVERY vehicle and redelivery timer. A delivered
|
|
559
|
-
* message is the lease this process holds for a job; `working()`
|
|
560
|
-
* heartbeats extend it, and a dispatcher that dies stops heartbeating, so
|
|
561
|
-
* `AckWait` redelivers the job to a live instance — dispatcher-death
|
|
562
|
-
* recovery, protocol-native.
|
|
563
|
-
*
|
|
564
|
-
* Worker-death recovery is NOT AckWait's job under the mediated topology
|
|
565
|
-
* (the dispatcher holding the lease is alive; the worker vanished): it is the
|
|
566
|
-
* `lastProgressAt` sweep (`recoverStaleRunningJobs`), the same contract the
|
|
567
|
-
* fs driver's mtime janitor implemented. The redelivery handler checks KV
|
|
568
|
-
* state, so the two recovery paths can never double-apply.
|
|
569
|
-
*
|
|
570
|
-
* Battery ownership (JOB-QUEUE-DRIVER P0): the retry decision belongs to
|
|
571
|
-
* `will-retry.ts` — `max_deliver` is unlimited and a deterministic failure
|
|
572
|
-
* is `term()`ed, so the backend's retry engine never becomes a second
|
|
573
|
-
* authority. The cursor merge belongs to `checkpoint-merge.ts`, shared with
|
|
574
|
-
* every driver.
|
|
575
|
-
*
|
|
576
|
-
* The boundary is the point: no NATS type, subject string, or delivery
|
|
577
|
-
* handle escapes this file.
|
|
578
|
-
*/
|
|
579
|
-
|
|
580
|
-
interface JetStreamJobQueueOptions {
|
|
581
|
-
/** NATS server address(es), e.g. "192.168.64.42:4222". */
|
|
582
|
-
servers: string | string[];
|
|
583
|
-
/**
|
|
584
|
-
* Broker credentials (INTER-COMPONENT-ACCESS P3). The same broker the signal
|
|
585
|
-
* plane connects to, so the same pair. Absent means an unauthenticated
|
|
586
|
-
* broker — which is what every deployment had before this, and means anyone
|
|
587
|
-
* who can reach NATS can read and write the job stream.
|
|
588
|
-
*/
|
|
589
|
-
user?: string;
|
|
590
|
-
pass?: string;
|
|
591
|
-
/** Worker presumed dead after this long without progress (default 30 min). */
|
|
592
|
-
staleRunningMs?: number;
|
|
593
|
-
/** Lease redelivery window when THIS process stops heartbeating (default 30 s). */
|
|
594
|
-
ackWaitMs?: number;
|
|
595
|
-
/** Re-announce + worker-death-sweep cadence (default 30 s; tests shrink it). */
|
|
596
|
-
tickMs?: number;
|
|
597
|
-
/** Reconnect on connection loss (default true; tests turn it off). */
|
|
598
|
-
reconnect?: boolean;
|
|
599
|
-
}
|
|
600
|
-
declare class JetStreamJobQueue implements JobQueue {
|
|
601
|
-
private readonly options;
|
|
602
|
-
private readonly logger;
|
|
603
|
-
private readonly eventBus?;
|
|
604
|
-
private nc;
|
|
605
|
-
private js;
|
|
606
|
-
private jsm;
|
|
607
|
-
private kv;
|
|
608
|
-
private iter;
|
|
609
|
-
/** Delivered messages this process holds — the lease, keyed by jobId,
|
|
610
|
-
* with the job's type so a by-type claim can walk them without a read. */
|
|
611
|
-
private readonly held;
|
|
612
|
-
private heartbeat;
|
|
613
|
-
private reconciling;
|
|
614
|
-
private tick;
|
|
615
|
-
private ticking;
|
|
616
|
-
private cleanupTimer;
|
|
617
|
-
private readonly lastProgressWrite;
|
|
618
|
-
private readonly staleRunningMs;
|
|
619
|
-
private readonly ackWaitMs;
|
|
620
|
-
private readonly tickMs;
|
|
621
|
-
constructor(options: JetStreamJobQueueOptions, logger: Logger, eventBus?: EventBus | undefined);
|
|
622
|
-
initialize(): Promise<void>;
|
|
623
|
-
/** An outage is never silent: one line down, one line back, one line if the client gives up. */
|
|
624
|
-
private watchConnection;
|
|
625
|
-
destroy(): void;
|
|
626
|
-
/**
|
|
627
|
-
* A delivery is a job arriving at this dispatcher. What it means depends on
|
|
628
|
-
* the job's authoritative (KV) state:
|
|
629
|
-
* - pending → hold the lease, announce for a worker to claim;
|
|
630
|
-
* - running → hold silently (a claim raced ahead of the delivery, or a
|
|
631
|
-
* redelivery reached a fresh instance after a dispatcher death);
|
|
632
|
-
* - terminal → the work already concluded elsewhere; consume the message;
|
|
633
|
-
* - unknown → not ours to run; terminate it.
|
|
634
|
-
*/
|
|
635
|
-
private onDelivery;
|
|
636
|
-
/** Same wire shape as the fs driver: only jobs with a resourceId announce. */
|
|
637
|
-
private announce;
|
|
638
|
-
private read;
|
|
639
|
-
private write;
|
|
640
|
-
/**
|
|
641
|
-
* Read-transform-CAS with bounded retries: the transform sees the current
|
|
642
|
-
* job and returns the replacement (or null to stop). Every state
|
|
643
|
-
* transition goes through here, which is what makes each one atomic.
|
|
644
|
-
*/
|
|
645
|
-
private cas;
|
|
646
|
-
createJob(job: AnyJob): Promise<void>;
|
|
647
|
-
getJob(jobIdArg: JobId): Promise<AnyJob | null>;
|
|
648
|
-
claimNextJob(types: string[]): Promise<{
|
|
649
|
-
job: AnyJob;
|
|
650
|
-
} | {
|
|
651
|
-
declined: 'none-available';
|
|
652
|
-
}>;
|
|
653
|
-
/** CAS one pending job to running; null when someone else won it. */
|
|
654
|
-
private tryClaim;
|
|
655
|
-
completeJob(jobIdArg: JobId, result: Record<string, unknown>): Promise<boolean>;
|
|
656
|
-
failJob(jobIdArg: JobId, error: string, completedUnits?: string[], failureClass?: 'transient' | 'deterministic', unitCursors?: Record<string, UnitCursor>): Promise<'retried' | 'failed' | null>;
|
|
657
|
-
checkpointUnits(jobIdArg: JobId, completedUnits: string[], unitCursors?: Record<string, UnitCursor>): Promise<void>;
|
|
658
|
-
recordProgress(jobIdArg: JobId, progress: Record<string, unknown>): Promise<void>;
|
|
659
|
-
cancelPendingJobs(category: 'annotation' | 'generation'): Promise<number>;
|
|
660
|
-
cancelJob(jobIdArg: JobId): Promise<boolean>;
|
|
661
|
-
/**
|
|
662
|
-
* Counts by status. The three terminal counts are the RETENTION WINDOW, not
|
|
663
|
-
* lifetime totals — `pruneTerminalJobs` drops a record a day after it
|
|
664
|
-
* concluded — so they answer "recently finished", never "finished ever".
|
|
665
|
-
*/
|
|
666
|
-
getStats(): Promise<{
|
|
667
|
-
pending: number;
|
|
668
|
-
running: number;
|
|
669
|
-
complete: number;
|
|
670
|
-
failed: number;
|
|
671
|
-
cancelled: number;
|
|
672
|
-
}>;
|
|
673
|
-
/**
|
|
674
|
-
* Worker-death recovery: the same contract the fs driver's mtime janitor
|
|
675
|
-
* implemented — a running job with no progress inside the stale window is
|
|
676
|
-
* retried or failed, checkpoint intact. Gateway-death recovery never comes
|
|
677
|
-
* through here; that is AckWait redelivery.
|
|
678
|
-
*/
|
|
679
|
-
recoverStaleRunningJobs(): Promise<number>;
|
|
680
|
-
/**
|
|
681
|
-
* Terminal-job retention: the fs driver's hourly janitor, in this driver's
|
|
682
|
-
* terms. Without it the bucket keeps every job it has ever seen, and
|
|
683
|
-
* `getStats` — an OTel observable gauge, so a full bucket scan every metric
|
|
684
|
-
* interval — pays for all of them forever. Measured on a live stack
|
|
685
|
-
* 2026-09-24: 139 concluded records re-read every 30 seconds by a knowledge
|
|
686
|
-
* base that had done very little work.
|
|
687
|
-
*
|
|
688
|
-
* A SWEEP AND NOT A BUCKET TTL, deliberately. `ttl` on `views.kv()` is a
|
|
689
|
-
* BUCKET limit — the backing stream's `max_age` — so it expires every key
|
|
690
|
-
* by age, not the terminal ones by status: a pending job nobody claimed for
|
|
691
|
-
* a day, a running job whose worker went quiet, both evaporate alongside
|
|
692
|
-
* the finished ones. That is not a slower version of this bug, it is a
|
|
693
|
-
* worse one. This bucket is the AUTHORITATIVE record: with the entry gone
|
|
694
|
-
* `read` returns null, every CAS falls through to its `onMissing`, and the
|
|
695
|
-
* next delivery for that job is `term()`ed as unknown — the job disappears
|
|
696
|
-
* with no failure and no trace. The rule retention needs to express is
|
|
697
|
-
* about STATUS, and status is exactly what a bucket-wide clock cannot see.
|
|
698
|
-
* (A per-message TTL set at the terminal write would express it, but that
|
|
699
|
-
* is nats-server 2.11 plus a client that exposes it; nats.js 2.x does not.)
|
|
700
|
-
*
|
|
701
|
-
* Terminal is absorbing — no transition leaves `complete`/`failed`/
|
|
702
|
-
* `cancelled` — so a record this sweep reads as expired cannot come back to
|
|
703
|
-
* life under it, and the lease was settled at the transition that made it
|
|
704
|
-
* terminal.
|
|
705
|
-
*/
|
|
706
|
-
pruneTerminalJobs(retentionMs: number): Promise<number>;
|
|
707
|
-
/** The heartbeat body — extend live leases, settle concluded ones. */
|
|
708
|
-
private reconcileHeld;
|
|
709
|
-
/**
|
|
710
|
-
* Materialize the KV key list BEFORE touching any entry: interleaving
|
|
711
|
-
* `get`/`update` with an open `keys()` iterator on the same connection
|
|
712
|
-
* makes the iterator drop entries (measured: the second pending job
|
|
713
|
-
* vanished from every sweep until this was split into two passes).
|
|
714
|
-
*/
|
|
715
|
-
private allKeys;
|
|
716
|
-
/** Conclude this process's lease on a job, if it holds one. */
|
|
717
|
-
private settleLease;
|
|
718
|
-
}
|
|
719
227
|
|
|
720
228
|
/**
|
|
721
229
|
* Citation-token resolver (INLINE-CITATIONS P1).
|
|
@@ -732,7 +240,7 @@ declare class JetStreamJobQueue implements JobQueue {
|
|
|
732
240
|
|
|
733
241
|
interface GenerationCitation {
|
|
734
242
|
/** The cited source resource (validated present in the embedded context). */
|
|
735
|
-
resourceId:
|
|
243
|
+
resourceId: ResourceId;
|
|
736
244
|
/** The contributing annotation, when the cited excerpt was annotation-derived. */
|
|
737
245
|
annotationId?: string;
|
|
738
246
|
/** Claim span in the FINAL (token-stripped) content; substring(start, end) === exact. */
|
|
@@ -741,6 +249,17 @@ interface GenerationCitation {
|
|
|
741
249
|
exact: string;
|
|
742
250
|
}
|
|
743
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Job Processors
|
|
254
|
+
*
|
|
255
|
+
* Pure functions that take content + inference client + params,
|
|
256
|
+
* report progress via callback, and return annotations + results.
|
|
257
|
+
*
|
|
258
|
+
* No EventBus, no queue, no side effects except calling inference.
|
|
259
|
+
* Driven by the remote worker process (worker-process.ts), which claims
|
|
260
|
+
* jobs over SSE and dispatches by jobType to these functions.
|
|
261
|
+
*/
|
|
262
|
+
|
|
744
263
|
/** A detected span — offsets into the extracted `.text`, plus optional context. */
|
|
745
264
|
type SpanMatch = {
|
|
746
265
|
exact: string;
|
|
@@ -860,7 +379,7 @@ declare function processGenerationJob(inferenceClient: InferenceClient, params:
|
|
|
860
379
|
title: string;
|
|
861
380
|
format: SupportedMediaType;
|
|
862
381
|
citations: GenerationCitation[];
|
|
863
|
-
|
|
382
|
+
truncated: boolean;
|
|
864
383
|
}>;
|
|
865
384
|
|
|
866
385
|
/**
|
|
@@ -1059,6 +578,186 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
|
|
|
1059
578
|
truncated: boolean;
|
|
1060
579
|
}>;
|
|
1061
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Job Claim Adapter — worker-side job lifecycle glue on top of a
|
|
583
|
+
* shared bus.
|
|
584
|
+
*
|
|
585
|
+
* Replaces the old `WorkerStateUnit`, which owned its own actor and
|
|
586
|
+
* duplicated the SSE connection that `SemiontClient` already held.
|
|
587
|
+
* Workers construct a `SemiontSession` normally (one actor, one
|
|
588
|
+
* SSE connection) and use this adapter to attach job-claim behaviour
|
|
589
|
+
* on top of the session's bus. It does **not** own the bus, has no HTTP
|
|
590
|
+
* concerns, and has no modal state.
|
|
591
|
+
*
|
|
592
|
+
* THE MODEL — a worker asks the queue at every moment it becomes idle,
|
|
593
|
+
* and never otherwise. `job:claim` carries this worker's types; the
|
|
594
|
+
* dispatcher answers with the next pending job of those types, atomically
|
|
595
|
+
* claimed, or declines. Queue state is the truth; no message carries
|
|
596
|
+
* correctness. The idle moments:
|
|
597
|
+
*
|
|
598
|
+
* - start, once the transport is open;
|
|
599
|
+
* - settle — `completeJob` / `failJob` — immediately, with no timer;
|
|
600
|
+
* - a matching `job:queued` while parked;
|
|
601
|
+
* - reconnect — every `state$` edge into `open` after the first.
|
|
602
|
+
*
|
|
603
|
+
* `job:queued` is a WAKE-UP with no memory. While parked it triggers a
|
|
604
|
+
* pull; while a claim is in flight it sets a dirty bit that earns exactly
|
|
605
|
+
* one more pull before parking, so a wake-up cannot be lost in that window;
|
|
606
|
+
* while a job is held it is ignored, because the settle pulls. One claim
|
|
607
|
+
* in flight at a time — under one-job-at-a-time processing "drain" is one
|
|
608
|
+
* claim per idle moment and one at settle, never a loop of claims while a
|
|
609
|
+
* job is held. The type filter on the wake-up is a PRE-FILTER on
|
|
610
|
+
* information the announcement already carries, so a worker that cannot
|
|
611
|
+
* run the announced type does not spend a round trip to be declined; the
|
|
612
|
+
* claim's `types` argument is the rule.
|
|
613
|
+
*
|
|
614
|
+
* What the dispatcher's decline codes mean here (`BusRequestError.code`,
|
|
615
|
+
* promoted from the reply's `CommandError.code`): `bus.none-pending` is
|
|
616
|
+
* the quiet park — nothing to do until a wake-up, nothing emitted. Every
|
|
617
|
+
* other refusal goes out on `refused$` for the runtime to judge;
|
|
618
|
+
* `bus.unauthorized` is the one it exits on, because a credential that
|
|
619
|
+
* can never claim has nothing to do here and the launcher's preflight
|
|
620
|
+
* names the repair.
|
|
621
|
+
*
|
|
622
|
+
* The queue drivers' 30 s re-announce tick survives as INSURANCE against a
|
|
623
|
+
* lost wake-up on an idle worker — never as dispatch. A healthy stack never
|
|
624
|
+
* sees it act.
|
|
625
|
+
*
|
|
626
|
+
* The `bus` parameter is typed against the small `BusRequestPrimitive`
|
|
627
|
+
* interface (from `@semiont/core`) so the adapter is transport-neutral.
|
|
628
|
+
* HTTP workers pass `(session.client.transport as HttpTransport).actor`;
|
|
629
|
+
* an in-process worker could pass a shim wrapping `client.bus`.
|
|
630
|
+
*/
|
|
631
|
+
|
|
632
|
+
/** The job a `job:claimed` reply carries, running under this worker: the spec's `JobRunning`. */
|
|
633
|
+
type ClaimedJob = EventMap['job:claimed']['response'];
|
|
634
|
+
/**
|
|
635
|
+
* The claimed job as the worker holds it: the fields of the claimed record the
|
|
636
|
+
* worker reads, each with the type the spec gives it there.
|
|
637
|
+
*/
|
|
638
|
+
interface ActiveJob {
|
|
639
|
+
jobId: JobId;
|
|
640
|
+
type: JobType$1;
|
|
641
|
+
resourceId: ResourceId;
|
|
642
|
+
params: ClaimedJob['params'];
|
|
643
|
+
/**
|
|
644
|
+
* Entity-type units earlier failed attempts fully emitted
|
|
645
|
+
* (ABANDONED-INFERENCE P2 checkpointed resume) — carried on the claimed
|
|
646
|
+
* record's metadata by `failJob`. The worker skips them, so a retry
|
|
647
|
+
* neither redoes nor duplicates completed work. Empty on first attempts.
|
|
648
|
+
*/
|
|
649
|
+
completedUnits: string[];
|
|
650
|
+
/**
|
|
651
|
+
* How far each UNFINISHED unit got on an earlier attempt
|
|
652
|
+
* (CHUNK-GRAIN-RESUME P2) — the grain `completedUnits` cannot express, and
|
|
653
|
+
* the only checkpoint a one-unit job can produce before it finishes. Empty
|
|
654
|
+
* on first attempts, and never overlapping `completedUnits`: a unit is
|
|
655
|
+
* either finished or partway, never both.
|
|
656
|
+
*/
|
|
657
|
+
unitCursors: Record<string, UnitCursor>;
|
|
658
|
+
/**
|
|
659
|
+
* The claimed record's retry budget, carried so the worker can report
|
|
660
|
+
* `willRetry` on `job:fail` (JOB-RESTART-SAFETY P5). It is the same budget
|
|
661
|
+
* the queue re-reads at `failJob`, and only `failJob` changes it, so the
|
|
662
|
+
* two evaluations of `willRetryAfter` agree.
|
|
663
|
+
*/
|
|
664
|
+
retryCount: number;
|
|
665
|
+
maxRetries: number;
|
|
666
|
+
}
|
|
667
|
+
interface JobClaimAdapterOptions {
|
|
668
|
+
/** Shared bus (typically the session's HTTP actor or an in-process bus shim). */
|
|
669
|
+
bus: BusRequestPrimitive;
|
|
670
|
+
/**
|
|
671
|
+
* Job types this worker can process — the claim's `types` argument, and
|
|
672
|
+
* the pre-filter on `job:queued` wake-ups. Empty array = accept any.
|
|
673
|
+
*/
|
|
674
|
+
jobTypes: string[];
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* A claim the dispatcher refused for a reason other than "nothing pending".
|
|
678
|
+
*
|
|
679
|
+
* `code` is the `BusRequestError` code the reply was promoted to, or `null`
|
|
680
|
+
* when the failure was local — a thrown non-bus error — never a manufactured
|
|
681
|
+
* bus code. `bus.none-pending` never appears here: it is the quiet park, and
|
|
682
|
+
* emitting it would make an empty queue look like a fault.
|
|
683
|
+
*/
|
|
684
|
+
interface ClaimRefusal {
|
|
685
|
+
code: BusRequestErrorCode | null;
|
|
686
|
+
message: string;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Point-in-time liveness snapshot (WORKER-LIVENESS.md P1). The adapter
|
|
690
|
+
* is the only component that sees every wake-up, claim, and finish,
|
|
691
|
+
* so its snapshot is what `/health` reports and the stall watchdog reads.
|
|
692
|
+
*
|
|
693
|
+
* `lastQueuedEventAt` is any `job:queued` received, matching or not. It
|
|
694
|
+
* proves the transport delivered a broadcast, but on an idle stack with an
|
|
695
|
+
* empty queue it stands still by design — no announcement is owed — so a
|
|
696
|
+
* still stamp alone is not a transport fault; transport liveness proper is
|
|
697
|
+
* the actor's SSE heartbeat. `lastActivityAt` (claim, progress emission, or
|
|
698
|
+
* finish) is processing liveness; a job stuck mid-inference stops advancing
|
|
699
|
+
* it, and that is what the watchdog reads.
|
|
700
|
+
*/
|
|
701
|
+
interface WorkerVitals {
|
|
702
|
+
lastQueuedEventAt: string | null;
|
|
703
|
+
lastClaimAt: string | null;
|
|
704
|
+
/** Last completion or failure — a failing-but-moving worker is alive. */
|
|
705
|
+
lastFinishedAt: string | null;
|
|
706
|
+
lastActivityAt: string | null;
|
|
707
|
+
activeJob: {
|
|
708
|
+
jobId: string;
|
|
709
|
+
type: string;
|
|
710
|
+
since: string;
|
|
711
|
+
} | null;
|
|
712
|
+
jobsCompleted: number;
|
|
713
|
+
}
|
|
714
|
+
interface JobClaimAdapter {
|
|
715
|
+
/** Currently-claimed job, or null when idle. */
|
|
716
|
+
readonly activeJob$: Observable<ActiveJob | null>;
|
|
717
|
+
/** True while a claim is in flight or a job is being processed. */
|
|
718
|
+
readonly isProcessing$: Observable<boolean>;
|
|
719
|
+
/** Monotonically-incrementing count of successfully-completed jobs. */
|
|
720
|
+
readonly jobsCompleted$: Observable<number>;
|
|
721
|
+
/** Stream of job failures reported through `failJob`. */
|
|
722
|
+
readonly errors$: Observable<{
|
|
723
|
+
jobId: string;
|
|
724
|
+
error: string;
|
|
725
|
+
}>;
|
|
726
|
+
/**
|
|
727
|
+
* Claims the dispatcher refused for a reason the runtime must judge.
|
|
728
|
+
* `bus.unauthorized` means this credential can never claim; the runtime
|
|
729
|
+
* exits on it. Anything else is logged and the worker stays parked until
|
|
730
|
+
* the next wake-up. See `ClaimRefusal`.
|
|
731
|
+
*/
|
|
732
|
+
readonly refused$: Observable<ClaimRefusal>;
|
|
733
|
+
/**
|
|
734
|
+
* Begin pulling: one claim now (once the transport is open), then on every
|
|
735
|
+
* settle, matching wake-up, and reconnect. Idempotent — calling `start()`
|
|
736
|
+
* twice is a no-op.
|
|
737
|
+
*/
|
|
738
|
+
start(): void;
|
|
739
|
+
/** Stop pulling. Does not cancel an in-flight job. */
|
|
740
|
+
stop(): void;
|
|
741
|
+
/** Signal successful completion of `activeJob$`. Pulls the next job. */
|
|
742
|
+
completeJob(): void;
|
|
743
|
+
/** Signal failure of `activeJob$`. Emits on `errors$`, then pulls the next job. */
|
|
744
|
+
failJob(jobId: string, error: string): void;
|
|
745
|
+
/** Liveness snapshot for `/health` and the stall watchdog. */
|
|
746
|
+
vitals(): WorkerVitals;
|
|
747
|
+
/**
|
|
748
|
+
* Record processing activity. The worker process calls this on every
|
|
749
|
+
* progress emission so a long multi-call job keeps proving liveness
|
|
750
|
+
* between inference calls.
|
|
751
|
+
*/
|
|
752
|
+
touchActivity(): void;
|
|
753
|
+
/** Release observables. Does not dispose the shared bus. */
|
|
754
|
+
dispose(): void;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Attach job-claim behaviour to a shared bus.
|
|
758
|
+
*/
|
|
759
|
+
declare function createJobClaimAdapter(options: JobClaimAdapterOptions): JobClaimAdapter;
|
|
760
|
+
|
|
1062
761
|
/**
|
|
1063
762
|
* Worker Runtime — the importable half of the worker host.
|
|
1064
763
|
*
|
|
@@ -1099,9 +798,9 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
|
|
|
1099
798
|
* wedged). The unbounded emit that caused that specific hang is now bounded
|
|
1100
799
|
* at the transport (`EMIT_TIMEOUT_MS`), so the loop errors instead of
|
|
1101
800
|
* blocking; but for any future blocked-loop bug the ONLY backstop is the
|
|
1102
|
-
* out-of-process one — the
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
801
|
+
* out-of-process one — the dispatcher's sweep, which fails a running job
|
|
802
|
+
* whose worker has gone silent (docs/protocol/JOBS.md). A liveness guarantee a
|
|
803
|
+
* blocked loop defeats is not one; the sweep is the guarantee.
|
|
1105
804
|
*/
|
|
1106
805
|
declare const STALL_THRESHOLD_MS: number;
|
|
1107
806
|
/**
|
|
@@ -1125,5 +824,5 @@ declare const WORKER_CONSUMED_BROADCASTS: readonly ["job:queued", "job:cancel-re
|
|
|
1125
824
|
*/
|
|
1126
825
|
declare const WORKER_CHANNELS: readonly (keyof EventMap)[];
|
|
1127
826
|
|
|
1128
|
-
export { AnnotationDetection,
|
|
1129
|
-
export type { AnyJob, AssessmentDetectionJob, AssessmentDetectionParams,
|
|
827
|
+
export { AnnotationDetection, STALL_THRESHOLD_MS, WORKER_CHANNELS, WORKER_CONSUMED_BROADCASTS, createJobClaimAdapter, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
|
|
828
|
+
export type { ActiveJob, AnyJob, AssessmentDetectionJob, AssessmentDetectionParams, CancelledJob, ClaimRefusal, CommentDetectionJob, CommentDetectionParams, CompleteJob, DetectionJob, DetectionParams, FailedJob, GenerationJob, HighlightDetectionJob, HighlightDetectionParams, JobClaimAdapter, JobClaimAdapterOptions, JobMetadata, JobStatus, JobType, OnProgress, PendingJob, ProcessorResult, RunningAnyJob, RunningJob, StoredProgress, TagDetectionJob, TagDetectionParams, WorkerVitals };
|