@semiont/jobs 0.6.8 → 0.6.10

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { JobId, components, UserId, UnitCursor, ResourceId, EntityType, JobReferenceAnnotationResult, GenerationJobParams, JobHighlightAnnotationResult, JobAssessmentAnnotationResult, JobCommentAnnotationResult, TagSchema, JobTagAnnotationResult, Annotation, Logger, SupportedMediaType, GatheredContext, BusRequestErrorCode, BusRequestPrimitive, EventMap } from '@semiont/core';
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';
2
2
  import { InferenceClient } from '@semiont/inference';
3
3
  import { Observable } from 'rxjs';
4
4
 
@@ -35,25 +35,24 @@ interface JobMetadata {
35
35
  retryCount: number;
36
36
  maxRetries: number;
37
37
  /**
38
- * Checkpointed resume (ABANDONED-INFERENCE P2, HD1): the entity-type
39
- * units whose annotations were fully emitted by earlier failed
40
- * attempts. Written only by `failJob`, unioned across attempts — and
41
- * because `failJob` rebuilds the retried record by spreading metadata,
42
- * the checkpoint survives every subsequent rebuild for free. A retried
43
- * claim skips these units, so completed work is neither redone nor
44
- * duplicated.
38
+ * Checkpointed resume: the entity-type units whose annotations are fully
39
+ * committed. Written by the dispatcher's queue from `job:checkpoint` (as
40
+ * each unit lands) and from `job:fail`, unioned across attempts — and
41
+ * because a retried record keeps its metadata, the checkpoint survives
42
+ * every retry. A retried claim skips these units, so completed work is
43
+ * neither redone nor duplicated.
45
44
  */
46
45
  completedUnits?: string[];
47
46
  /**
48
- * The finer grain `completedUnits` cannot express (CHUNK-GRAIN-RESUME P2):
49
- * how far each UNFINISHED unit got, keyed by unit. Written per committed
50
- * chunk, so a job that dies mid-unit resumes there rather than at the top —
51
- * which for a one-unit job (every motivation job, and the 1958 document's
52
- * single `Person` type) is the difference between resuming and restarting.
47
+ * The finer grain `completedUnits` cannot express: how far each UNFINISHED
48
+ * unit got, keyed by unit. Written per committed chunk, so a job that dies
49
+ * mid-unit resumes there rather than at the top — which for a one-unit job
50
+ * (every motivation job, and the 1958 document's single `Person` type) is
51
+ * the difference between resuming and restarting.
53
52
  *
54
53
  * A unit here is in progress, never complete; the two sets are disjoint by
55
- * construction in `checkpointUnits`. Merged monotonically per unit, never
56
- * unioned — see `mergeUnitCursors`.
54
+ * construction — the dispatcher's merge drops a completed unit's cursor.
55
+ * Merged monotonically per unit (the furthest cursor wins), never unioned.
57
56
  */
58
57
  unitCursors?: Record<string, UnitCursor>;
59
58
  }
@@ -142,16 +141,6 @@ type TagDetectionParams = {
142
141
  /** Source-resource locale — see locale conventions above. */
143
142
  sourceLanguage?: string;
144
143
  };
145
- /**
146
- * Generation job result
147
- */
148
- interface GenerationResult {
149
- kind: 'generation';
150
- resourceId: ResourceId;
151
- resourceName: string;
152
- /** True when the model stopped at the maxTokens ceiling — the artifact is cut off, not complete (GENERATE-FROM-RESOURCE D6). */
153
- truncated: boolean;
154
- }
155
144
  /**
156
145
  * The progress a running job carries: the last its worker reported with
157
146
  * `job:report-progress`, or an empty object before the first report. One shape
@@ -216,7 +205,7 @@ type DetectionJob = Job<DetectionParams, JobReferenceAnnotationResult>;
216
205
  /** A generation job's params carry the resource the dispatcher derived from the context's focus. */
217
206
  type GenerationJob = Job<GenerationJobParams & {
218
207
  resourceId: ResourceId;
219
- }, GenerationResult>;
208
+ }, components['schemas']['JobGenerationResult']>;
220
209
  type HighlightDetectionJob = Job<HighlightDetectionParams, JobHighlightAnnotationResult>;
221
210
  type AssessmentDetectionJob = Job<AssessmentDetectionParams, JobAssessmentAnnotationResult>;
222
211
  type CommentDetectionJob = Job<CommentDetectionParams, JobCommentAnnotationResult>;
@@ -236,21 +225,21 @@ declare function isFailedJob(job: AnyJob): job is FailedJob<any>;
236
225
  declare function isCancelledJob(job: AnyJob): job is CancelledJob<any>;
237
226
 
238
227
  /**
239
- * Citation-token resolver (INLINE-CITATIONS P1).
228
+ * Citation-token resolver.
240
229
  *
241
230
  * Under `cite`, the generation prompt asks the model to emit `[[<resourceId>]]` /
242
231
  * `[[<resourceId>/<annotationId>]]` transport tokens after each claim, citing ids
243
- * the context embedding put in front of it (CONTEXT-IDENTIFIERS). Tokens are
244
- * TRANSPORT, not content: this resolver parses them, validates each id against the
245
- * ids actually present in the embedded context (the hallucination guard — an
246
- * unknown id is dropped loudly, never silently linked), STRIPS them from the
247
- * content before upload, and returns claim-span citations the worker mints as W3C
248
- * linking annotations on the derived resource. See .plans/INLINE-CITATIONS.md.
232
+ * the context embedding put in front of it: every embedded excerpt carries a
233
+ * `[<resourceId>]` handle. Tokens are TRANSPORT, not content: this resolver parses
234
+ * them, validates each id against the ids actually present in the embedded context
235
+ * (the hallucination guard — an unknown id is dropped loudly, never silently
236
+ * linked), STRIPS them from the content before upload, and returns claim-span
237
+ * citations the worker mints as W3C linking annotations on the derived resource.
249
238
  */
250
239
 
251
240
  interface GenerationCitation {
252
241
  /** The cited source resource (validated present in the embedded context). */
253
- resourceId: string;
242
+ resourceId: ResourceId;
254
243
  /** The contributing annotation, when the cited excerpt was annotation-derived. */
255
244
  annotationId?: string;
256
245
  /** Claim span in the FINAL (token-stripped) content; substring(start, end) === exact. */
@@ -259,6 +248,17 @@ interface GenerationCitation {
259
248
  exact: string;
260
249
  }
261
250
 
251
+ /**
252
+ * Job Processors
253
+ *
254
+ * Pure functions that take content + inference client + params,
255
+ * report progress via callback, and return annotations + results.
256
+ *
257
+ * No EventBus, no queue, no side effects except calling inference.
258
+ * Driven by the remote worker process (worker-process.ts), which claims
259
+ * jobs over SSE and dispatches by jobType to these functions.
260
+ */
261
+
262
262
  /** A detected span — offsets into the extracted `.text`, plus optional context. */
263
263
  type SpanMatch = {
264
264
  exact: string;
@@ -285,13 +285,12 @@ type BuildAnnotation = (motivation: Motivation, match: SpanMatch, body?: Annotat
285
285
  * on EVERY call: the client's `progress$` replaces its value per event, so a
286
286
  * field sent once disappears on the next tick.
287
287
  *
288
- * `message` is a CODE plus typed params, never a prose sentence
289
- * (ASSIST-PROGRESS-CONSOLIDATION A6). The producer reports what happened;
290
- * each client renders it in the user's language — react-ui from its 29
291
- * locales, the Go launcher from its English map. The vocabulary is frozen
292
- * by the census of these call sites: adding a shape means adding a variant
293
- * to `JobProgressMessage.json` and copy in every client, not composing a
294
- * new sentence here.
288
+ * `message` is a CODE plus typed params, never a prose sentence. The producer
289
+ * reports what happened; each client renders it in the user's language —
290
+ * react-ui from its 29 locales, the Go launcher from its English map. The
291
+ * vocabulary is frozen by the census of these call sites: adding a shape
292
+ * means adding a variant to `JobProgressMessage.json` and copy in every
293
+ * client, not composing a new sentence here.
295
294
  */
296
295
  type OnProgress = (percentage: number, message: JobProgressMessage, extra?: Partial<JobProgress>) => void;
297
296
  type JobProgress = components['schemas']['JobProgress'];
@@ -308,8 +307,9 @@ interface ProcessorResult<R> {
308
307
  result: R;
309
308
  }
310
309
  /**
311
- * Where one unit stands once the chunk just handed over is durable
312
- * (CHUNK-GRAIN-RESUME P2).
310
+ * Where one unit stands once the chunk just handed over is durable: the
311
+ * cursor a retry resumes that unit from, instead of re-running the chunks it
312
+ * already committed.
313
313
  *
314
314
  * The unit is named HERE rather than in the detection layer, which knows about
315
315
  * chunks and nothing about jobs: for `reference-annotation` a unit is an entity
@@ -323,32 +323,32 @@ interface UnitCheckpoint {
323
323
  declare function processHighlightJob(content: string, inferenceClient: InferenceClient, params: HighlightDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
324
324
  /** This chunk's novel annotations, awaited: the durability write. */
325
325
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
326
- /** Where earlier attempts left each unit (CHUNK-GRAIN-RESUME P3), keyed the
327
- * same way the checkpoint is. A unit absent here starts at the top, which is
328
- * every unit of a first attempt. */
326
+ /** Where earlier attempts left each unit, keyed the same way the checkpoint
327
+ * is. A unit absent here starts at the top, which is every unit of a first
328
+ * attempt. */
329
329
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobHighlightAnnotationResult>>;
330
330
  declare function processCommentJob(content: string, inferenceClient: InferenceClient, params: CommentDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
331
331
  /** This chunk's novel annotations, awaited: the durability write. */
332
332
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
333
- /** Where earlier attempts left each unit (CHUNK-GRAIN-RESUME P3), keyed the
334
- * same way the checkpoint is. A unit absent here starts at the top, which is
335
- * every unit of a first attempt. */
333
+ /** Where earlier attempts left each unit, keyed the same way the checkpoint
334
+ * is. A unit absent here starts at the top, which is every unit of a first
335
+ * attempt. */
336
336
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobCommentAnnotationResult>>;
337
337
  declare function processAssessmentJob(content: string, inferenceClient: InferenceClient, params: AssessmentDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
338
338
  /** This chunk's novel annotations, awaited: the durability write. */
339
339
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
340
- /** Where earlier attempts left each unit (CHUNK-GRAIN-RESUME P3), keyed the
341
- * same way the checkpoint is. A unit absent here starts at the top, which is
342
- * every unit of a first attempt. */
340
+ /** Where earlier attempts left each unit, keyed the same way the checkpoint
341
+ * is. A unit absent here starts at the top, which is every unit of a first
342
+ * attempt. */
343
343
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobAssessmentAnnotationResult>>;
344
344
  /**
345
- * Reference detection commits per UNIT — one entity type — through
346
- * `onUnitComplete` (ABANDONED-INFERENCE P2, checkpointed resume): the
347
- * callback receives the unit's deduped annotations, and only after it
348
- * resolves does the unit count as complete. Emission belongs to the
349
- * callback alone; the processor returns only the result — returning the
350
- * annotations as well would recreate the post-run batch that N2 showed
351
- * discards completed work wholesale.
345
+ * Reference detection checkpoints per UNIT — one entity type — through
346
+ * `onUnitComplete`: only after the callback resolves does the unit count as
347
+ * complete, and a retried claim skips the units recorded that way.
348
+ * Annotations leave per chunk through `onChunkComplete`; the processor
349
+ * returns only the result — returning the annotations as well would make a
350
+ * post-run batch, where one failed call discards every completed unit's
351
+ * work wholesale.
352
352
  */
353
353
  declare function processReferenceJob(content: string, inferenceClient: InferenceClient, params: DetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress, logger: Logger,
354
354
  /**
@@ -360,25 +360,25 @@ declare function processReferenceJob(content: string, inferenceClient: Inference
360
360
  onUnitComplete: (entityType: string) => Promise<void>, signal?: AbortSignal,
361
361
  /** This chunk's novel annotations, awaited: the durability write. */
362
362
  onChunkComplete?: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
363
- /** Where earlier attempts left each entity-type unit (CHUNK-GRAIN-RESUME P3).
364
- * A unit absent here starts at the top; units already COMPLETE never reach
365
- * this function at all, the caller having filtered them out. */
363
+ /** Where earlier attempts left each entity-type unit. A unit absent here
364
+ * starts at the top; units already COMPLETE never reach this function at
365
+ * all, the caller having filtered them out. */
366
366
  resumeCursors?: Record<string, UnitCursor>): Promise<{
367
367
  result: JobReferenceAnnotationResult;
368
368
  }>;
369
369
  declare function processTagJob(content: string, inferenceClient: InferenceClient, params: TagDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
370
370
  /** This chunk's novel annotations, awaited: the durability write. */
371
371
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
372
- /** Where earlier attempts left each CATEGORY (CHUNK-GRAIN-RESUME P3) — a tag
373
- * job's units are its categories, not its motivation: each walks the whole
374
- * document, so one shared cursor would skip text for all but one of them. */
372
+ /** Where earlier attempts left each CATEGORY — a tag job's units are its
373
+ * categories, not its motivation: each walks the whole document, so one
374
+ * shared cursor would skip text for all but one of them. */
375
375
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobTagAnnotationResult>>;
376
376
  declare function processGenerationJob(inferenceClient: InferenceClient, params: GenerationJobParams, onProgress: OnProgress, logger: Logger): Promise<{
377
377
  content: Uint8Array;
378
378
  title: string;
379
379
  format: SupportedMediaType;
380
380
  citations: GenerationCitation[];
381
- result: GenerationResult;
381
+ truncated: boolean;
382
382
  }>;
383
383
 
384
384
  /**
@@ -395,22 +395,17 @@ declare function processGenerationJob(inferenceClient: InferenceClient, params:
395
395
  * `num_ctx`. What remains after the prompt scaffold is split
396
396
  * input:output = 1:2 (annotation JSON echoes each span plus a fixed
397
397
  * key/context envelope, so output needs the larger share). The 1:2 ratio
398
- * is the plan's one allocation policy — doc-independent, tuned only on
399
- * live evidence.
398
+ * is the one allocation policy — doc-independent, tuned only on live
399
+ * evidence.
400
400
  * - Separate ceilings (Anthropic): output takes its full ceiling (duration-
401
401
  * capped below), and input follows the same 1:2 allocation — never more
402
402
  * than half the output budget. Input does NOT get "the rest of the
403
- * window": measured 2026-09-02, a window-sized chunk of entity-dense text
404
- * demands more output than any budget holds, so the model grinds toward
405
- * max_tokens for minutes (killed at the call bound as a "stall") or
406
- * collapses to the degenerate []. Large documents chunk; that is the fix,
407
- * not a cost.
403
+ * window": a window-sized chunk of entity-dense text demands more output
404
+ * than any budget holds, so the model grinds toward max_tokens for minutes
405
+ * (killed at the call bound as a "stall") or collapses to the degenerate
406
+ * []. Large documents chunk; that is the fix, not a cost.
408
407
  */
409
408
 
410
- /** One chunk handed out by `runAdaptiveChunks`, with the cursor either side of
411
- * it. `at`/`next` over `totalChars` is exact progress — and, once
412
- * CHUNK-GRAIN-RESUME lands, the checkpoint identity a variable boundary forces
413
- * (an ordinal cannot name a chunk whose size is decided while the job runs). */
414
409
  /**
415
410
  * The half of a `UnitCursor` the DETECTION layer can honestly report: where the
416
411
  * walk stands and how it is cutting. The tallies belong to the processor, which
@@ -422,9 +417,9 @@ type ChunkCursor = Pick<UnitCursor, 'next' | 'size'>;
422
417
  /**
423
418
  * Response parsers for annotation detection motivations
424
419
  *
425
- * Provides static methods to parse and validate AI responses for each motivation type.
426
- * Includes offset validation and correction logic.
427
- * Extracted from worker implementations to centralize parsing logic.
420
+ * Static methods that validate the model's already-parsed elements for each
421
+ * motivation type and anchor each span in the document: the model emits no
422
+ * offsets, `reconcileSelector` computes them.
428
423
  *
429
424
  * NOTE: These are static utility methods without logger access.
430
425
  * Console statements kept for debugging - consider adding logger parameter in future.
@@ -496,8 +491,7 @@ declare class AnnotationDetection {
496
491
  * full discussion.
497
492
  */
498
493
  static detectComments(content: string, client: InferenceClient, instructions?: string, tone?: string, density?: number, language?: string, sourceLanguage?: string, onActivity?: (consumedChars: number, totalChars: number) => void,
499
- /** This chunk's matches, as the chunk completes. */
500
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
494
+ /** Where an earlier attempt left this unit. */
501
495
  resume?: UnitCursor,
502
496
  /** This chunk's matches, as the chunk completes. Kept LAST. */
503
497
  onChunkResults?: (matches: CommentMatch[], cursor: ChunkCursor) => Promise<void>): Promise<CommentMatch[]>;
@@ -509,8 +503,7 @@ declare class AnnotationDetection {
509
503
  * correctly.
510
504
  */
511
505
  static detectHighlights(content: string, client: InferenceClient, instructions?: string, density?: number, sourceLanguage?: string, onActivity?: (consumedChars: number, totalChars: number) => void,
512
- /** This chunk's matches, as the chunk completes. */
513
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
506
+ /** Where an earlier attempt left this unit. */
514
507
  resume?: UnitCursor,
515
508
  /** This chunk's matches, as the chunk completes. Kept LAST. */
516
509
  onChunkResults?: (matches: HighlightMatch[], cursor: ChunkCursor) => Promise<void>): Promise<HighlightMatch[]>;
@@ -522,8 +515,7 @@ declare class AnnotationDetection {
522
515
  * being analyzed (source-resource locale).
523
516
  */
524
517
  static detectAssessments(content: string, client: InferenceClient, instructions?: string, tone?: string, density?: number, language?: string, sourceLanguage?: string, onActivity?: (consumedChars: number, totalChars: number) => void,
525
- /** This chunk's matches, as the chunk completes. */
526
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
518
+ /** Where an earlier attempt left this unit. */
527
519
  resume?: UnitCursor,
528
520
  /** This chunk's matches, as the chunk completes. Kept LAST. */
529
521
  onChunkResults?: (matches: AssessmentMatch[], cursor: ChunkCursor) => Promise<void>): Promise<AssessmentMatch[]>;
@@ -540,21 +532,22 @@ declare class AnnotationDetection {
540
532
  * site, not here.
541
533
  */
542
534
  static detectTags(content: string, client: InferenceClient, schema: TagSchema, category: string, sourceLanguage?: string, onActivity?: (consumedChars: number, totalChars: number) => void,
535
+ /** Where an earlier attempt left this unit. */
536
+ resume?: UnitCursor,
543
537
  /**
544
- * This chunk's matches, ANCHORED before they leave: `parse` here yields
545
- * raw tags, so this path runs `validateTagOffsets` per chunk — a per-item
546
- * anchor against the full document, so partitioning changes nothing.
538
+ * This chunk's matches, as the chunk completes, ANCHORED before they
539
+ * leave: `parse` here yields raw tags, so this path runs
540
+ * `validateTagOffsets` per chunk — a per-item anchor against the full
541
+ * document, so partitioning changes nothing. Kept LAST.
547
542
  */
548
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
549
- resume?: UnitCursor,
550
- /** This chunk's matches, as the chunk completes. Kept LAST. */
551
543
  onChunkResults?: (matches: TagMatch[], cursor: ChunkCursor) => Promise<void>): Promise<TagMatch[]>;
552
544
  }
553
545
 
554
546
  /**
555
547
  * Resource Generation
556
548
  *
557
- * Generates markdown resources from topics using AI inference.
549
+ * Generates resource content from a topic using AI inference: markdown, plain
550
+ * text, or Typst source (which the worker compiles to PDF), by `outputMediaType`.
558
551
  */
559
552
 
560
553
  /**
@@ -581,12 +574,11 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
581
574
  * Job Claim Adapter — worker-side job lifecycle glue on top of a
582
575
  * shared bus.
583
576
  *
584
- * Replaces the old `WorkerStateUnit`, which owned its own actor and
585
- * duplicated the SSE connection that `SemiontClient` already held.
586
577
  * Workers construct a `SemiontSession` normally (one actor, one
587
578
  * SSE connection) and use this adapter to attach job-claim behaviour
588
- * on top of the session's bus. It does **not** own the bus, has no HTTP
589
- * concerns, and has no modal state.
579
+ * on top of the session's bus, so the worker holds no second connection.
580
+ * It does **not** own the bus, has no HTTP concerns, and has no modal
581
+ * state.
590
582
  *
591
583
  * THE MODEL — a worker asks the queue at every moment it becomes idle,
592
584
  * and never otherwise. `job:claim` carries this worker's types; the
@@ -618,9 +610,9 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
618
610
  * can never claim has nothing to do here and the launcher's preflight
619
611
  * names the repair.
620
612
  *
621
- * The queue drivers' 30 s re-announce tick survives as INSURANCE against a
622
- * lost wake-up on an idle worker — never as dispatch. A healthy stack never
623
- * sees it act.
613
+ * The queue drivers' 30 s re-announce tick is INSURANCE against a lost
614
+ * wake-up on an idle worker — never dispatch. A healthy stack never sees it
615
+ * act.
624
616
  *
625
617
  * The `bus` parameter is typed against the small `BusRequestPrimitive`
626
618
  * interface (from `@semiont/core`) so the adapter is transport-neutral.
@@ -628,32 +620,39 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
628
620
  * an in-process worker could pass a shim wrapping `client.bus`.
629
621
  */
630
622
 
623
+ /** The job a `job:claimed` reply carries, running under this worker: the spec's `JobRunning`. */
624
+ type ClaimedJob = EventMap['job:claimed']['response'];
625
+ /**
626
+ * The claimed job as the worker holds it: the fields of the claimed record the
627
+ * worker reads, each with the type the spec gives it there.
628
+ */
631
629
  interface ActiveJob {
632
- jobId: string;
633
- type: string;
634
- resourceId: string;
635
- userId: string;
636
- params: Record<string, unknown>;
630
+ jobId: JobId;
631
+ type: JobType$1;
632
+ resourceId: ResourceId;
633
+ params: ClaimedJob['params'];
637
634
  /**
638
- * Entity-type units earlier failed attempts fully emitted
639
- * (ABANDONED-INFERENCE P2 checkpointed resume) — carried on the claimed
640
- * record's metadata by `failJob`. The worker skips them, so a retry
641
- * neither redoes nor duplicates completed work. Empty on first attempts.
635
+ * Entity-type units earlier attempts fully committed — the checkpoint a
636
+ * resume reads, carried on the claimed record's metadata, where the queue
637
+ * writes it from `job:checkpoint` and `job:fail`. The worker skips them, so
638
+ * a retry neither redoes nor duplicates completed work. Empty on first
639
+ * attempts.
642
640
  */
643
641
  completedUnits: string[];
644
642
  /**
645
- * How far each UNFINISHED unit got on an earlier attempt
646
- * (CHUNK-GRAIN-RESUME P2) — the grain `completedUnits` cannot express, and
647
- * the only checkpoint a one-unit job can produce before it finishes. Empty
648
- * on first attempts, and never overlapping `completedUnits`: a unit is
649
- * either finished or partway, never both.
643
+ * How far each UNFINISHED unit got on an earlier attempt: the offset where
644
+ * its last committed chunk ended and the size that chunk was cut at — the
645
+ * grain `completedUnits` cannot express, and the only checkpoint a one-unit
646
+ * job can produce before it finishes. Empty on first attempts, and never
647
+ * overlapping `completedUnits`: a unit is either finished or partway,
648
+ * never both.
650
649
  */
651
650
  unitCursors: Record<string, UnitCursor>;
652
651
  /**
653
652
  * The claimed record's retry budget, carried so the worker can report
654
- * `willRetry` on `job:fail` (JOB-RESTART-SAFETY P5). It is the same budget
655
- * the queue re-reads at `failJob`, and only `failJob` changes it, so the
656
- * two evaluations of `willRetryAfter` agree.
653
+ * `willRetry` on `job:fail`. It is the same budget the queue re-reads at
654
+ * `failJob`, and only `failJob` changes it, so the two evaluations of
655
+ * `willRetryAfter` agree.
657
656
  */
658
657
  retryCount: number;
659
658
  maxRetries: number;
@@ -671,18 +670,18 @@ interface JobClaimAdapterOptions {
671
670
  * A claim the dispatcher refused for a reason other than "nothing pending".
672
671
  *
673
672
  * `code` is the `BusRequestError` code the reply was promoted to, or `null`
674
- * when the failure was local — an unusable record, a thrown non-bus error —
675
- * never a manufactured bus code. `bus.none-pending` never appears here: it is
676
- * the quiet park, and emitting it would make an empty queue look like a fault.
673
+ * when the failure was local — a thrown non-bus error — never a manufactured
674
+ * bus code. `bus.none-pending` never appears here: it is the quiet park, and
675
+ * emitting it would make an empty queue look like a fault.
677
676
  */
678
677
  interface ClaimRefusal {
679
678
  code: BusRequestErrorCode | null;
680
679
  message: string;
681
680
  }
682
681
  /**
683
- * Point-in-time liveness snapshot (WORKER-LIVENESS.md P1). The adapter
684
- * is the only component that sees every wake-up, claim, and finish,
685
- * so its snapshot is what `/health` reports and the stall watchdog reads.
682
+ * Point-in-time liveness snapshot. The adapter is the only component that
683
+ * sees every wake-up, claim, and finish, so its snapshot is what `/health`
684
+ * reports and the stall watchdog reads.
686
685
  *
687
686
  * `lastQueuedEventAt` is any `job:queued` received, matching or not. It
688
687
  * proves the transport delivered a broadcast, but on an idle stack with an
@@ -760,53 +759,47 @@ declare function createJobClaimAdapter(options: JobClaimAdapterOptions): JobClai
760
759
  * a unit test needs to reach lives here instead, fully parameterized — no
761
760
  * module-scope env reads, no side effects at import.
762
761
  *
763
- * The load-bearing contract this module owns (and the reason it was
764
- * extracted): a worker's stamped identity is the DID the
765
- * `/api/tokens/agent` exchange MINTED for it, carried verbatim — never
766
- * re-derived from the URL the worker happens to dial. One logical agent
767
- * previously got two DIDs that way (.plans/bugs/agent-did-host-skew.md).
762
+ * The load-bearing contract this module owns: a worker's stamped identity is
763
+ * the DID the `/api/tokens/agent` exchange MINTED for it, carried verbatim —
764
+ * never re-derived from the URL the worker happens to dial: re-deriving gives
765
+ * one logical agent two DIDs.
768
766
  */
769
767
 
770
768
  /**
771
- * Stall watchdog (WORKER-LIVENESS.md P3) — the fail-fast line behind the
772
- * inference timeout. There is no poll timer to heartbeat — the worker pulls
773
- * when idle, and a parked idle worker is not a stall; the honest stall
774
- * signal is *processing without activity*: an agent holding a claimed job
775
- * whose `lastActivityAt` (claim / progress / finish) has stopped advancing
776
- * is wedged — the adapter defers every wake-up while a job is held and
777
- * pulls at settle, so a wedged agent never settles and never recovers on
778
- * its own. Silent hang → loud crash → whatever
779
- * restart policy the deployment chose.
769
+ * Stall watchdog — the fail-fast line behind the inference timeout. There
770
+ * is no poll timer to heartbeat — the worker pulls when idle, and a parked
771
+ * idle worker is not a stall; the honest stall signal is *processing
772
+ * without activity*: an agent holding a claimed job whose `lastActivityAt`
773
+ * (claim / progress / finish) has stopped advancing is wedged — the adapter
774
+ * defers every wake-up while a job is held and pulls at settle, so a wedged
775
+ * agent never settles and never recovers on its own. Silent hang → loud
776
+ * crash → whatever restart policy the deployment chose.
780
777
  *
781
778
  * Thresholds are fixed by design (no env knobs) and deliberately
782
- * layered: inference timeout (10 min, P2) fires first; this watchdog
779
+ * layered: inference timeout (10 min) fires first; this watchdog
783
780
  * (15 min) catches wedges where the loop still turns but activity has
784
781
  * stopped; the gateway's dead-worker janitor (30 min) re-queues the job
785
782
  * regardless.
786
783
  *
787
784
  * The layering matters because this watchdog has a hard limit: it is an
788
785
  * IN-PROCESS timer, so it cannot fire while the event loop itself is
789
- * blocked — the exact condition a blocked loop creates
790
- * (JOB-RESTART-SAFETY P7, the 2026-09-03 finalization hang: 18 min silent,
791
- * this watchdog never fired, an empty /health confirming the loop was
792
- * wedged). The unbounded emit that caused that specific hang is now bounded
793
- * at the transport (`EMIT_TIMEOUT_MS`), so the loop errors instead of
794
- * blocking; but for any future blocked-loop bug the ONLY backstop is the
786
+ * blocked — the exact condition a blocked loop creates. An emit is bounded
787
+ * at the transport (`EMIT_TIMEOUT_MS`), so the loop errors on one instead of
788
+ * blocking; but for any other blocked-loop bug the ONLY backstop is the
795
789
  * out-of-process one — the dispatcher's sweep, which fails a running job
796
- * whose worker has gone silent (docs/protocol/JOBS.md). A liveness guarantee a
797
- * blocked loop defeats is not one; the sweep is the guarantee.
790
+ * whose worker has gone silent (docs/protocol/JOBS.md). A liveness guarantee
791
+ * a blocked loop defeats is not one; the sweep is the guarantee.
798
792
  */
799
793
  declare const STALL_THRESHOLD_MS: number;
800
794
  /**
801
795
  * The broadcasts a worker CONSUMES — announcements nobody replies to, which
802
796
  * therefore derive from no operation.
803
797
  *
804
- * One home, because the alternative was proven: these lived as `addChannels`
805
- * calls beside their consumers, so the worker's complete set existed nowhere
806
- * and a widening could be deleted without any list getting shorter. That is
807
- * the 2026-09-16 outage — every worker idle on a `job:queued` its transport
808
- * would never carry, `lastQueuedEventAt: null`, nothing thrown and nothing
809
- * logged.
798
+ * One home, because the alternative fails silently: as `addChannels` calls
799
+ * beside their consumers, the worker's complete set exists nowhere and a
800
+ * widening can be deleted without any list getting shorter — every worker
801
+ * then idles on a `job:queued` its transport never carries,
802
+ * `lastQueuedEventAt: null`, nothing thrown and nothing logged.
810
803
  *
811
804
  * An entry here must have a consumer and a consumer must have an entry; the
812
805
  * census beside this file asserts both directions.
@@ -819,4 +812,4 @@ declare const WORKER_CONSUMED_BROADCASTS: readonly ["job:queued", "job:cancel-re
819
812
  declare const WORKER_CHANNELS: readonly (keyof EventMap)[];
820
813
 
821
814
  export { AnnotationDetection, STALL_THRESHOLD_MS, WORKER_CHANNELS, WORKER_CONSUMED_BROADCASTS, createJobClaimAdapter, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
822
- export type { ActiveJob, AnyJob, AssessmentDetectionJob, AssessmentDetectionParams, CancelledJob, ClaimRefusal, CommentDetectionJob, CommentDetectionParams, CompleteJob, DetectionJob, DetectionParams, FailedJob, GenerationJob, GenerationResult, HighlightDetectionJob, HighlightDetectionParams, JobClaimAdapter, JobClaimAdapterOptions, JobMetadata, JobStatus, JobType, OnProgress, PendingJob, ProcessorResult, RunningAnyJob, RunningJob, StoredProgress, TagDetectionJob, TagDetectionParams, WorkerVitals };
815
+ 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 };