@semiont/jobs 0.5.39 → 0.6.1
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 +4 -10
- package/dist/index.d.ts +31 -21
- package/dist/index.js +32 -3
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +142 -77
- package/dist/worker-main.js.map +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -48,10 +48,7 @@ const job: PendingJob<DetectionParams> = {
|
|
|
48
48
|
metadata: {
|
|
49
49
|
id: jobId('job-abc123'),
|
|
50
50
|
type: 'reference-annotation',
|
|
51
|
-
userId: userId('
|
|
52
|
-
userName: 'Jane Doe',
|
|
53
|
-
userEmail: 'jane@example.com',
|
|
54
|
-
userDomain: 'example.com',
|
|
51
|
+
userId: userId('did:web:example.com:users:f47ac10b-58cc-4372-a567-0e02b2c3d479'),
|
|
55
52
|
created: new Date().toISOString(),
|
|
56
53
|
retryCount: 0,
|
|
57
54
|
maxRetries: 1,
|
|
@@ -91,10 +88,7 @@ All jobs share common metadata:
|
|
|
91
88
|
interface JobMetadata {
|
|
92
89
|
id: JobId;
|
|
93
90
|
type: JobType;
|
|
94
|
-
userId: UserId;
|
|
95
|
-
userName: string; // Audit-only snapshot of the requesting user
|
|
96
|
-
userEmail: string; // Audit-only snapshot of the requesting user
|
|
97
|
-
userDomain: string; // Audit-only snapshot of the requesting user
|
|
91
|
+
userId: UserId; // Who requested it — the verified DID, the job's only identity
|
|
98
92
|
created: string;
|
|
99
93
|
retryCount: number;
|
|
100
94
|
maxRetries: number;
|
|
@@ -103,7 +97,7 @@ interface JobMetadata {
|
|
|
103
97
|
}
|
|
104
98
|
```
|
|
105
99
|
|
|
106
|
-
|
|
100
|
+
`userId` is the job's only identity — the DID the gateway verified on the `job:create`. The dispatcher records it as the requester when it accepts a claim, and that record is what lets the knowledge base attribute a write citing this job; a worker never states it. `completedUnits` is written only by `failJob`, unioned across attempts, and carried on the `job:fail` event — see Failure discipline below.
|
|
107
101
|
|
|
108
102
|
## Annotation Workers
|
|
109
103
|
|
|
@@ -142,7 +136,7 @@ Workers are not subclassed. To add a job type:
|
|
|
142
136
|
2. Add a `process*Job` function in `src/processors.ts` that runs the inference and returns the annotations/result.
|
|
143
137
|
3. Dispatch the new `jobType` to that processor in `handleJobInner()` in `src/worker-process.ts`.
|
|
144
138
|
|
|
145
|
-
Processors are transport-agnostic: they take content, an `InferenceClient`, the job params,
|
|
139
|
+
Processors are transport-agnostic: they take content, an `InferenceClient`, the job params, a `buildAnnotation` closure (which carries the `generator` — the worker's own `Software` agent), an `onProgress` callback and a per-chunk commit callback, and return a result. No user identity reaches a processor: an annotation states what produced it, and who requested it is derived by the knowledge base from the job the commit cites. The worker process handles claiming, content fetching, committing, and lifecycle event emission.
|
|
146
140
|
|
|
147
141
|
## Discriminated Unions
|
|
148
142
|
|
package/dist/index.d.ts
CHANGED
|
@@ -23,17 +23,14 @@ type JobStatus = 'pending' | 'running' | 'complete' | 'failed' | 'cancelled';
|
|
|
23
23
|
interface JobMetadata {
|
|
24
24
|
id: JobId;
|
|
25
25
|
type: JobType;
|
|
26
|
-
userId: UserId;
|
|
27
26
|
/**
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* Who requested the job: the verified DID the gateway stamped on the
|
|
28
|
+
* `job:create`, and the ONLY identity a job carries. The dispatcher
|
|
29
|
+
* records it as the requester on `job:assigned`, which is what lets a
|
|
30
|
+
* write citing this job be attributed — so nothing else about the
|
|
31
|
+
* requester needs to travel with the job, or be trusted from it.
|
|
33
32
|
*/
|
|
34
|
-
|
|
35
|
-
userEmail: string;
|
|
36
|
-
userDomain: string;
|
|
33
|
+
userId: UserId;
|
|
37
34
|
created: string;
|
|
38
35
|
retryCount: number;
|
|
39
36
|
maxRetries: number;
|
|
@@ -524,7 +521,9 @@ declare class FsJobQueue implements JobQueue {
|
|
|
524
521
|
|
|
525
522
|
/**
|
|
526
523
|
* JetStreamJobQueue — the JetStream driver behind the `JobQueue` interface
|
|
527
|
-
* (JOB-QUEUE-DRIVER P1, topology ruling M:
|
|
524
|
+
* (JOB-QUEUE-DRIVER P1, topology ruling M: broker-mediated — the queue's own
|
|
525
|
+
* process holds the broker connection and the leases; workers never touch the
|
|
526
|
+
* broker. That process was the gateway; it is the dispatcher now.)
|
|
528
527
|
*
|
|
529
528
|
* Two primitives, one authority each:
|
|
530
529
|
*
|
|
@@ -535,12 +534,12 @@ declare class FsJobQueue implements JobQueue {
|
|
|
535
534
|
* - **Stream `JOBS`** (subjects `jobs.<category>.<type>`, work-queue
|
|
536
535
|
* retention) is the DELIVERY vehicle and redelivery timer. A delivered
|
|
537
536
|
* message is the lease this process holds for a job; `working()`
|
|
538
|
-
* heartbeats extend it, and a
|
|
539
|
-
* `AckWait` redelivers the job to a live instance —
|
|
537
|
+
* heartbeats extend it, and a dispatcher that dies stops heartbeating, so
|
|
538
|
+
* `AckWait` redelivers the job to a live instance — dispatcher-death
|
|
540
539
|
* recovery, protocol-native.
|
|
541
540
|
*
|
|
542
541
|
* Worker-death recovery is NOT AckWait's job under the mediated topology
|
|
543
|
-
* (the
|
|
542
|
+
* (the dispatcher holding the lease is alive; the worker vanished): it is the
|
|
544
543
|
* `lastProgressAt` sweep (`recoverStaleRunningJobs`), the same contract the
|
|
545
544
|
* fs driver's mtime janitor implemented. The redelivery handler checks KV
|
|
546
545
|
* state, so the two recovery paths can never double-apply.
|
|
@@ -566,6 +565,14 @@ declare const JOBS_STREAM_SUBJECTS: readonly ["jobs.>"];
|
|
|
566
565
|
interface JetStreamJobQueueOptions {
|
|
567
566
|
/** NATS server address(es), e.g. "192.168.64.42:4222". */
|
|
568
567
|
servers: string | string[];
|
|
568
|
+
/**
|
|
569
|
+
* Broker credentials (INTER-COMPONENT-ACCESS P3). The same broker the signal
|
|
570
|
+
* plane connects to, so the same pair. Absent means an unauthenticated
|
|
571
|
+
* broker — which is what every deployment had before this, and means anyone
|
|
572
|
+
* who can reach NATS can read and write the job stream.
|
|
573
|
+
*/
|
|
574
|
+
user?: string;
|
|
575
|
+
pass?: string;
|
|
569
576
|
/** Worker presumed dead after this long without progress (default 30 min). */
|
|
570
577
|
staleRunningMs?: number;
|
|
571
578
|
/** Lease redelivery window when THIS process stops heartbeating (default 30 s). */
|
|
@@ -597,13 +604,15 @@ declare class JetStreamJobQueue implements JobQueue {
|
|
|
597
604
|
private readonly tickMs;
|
|
598
605
|
constructor(options: JetStreamJobQueueOptions, logger: Logger, eventBus?: EventBus | undefined);
|
|
599
606
|
initialize(): Promise<void>;
|
|
607
|
+
/** An outage is never silent: one line down, one line back, one line if the client gives up. */
|
|
608
|
+
private watchConnection;
|
|
600
609
|
destroy(): void;
|
|
601
610
|
/**
|
|
602
|
-
* A delivery is a job arriving at this
|
|
611
|
+
* A delivery is a job arriving at this dispatcher. What it means depends on
|
|
603
612
|
* the job's authoritative (KV) state:
|
|
604
613
|
* - pending → hold the lease, announce for a worker to claim;
|
|
605
614
|
* - running → hold silently (a claim raced ahead of the delivery, or a
|
|
606
|
-
* redelivery reached a fresh instance after a
|
|
615
|
+
* redelivery reached a fresh instance after a dispatcher death);
|
|
607
616
|
* - terminal → the work already concluded elsewhere; consume the message;
|
|
608
617
|
* - unknown → not ours to run; terminate it.
|
|
609
618
|
*/
|
|
@@ -1019,12 +1028,13 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
|
|
|
1019
1028
|
|
|
1020
1029
|
/**
|
|
1021
1030
|
* Stall watchdog (WORKER-LIVENESS.md P3) — the fail-fast line behind the
|
|
1022
|
-
* inference timeout. There is no poll
|
|
1023
|
-
*
|
|
1024
|
-
* activity*: an agent holding a claimed job
|
|
1025
|
-
* (claim / progress / finish) has stopped advancing
|
|
1026
|
-
* adapter
|
|
1027
|
-
*
|
|
1031
|
+
* inference timeout. There is no poll timer to heartbeat — the worker pulls
|
|
1032
|
+
* when idle, and a parked idle worker is not a stall; the honest stall
|
|
1033
|
+
* signal is *processing without activity*: an agent holding a claimed job
|
|
1034
|
+
* whose `lastActivityAt` (claim / progress / finish) has stopped advancing
|
|
1035
|
+
* is wedged — the adapter defers every wake-up while a job is held and
|
|
1036
|
+
* pulls at settle, so a wedged agent never settles and never recovers on
|
|
1037
|
+
* its own. Silent hang → loud crash → whatever
|
|
1028
1038
|
* restart policy the deployment chose.
|
|
1029
1039
|
*
|
|
1030
1040
|
* Thresholds are fixed by design (no env knobs) and deliberately
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { promises, mkdtempSync, writeFileSync, readFileSync, rmSync } from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { replyChannelsFor, jobId, deriveViews, GENERATABLE_MEDIA_TYPES, estimateTokens, isObject, isString, reconcileSelector, getLocaleEnglishName, cutChunk, chunkText } from '@semiont/core';
|
|
5
|
-
import { connect, RetentionPolicy, DeliverPolicy, AckPolicy, nanos } from 'nats';
|
|
5
|
+
import { connect, RetentionPolicy, DeliverPolicy, AckPolicy, nanos, NatsError } from 'nats';
|
|
6
6
|
import { withSpan, recordAnchorOutcome, recordDetectionCall } from '@semiont/observability';
|
|
7
7
|
import { StructuredReadError } from '@semiont/inference';
|
|
8
8
|
import { execFileSync } from 'child_process';
|
|
@@ -556,9 +556,18 @@ var JetStreamJobQueue = class {
|
|
|
556
556
|
async initialize() {
|
|
557
557
|
this.nc = await connect({
|
|
558
558
|
servers: this.options.servers,
|
|
559
|
+
// The same broker as the signal plane, so the same credentials. Connect
|
|
560
|
+
// options rather than a URL: this address is logged on every reconnect.
|
|
561
|
+
...this.options.user === void 0 ? {} : { user: this.options.user },
|
|
562
|
+
...this.options.pass === void 0 ? {} : { pass: this.options.pass },
|
|
559
563
|
reconnect: this.options.reconnect ?? true,
|
|
564
|
+
// For as long as the broker is unreachable: a manual broker restart is
|
|
565
|
+
// the recovery, and the library's ten attempts (~20 s) closed the
|
|
566
|
+
// signal plane's connection for good in its first live outage.
|
|
567
|
+
maxReconnectAttempts: -1,
|
|
560
568
|
timeout: 1e4
|
|
561
569
|
});
|
|
570
|
+
this.watchConnection();
|
|
562
571
|
this.js = this.nc.jetstream();
|
|
563
572
|
this.jsm = await this.nc.jetstreamManager();
|
|
564
573
|
this.kv = await this.js.views.kv(BUCKET);
|
|
@@ -625,6 +634,26 @@ var JetStreamJobQueue = class {
|
|
|
625
634
|
}, this.tickMs);
|
|
626
635
|
this.tick.unref?.();
|
|
627
636
|
}
|
|
637
|
+
/** An outage is never silent: one line down, one line back, one line if the client gives up. */
|
|
638
|
+
watchConnection() {
|
|
639
|
+
const servers = this.options.servers;
|
|
640
|
+
void (async () => {
|
|
641
|
+
for await (const status of this.nc.status()) {
|
|
642
|
+
if (status.type === "disconnect") {
|
|
643
|
+
this.logger.warn("[jobs BROKER-DOWN] NATS connection lost; queue operations fail until reconnect", { servers });
|
|
644
|
+
} else if (status.type === "reconnect") {
|
|
645
|
+
this.logger.info("[jobs BROKER-RECONNECTED] NATS connection restored", { servers });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
})();
|
|
649
|
+
void this.nc.closed().then((err) => {
|
|
650
|
+
if (!err) return;
|
|
651
|
+
this.logger.error("[jobs BROKER-CLOSED] NATS connection closed; the client will not reconnect", {
|
|
652
|
+
servers,
|
|
653
|
+
reason: err instanceof NatsError ? err.code : err.message
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
}
|
|
628
657
|
destroy() {
|
|
629
658
|
this.iter?.stop();
|
|
630
659
|
this.iter = null;
|
|
@@ -636,11 +665,11 @@ var JetStreamJobQueue = class {
|
|
|
636
665
|
void this.nc?.close();
|
|
637
666
|
}
|
|
638
667
|
/**
|
|
639
|
-
* A delivery is a job arriving at this
|
|
668
|
+
* A delivery is a job arriving at this dispatcher. What it means depends on
|
|
640
669
|
* the job's authoritative (KV) state:
|
|
641
670
|
* - pending → hold the lease, announce for a worker to claim;
|
|
642
671
|
* - running → hold silently (a claim raced ahead of the delivery, or a
|
|
643
|
-
* redelivery reached a fresh instance after a
|
|
672
|
+
* redelivery reached a fresh instance after a dispatcher death);
|
|
644
673
|
* - terminal → the work already concluded elsewhere; consume the message;
|
|
645
674
|
* - unknown → not ours to run; terminate it.
|
|
646
675
|
*/
|