@kici-dev/orchestrator 0.1.10 → 0.1.12
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/agent/dispatcher.d.ts +1 -1
- package/dist/cli/service/types.d.ts +13 -0
- package/dist/cli.js +159 -106
- package/dist/cluster/coordinator.d.ts +11 -1
- package/dist/cluster/peer-handler.d.ts +3 -1
- package/dist/db/migrations/024_dispatch_queue_provisioning_error.d.ts +18 -0
- package/dist/db/types.d.ts +7 -0
- package/dist/metrics/prometheus.d.ts +28 -1
- package/dist/queue/job-queue.d.ts +2 -0
- package/dist/reporting/execution-tracker.d.ts +2 -2
- package/dist/scaler/index.d.ts +1 -1
- package/dist/scaler/manager.d.ts +16 -3
- package/dist/scaler/types.d.ts +10 -6
- package/dist/server.js +1565 -1335
- package/dist/standalone.js +1419 -1189
- package/dist/startup-backoff.d.ts +24 -0
- package/package.json +5 -4
- package/sbom.spdx.json +36 -36
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - "Peers report step-by-step progress back to coordinator"
|
|
11
11
|
* - "Cancel mode: graceful -- finish current step, cancel remaining"
|
|
12
12
|
*/
|
|
13
|
-
import type { JobReroute, JobProgress, PeerToPeerMessage, ResourceRequest } from '@kici-dev/engine';
|
|
13
|
+
import type { JobReroute, JobProgress, PeerScalerEvent, PeerToPeerMessage, ResourceRequest } from '@kici-dev/engine';
|
|
14
14
|
import type { PeerRegistry } from './peer-registry.js';
|
|
15
15
|
import type { PeerClient } from './peer-client.js';
|
|
16
16
|
import type { Dispatcher } from '../agent/dispatcher.js';
|
|
@@ -139,6 +139,16 @@ export declare class RunCoordinator {
|
|
|
139
139
|
* `onStepStatus` and the run stayed in `running` forever.
|
|
140
140
|
*/
|
|
141
141
|
onPeerJobProgress(msg: JobProgress): void;
|
|
142
|
+
/**
|
|
143
|
+
* Handle a scaler provisioning event forwarded by a worker peer.
|
|
144
|
+
*
|
|
145
|
+
* Workers have no database, so they relay scaler events for jobs the
|
|
146
|
+
* worker is provisioning to the coordinator that owns the run. The
|
|
147
|
+
* coordinator's ExecutionTracker persists the event (provisioning log
|
|
148
|
+
* line + dispatch-queue last-error column) just as it would for a
|
|
149
|
+
* locally-emitted scaler event.
|
|
150
|
+
*/
|
|
151
|
+
onPeerScalerEvent(msg: PeerScalerEvent): void;
|
|
142
152
|
/**
|
|
143
153
|
* Handle peer job completion. Updates ExecutionTracker.
|
|
144
154
|
* The execution tracker's onJobStatus fires the needs-aware scheduler hook,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* proof (reconnection), registers the peer in PeerRegistry, and routes
|
|
7
7
|
* messages bidirectionally. Sends periodic heartbeats to the connecting peer.
|
|
8
8
|
*/
|
|
9
|
-
import { type PeerHeartbeat, type PeerToPeerMessage, type JobReroute, type JobProgress, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, type PeerLeaving, type PeerAgentTokenRevoke, type RaftVoteRequest, type RaftVoteResponse, type RaftAppendEntries } from '@kici-dev/engine';
|
|
9
|
+
import { type PeerHeartbeat, type PeerToPeerMessage, type JobReroute, type JobProgress, type PeerScalerEvent, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, type PeerLeaving, type PeerAgentTokenRevoke, type RaftVoteRequest, type RaftVoteResponse, type RaftAppendEntries } from '@kici-dev/engine';
|
|
10
10
|
import type { PeerRegistry } from './peer-registry.js';
|
|
11
11
|
import type { PeerCredentialStore } from './peer-credentials.js';
|
|
12
12
|
import { type JoinTokenManager } from './join-token.js';
|
|
@@ -41,6 +41,8 @@ export interface PeerHandlerDeps {
|
|
|
41
41
|
onJobReroute: (msg: JobReroute) => Promise<void>;
|
|
42
42
|
/** Callback when a job progress update is received from peer. */
|
|
43
43
|
onJobProgress: (msg: JobProgress) => void;
|
|
44
|
+
/** Callback when a scaler provisioning event is forwarded by a worker peer. */
|
|
45
|
+
onPeerScalerEvent?: (msg: PeerScalerEvent) => void;
|
|
44
46
|
/** Callback when a job cancel request is received from peer. */
|
|
45
47
|
onJobCancel: (msg: PeerJobCancel) => void;
|
|
46
48
|
/** Callback when a log chunk is received from a worker peer. */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Add `dispatch_queue.last_provisioning_error TEXT` recording the most
|
|
4
|
+
* recent scaler spawn-failure detail for a queued job.
|
|
5
|
+
*
|
|
6
|
+
* When the scaler fails to provision an agent for a job (e.g. the agent
|
|
7
|
+
* process exits with "spawn node ENOENT", or a container backend rejects
|
|
8
|
+
* the run), a `scaler.failed` event bound to the job writes the failure
|
|
9
|
+
* detail into this column. The queue-timeout reaper reads it so the job's
|
|
10
|
+
* eventual failure surfaces the real provisioning cause instead of a bare
|
|
11
|
+
* "no agent available" timeout. The column is cleared on dispatch and is
|
|
12
|
+
* NULL whenever no provisioning failure has been recorded.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent: re-running on a DB that already has the column is a no-op.
|
|
15
|
+
*/
|
|
16
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
17
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=024_dispatch_queue_provisioning_error.d.ts.map
|
package/dist/db/types.d.ts
CHANGED
|
@@ -150,6 +150,13 @@ export interface DispatchQueueTable {
|
|
|
150
150
|
* the rightful claimant. NULL for non-recovering rows.
|
|
151
151
|
*/
|
|
152
152
|
recovery_agent_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
153
|
+
/**
|
|
154
|
+
* Most recent scaler spawn-failure detail for this queued job (e.g.
|
|
155
|
+
* "agent process error: spawn node ENOENT"). Written on a `scaler.failed`
|
|
156
|
+
* event bound to the job; read by the queue-timeout reaper to surface the
|
|
157
|
+
* real provisioning cause; cleared on dispatch. NULL when none recorded.
|
|
158
|
+
*/
|
|
159
|
+
last_provisioning_error: ColumnType<string | null, string | null | undefined, string | null>;
|
|
153
160
|
}
|
|
154
161
|
/**
|
|
155
162
|
* Deduplication cache table
|
|
@@ -21,12 +21,22 @@ interface ScalerUsageRow {
|
|
|
21
21
|
export declare function setScalerUsageBreakdown(rows: ScalerUsageRow[]): void;
|
|
22
22
|
/** Increment the spawn-refusal counter (called by ScalerManager when a request fails the cap check). */
|
|
23
23
|
export declare function incScalerSpawnRefusals(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Merge current per-label counts with explicit `0`s for previously-seen labels
|
|
26
|
+
* whose queue has drained. Mutates `everSeen` to include the current labels.
|
|
27
|
+
*
|
|
28
|
+
* Exported for direct unit testing of the sticky-zero invariant: a label that
|
|
29
|
+
* appears in one snapshot and is absent from the next must report `0` rather
|
|
30
|
+
* than vanish, otherwise the by-label gauge series freezes at its last value.
|
|
31
|
+
*/
|
|
32
|
+
export declare function applyStickyQueueLabels(everSeen: Set<string>, byLabel: Record<string, number>): Record<string, number>;
|
|
24
33
|
/**
|
|
25
34
|
* Update the dispatch-queue depth breakdown used by the Prometheus gauges.
|
|
26
35
|
*
|
|
27
36
|
* Called by the orchestrator's periodic depth refresher after polling the
|
|
28
37
|
* JobQueue. Normalizes the input so the gauge callback can emit explicit
|
|
29
|
-
* zero series for known statuses even when the queue is empty
|
|
38
|
+
* zero series for known statuses even when the queue is empty, and keeps
|
|
39
|
+
* emitting `0` for any per-label series whose queue has drained.
|
|
30
40
|
*/
|
|
31
41
|
export declare function setDispatchQueueDepthBreakdown(snapshot: {
|
|
32
42
|
byStatus: Partial<Record<'pending' | 'dispatched', number>>;
|
|
@@ -113,6 +123,23 @@ export declare const logBytesStoredTotal: import("@opentelemetry/api").Counter<i
|
|
|
113
123
|
* - result: attempted | success | failed
|
|
114
124
|
*/
|
|
115
125
|
export declare const scalerConfigReloadsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
|
|
126
|
+
/**
|
|
127
|
+
* `bound` label for `kici_orch_scaler_spawn_failures_total`.
|
|
128
|
+
* - `true` — the failed spawn was bound to a queued job (a run is affected).
|
|
129
|
+
* - `false` — a warm-pool / unbound pre-spawn (no run impact yet; fleet-health signal).
|
|
130
|
+
*/
|
|
131
|
+
export declare const ScalerSpawnFailureBound: {
|
|
132
|
+
readonly Bound: "true";
|
|
133
|
+
readonly Unbound: "false";
|
|
134
|
+
};
|
|
135
|
+
export type ScalerSpawnFailureBound = (typeof ScalerSpawnFailureBound)[keyof typeof ScalerSpawnFailureBound];
|
|
136
|
+
/**
|
|
137
|
+
* Total scaler agent spawn failures.
|
|
138
|
+
* Labels:
|
|
139
|
+
* - backend: scaler backend type (bare-metal | container | firecracker | unknown)
|
|
140
|
+
* - bound: true (job-bound spawn) | false (warm-pool/unbound)
|
|
141
|
+
*/
|
|
142
|
+
export declare const scalerSpawnFailuresTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
|
|
116
143
|
/**
|
|
117
144
|
* Total number of config reload operations.
|
|
118
145
|
* Labels:
|
|
@@ -7,6 +7,8 @@ export interface ExpiredJobInfo {
|
|
|
7
7
|
id: string;
|
|
8
8
|
runId: string;
|
|
9
9
|
jobName: string;
|
|
10
|
+
/** Most recent scaler spawn-failure detail, if any was recorded. */
|
|
11
|
+
lastProvisioningError: string | null;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Point-in-time breakdown of dispatch_queue depth used for Prometheus gauges
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { type Kysely } from 'kysely';
|
|
15
15
|
import type { Database } from '../db/types.js';
|
|
16
|
-
import { ExecutionRunStatus } from '@kici-dev/engine';
|
|
16
|
+
import { ExecutionRunStatus, ScalerEventType } from '@kici-dev/engine';
|
|
17
17
|
import type { ObserverRegistry } from '../ws/observer-registry.js';
|
|
18
18
|
import type { LogStorage } from './log-storage.js';
|
|
19
19
|
import type { JobQueue } from '../queue/job-queue.js';
|
|
@@ -509,7 +509,7 @@ export declare class ExecutionTracker {
|
|
|
509
509
|
*/
|
|
510
510
|
emitScalerEvent(runId: string, jobId: string, event: {
|
|
511
511
|
agentId: string;
|
|
512
|
-
eventType:
|
|
512
|
+
eventType: ScalerEventType;
|
|
513
513
|
detail: string;
|
|
514
514
|
timestampMs: number;
|
|
515
515
|
}): void;
|
package/dist/scaler/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Supports Docker, bare-metal, and Firecracker backends with YAML configuration,
|
|
6
6
|
* label-set matching, and warm pool management.
|
|
7
7
|
*/
|
|
8
|
-
export type { ResourceLimits, LabelSetConfig, ManagedAgent, ScaleResult, ValidationResult, ScalerBackend, ScalerConfig, ScalerEntry, WarmPoolConfig, FirecrackerNetworkConfig, } from './types.js';
|
|
8
|
+
export type { ResourceLimits, LabelSetConfig, ManagedAgent, ScaleResult, ValidationResult, ScalerBackend, ScalerConfig, ScalerEntry, ScalerEvent, WarmPoolConfig, FirecrackerNetworkConfig, } from './types.js';
|
|
9
9
|
export { loadScalerConfig, scalerFileSchema, firecrackerNetworkSchema, parseMemoryString, } from './config.js';
|
|
10
10
|
export { normalizeLabelSet, labelSetsMatch, detectLabelSetOverlaps, findBackendForLabels, } from './label-matcher.js';
|
|
11
11
|
export { ContainerScalerBackend, detectRuntime } from './container-backend.js';
|
package/dist/scaler/manager.d.ts
CHANGED
|
@@ -192,7 +192,7 @@ export declare class ScalerManager {
|
|
|
192
192
|
* back to scaler defaults). Used by per-scaler / per-orchestrator / per-machine
|
|
193
193
|
* resource caps; the cap math is wired up by ScalerManager itself.
|
|
194
194
|
*/
|
|
195
|
-
requestScale(labels: string[], jobId: string, excludeLabels?: string[], resources?: ResourceRequest): Promise<ScaleResult>;
|
|
195
|
+
requestScale(labels: string[], jobId: string, runId: string, excludeLabels?: string[], resources?: ResourceRequest): Promise<ScaleResult>;
|
|
196
196
|
/**
|
|
197
197
|
* Run a critical section under the reservation lock. Serializes concurrent
|
|
198
198
|
* `requestScale()` calls so the check+reserve sequence is atomic.
|
|
@@ -381,8 +381,21 @@ export declare class ScalerManager {
|
|
|
381
381
|
private createEventEmitter;
|
|
382
382
|
/**
|
|
383
383
|
* Handle a scaler event from a backend.
|
|
384
|
-
*
|
|
385
|
-
*
|
|
384
|
+
*
|
|
385
|
+
* For spawn FAILURES the bound spawning entry is the usual resolver, since
|
|
386
|
+
* correlation isn't set until a job is dispatched post-registration. The
|
|
387
|
+
* correlation map wins only in the rarer post-registration failure window
|
|
388
|
+
* (e.g. a bare-metal child 'error' firing after the agent already
|
|
389
|
+
* registered, by which point the spawning entry is gone). Warm-pool /
|
|
390
|
+
* unbound spawns resolve to neither. When attributed, the event is relayed
|
|
391
|
+
* immediately; otherwise it is buffered until correlateAgentToJob() flushes
|
|
392
|
+
* it.
|
|
393
|
+
*
|
|
394
|
+
* Every `scaler.failed` also increments the fleet-wide spawn-failure counter
|
|
395
|
+
* and emits a structured warn, regardless of whether it could be attributed.
|
|
396
|
+
* The backend label is resolved from the spawning entry first, then the
|
|
397
|
+
* managed-agent index — so a late failure after the spawning entry is gone
|
|
398
|
+
* still buckets under the real backend instead of "unknown".
|
|
386
399
|
*/
|
|
387
400
|
private handleScalerEvent;
|
|
388
401
|
}
|
package/dist/scaler/types.d.ts
CHANGED
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
* Provides the foundational types that all scaler backends, the ScalerManager,
|
|
5
5
|
* and the configuration layer depend on.
|
|
6
6
|
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { ScalerEventType } from '@kici-dev/engine';
|
|
7
9
|
import type { ResourceRequest, ResourceSpec, ScalerBackendType } from '@kici-dev/engine';
|
|
8
10
|
export type { ResourceRequest, ResourceSpec } from '@kici-dev/engine';
|
|
11
|
+
export { ScalerEventType } from '@kici-dev/engine';
|
|
9
12
|
/**
|
|
10
13
|
* Resource limits for spawned agents.
|
|
11
14
|
*
|
|
@@ -353,18 +356,13 @@ export interface WarmPoolConfig {
|
|
|
353
356
|
*/
|
|
354
357
|
idleTimeoutSeconds: number;
|
|
355
358
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Standardized scaler lifecycle event types emitted by all backends.
|
|
358
|
-
* These are stored in the event_log table for timeline rendering.
|
|
359
|
-
*/
|
|
360
|
-
type ScalerEventType = 'scaler.provisioning' | 'scaler.network' | 'scaler.ready' | 'scaler.failed' | 'agent.connecting';
|
|
361
359
|
/**
|
|
362
360
|
* A single scaler lifecycle event.
|
|
363
361
|
* Each backend emits these at key provisioning milestones.
|
|
364
362
|
*/
|
|
365
363
|
export interface ScalerEvent {
|
|
366
364
|
agentId: string;
|
|
367
|
-
eventType: ScalerEventType
|
|
365
|
+
eventType: z.infer<typeof ScalerEventType>;
|
|
368
366
|
/** Backend-specific detail text (e.g. "pulling image nginx:latest", "booting microVM") */
|
|
369
367
|
detail: string;
|
|
370
368
|
timestampMs: number;
|
|
@@ -382,6 +380,12 @@ export type ScalerEventCallback = (event: ScalerEvent) => void;
|
|
|
382
380
|
export interface LogCapture {
|
|
383
381
|
/** Async iterable yielding individual log lines from the agent process. */
|
|
384
382
|
lines(): AsyncIterable<string>;
|
|
383
|
+
/**
|
|
384
|
+
* Return the most recent buffered output lines (bounded ring buffer),
|
|
385
|
+
* joined oldest→newest. Used to enrich a `scaler.failed` event when a
|
|
386
|
+
* spawn dies before WS registration. Empty string when nothing captured.
|
|
387
|
+
*/
|
|
388
|
+
tail(): string;
|
|
385
389
|
/** Stop capturing and destroy underlying streams. Safe to call multiple times. */
|
|
386
390
|
close(): void;
|
|
387
391
|
}
|