@exellix/exellix-jobs 1.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 +103 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +100 -0
- package/dist/cli.js.map +1 -0
- package/dist/collections.d.ts +28 -0
- package/dist/collections.d.ts.map +1 -0
- package/dist/collections.js +2 -0
- package/dist/collections.js.map +1 -0
- package/dist/env.d.ts +17 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +38 -0
- package/dist/env.js.map +1 -0
- package/dist/http/health.d.ts +10 -0
- package/dist/http/health.d.ts.map +1 -0
- package/dist/http/health.js +20 -0
- package/dist/http/health.js.map +1 -0
- package/dist/http/server.d.ts +10 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +85 -0
- package/dist/http/server.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-matrix-tier.d.ts +6 -0
- package/dist/memory-matrix-tier.d.ts.map +1 -0
- package/dist/memory-matrix-tier.js +20 -0
- package/dist/memory-matrix-tier.js.map +1 -0
- package/dist/metrics.d.ts +13 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +57 -0
- package/dist/metrics.js.map +1 -0
- package/dist/open-matrix-persistence.d.ts +7 -0
- package/dist/open-matrix-persistence.d.ts.map +1 -0
- package/dist/open-matrix-persistence.js +29 -0
- package/dist/open-matrix-persistence.js.map +1 -0
- package/dist/persistence-env.d.ts +7 -0
- package/dist/persistence-env.d.ts.map +1 -0
- package/dist/persistence-env.js +14 -0
- package/dist/persistence-env.js.map +1 -0
- package/dist/runtime-matrix-tier.d.ts +8 -0
- package/dist/runtime-matrix-tier.d.ts.map +1 -0
- package/dist/runtime-matrix-tier.js +57 -0
- package/dist/runtime-matrix-tier.js.map +1 -0
- package/dist/worker/continuous-loop.d.ts +22 -0
- package/dist/worker/continuous-loop.d.ts.map +1 -0
- package/dist/worker/continuous-loop.js +73 -0
- package/dist/worker/continuous-loop.js.map +1 -0
- package/dist/worker/cycle-state.d.ts +22 -0
- package/dist/worker/cycle-state.d.ts.map +1 -0
- package/dist/worker/cycle-state.js +25 -0
- package/dist/worker/cycle-state.js.map +1 -0
- package/dist/worker/discovery.d.ts +32 -0
- package/dist/worker/discovery.d.ts.map +1 -0
- package/dist/worker/discovery.js +104 -0
- package/dist/worker/discovery.js.map +1 -0
- package/dist/worker/matrix-worker.d.ts +57 -0
- package/dist/worker/matrix-worker.d.ts.map +1 -0
- package/dist/worker/matrix-worker.js +103 -0
- package/dist/worker/matrix-worker.js.map +1 -0
- package/dist/worker/wrap-execute-graph.d.ts +14 -0
- package/dist/worker/wrap-execute-graph.d.ts.map +1 -0
- package/dist/worker/wrap-execute-graph.js +61 -0
- package/dist/worker/wrap-execute-graph.js.map +1 -0
- package/dist/xmemory-matrix-tier.d.ts +7 -0
- package/dist/xmemory-matrix-tier.d.ts.map +1 -0
- package/dist/xmemory-matrix-tier.js +65 -0
- package/dist/xmemory-matrix-tier.js.map +1 -0
- package/docs/execution-matrix-worker-service.md +8 -0
- package/docs/openapi.yaml +122 -0
- package/docs/specs.md +305 -0
- package/docs/troubleshooting.md +252 -0
- package/examples/graph-engine-stub.mjs +40 -0
- package/examples/host-bootstrap.mjs +74 -0
- package/package.json +67 -0
package/docs/specs.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# `@exellix/exellix-jobs` — detailed specification (draft)
|
|
2
|
+
|
|
3
|
+
**Status:** specification only — package **does not exist** yet.
|
|
4
|
+
**Audience:** implementers of the new worker package and maintainers of **`@exellix/exellix-runtime`**, **`@exellix/graph-engine`**, **`@x12i/catalox`**.
|
|
5
|
+
**Supersedes as normative worker doc:** the narrative in [`docs/execution-matrix-worker-service.md`](../execution-matrix-worker-service.md) once this package ships (that file should become a short pointer).
|
|
6
|
+
|
|
7
|
+
**Hard rule:** **`@exellix/exellix-jobs`** must **not** import **`mongodb`** or **`@x12i/xronox-store`**. All matrix **data** access goes through **`createExellixMatrixDataTier`** on **`@exellix/exellix-runtime` ≥ 3.1**. **Metadata** (catalog listing, optional graph JSON from Catalox) uses **`@x12i/catalox`** where applicable.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Package identity
|
|
12
|
+
|
|
13
|
+
| Item | Recommendation |
|
|
14
|
+
|------|----------------|
|
|
15
|
+
| **npm name** | `@exellix/exellix-jobs` (or `@exellix/matrix-worker` — pick one and keep it stable). |
|
|
16
|
+
| **Type** | `type: "module"` Node package; **no** browser bundle required for v1. |
|
|
17
|
+
| **Entrypoints** | `dist/index.js` — programmatic API (`createMatrixWorker`, `startMatrixWorkerHttp`, …); optional `dist/cli.js` for `exellix-jobs serve`. |
|
|
18
|
+
| **Node** | Align with **`@exellix/exellix-runtime`** (e.g. Node ≥ 20). |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Goals
|
|
23
|
+
|
|
24
|
+
1. Run a **long-lived process** (or Kubernetes Deployment) that **claims and executes** execution-matrix graph work using **`@exellix/exellix-runtime`** orchestrator + run-loop APIs.
|
|
25
|
+
2. Enforce **graph-level** operational policy: only **Published** graphs in **Ready** or **Running** (per [`assertGraphSchedulable`](../src/execution-matrix/graph-operational-lifecycle.ts)) participate in claims.
|
|
26
|
+
3. Support **configurable parallelism** (`maxConcurrentClaims`, optional `perGraphConcurrency`).
|
|
27
|
+
4. Support **graceful drain**: stop new claims without aborting in-flight **`executeGraph`**.
|
|
28
|
+
5. Expose **operator HTTP** (health, graph PATCH, worker status, Prometheus metrics) with **auth left to host middleware** or built-in minimal API key (product choice).
|
|
29
|
+
6. Emit **structured metrics** for SRE dashboards (see §10).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 3. Non-goals (v1)
|
|
34
|
+
|
|
35
|
+
- **Not** re-implementing **`ExecutionMatrixRuntime`**, **`ExellixConfigStore`**, or **`runMatrixCycle`** — **import** from **`@exellix/exellix-runtime`**.
|
|
36
|
+
- **Not** defining new row/config **document shapes** — types live in **`@exellix/exellix-runtime`** `contracts.ts`.
|
|
37
|
+
- **Not** replacing **BFF** read paths — read-only dashboards can keep using **`fetchGraphOperationalDashboardSlice`** from a BFF that holds read-only credentials; **jobs** focuses on **write/claim/execute** and **operator control plane**.
|
|
38
|
+
- **Not** implementing **Catalox** itself — only **clients** of **`@x12i/catalox`** if graph models are catalog-backed.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 4. Architectural boundaries
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
46
|
+
│ @exellix/exellix-jobs │
|
|
47
|
+
│ • process supervision, HTTP, metrics, concurrency limits │
|
|
48
|
+
│ • builds ProcessMatrixClaimDeps + RunMatrixCycleDeps │
|
|
49
|
+
└───────────────┬─────────────────────────────┬───────────────┘
|
|
50
|
+
│ │
|
|
51
|
+
▼ ▼
|
|
52
|
+
┌────────────────────────────────────────────────────────────┐
|
|
53
|
+
│ @exellix/exellix-runtime (≥ 3.1) │
|
|
54
|
+
│ • createExellixMatrixDataTier • ExecutionMatrixRuntime │
|
|
55
|
+
│ • ExellixConfigStore • runMatrixCycle / runMatrixContinuously│
|
|
56
|
+
│ • orchestrator, claim pause │
|
|
57
|
+
└───────────────┬────────────────────────────────────────────┘
|
|
58
|
+
│
|
|
59
|
+
▼
|
|
60
|
+
┌───────────────────────────┐ ┌──────────────────────────┐
|
|
61
|
+
│ @exellix/graph-engine │ │ @x12i/catalox (optional) │
|
|
62
|
+
│ • executeGraph │ │ • graph / matrix catalog │
|
|
63
|
+
└────────────────────────────┘ └──────────────────────────┘
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Data flow:** Jobs opens **`createExellixMatrixDataTier`** on runtime → obtains collection handles → passes them into worker **`createMatrixWorker`** (which builds **`ExecutionMatrixRuntime`** + **`ExellixConfigStore`** internally). Jobs then drives **`runMatrixContinuously`** via **`ProcessMatrixClaimDeps`**.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 5. Dependencies (required)
|
|
71
|
+
|
|
72
|
+
| Package | Semver policy | Usage |
|
|
73
|
+
|---------|----------------|--------|
|
|
74
|
+
| **`@exellix/exellix-runtime`** | **`^3.1.0`** | `createExellixMatrixDataTier`, `ExecutionMatrixRuntime`, `createExecutionMatrixRuntime`, `ExellixConfigStore`, `createExellixConfigStore`, `runMatrixCycle`, `runMatrixContinuously`, `processMatrixGraphBatch`, `processNextMatrixClaim`, `createExecutionMatrixClaimPauseController`, `buildExellixRuntimeRealLivenessPayload`, `runExellixRuntimeConnectivityProbe`, types from `contracts` |
|
|
75
|
+
| **`@exellix/graph-engine`** | **≥ 5.16** (via runtime) | **`executeGraph`** — host supplies executor; record on **`runtime.input`** only (see §6.1) |
|
|
76
|
+
| **`@x12i/catalox`** | Optional | Only if **`resolveGraphModel`** loads graph JSON from Firestore catalogs |
|
|
77
|
+
| **`@x12i/env`** | Optional | `initConfig` for `.env` in dev |
|
|
78
|
+
|
|
79
|
+
**Forbidden direct deps:** `mongodb`, `@x12i/xronox-store`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 6. Bootstrap sequence (normative)
|
|
84
|
+
|
|
85
|
+
Normative bootstrap (runtime **3.1+**):
|
|
86
|
+
|
|
87
|
+
1. **`initConfig`** (optional) — load `.env` for local dev.
|
|
88
|
+
2. **`const matrixTier = await createExellixMatrixDataTier({ … })`** from **`@exellix/exellix-runtime`** — `await matrixTier.init()` when defined.
|
|
89
|
+
3. **Extract handles:** `rows`, `failures`, `snapshots`, `matrices`, `graphs` (or facades that implement the same method contract **`@exellix/exellix-runtime`** expects — see `runtime-store.ts` / `config-store.ts`).
|
|
90
|
+
4. **`const runtime = createExecutionMatrixRuntime(rows, failures, { snapshots, serializeClaims: true })`**.
|
|
91
|
+
5. **`const configStore = createExellixConfigStore({ matrices, graphs })`**.
|
|
92
|
+
6. **Build graph executor** — host function wrapping **`executeGraph`** with any adapter (**`createMatrixExecuteGraphAdapter`** from runtime).
|
|
93
|
+
7. **Build `ProcessMatrixClaimDeps`:**
|
|
94
|
+
- `runtime`, `executeGraph`, `resolveGraphModel` (and/or `graphModel`),
|
|
95
|
+
- `resolveGraphSchedulability: (graphId) => configStore.graphs.get(graphId)`,
|
|
96
|
+
- `graphSchedulabilityRequireConfig` per deploy policy,
|
|
97
|
+
- `shouldClaimNext` from **`createExecutionMatrixClaimPauseController`** or custom,
|
|
98
|
+
- optional `matrixActivix`, `matrixLogxer`, `executeOverrides`, `runtimeScope`, `sourceResolver` for **`runMatrixCycle`**.
|
|
99
|
+
8. **Start supervisor loop** (§8) or **HTTP server** (§9) that drives **`runMatrixContinuously`** / **`runMatrixCycle`** per assignment (§7).
|
|
100
|
+
|
|
101
|
+
**Shutdown:** `signal` abort → await in-flight graph tasks → `await matrixTier.close()` → `process.exit(0)`.
|
|
102
|
+
|
|
103
|
+
### 6.1 Graph execution input (record placement)
|
|
104
|
+
|
|
105
|
+
Normative peer contract (flat `runtime.input`, no top-level `input`, no `input.raw`): [`../../exellix-runtime/docs/graph-execution-record-placement.md`](../../exellix-runtime/docs/graph-execution-record-placement.md).
|
|
106
|
+
|
|
107
|
+
Worker-specific rules:
|
|
108
|
+
|
|
109
|
+
1. **`@exellix/exellix-jobs` never constructs `ExecuteGraphInput`** — it imports `runMatrixCycle` / `processMatrixGraphBatch` from **`@exellix/exellix-runtime`** and wraps the host `executeGraph` only for concurrency and metrics ([`wrap-execute-graph.ts`](../src/worker/wrap-execute-graph.ts)).
|
|
110
|
+
2. **Materialize** — runtime persists the work-unit on **`ExecutionMatrixRuntimeRecord.input`**. The host must ensure **`sourceResolver`** / **`inputRows`** produce **flat** field names aligned with graph `graphEntry.inputs` (e.g. `subnetId`, `question` on the row object — not nested `raw` wrappers).
|
|
111
|
+
3. **Execute** — orchestrator **`executeMatrixGraphForClaim`** sets **`runtime.input = record.input`**, seeds **`executionMemory`** via `buildExecutionSeedFromGraphEntry`, then calls **`executeGraph({ model, runtime })`**.
|
|
112
|
+
4. **Host `executeGraph`** — must accept `{ model, runtime }` only; use **`createMatrixExecuteGraphAdapter`** when layering defaults without overriding orchestrator-owned `jobId` / `job` / `input` / `executionMemory`.
|
|
113
|
+
5. **Dependencies** — **`@exellix/graph-engine` ≥ 5.16** (no implicit `job.raw` → `execution.input.raw` promotion). Align versions with **`@exellix/exellix-runtime`**.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 7. Assignment set — which work to run (algorithm)
|
|
118
|
+
|
|
119
|
+
**Inputs:** `configStore`, optional cache TTL.
|
|
120
|
+
|
|
121
|
+
**Steps (each supervisor tick or on a timer):**
|
|
122
|
+
|
|
123
|
+
1. **`listGraphConfigs`** (or `graphs.list({ limit })`) — filter in memory or via future server-side filter: **`publicationStatus === 'Published'`** and **`operationalState` in `('Ready','Running')`** (exact set is a **product flag** on the worker).
|
|
124
|
+
2. For each **candidate `graphId`**:
|
|
125
|
+
- **`configStore.graphs.get(graphId)`** — re-fetch if using cache; ensure version freshness for operational flips.
|
|
126
|
+
3. **Discover matrices:** **`configStore.matrices.list({ limit })`** (paginate until done) — for each matrix, parse **`payload.graphs`**; if array contains **`graphId`**, enqueue tuple **`(matrixCatalogId, graphId)`**.
|
|
127
|
+
4. For each tuple (and optional **`matrixRunId`** scope from env or config):
|
|
128
|
+
- **`configStore.matrices.getEffectiveConfig(matrixCatalogId)`** or equivalent **`getEffectiveConfig`** on the surface **`@exellix/exellix-runtime`** exposes (`config-store.ts`).
|
|
129
|
+
- Load **graph records** for all **`payload.graphs`** via **`configStore.graphs.get`** in parallel (bounded concurrency).
|
|
130
|
+
5. **Invoke** **`runMatrixCycle(deps, { matrixCatalogId, matrixItemId, matrixRunId, payload, graphRecords, … })`**
|
|
131
|
+
- **`matrixItemId`**: worker config (per-deployment catalog item id) or derived convention documented in worker README.
|
|
132
|
+
- For **`event`** matrices without pushed **`inputRows`**, cycle may be **`emptyPass`** — worker should not spin hot; use **`pollWhenEmptySeconds`** from payload / `runMatrixContinuously`.
|
|
133
|
+
|
|
134
|
+
**Re-fetch rule:** Before **`processMatrixGraphBatch`** or **`processNextMatrixClaim`**, re-run **`resolveGraphSchedulability(graphId)`** so **`operationalState`** changes stop **new** claims quickly; in-flight **`executeGraph`** completes normally.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 8. Concurrency model (normative)
|
|
139
|
+
|
|
140
|
+
| Mechanism | Specification |
|
|
141
|
+
|-----------|----------------|
|
|
142
|
+
| **Global cap** | **`maxConcurrentClaims`** (integer ≥ 1). Implement with **`p-limit`** (or equivalent) around **`executeMatrixGraphForClaim`** entry points invoked from **`processMatrixGraphBatch`** / **`processNextMatrixClaim`**, **or** run batch with **`limit: 1`** and outer parallelism — document chosen pattern. |
|
|
143
|
+
| **Per-graph cap** | Optional **`Record<graphId, number>`** — secondary limiter inside the global pool to avoid one graph starving others. |
|
|
144
|
+
| **Pause / drain** | **`shouldClaimNext`** returns **`false`** → orchestrator stops scheduling **new** claims; **never** abort **`executeGraph`** mid-flight. Prefer **`createExecutionMatrixClaimPauseController`** from runtime for **`pause()` / `resume()`** wired to HTTP or file signal. |
|
|
145
|
+
| **Continuous loop** | **`runMatrixContinuously`** — one loop per **`(matrixCatalogId, matrixItemId, matrixRunId?)`** worker instance; run **N** parallel worker tasks for **N** active matrices if needed. |
|
|
146
|
+
| **Abort** | **`AbortSignal`** on **`runMatrixContinuously`** — cooperative shutdown between cycles and during sleep. |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 9. Matrix kinds vs worker behavior
|
|
151
|
+
|
|
152
|
+
| `dataSource.kind` | Worker responsibility |
|
|
153
|
+
|-------------------|-------------------------|
|
|
154
|
+
| **`record`** / **`storage`** | **`runMatrixCycle`** pulls source ids via **`MatrixSourceResolver`**, diffs, materializes — worker must supply **`sourceResolver`** in **`RunMatrixCycleDeps`**. |
|
|
155
|
+
| **`query-snapshot`** | Cycle runs snapshot transform + persist snapshot + materialize — ensure **VM / sandbox** policy matches host security baseline. |
|
|
156
|
+
| **`event`** | **No** self-scan — **`emptyPass`** unless host passes **`inputRows`** into the cycle options. Worker either **skips** these matrices in autonomous mode or subscribes to an **ingress** that calls **`runMatrixCycle`** with **`inputRows`** (out of scope for minimal v1 — document “event requires ingress”). |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 10. HTTP API (suggested — OpenAPI-friendly)
|
|
161
|
+
|
|
162
|
+
Base path prefix configurable (e.g. **`JOBS_HTTP_PREFIX=/v1`**).
|
|
163
|
+
|
|
164
|
+
### 10.1 Health
|
|
165
|
+
|
|
166
|
+
| Method | Path | Behavior |
|
|
167
|
+
|--------|------|----------|
|
|
168
|
+
| **GET** | `/health` | **Liveness:** process up; body from **`buildExellixRuntimeRealLivenessPayload()`** — **no** dependency I/O required. |
|
|
169
|
+
| **GET** | `/health?probe=dependencies` | **Readiness-style:** call **`runExellixRuntimeConnectivityProbe`** with options allowed **after** [`missing.md`](./missing.md) §1.4 (XMemory-only execution/config probes). Until then, probe only **`includeXmemoryEnv`**, **`includeActivixSchedulerProbe`**, etc., without xronox-shaped stores. |
|
|
170
|
+
|
|
171
|
+
### 10.2 Graph operational control
|
|
172
|
+
|
|
173
|
+
| Method | Path | Body | Behavior |
|
|
174
|
+
|--------|------|------|----------|
|
|
175
|
+
| **PATCH** | `/graphs/:graphId/operational` | `{ operationalState, expectedVersion }` | **`configStore.graphs.patchOperational`** — **401/403** without auth. |
|
|
176
|
+
| **PATCH** | `/graphs/:graphId/publication` | `{ publicationStatus, expectedVersion }` | Same pattern if split from operational. |
|
|
177
|
+
|
|
178
|
+
### 10.3 Discovery
|
|
179
|
+
|
|
180
|
+
| Method | Path | Response (informative) |
|
|
181
|
+
|--------|------|------------------------|
|
|
182
|
+
| **GET** | `/graphs` | List graphs with **`publicationStatus`**, **`operationalState`**, **`version`**, derived **`matrixCatalogIds`** referencing each **`graphId`**. |
|
|
183
|
+
|
|
184
|
+
### 10.4 Worker introspection
|
|
185
|
+
|
|
186
|
+
| Method | Path | Response (informative) |
|
|
187
|
+
|--------|------|------------------------|
|
|
188
|
+
| **GET** | `/workers/status` | **`assignedGraphIds`**, **`activeJobs`**, **`maxConcurrency`**, per-graph in-flight counts, last cycle timestamps, optional **`matrixCatalogId`** filter. |
|
|
189
|
+
|
|
190
|
+
### 10.5 Metrics
|
|
191
|
+
|
|
192
|
+
| Method | Path | Content-Type |
|
|
193
|
+
|--------|------|----------------|
|
|
194
|
+
| **GET** | `/metrics` | `text/plain; version=0.0.4` — Prometheus exposition format (§11). |
|
|
195
|
+
|
|
196
|
+
**Auth:** All mutating routes **must** require authentication (API key header, mTLS, or reverse-proxy auth — specify in deploy doc).
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 11. Metrics (minimum set)
|
|
201
|
+
|
|
202
|
+
Counters / histograms (names are **suggestions** — keep stable after v1):
|
|
203
|
+
|
|
204
|
+
| Metric | Type | Labels | When incremented |
|
|
205
|
+
|--------|------|--------|------------------|
|
|
206
|
+
| `matrix_claims_started_total` | counter | `graph_id`, `matrix_catalog_id` | After successful **claim** before **`executeGraph`**. |
|
|
207
|
+
| `matrix_claims_completed_total` | counter | `graph_id`, `status` | After **`executeGraph`** terminal success (graph-engine status). |
|
|
208
|
+
| `matrix_claims_failed_total` | counter | `graph_id` | Executor throw or persisted failure path. |
|
|
209
|
+
| `matrix_rows_materialized_total` | counter | `matrix_catalog_id` | After **`insertRuntimeRow`** from materializer in a cycle. |
|
|
210
|
+
| `matrix_worker_idle_seconds_total` | counter | `matrix_catalog_id` | Time spent in **`emptyPass`** cycles (or histogram of idle duration per cycle). |
|
|
211
|
+
| `matrix_worker_cycles_total` | counter | `matrix_catalog_id`, `empty` (`true`/`false`)` | Each **`runMatrixCycle`** completion. |
|
|
212
|
+
|
|
213
|
+
Optional: **`histogram`** for **`executeGraph`** duration per **`graph_id`**.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 12. Configuration (environment variables)
|
|
218
|
+
|
|
219
|
+
| Variable | Purpose | Default |
|
|
220
|
+
|----------|---------|---------|
|
|
221
|
+
| **`MONGO_URI`** | Passed only into **XMemory** factories — jobs does not parse for its own driver. | — |
|
|
222
|
+
| **`execution_db`** | Operational DB name | `exellix-runtime` |
|
|
223
|
+
| **`config_db`** | Config DB name | `exellix` |
|
|
224
|
+
| **`PORT`** | HTTP listen port | e.g. `8080` |
|
|
225
|
+
| **`MATRIX_WORKER_ID`** | Identity in health / scheduler lane | optional |
|
|
226
|
+
| **`EXELLIX_JOBS_MAX_CONCURRENT_CLAIMS`** | Global parallelism | `4` |
|
|
227
|
+
| **`EXELLIX_JOBS_GRAPH_SCHEDULABILITY_STRICT`** | Maps to **`graphSchedulabilityRequireConfig`** | `false` |
|
|
228
|
+
| **`EXELLIX_JOBS_HTTP_ENABLED`** | Start HTTP server | `true` |
|
|
229
|
+
| **`EXELLIX_JOBS_ACTIVIX_SCHEDULER_PROBE`** | Include Activix scheduler lane on **`/health?probe=dependencies`** | `false` |
|
|
230
|
+
|
|
231
|
+
(Extend as needed; document every env key in **jobs** README.)
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 13. Activix (optional)
|
|
236
|
+
|
|
237
|
+
If the product keeps **Activix** boundary activities:
|
|
238
|
+
|
|
239
|
+
- Pass **`matrixActivix`** on **`ProcessMatrixClaimDeps`** per **`orchestrator.ts`**.
|
|
240
|
+
- Health lane: reuse **`testActivixMongoConnection`** / **`getActivixActivityPersistenceSnapshotInMongo`** from **`@x12i/activix`** as **`@exellix/exellix-runtime`** already does in **`runtime-connectivity-health.ts`** — **jobs** calls the same probe builder with **`includeActivixSchedulerProbe: true`** when configured.
|
|
241
|
+
|
|
242
|
+
**Jobs** does **not** fork Activix behavior into runtime.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 14. CLI split (decision framework)
|
|
247
|
+
|
|
248
|
+
| Option | Pros | Cons |
|
|
249
|
+
|--------|------|------|
|
|
250
|
+
| **Keep `catalox:inventory` in `@exellix/exellix-runtime`** | Single devDependency for library consumers | Mixes library + operator CLI |
|
|
251
|
+
| **Move to `@exellix/exellix-jobs`** | Operator tooling colocated with worker | Runtime tarball has no inventory script |
|
|
252
|
+
| **`@exellix/cli` meta-package** | Clear “tools” boundary | Extra package to release |
|
|
253
|
+
|
|
254
|
+
**Spec requirement:** pick one in **jobs** v1 README and link from **`@exellix/exellix-runtime`** `package.json` `scripts` if moved.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 15. High availability
|
|
259
|
+
|
|
260
|
+
- **Multiple replicas** claiming the **same** **`(matrixCatalogId, graphId)`** without coordination **will** double-execute unless an external leader elector or partition strategy exists — **document:** default deployment is **one active worker per matrix scope** OR use **`serializeClaims: true`** (runtime default) + external single-claimer guarantee.
|
|
261
|
+
- **Recommended:** one Deployment per **matrix family** or global worker with **fair** assignment — product choice.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 16. Testing strategy
|
|
266
|
+
|
|
267
|
+
| Layer | Tests |
|
|
268
|
+
|-------|--------|
|
|
269
|
+
| **Unit** | Mock **`ExecutionMatrixRuntime`** / **`ExellixConfigStore`** with in-memory fakes from **`@exellix/exellix-runtime`** `memory-store` patterns (if exported) or local test doubles. |
|
|
270
|
+
| **Integration** | Testcontainers or shared dev cluster — **only** through **XMemory** matrix tier (once §1.1 exists). |
|
|
271
|
+
| **Contract** | HTTP golden files for **`/health`** JSON shape. |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 17. Versioning
|
|
276
|
+
|
|
277
|
+
- **`@exellix/exellix-jobs` major** tracks **`@exellix/exellix-runtime`** breaking changes to **`ProcessMatrixClaimDeps`**, **`RunMatrixCycleDeps`**, or factory signatures.
|
|
278
|
+
- **Changelog** must call out required **`@exellix/exellix-runtime`** minimum (e.g. **`^3.1.0`** for **`createExellixMatrixDataTier`**).
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 18. Acceptance checklist (jobs package)
|
|
283
|
+
|
|
284
|
+
- [x] No **`mongodb`** / **`@x12i/xronox-store`** in **jobs** `package.json` or source.
|
|
285
|
+
- [x] Matrix persistence opened via **`createExellixMatrixDataTier`** on **`@exellix/exellix-runtime` ≥ 3.1**; **`openExellixMatrixPersistence()`** may fall back to internal xronox factories.
|
|
286
|
+
- [x] **`ProcessMatrixClaimDeps.resolveGraphSchedulability`** wired to **`configStore.graphs.get`**.
|
|
287
|
+
- [x] **`shouldClaimNext`** + graceful shutdown verified.
|
|
288
|
+
- [x] **`/health`** + **`/metrics`** implemented; **`PATCH`** routes auth-protected.
|
|
289
|
+
- [x] Prometheus metrics from §11 exposed.
|
|
290
|
+
- [x] README links **`execution-matrix-worker-service.md`** as **historical** and points here as normative.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 19. Reference — runtime source files
|
|
295
|
+
|
|
296
|
+
| Concern | Path |
|
|
297
|
+
|---------|------|
|
|
298
|
+
| Claim deps | `src/execution-matrix/orchestrator.ts` — **`ProcessMatrixClaimDeps`** |
|
|
299
|
+
| Loops | `src/execution-matrix/run-loop.ts` — **`runMatrixCycle`**, **`runMatrixContinuously`** |
|
|
300
|
+
| Pause | `src/execution-matrix/execution-matrix-claim-pause.ts` |
|
|
301
|
+
| Graph schedulability | `src/execution-matrix/graph-operational-lifecycle.ts` |
|
|
302
|
+
| Health | `src/execution-matrix/runtime-connectivity-health.ts` |
|
|
303
|
+
| Prior worker narrative | `docs/execution-matrix-worker-service.md` |
|
|
304
|
+
|
|
305
|
+
When **`missing.md`** §1.1 is implemented, replace any remaining references in this spec to **`createExecutionMatrixRuntimeFromXronox`** with the **XMemory matrix tier** bootstrap (§6).
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Troubleshooting — `@exellix/exellix-jobs`
|
|
2
|
+
|
|
3
|
+
Quick fixes for local `exellix-jobs serve`, operator HTTP, and “why isn’t my graph running?”
|
|
4
|
+
|
|
5
|
+
**Related:** [openapi.yaml](./openapi.yaml) (routes), [README](../README.md), [`@exellix/exellix-runtime` client-integration](https://github.com/exellix/exellix-runtime/blob/main/docs/client-integration.md) (materialize + matrix domain).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Five-minute checklist
|
|
10
|
+
|
|
11
|
+
| Check | Command / where | Healthy |
|
|
12
|
+
|-------|------------------|---------|
|
|
13
|
+
| Server up | `curl -s http://127.0.0.1:$PORT/v1/health` | `"ok": true` |
|
|
14
|
+
| Mongo wired | Logs: `Successfully connected` to `exellix-runtime` + `exellix` | Both DBs |
|
|
15
|
+
| Graph listed | `curl -s http://127.0.0.1:$PORT/v1/graphs` | Your `graphId` appears |
|
|
16
|
+
| On a matrix | Same response: `matrixCatalogIds` | **Non-empty** array |
|
|
17
|
+
| Runnable state | Graph record in Compass | `Published` + `Ready` or `Running`, **no** `deletedAt` |
|
|
18
|
+
| Work exists | DB `exellix-runtime` → `exellix_execution_matrix_rows` | Rows for your matrix/graph |
|
|
19
|
+
| Real executor | Logs | **Not** `graph-engine-stub.mjs` for production |
|
|
20
|
+
|
|
21
|
+
`exellix-jobs` **claims and executes** rows; it does **not** create matrix configs or materialize rows (Studio / BFF / script does that).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. Operator HTTP
|
|
26
|
+
|
|
27
|
+
Base URL: `http://127.0.0.1:<PORT><JOBS_HTTP_PREFIX>` (default prefix `/v1`).
|
|
28
|
+
|
|
29
|
+
### `404` — `Route PATCH:/v1/graphs/{graphId} not found`
|
|
30
|
+
|
|
31
|
+
**Symptom:** Fastify message includes `Route PATCH:/v1/graphs/... not found`.
|
|
32
|
+
|
|
33
|
+
**Cause:** Wrong path. There is **no** `PATCH /v1/graphs/:graphId`.
|
|
34
|
+
|
|
35
|
+
**Fix:** Use the suffix:
|
|
36
|
+
|
|
37
|
+
| Intent | Method | Path |
|
|
38
|
+
|--------|--------|------|
|
|
39
|
+
| Operational state | `PATCH` | `/v1/graphs/{graphId}/operational` |
|
|
40
|
+
| Publication status | `PATCH` | `/v1/graphs/{graphId}/publication` |
|
|
41
|
+
| Pause claims | `POST` | `/v1/workers/pause` |
|
|
42
|
+
| Resume claims | `POST` | `/v1/workers/resume` |
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
curl -X PATCH "http://127.0.0.1:5173/v1/graphs/graph-qcrbz6t/operational" \
|
|
46
|
+
-H "x-exellix-jobs-api-key: $EXELLIX_JOBS_API_KEY" \
|
|
47
|
+
-H "content-type: application/json" \
|
|
48
|
+
-d '{"expectedVersion":1,"operationalState":"Running"}'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Reads (`GET /health`, `/graphs`, `/workers/status`, `/metrics`) need **no** API key.
|
|
52
|
+
|
|
53
|
+
### `501` — mutations disabled
|
|
54
|
+
|
|
55
|
+
**Symptom:** `Mutations are disabled until EXELLIX_JOBS_API_KEY is configured`.
|
|
56
|
+
|
|
57
|
+
**Fix:** Add to `.env` and restart serve:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
EXELLIX_JOBS_API_KEY=<your-secret>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Send the same value as `x-exellix-jobs-api-key: …` or `Authorization: Bearer …`.
|
|
64
|
+
|
|
65
|
+
### `401` Unauthorized
|
|
66
|
+
|
|
67
|
+
**Cause:** Header missing or does not match `EXELLIX_JOBS_API_KEY`.
|
|
68
|
+
|
|
69
|
+
**Fix:** Match `.env` exactly; restart after changing `.env`.
|
|
70
|
+
|
|
71
|
+
### `409` / concurrency errors on PATCH
|
|
72
|
+
|
|
73
|
+
**Cause:** `expectedVersion` in the body does not match the document’s `version` in Mongo.
|
|
74
|
+
|
|
75
|
+
**Fix:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -s "http://127.0.0.1:5173/v1/graphs"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use the `version` for your `graphId` in the next PATCH (increment after each successful patch).
|
|
82
|
+
|
|
83
|
+
### `ERR_MODULE_NOT_FOUND` for graph bootstrap
|
|
84
|
+
|
|
85
|
+
**Symptom:** Cannot find `C:/path/to/your/graph-engine-bootstrap.mjs`.
|
|
86
|
+
|
|
87
|
+
**Cause:** Placeholder path from docs was exported literally.
|
|
88
|
+
|
|
89
|
+
**Fix:** Unset `EXELLIX_JOBS_GRAPH_ENGINE_BOOTSTRAP` (uses stub), or point at a real file:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
export EXELLIX_JOBS_GRAPH_ENGINE_BOOTSTRAP="C:/prometheus/exellix/exellix-jobs/examples/graph-engine-stub.mjs"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 3. Mongo — two databases
|
|
98
|
+
|
|
99
|
+
| DB (`env`) | Default name | Collections | Role |
|
|
100
|
+
|------------|--------------|-------------|------|
|
|
101
|
+
| `config_db` | `exellix` | `exellix_graph_configs`, `exellix_matrix_configs` | **What** to run (graphs + matrix definition) |
|
|
102
|
+
| `execution_db` | `exellix-runtime` | `exellix_execution_matrix_rows`, failures, snapshots | **Work units** to claim |
|
|
103
|
+
|
|
104
|
+
BFF, Studio, jobs, and runtime smoke tests must share the same `MONGO_URI`, `execution_db`, and `config_db`.
|
|
105
|
+
|
|
106
|
+
### Which id to use in Compass vs HTTP
|
|
107
|
+
|
|
108
|
+
| Field | Meaning | Example |
|
|
109
|
+
|-------|---------|---------|
|
|
110
|
+
| `_id` | Mongo ObjectId | Ignore for APIs |
|
|
111
|
+
| `graphId` | Graph config primary key | `graph-qcrbz6t` — use in PATCH paths |
|
|
112
|
+
| `matrixCatalogId` | Matrix config primary key | `live_matrix_…` — use in materialize / worker status |
|
|
113
|
+
| `payload.graphId` | Must equal top-level `graphId` | Same as `graphId` |
|
|
114
|
+
|
|
115
|
+
**`matrixCatalogId`** is the matrix’s catalog id (one batch definition listing `payload.graphs`). It is **not** the graph id. Find it in `exellix_matrix_configs.matrixCatalogId`.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 4. Graph config (`exellix_graph_configs`)
|
|
120
|
+
|
|
121
|
+
### Schedulable state (worker will consider the graph)
|
|
122
|
+
|
|
123
|
+
| Field | Required value |
|
|
124
|
+
|-------|----------------|
|
|
125
|
+
| `publicationStatus` | `Published` |
|
|
126
|
+
| `operationalState` | `Ready` or `Running` |
|
|
127
|
+
| `deletedAt` | **absent** |
|
|
128
|
+
| `tombstoneId` | **absent** |
|
|
129
|
+
|
|
130
|
+
Defaults if omitted: `Draft` + `NotOperational` → **not** schedulable.
|
|
131
|
+
|
|
132
|
+
### Healthy example (`graph-qcrbz6t`)
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"graphId": "graph-qcrbz6t",
|
|
137
|
+
"version": 1,
|
|
138
|
+
"payload": {
|
|
139
|
+
"graphId": "graph-qcrbz6t",
|
|
140
|
+
"mappedInput": [{ "tag": "two" }],
|
|
141
|
+
"execution": { "priority": 20 }
|
|
142
|
+
},
|
|
143
|
+
"publicationStatus": "Published",
|
|
144
|
+
"operationalState": "Running",
|
|
145
|
+
"createdAt": "2026-05-17T06:11:26.354Z",
|
|
146
|
+
"updatedAt": "2026-05-17T06:11:28.850Z"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Common bad records
|
|
151
|
+
|
|
152
|
+
| Symptom | What’s wrong | Fix |
|
|
153
|
+
|---------|--------------|-----|
|
|
154
|
+
| `Published` + `Running` but worker ignores | `deletedAt` / `tombstoneId` set | Create a **new** graph config from Studio; don’t reuse tombstone |
|
|
155
|
+
| Validation / weird PATCH | `graphId` ≠ `payload.graphId` | Align both to the same string |
|
|
156
|
+
| PATCH 404 (correct URL) | Graph row missing | Create config or fix `graphId` key |
|
|
157
|
+
| `matrixCatalogIds: []` in `/v1/graphs` | Graph not on any matrix | Add `graphId` to `exellix_matrix_configs.payload.graphs` |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 5. Matrix config (`exellix_matrix_configs`)
|
|
162
|
+
|
|
163
|
+
Minimal shape:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"matrixCatalogId": "my-matrix-1",
|
|
168
|
+
"version": 1,
|
|
169
|
+
"payload": {
|
|
170
|
+
"input": {},
|
|
171
|
+
"graphs": ["graph-qcrbz6t"]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`payload.graphs` must be a **non-empty** list of existing `graphId` values.
|
|
177
|
+
|
|
178
|
+
**Linking an existing graph:** `matrices.update` with `expectedVersion` and `graphs` including your id — via Studio, BFF, or a small runtime script (see [client-integration §6](https://github.com/exellix/exellix-runtime/blob/main/docs/client-integration.md) in exellix-runtime).
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 6. Worker idle / nothing executes
|
|
183
|
+
|
|
184
|
+
| Cause | How to confirm | Fix |
|
|
185
|
+
|-------|----------------|-----|
|
|
186
|
+
| No rows | `exellix_execution_matrix_rows` empty | **Materialize** + `insertRuntimeRow` (not jobs) |
|
|
187
|
+
| Graph not schedulable | Draft / NotOperational / deleted | PATCH publication + operational, or Studio |
|
|
188
|
+
| Graph not on matrix | `/v1/graphs` → `matrixCatalogIds: []` | Update matrix `payload.graphs` |
|
|
189
|
+
| Stub executor | Log: `graph-engine-stub.mjs` | Set real `EXELLIX_JOBS_GRAPH_ENGINE_BOOTSTRAP` |
|
|
190
|
+
| Worker paused | POST pause was called | `POST /v1/workers/resume` with API key |
|
|
191
|
+
| Wrong Mongo | Different `MONGO_URI` / db names | Align `.env` across Studio, jobs, BFF |
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
curl -s "http://127.0.0.1:5173/v1/workers/status"
|
|
195
|
+
curl -s "http://127.0.0.1:5173/v1/health?probe=dependencies"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 7. “Server runs” vs “runs for real”
|
|
201
|
+
|
|
202
|
+
| Layer | Real? | Signal |
|
|
203
|
+
|-------|-------|--------|
|
|
204
|
+
| HTTP + Mongo | Yes | `listening on port …`, Mongo connect logs |
|
|
205
|
+
| Config PATCH | Yes | Correct `/operational` / `/publication` paths |
|
|
206
|
+
| Matrix execution | Only if rows + schedulable graph + real `executeGraph` | Rows in `exellix_execution_matrix_rows`; not stub bootstrap |
|
|
207
|
+
|
|
208
|
+
Default bootstrap uses **`examples/graph-engine-stub.mjs`** — always returns fake `completed`. That is intentional for smoke tests.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 8. Useful commands (no `jq`)
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Health
|
|
216
|
+
curl -s "http://127.0.0.1:5173/v1/health"
|
|
217
|
+
|
|
218
|
+
# One graph from list
|
|
219
|
+
curl -s "http://127.0.0.1:5173/v1/graphs" | node -e "
|
|
220
|
+
const g=JSON.parse(require('fs').readFileSync(0,'utf8'));
|
|
221
|
+
console.log(g.find(x=>x.graphId==='graph-qcrbz6t') ?? 'not found');
|
|
222
|
+
"
|
|
223
|
+
|
|
224
|
+
# Worker status (optional matrix filter)
|
|
225
|
+
curl -s "http://127.0.0.1:5173/v1/workers/status?matrixCatalogId=my-matrix-1"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 9. Error message → meaning
|
|
231
|
+
|
|
232
|
+
| Message | Meaning |
|
|
233
|
+
|---------|---------|
|
|
234
|
+
| `Route PATCH:/v1/graphs/... not found` | Missing `/operational` or `/publication` |
|
|
235
|
+
| `Route … not found` (other) | Wrong prefix/port; check `PORT` and `JOBS_HTTP_PREFIX` |
|
|
236
|
+
| `Mutations are disabled until EXELLIX_JOBS_API_KEY` | Set key in `.env`, restart |
|
|
237
|
+
| `Unauthorized` | Wrong or missing API key header |
|
|
238
|
+
| `expectedVersion (number) is required` | PATCH body needs numeric `expectedVersion` |
|
|
239
|
+
| `ConfigStoreConcurrencyError` / version mismatch | Stale `expectedVersion` |
|
|
240
|
+
| `ConfigStoreNotFoundError` | No graph row for that `graphId` |
|
|
241
|
+
| `GraphNotSchedulableError` (logs) | Not Published or not Ready/Running |
|
|
242
|
+
| `using default graph-engine bootstrap … stub` | Not running real graphs |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 10. Still stuck?
|
|
247
|
+
|
|
248
|
+
1. Capture: `curl /v1/health`, `/v1/graphs`, `/v1/workers/status`.
|
|
249
|
+
2. Compass screenshots: one `exellix_graph_configs` doc, one `exellix_matrix_configs` doc, count of `exellix_execution_matrix_rows`.
|
|
250
|
+
3. Confirm peer: `@exellix/exellix-runtime` **^3.1.0** (`buildId` in `/health`).
|
|
251
|
+
|
|
252
|
+
For materialize and Catalox wiring, use **exellix-runtime** docs — jobs only operates on data that already exists in Mongo.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal graph-engine bootstrap for local smoke tests.
|
|
3
|
+
* Replace with a real module in production (Catalox + graph-engine wiring).
|
|
4
|
+
*
|
|
5
|
+
* Contract: executeGraph({ model, runtime }) — record on runtime.input (flat keys).
|
|
6
|
+
* See ../exellix-runtime/docs/graph-execution-record-placement.md
|
|
7
|
+
*/
|
|
8
|
+
export default async function graphEngineBootstrap() {
|
|
9
|
+
return {
|
|
10
|
+
executeGraph: async (input) => {
|
|
11
|
+
if ('input' in input && input.input !== undefined) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'graph-engine-stub: invalid request — use { model, runtime } only; put the record on runtime.input',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
const record = input.runtime?.input;
|
|
17
|
+
const matrixRowId = input.runtime?.job?.matrixRowId;
|
|
18
|
+
if (matrixRowId && record && typeof record === 'object') {
|
|
19
|
+
// Smoke: matrix orchestrator forwarded row.input → runtime.input (flat fields).
|
|
20
|
+
const flat = record;
|
|
21
|
+
if (flat.subnetId !== undefined || flat.question !== undefined) {
|
|
22
|
+
console.error('@exellix/exellix-jobs stub: runtime.input keys', Object.keys(flat));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
status: 'completed',
|
|
27
|
+
jobId: typeof input.runtime?.jobId === 'string' ? input.runtime.jobId : 'stub-job',
|
|
28
|
+
taskId: 'stub-task',
|
|
29
|
+
graphId: typeof input.model?.id === 'string' ? input.model.id : 'stub-graph',
|
|
30
|
+
outputsByNodeId: {},
|
|
31
|
+
stepsResponses: [],
|
|
32
|
+
engineSnapshot: {},
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
resolveGraphModel: async ({ graphId }) => ({
|
|
36
|
+
id: graphId,
|
|
37
|
+
nodes: [],
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example host bootstrap for `exellix-jobs serve`.
|
|
3
|
+
*
|
|
4
|
+
* Copy or symlink this file and set:
|
|
5
|
+
* EXELLIX_JOBS_HOST_BOOTSTRAP=/absolute/path/to/host-bootstrap.mjs
|
|
6
|
+
*
|
|
7
|
+
* Requires:
|
|
8
|
+
* MONGO_URI
|
|
9
|
+
* EXELLIX_JOBS_GRAPH_ENGINE_BOOTSTRAP — ESM module default-exporting async () => ({ executeGraph, resolveGraphModel?, sourceResolver? })
|
|
10
|
+
*
|
|
11
|
+
* Record placement: ../exellix-runtime/docs/graph-execution-record-placement.md
|
|
12
|
+
* Matrix rows store flat fields on row.input; orchestrator sets runtime.input = record.input at claim.
|
|
13
|
+
*/
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
|
+
import { dirname, join, resolve } from 'node:path';
|
|
16
|
+
import { createExellixMatrixDataTier } from '@exellix/exellix-runtime';
|
|
17
|
+
import { loadJobsEnv } from '@exellix/exellix-jobs';
|
|
18
|
+
|
|
19
|
+
const examplesDir = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const defaultGraphBootstrap = join(examplesDir, 'graph-engine-stub.mjs');
|
|
21
|
+
|
|
22
|
+
async function loadGraphEngineBootstrap() {
|
|
23
|
+
const path = process.env.EXELLIX_JOBS_GRAPH_ENGINE_BOOTSTRAP ?? defaultGraphBootstrap;
|
|
24
|
+
if (!process.env.EXELLIX_JOBS_GRAPH_ENGINE_BOOTSTRAP) {
|
|
25
|
+
console.error(`@exellix/exellix-jobs: using default graph-engine bootstrap ${path}`);
|
|
26
|
+
}
|
|
27
|
+
const href = path.startsWith('file:') ? path : pathToFileURL(resolve(path)).href;
|
|
28
|
+
const mod = await import(href);
|
|
29
|
+
if (typeof mod.default !== 'function') {
|
|
30
|
+
throw new Error('Graph-engine bootstrap must default-export an async function.');
|
|
31
|
+
}
|
|
32
|
+
return mod.default();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default async function bootstrap() {
|
|
36
|
+
const env = loadJobsEnv();
|
|
37
|
+
const tier = await createExellixMatrixDataTier({
|
|
38
|
+
mongoUri: process.env.MONGO_URI,
|
|
39
|
+
executionDb: env.executionDb,
|
|
40
|
+
configDb: env.configDb,
|
|
41
|
+
});
|
|
42
|
+
if (tier.init) {
|
|
43
|
+
await tier.init();
|
|
44
|
+
}
|
|
45
|
+
const persistence = {
|
|
46
|
+
rows: tier.rows,
|
|
47
|
+
failures: tier.failures,
|
|
48
|
+
snapshots: tier.snapshots,
|
|
49
|
+
matrices: tier.matrices,
|
|
50
|
+
graphs: tier.graphs,
|
|
51
|
+
close: () => tier.close(),
|
|
52
|
+
...(tier.probe ? { probe: tier.probe } : {}),
|
|
53
|
+
};
|
|
54
|
+
const graph = await loadGraphEngineBootstrap();
|
|
55
|
+
// Production: wrap graph.executeGraph with createMatrixExecuteGraphAdapter from
|
|
56
|
+
// @exellix/exellix-runtime (see docs/execution-matrix-runtime.md) so modelConfig defaults
|
|
57
|
+
// merge under orchestrator-owned jobId / job / input / executionMemory.
|
|
58
|
+
return {
|
|
59
|
+
workerOptions: {
|
|
60
|
+
persistence,
|
|
61
|
+
executeGraph: graph.executeGraph,
|
|
62
|
+
...(graph.resolveGraphModel ? { resolveGraphModel: graph.resolveGraphModel } : {}),
|
|
63
|
+
...(graph.sourceResolver ? { sourceResolver: graph.sourceResolver } : {}),
|
|
64
|
+
matrixWorkerId: env.matrixWorkerId,
|
|
65
|
+
maxConcurrentClaims: env.maxConcurrentClaims,
|
|
66
|
+
graphSchedulabilityRequireConfig: env.graphSchedulabilityStrict,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// When run directly: `node examples/host-bootstrap.mjs` prints resolved paths for local dev.
|
|
72
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
|
|
73
|
+
console.error('host-bootstrap module path:', fileURLToPath(import.meta.url));
|
|
74
|
+
}
|