@semiont/jobs 0.6.9 → 0.6.11

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
@@ -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
  }
@@ -226,16 +225,16 @@ declare function isFailedJob(job: AnyJob): job is FailedJob<any>;
226
225
  declare function isCancelledJob(job: AnyJob): job is CancelledJob<any>;
227
226
 
228
227
  /**
229
- * Citation-token resolver (INLINE-CITATIONS P1).
228
+ * Citation-token resolver.
230
229
  *
231
230
  * Under `cite`, the generation prompt asks the model to emit `[[<resourceId>]]` /
232
231
  * `[[<resourceId>/<annotationId>]]` transport tokens after each claim, citing ids
233
- * the context embedding put in front of it (CONTEXT-IDENTIFIERS). Tokens are
234
- * TRANSPORT, not content: this resolver parses them, validates each id against the
235
- * ids actually present in the embedded context (the hallucination guard — an
236
- * unknown id is dropped loudly, never silently linked), STRIPS them from the
237
- * content before upload, and returns claim-span citations the worker mints as W3C
238
- * 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.
239
238
  */
240
239
 
241
240
  interface GenerationCitation {
@@ -286,13 +285,12 @@ type BuildAnnotation = (motivation: Motivation, match: SpanMatch, body?: Annotat
286
285
  * on EVERY call: the client's `progress$` replaces its value per event, so a
287
286
  * field sent once disappears on the next tick.
288
287
  *
289
- * `message` is a CODE plus typed params, never a prose sentence
290
- * (ASSIST-PROGRESS-CONSOLIDATION A6). The producer reports what happened;
291
- * each client renders it in the user's language — react-ui from its 29
292
- * locales, the Go launcher from its English map. The vocabulary is frozen
293
- * by the census of these call sites: adding a shape means adding a variant
294
- * to `JobProgressMessage.json` and copy in every client, not composing a
295
- * 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.
296
294
  */
297
295
  type OnProgress = (percentage: number, message: JobProgressMessage, extra?: Partial<JobProgress>) => void;
298
296
  type JobProgress = components['schemas']['JobProgress'];
@@ -309,8 +307,9 @@ interface ProcessorResult<R> {
309
307
  result: R;
310
308
  }
311
309
  /**
312
- * Where one unit stands once the chunk just handed over is durable
313
- * (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.
314
313
  *
315
314
  * The unit is named HERE rather than in the detection layer, which knows about
316
315
  * chunks and nothing about jobs: for `reference-annotation` a unit is an entity
@@ -324,32 +323,32 @@ interface UnitCheckpoint {
324
323
  declare function processHighlightJob(content: string, inferenceClient: InferenceClient, params: HighlightDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
325
324
  /** This chunk's novel annotations, awaited: the durability write. */
326
325
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
327
- /** Where earlier attempts left each unit (CHUNK-GRAIN-RESUME P3), keyed the
328
- * same way the checkpoint is. A unit absent here starts at the top, which is
329
- * 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. */
330
329
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobHighlightAnnotationResult>>;
331
330
  declare function processCommentJob(content: string, inferenceClient: InferenceClient, params: CommentDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
332
331
  /** This chunk's novel annotations, awaited: the durability write. */
333
332
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
334
- /** Where earlier attempts left each unit (CHUNK-GRAIN-RESUME P3), keyed the
335
- * same way the checkpoint is. A unit absent here starts at the top, which is
336
- * 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. */
337
336
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobCommentAnnotationResult>>;
338
337
  declare function processAssessmentJob(content: string, inferenceClient: InferenceClient, params: AssessmentDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
339
338
  /** This chunk's novel annotations, awaited: the durability write. */
340
339
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
341
- /** Where earlier attempts left each unit (CHUNK-GRAIN-RESUME P3), keyed the
342
- * same way the checkpoint is. A unit absent here starts at the top, which is
343
- * 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. */
344
343
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobAssessmentAnnotationResult>>;
345
344
  /**
346
- * Reference detection commits per UNIT — one entity type — through
347
- * `onUnitComplete` (ABANDONED-INFERENCE P2, checkpointed resume): the
348
- * callback receives the unit's deduped annotations, and only after it
349
- * resolves does the unit count as complete. Emission belongs to the
350
- * callback alone; the processor returns only the result — returning the
351
- * annotations as well would recreate the post-run batch that N2 showed
352
- * 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.
353
352
  */
354
353
  declare function processReferenceJob(content: string, inferenceClient: InferenceClient, params: DetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress, logger: Logger,
355
354
  /**
@@ -361,18 +360,18 @@ declare function processReferenceJob(content: string, inferenceClient: Inference
361
360
  onUnitComplete: (entityType: string) => Promise<void>, signal?: AbortSignal,
362
361
  /** This chunk's novel annotations, awaited: the durability write. */
363
362
  onChunkComplete?: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
364
- /** Where earlier attempts left each entity-type unit (CHUNK-GRAIN-RESUME P3).
365
- * A unit absent here starts at the top; units already COMPLETE never reach
366
- * 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. */
367
366
  resumeCursors?: Record<string, UnitCursor>): Promise<{
368
367
  result: JobReferenceAnnotationResult;
369
368
  }>;
370
369
  declare function processTagJob(content: string, inferenceClient: InferenceClient, params: TagDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress,
371
370
  /** This chunk's novel annotations, awaited: the durability write. */
372
371
  onChunkComplete: (annotations: Annotation[], checkpoint: UnitCheckpoint) => Promise<void>,
373
- /** Where earlier attempts left each CATEGORY (CHUNK-GRAIN-RESUME P3) — a tag
374
- * job's units are its categories, not its motivation: each walks the whole
375
- * 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. */
376
375
  resumeCursors?: Record<string, UnitCursor>): Promise<ProcessorResult<JobTagAnnotationResult>>;
377
376
  declare function processGenerationJob(inferenceClient: InferenceClient, params: GenerationJobParams, onProgress: OnProgress, logger: Logger): Promise<{
378
377
  content: Uint8Array;
@@ -396,22 +395,17 @@ declare function processGenerationJob(inferenceClient: InferenceClient, params:
396
395
  * `num_ctx`. What remains after the prompt scaffold is split
397
396
  * input:output = 1:2 (annotation JSON echoes each span plus a fixed
398
397
  * key/context envelope, so output needs the larger share). The 1:2 ratio
399
- * is the plan's one allocation policy — doc-independent, tuned only on
400
- * live evidence.
398
+ * is the one allocation policy — doc-independent, tuned only on live
399
+ * evidence.
401
400
  * - Separate ceilings (Anthropic): output takes its full ceiling (duration-
402
401
  * capped below), and input follows the same 1:2 allocation — never more
403
402
  * than half the output budget. Input does NOT get "the rest of the
404
- * window": measured 2026-09-02, a window-sized chunk of entity-dense text
405
- * demands more output than any budget holds, so the model grinds toward
406
- * max_tokens for minutes (killed at the call bound as a "stall") or
407
- * collapses to the degenerate []. Large documents chunk; that is the fix,
408
- * 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.
409
407
  */
410
408
 
411
- /** One chunk handed out by `runAdaptiveChunks`, with the cursor either side of
412
- * it. `at`/`next` over `totalChars` is exact progress — and, once
413
- * CHUNK-GRAIN-RESUME lands, the checkpoint identity a variable boundary forces
414
- * (an ordinal cannot name a chunk whose size is decided while the job runs). */
415
409
  /**
416
410
  * The half of a `UnitCursor` the DETECTION layer can honestly report: where the
417
411
  * walk stands and how it is cutting. The tallies belong to the processor, which
@@ -423,9 +417,9 @@ type ChunkCursor = Pick<UnitCursor, 'next' | 'size'>;
423
417
  /**
424
418
  * Response parsers for annotation detection motivations
425
419
  *
426
- * Provides static methods to parse and validate AI responses for each motivation type.
427
- * Includes offset validation and correction logic.
428
- * 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.
429
423
  *
430
424
  * NOTE: These are static utility methods without logger access.
431
425
  * Console statements kept for debugging - consider adding logger parameter in future.
@@ -497,8 +491,7 @@ declare class AnnotationDetection {
497
491
  * full discussion.
498
492
  */
499
493
  static detectComments(content: string, client: InferenceClient, instructions?: string, tone?: string, density?: number, language?: string, sourceLanguage?: string, onActivity?: (consumedChars: number, totalChars: number) => void,
500
- /** This chunk's matches, as the chunk completes. */
501
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
494
+ /** Where an earlier attempt left this unit. */
502
495
  resume?: UnitCursor,
503
496
  /** This chunk's matches, as the chunk completes. Kept LAST. */
504
497
  onChunkResults?: (matches: CommentMatch[], cursor: ChunkCursor) => Promise<void>): Promise<CommentMatch[]>;
@@ -510,8 +503,7 @@ declare class AnnotationDetection {
510
503
  * correctly.
511
504
  */
512
505
  static detectHighlights(content: string, client: InferenceClient, instructions?: string, density?: number, sourceLanguage?: string, onActivity?: (consumedChars: number, totalChars: number) => void,
513
- /** This chunk's matches, as the chunk completes. */
514
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
506
+ /** Where an earlier attempt left this unit. */
515
507
  resume?: UnitCursor,
516
508
  /** This chunk's matches, as the chunk completes. Kept LAST. */
517
509
  onChunkResults?: (matches: HighlightMatch[], cursor: ChunkCursor) => Promise<void>): Promise<HighlightMatch[]>;
@@ -523,8 +515,7 @@ declare class AnnotationDetection {
523
515
  * being analyzed (source-resource locale).
524
516
  */
525
517
  static detectAssessments(content: string, client: InferenceClient, instructions?: string, tone?: string, density?: number, language?: string, sourceLanguage?: string, onActivity?: (consumedChars: number, totalChars: number) => void,
526
- /** This chunk's matches, as the chunk completes. */
527
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
518
+ /** Where an earlier attempt left this unit. */
528
519
  resume?: UnitCursor,
529
520
  /** This chunk's matches, as the chunk completes. Kept LAST. */
530
521
  onChunkResults?: (matches: AssessmentMatch[], cursor: ChunkCursor) => Promise<void>): Promise<AssessmentMatch[]>;
@@ -541,21 +532,22 @@ declare class AnnotationDetection {
541
532
  * site, not here.
542
533
  */
543
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,
544
537
  /**
545
- * This chunk's matches, ANCHORED before they leave: `parse` here yields
546
- * raw tags, so this path runs `validateTagOffsets` per chunk — a per-item
547
- * 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.
548
542
  */
549
- /** Where an earlier attempt left this unit (CHUNK-GRAIN-RESUME P3). */
550
- resume?: UnitCursor,
551
- /** This chunk's matches, as the chunk completes. Kept LAST. */
552
543
  onChunkResults?: (matches: TagMatch[], cursor: ChunkCursor) => Promise<void>): Promise<TagMatch[]>;
553
544
  }
554
545
 
555
546
  /**
556
547
  * Resource Generation
557
548
  *
558
- * 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`.
559
551
  */
560
552
 
561
553
  /**
@@ -582,12 +574,11 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
582
574
  * Job Claim Adapter — worker-side job lifecycle glue on top of a
583
575
  * shared bus.
584
576
  *
585
- * Replaces the old `WorkerStateUnit`, which owned its own actor and
586
- * duplicated the SSE connection that `SemiontClient` already held.
587
577
  * Workers construct a `SemiontSession` normally (one actor, one
588
578
  * 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.
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.
591
582
  *
592
583
  * THE MODEL — a worker asks the queue at every moment it becomes idle,
593
584
  * and never otherwise. `job:claim` carries this worker's types; the
@@ -619,9 +610,9 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
619
610
  * can never claim has nothing to do here and the launcher's preflight
620
611
  * names the repair.
621
612
  *
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.
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.
625
616
  *
626
617
  * The `bus` parameter is typed against the small `BusRequestPrimitive`
627
618
  * interface (from `@semiont/core`) so the adapter is transport-neutral.
@@ -641,25 +632,27 @@ interface ActiveJob {
641
632
  resourceId: ResourceId;
642
633
  params: ClaimedJob['params'];
643
634
  /**
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.
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.
648
640
  */
649
641
  completedUnits: string[];
650
642
  /**
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.
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.
656
649
  */
657
650
  unitCursors: Record<string, UnitCursor>;
658
651
  /**
659
652
  * 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.
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.
663
656
  */
664
657
  retryCount: number;
665
658
  maxRetries: number;
@@ -686,9 +679,9 @@ interface ClaimRefusal {
686
679
  message: string;
687
680
  }
688
681
  /**
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.
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.
692
685
  *
693
686
  * `lastQueuedEventAt` is any `job:queued` received, matching or not. It
694
687
  * proves the transport delivered a broadcast, but on an idle stack with an
@@ -766,53 +759,47 @@ declare function createJobClaimAdapter(options: JobClaimAdapterOptions): JobClai
766
759
  * a unit test needs to reach lives here instead, fully parameterized — no
767
760
  * module-scope env reads, no side effects at import.
768
761
  *
769
- * The load-bearing contract this module owns (and the reason it was
770
- * extracted): a worker's stamped identity is the DID the
771
- * `/api/tokens/agent` exchange MINTED for it, carried verbatim — never
772
- * re-derived from the URL the worker happens to dial. One logical agent
773
- * 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.
774
766
  */
775
767
 
776
768
  /**
777
- * Stall watchdog (WORKER-LIVENESS.md P3) — the fail-fast line behind the
778
- * inference timeout. There is no poll timer to heartbeat — the worker pulls
779
- * when idle, and a parked idle worker is not a stall; the honest stall
780
- * signal is *processing without activity*: an agent holding a claimed job
781
- * whose `lastActivityAt` (claim / progress / finish) has stopped advancing
782
- * is wedged — the adapter defers every wake-up while a job is held and
783
- * pulls at settle, so a wedged agent never settles and never recovers on
784
- * its own. Silent hang → loud crash → whatever
785
- * 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.
786
777
  *
787
778
  * Thresholds are fixed by design (no env knobs) and deliberately
788
- * layered: inference timeout (10 min, P2) fires first; this watchdog
779
+ * layered: inference timeout (10 min) fires first; this watchdog
789
780
  * (15 min) catches wedges where the loop still turns but activity has
790
781
  * stopped; the gateway's dead-worker janitor (30 min) re-queues the job
791
782
  * regardless.
792
783
  *
793
784
  * The layering matters because this watchdog has a hard limit: it is an
794
785
  * IN-PROCESS timer, so it cannot fire while the event loop itself is
795
- * blocked — the exact condition a blocked loop creates
796
- * (JOB-RESTART-SAFETY P7, the 2026-09-03 finalization hang: 18 min silent,
797
- * this watchdog never fired, an empty /health confirming the loop was
798
- * wedged). The unbounded emit that caused that specific hang is now bounded
799
- * at the transport (`EMIT_TIMEOUT_MS`), so the loop errors instead of
800
- * 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
801
789
  * 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.
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.
804
792
  */
805
793
  declare const STALL_THRESHOLD_MS: number;
806
794
  /**
807
795
  * The broadcasts a worker CONSUMES — announcements nobody replies to, which
808
796
  * therefore derive from no operation.
809
797
  *
810
- * One home, because the alternative was proven: these lived as `addChannels`
811
- * calls beside their consumers, so the worker's complete set existed nowhere
812
- * and a widening could be deleted without any list getting shorter. That is
813
- * the 2026-09-16 outage — every worker idle on a `job:queued` its transport
814
- * would never carry, `lastQueuedEventAt: null`, nothing thrown and nothing
815
- * 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.
816
803
  *
817
804
  * An entry here must have a consumer and a consumer must have an entry; the
818
805
  * census beside this file asserts both directions.
package/dist/index.js CHANGED
@@ -9760,7 +9760,8 @@ Example format:
9760
9760
  /**
9761
9761
  * Build a prompt for detecting structural tags
9762
9762
  *
9763
- * @param content - The full text content to analyze (NOT truncated for structural analysis)
9763
+ * @param content - The text content to analyze — a chunk sized by the
9764
+ * caller from derived provider limits; NEVER re-truncated here
9764
9765
  * @param category - The specific category to detect
9765
9766
  * @param schemaName - Human-readable schema name
9766
9767
  * @param schemaDescription - Schema description
@@ -10188,7 +10189,7 @@ ${piece}
10188
10189
  }
10189
10190
  if (items.length * YIELD_COLLAPSE_BAND < counted) {
10190
10191
  throw new YieldCollapseError(
10191
- `Extraction found ${items.length} entities where a count call reports ~${counted} mentions (band \xD7${YIELD_COLLAPSE_BAND}) on a ${piece.length}-char chunk \u2014 silent yield collapse (F7): deterministic \u2014 a same-size retry returns the identical under-report.`,
10192
+ `Extraction found ${items.length} entities where a count call reports ~${counted} mentions (band \xD7${YIELD_COLLAPSE_BAND}) on a ${piece.length}-char chunk \u2014 silent yield collapse: deterministic \u2014 a same-size retry returns the identical under-report.`,
10192
10193
  [...items],
10193
10194
  { found: items.length, counted, pieceChars: piece.length }
10194
10195
  );
@@ -10737,9 +10738,8 @@ async function processCommentJob(content, inferenceClient, params, buildAnnotati
10737
10738
  found += comments.length;
10738
10739
  const fresh = dedupe(comments.map(
10739
10740
  (c) => (
10740
- // Match the pre-#651 CommentAnnotationWorker: include format and
10741
- // language on the body TextualBody. Optional in the schema, but
10742
- // consumers that do language-aware rendering rely on them.
10741
+ // Format and language go on the body TextualBody: optional in the
10742
+ // schema, but consumers that do language-aware rendering rely on them.
10743
10743
  buildAnnotation("commenting", c, [
10744
10744
  { type: "TextualBody", value: c.comment, purpose: "commenting", format: "text/plain", language: bodyLanguage }
10745
10745
  ])
@@ -10780,8 +10780,7 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
10780
10780
  const fresh = dedupe(assessments.map(
10781
10781
  (a) => (
10782
10782
  // Single-object body with purpose aligned to motivation, matching the
10783
- // pre-#651 AssessmentAnnotationWorker's shape and the majority of
10784
- // persisted assessments. Do not switch to an array or to
10783
+ // majority of persisted assessments. Do not switch to an array or to
10785
10784
  // purpose='describing' — that loses the "this is an assessment, not
10786
10785
  // a description" signal and breaks existing readers that access
10787
10786
  // `body.value` directly on the object.
@@ -10849,11 +10848,11 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
10849
10848
  params.includeDescriptiveReferences ?? false,
10850
10849
  logger,
10851
10850
  params.sourceLanguage,
10852
- // Liveness heartbeat (DETECTION-HEARTBEAT): fires at chunk boundaries and
10853
- // every ~15 s while a call is in flight, so a long single-chunk call is
10854
- // not silent. It repeats the current position rather than inventing an
10855
- // advance — the stall watchdog, janitor and client timeout need a signal,
10856
- // not a monotone.
10851
+ // Liveness heartbeat: fires at chunk boundaries and every ~15 s while a
10852
+ // call is in flight, so a long single-chunk call is not silent. It
10853
+ // repeats the current position rather than inventing an advance — the
10854
+ // stall watchdog, janitor and client timeout need a signal, not a
10855
+ // monotone.
10857
10856
  () => emitTypeProgress(entityTypeName),
10858
10857
  (verdict) => {
10859
10858
  underReported = {
@@ -11010,7 +11009,7 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
11010
11009
  `Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(" or ")}.`
11011
11010
  );
11012
11011
  }
11013
- const title = params.title ?? "Untitled";
11012
+ const title = params.title;
11014
11013
  const entityTypes = (params.entityTypes ?? []).map(String);
11015
11014
  if (outputMediaType === "application/pdf") {
11016
11015
  onProgress(5, { code: "generating-resource" });
@@ -11289,26 +11288,25 @@ var WORKER_AWAITED_OPERATIONS = [
11289
11288
  "job:claim",
11290
11289
  "browse:resource-requested",
11291
11290
  // Canonical geometry for a geometry-bearing detection: the consult behind
11292
- // `ConsultAnchoredText` (SMELTER-OWNS-OCR P2). Its omission broke every
11293
- // PDF detection job at the transport probe
11294
- // (.plans/WORKER-ANCHORED-TEXT-CHANNEL.md); the census below now fails the
11291
+ // `ConsultAnchoredText`, answered by the Smelter. Without it every PDF
11292
+ // detection job fails at the transport probe; the census below fails the
11295
11293
  // BUILD when this list and the declared awaits drift.
11296
11294
  "browse:anchored-text-requested",
11297
- // Durability acknowledgement for a unit's annotations (JOB-RESTART-SAFETY
11298
- // P6). The worker AWAITS this one — a unit may not advance until its
11299
- // annotations are in the event log — so its replies must be in the narrow
11300
- // channel set or every commit fails fast with `bus.unsubscribed`.
11295
+ // Durability acknowledgement for a unit's annotations. The worker AWAITS
11296
+ // this one — a unit may not advance until its annotations are in the event
11297
+ // log — so its replies must be in the narrow channel set or every commit
11298
+ // fails fast with `bus.unsubscribed`.
11301
11299
  "mark:commit",
11302
- // The durability probe for a commit whose acknowledgement never routed
11303
- // (COMMIT-ACK-FALSE-FAILURE F1). SINGULAR by design: the annotation LIST
11304
- // channel is the multi-MB fan-out this narrowing exists to keep out, and a
11305
- // rare error path is no reason to let it back in.
11300
+ // The durability probe for a commit whose acknowledgement never routed.
11301
+ // SINGULAR by design: the annotation LIST channel is the multi-MB fan-out
11302
+ // this narrowing exists to keep out, and a rare error path is no reason to
11303
+ // let it in.
11306
11304
  "browse:annotation-requested"
11307
11305
  ];
11308
11306
  var WORKER_CONSUMED_BROADCASTS = [
11309
11307
  // The queue announcement the claim adapter races for.
11310
11308
  "job:queued",
11311
- // Cooperative cancellation of the ACTIVE job (JOB-RESTART-SAFETY P4).
11309
+ // Cooperative cancellation of the ACTIVE job: it stops at a unit boundary.
11312
11310
  "job:cancel-requested"
11313
11311
  ];
11314
11312
  var WORKER_CHANNELS = [