@semiont/jobs 0.5.36 → 0.5.38

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
@@ -290,7 +290,22 @@ interface JobQueue {
290
290
  destroy(): void;
291
291
  createJob(job: AnyJob): Promise<void>;
292
292
  getJob(jobId: JobId): Promise<AnyJob | null>;
293
- updateJob(job: AnyJob, oldStatus?: JobStatus): Promise<void>;
293
+ /**
294
+ * Atomically claim the NEXT pending job matching one of `types` — the ONE
295
+ * transition that makes this a queue rather than a state store
296
+ * (JOB-QUEUE-DRIVER P0; reshaped claim-by-TYPE in P2, while every worker
297
+ * is still ours). pending → running, `startedAt` stamped, progress empty.
298
+ * An announcement is a WAKE-UP, not a reservation: the claimed job may
299
+ * differ from any announced one, no ordering among matching pending jobs
300
+ * is promised, and an empty `types` accepts any type. Simultaneous claims
301
+ * admit one winner PER pending job; a claim that finds nothing is
302
+ * DECLINED (`none-available`), never an error.
303
+ */
304
+ claimNextJob(types: string[]): Promise<{
305
+ job: AnyJob;
306
+ } | {
307
+ declined: 'none-available';
308
+ }>;
294
309
  /** Move a running job to `complete`. Returns false if the job isn't running. */
295
310
  completeJob(jobId: JobId, result: Record<string, unknown>): Promise<boolean>;
296
311
  /**
@@ -348,6 +363,15 @@ interface JobQueue {
348
363
  cancelled: number;
349
364
  }>;
350
365
  }
366
+ /**
367
+ * Every bus channel a queue DRIVER emits, maintained beside the interface
368
+ * both drivers implement and censused against their sources
369
+ * (queue-emits-census.test.ts). The gateway's signal bridge forwards these
370
+ * from its local bus onto the plane — a queue announcement that stays on
371
+ * the raw bus reaches no worker under a remote driver (the job:queued
372
+ * starvation, 2026-09-15).
373
+ */
374
+ declare const JOB_QUEUE_EMITS: readonly ["job:queued"];
351
375
 
352
376
  /**
353
377
  * Job Queue Manager
@@ -398,10 +422,30 @@ declare class FsJobQueue implements JobQueue {
398
422
  * Get a job by ID (searches all status directories)
399
423
  */
400
424
  getJob(jobId: JobId): Promise<AnyJob | null>;
425
+ /**
426
+ * Serializes claims. This driver's atomicity is single-process by design,
427
+ * but `claimJob`'s contract must hold even against interleaved awaits in
428
+ * that one process — a read-check-write with await points in it can admit
429
+ * two winners without this chain (the TOCTOU the operation exists to kill).
430
+ */
431
+ private claimChain;
432
+ claimNextJob(types: string[]): Promise<{
433
+ job: AnyJob;
434
+ } | {
435
+ declined: 'none-available';
436
+ }>;
437
+ private doClaimNext;
401
438
  /**
402
439
  * Update a job (atomic: delete old, write new)
403
440
  */
404
- updateJob(job: AnyJob, oldStatus?: JobStatus): Promise<void>;
441
+ /**
442
+ * Cross-status move (or in-place rewrite): delete old slot, write new.
443
+ * Private — the interface exposes named transitions only (JOB-QUEUE-DRIVER
444
+ * P0); a generic public patch is trivial on files and impossible on an
445
+ * in-flight message, so no driver may offer one. Re-entering `pending`
446
+ * announces, which is how failJob's retry reaches a listening worker.
447
+ */
448
+ private transition;
405
449
  /**
406
450
  * Move a running job to `complete`. Returns false (and changes
407
451
  * nothing) if the job is missing or not running — which also makes
@@ -478,6 +522,144 @@ declare class FsJobQueue implements JobQueue {
478
522
  }>;
479
523
  }
480
524
 
525
+ /**
526
+ * JetStreamJobQueue — the JetStream driver behind the `JobQueue` interface
527
+ * (JOB-QUEUE-DRIVER P1, topology ruling M: gateway-mediated).
528
+ *
529
+ * Two primitives, one authority each:
530
+ *
531
+ * - **KV bucket `jobs`** is the AUTHORITATIVE operational state: one entry
532
+ * per job (`{ job, lastProgressAt }`), every transition a revision-CAS —
533
+ * which is what makes `claimJob` atomic: simultaneous claims race on one
534
+ * revision and exactly one update wins.
535
+ * - **Stream `JOBS`** (subjects `jobs.<category>.<type>`, work-queue
536
+ * retention) is the DELIVERY vehicle and redelivery timer. A delivered
537
+ * message is the lease this process holds for a job; `working()`
538
+ * heartbeats extend it, and a gateway that dies stops heartbeating, so
539
+ * `AckWait` redelivers the job to a live instance — gateway-death
540
+ * recovery, protocol-native.
541
+ *
542
+ * Worker-death recovery is NOT AckWait's job under the mediated topology
543
+ * (the gateway holding the lease is alive; the worker vanished): it is the
544
+ * `lastProgressAt` sweep (`recoverStaleRunningJobs`), the same contract the
545
+ * fs driver's mtime janitor implemented. The redelivery handler checks KV
546
+ * state, so the two recovery paths can never double-apply.
547
+ *
548
+ * Battery ownership (JOB-QUEUE-DRIVER P0): the retry decision belongs to
549
+ * `will-retry.ts` — `max_deliver` is unlimited and a deterministic failure
550
+ * is `term()`ed, so the backend's retry engine never becomes a second
551
+ * authority. The cursor merge belongs to `checkpoint-merge.ts`, shared with
552
+ * every driver.
553
+ *
554
+ * The boundary is the point: no NATS type, subject string, or delivery
555
+ * handle escapes this file.
556
+ */
557
+
558
+ /**
559
+ * The JOBS stream's capture filter — exported as the ONE home of the fact.
560
+ * A JetStream stream is a server-side subscription: anything published under
561
+ * these subjects is persisted regardless of which client API produced it.
562
+ * The signal plane's disjointness gate derives from this export instead of
563
+ * restating it (SIGNAL-PLANE D3 gate 3).
564
+ */
565
+ declare const JOBS_STREAM_SUBJECTS: readonly ["jobs.>"];
566
+ interface JetStreamJobQueueOptions {
567
+ /** NATS server address(es), e.g. "192.168.64.42:4222". */
568
+ servers: string | string[];
569
+ /** Worker presumed dead after this long without progress (default 30 min). */
570
+ staleRunningMs?: number;
571
+ /** Lease redelivery window when THIS process stops heartbeating (default 30 s). */
572
+ ackWaitMs?: number;
573
+ /** Re-announce + worker-death-sweep cadence (default 30 s; tests shrink it). */
574
+ tickMs?: number;
575
+ /** Reconnect on connection loss (default true; tests turn it off). */
576
+ reconnect?: boolean;
577
+ }
578
+ declare class JetStreamJobQueue implements JobQueue {
579
+ private readonly options;
580
+ private readonly logger;
581
+ private readonly eventBus?;
582
+ private nc;
583
+ private js;
584
+ private jsm;
585
+ private kv;
586
+ private iter;
587
+ /** Delivered messages this process holds — the lease, keyed by jobId,
588
+ * with the job's type so a by-type claim can walk them without a read. */
589
+ private readonly held;
590
+ private heartbeat;
591
+ private reconciling;
592
+ private tick;
593
+ private ticking;
594
+ private readonly lastProgressWrite;
595
+ private readonly staleRunningMs;
596
+ private readonly ackWaitMs;
597
+ private readonly tickMs;
598
+ constructor(options: JetStreamJobQueueOptions, logger: Logger, eventBus?: EventBus | undefined);
599
+ initialize(): Promise<void>;
600
+ destroy(): void;
601
+ /**
602
+ * A delivery is a job arriving at this gateway. What it means depends on
603
+ * the job's authoritative (KV) state:
604
+ * - pending → hold the lease, announce for a worker to claim;
605
+ * - running → hold silently (a claim raced ahead of the delivery, or a
606
+ * redelivery reached a fresh instance after a gateway death);
607
+ * - terminal → the work already concluded elsewhere; consume the message;
608
+ * - unknown → not ours to run; terminate it.
609
+ */
610
+ private onDelivery;
611
+ /** Same wire shape as the fs driver: only jobs with a resourceId announce. */
612
+ private announce;
613
+ private read;
614
+ private write;
615
+ /**
616
+ * Read-transform-CAS with bounded retries: the transform sees the current
617
+ * job and returns the replacement (or null to stop). Every state
618
+ * transition goes through here, which is what makes each one atomic.
619
+ */
620
+ private cas;
621
+ createJob(job: AnyJob): Promise<void>;
622
+ getJob(jobIdArg: JobId): Promise<AnyJob | null>;
623
+ claimNextJob(types: string[]): Promise<{
624
+ job: AnyJob;
625
+ } | {
626
+ declined: 'none-available';
627
+ }>;
628
+ /** CAS one pending job to running; null when someone else won it. */
629
+ private tryClaim;
630
+ completeJob(jobIdArg: JobId, result: Record<string, unknown>): Promise<boolean>;
631
+ failJob(jobIdArg: JobId, error: string, completedUnits?: string[], failureClass?: 'transient' | 'deterministic', unitCursors?: Record<string, UnitCursor>): Promise<'retried' | 'failed' | null>;
632
+ checkpointUnits(jobIdArg: JobId, completedUnits: string[], unitCursors?: Record<string, UnitCursor>): Promise<void>;
633
+ recordProgress(jobIdArg: JobId, progress: Record<string, unknown>): Promise<void>;
634
+ cancelPendingJobs(category: 'annotation' | 'generation'): Promise<number>;
635
+ cancelJob(jobIdArg: JobId): Promise<boolean>;
636
+ getStats(): Promise<{
637
+ pending: number;
638
+ running: number;
639
+ complete: number;
640
+ failed: number;
641
+ cancelled: number;
642
+ }>;
643
+ /**
644
+ * Worker-death recovery: the same contract the fs driver's mtime janitor
645
+ * implemented — a running job with no progress inside the stale window is
646
+ * retried or failed, checkpoint intact. Gateway-death recovery never comes
647
+ * through here; that is AckWait redelivery.
648
+ */
649
+ recoverStaleRunningJobs(): Promise<number>;
650
+ /** The heartbeat body — extend live leases, settle concluded ones. */
651
+ private reconcileHeld;
652
+ /**
653
+ * Materialize the KV key list BEFORE touching any entry: interleaving
654
+ * `get`/`update` with an open `keys()` iterator on the same connection
655
+ * makes the iterator drop entries (measured: the second pending job
656
+ * vanished from every sweep until this was split into two passes).
657
+ */
658
+ private allKeys;
659
+ /** Conclude this process's lease on a job, if it holds one. */
660
+ private settleLease;
661
+ }
662
+
481
663
  /**
482
664
  * Citation-token resolver (INLINE-CITATIONS P1).
483
665
  *
@@ -865,5 +1047,5 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
865
1047
  */
866
1048
  declare const STALL_THRESHOLD_MS: number;
867
1049
 
868
- export { AnnotationDetection, FsJobQueue, STALL_THRESHOLD_MS, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
869
- export type { AnyJob, AssessmentDetectionJob, AssessmentDetectionParams, AssessmentDetectionProgress, CancelledJob, CommentDetectionJob, CommentDetectionParams, CommentDetectionProgress, CompleteJob, DetectionJob, DetectionParams, DetectionProgress, FailedJob, GenerationJob, GenerationResult, HighlightDetectionJob, HighlightDetectionParams, HighlightDetectionProgress, JobMetadata, JobQueryFilters, JobQueue, JobStatus, JobType, OnProgress, PendingJob, ProcessorResult, RunningJob, TagDetectionJob, TagDetectionParams, TagDetectionProgress, YieldProgress };
1050
+ export { AnnotationDetection, FsJobQueue, JOBS_STREAM_SUBJECTS, JOB_QUEUE_EMITS, JetStreamJobQueue, STALL_THRESHOLD_MS, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
1051
+ export type { AnyJob, AssessmentDetectionJob, AssessmentDetectionParams, AssessmentDetectionProgress, CancelledJob, CommentDetectionJob, CommentDetectionParams, CommentDetectionProgress, CompleteJob, DetectionJob, DetectionParams, DetectionProgress, FailedJob, GenerationJob, GenerationResult, HighlightDetectionJob, HighlightDetectionParams, HighlightDetectionProgress, JetStreamJobQueueOptions, JobMetadata, JobQueryFilters, JobQueue, JobStatus, JobType, OnProgress, PendingJob, ProcessorResult, RunningJob, TagDetectionJob, TagDetectionParams, TagDetectionProgress, YieldProgress };