@kici-dev/orchestrator 0.12.0 → 0.14.0
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 +39 -0
- package/dist/agent/registry.d.ts +31 -0
- package/dist/app.d.ts +6 -0
- package/dist/cache/cache-upload-settle.d.ts +75 -0
- package/dist/cache/pending-inits.d.ts +9 -0
- package/dist/cancel/cancel-run.d.ts +22 -4
- package/dist/cancel/peer-job-cancel.d.ts +62 -0
- package/dist/cancel/sweep-stuck-cancelling.d.ts +3 -1
- package/dist/cli/api-client.d.ts +73 -0
- package/dist/cli/commands/cluster-settings.d.ts +31 -1
- package/dist/cli/commands/local-hook.d.ts +3 -2
- package/dist/cli/commands/local-trigger.d.ts +7 -0
- package/dist/cli/commands/peer.d.ts +27 -1
- package/dist/cli/commands/scaler.d.ts +40 -4
- package/dist/cli.js +2549 -972
- package/dist/cluster/cluster-settings-reader.d.ts +8 -2
- package/dist/cluster/coordinator-credential.d.ts +45 -0
- package/dist/cluster/coordinator.d.ts +52 -10
- package/dist/cluster/index.d.ts +1 -0
- package/dist/cluster/instance-heartbeat.d.ts +6 -1
- package/dist/cluster/join-token.d.ts +2 -0
- package/dist/cluster/peer-auth-coordinator.d.ts +13 -0
- package/dist/cluster/peer-client.d.ts +41 -4
- package/dist/cluster/peer-credentials.d.ts +31 -0
- package/dist/cluster/peer-forget.d.ts +138 -0
- package/dist/cluster/peer-handler.d.ts +18 -1
- package/dist/cluster/scaler-orphans-peer.d.ts +40 -0
- package/dist/cluster/settings-propagation.d.ts +85 -0
- package/dist/cluster/worker-cluster-settings.d.ts +29 -0
- package/dist/config.d.ts +12 -0
- package/dist/db/migrations/152_org_settings_cache_upload_settle_timeout.d.ts +15 -0
- package/dist/db/migrations/153_execution_runs_cancel_force.d.ts +16 -0
- package/dist/db/migrations/154_cluster_settings_firecracker_api_socket_wait.d.ts +4 -0
- package/dist/db/migrations/155_org_settings_reroute_spawn_retry.d.ts +17 -0
- package/dist/db/migrations/156_kici_events_match_outcome.d.ts +17 -0
- package/dist/db/types.d.ts +31 -0
- package/dist/events/event-router.d.ts +7 -1
- package/dist/events/event-store.d.ts +20 -3
- package/dist/events/types.d.ts +45 -0
- package/dist/helpers/windows-batch.d.ts +59 -0
- package/dist/index.js +252 -53
- package/dist/metrics/prometheus.d.ts +2 -0
- package/dist/orchestrator-core.d.ts +7 -0
- package/dist/pipeline/changed-files.d.ts +51 -0
- package/dist/pipeline/cross-source-tally.d.ts +21 -0
- package/dist/pipeline/degraded-reason.d.ts +8 -0
- package/dist/pipeline/dispatch-matched-workflow.d.ts +10 -0
- package/dist/pipeline/global-eval-round.d.ts +3 -2
- package/dist/pipeline/processor.d.ts +9 -1
- package/dist/pipeline/rerun-inputs.d.ts +16 -7
- package/dist/providers/github/changed-files.d.ts +14 -4
- package/dist/providers/universal-git/changed-files.d.ts +6 -0
- package/dist/providers/universal-git/config.d.ts +4 -0
- package/dist/registration/registration-store.d.ts +10 -0
- package/dist/routes/admin-cluster-settings.d.ts +13 -1
- package/dist/routes/admin-event-inspect.d.ts +36 -0
- package/dist/routes/admin-peer-forget.d.ts +43 -0
- package/dist/routes/admin-scaler-orphans.d.ts +64 -0
- package/dist/routes/admin.d.ts +23 -0
- package/dist/scaler/backend-factory.d.ts +6 -1
- package/dist/scaler/bare-metal-backend.d.ts +17 -15
- package/dist/scaler/bare-metal-launch.d.ts +90 -0
- package/dist/scaler/container-backend.d.ts +2 -0
- package/dist/scaler/container-runtime.d.ts +9 -0
- package/dist/scaler/firecracker-backend.d.ts +181 -7
- package/dist/scaler/live-vms.d.ts +57 -0
- package/dist/scaler/manager.d.ts +138 -5
- package/dist/scaler/orphan-requests.d.ts +23 -0
- package/dist/scaler/overlay-template.d.ts +38 -0
- package/dist/scaler/scaler-state-store.d.ts +7 -0
- package/dist/scaler/spawn-errors.d.ts +17 -0
- package/dist/scaler/startup-orphan-sweep.d.ts +15 -0
- package/dist/scaler/types.d.ts +21 -2
- package/dist/scaler/vm-job-bindings.d.ts +17 -0
- package/dist/secrets/rbac.d.ts +7 -1
- package/dist/server.js +7086 -2557
- package/dist/stale-detector/workflow-deadline-detector.d.ts +7 -1
- package/dist/standalone.js +16473 -11945
- package/dist/storage/s3.d.ts +36 -0
- package/dist/worker/in-memory-execution-tracker.d.ts +5 -0
- package/dist/worker/in-memory-job-queue.d.ts +21 -1
- package/dist/worker/queued-reroute-release.d.ts +39 -0
- package/dist/worker/reroute-spawn-control.d.ts +58 -0
- package/dist/worker/spawn-retry-budget.d.ts +87 -0
- package/dist/worker-core.d.ts +11 -2
- package/dist/ws/agent-handler.d.ts +24 -3
- package/dist/ws/agent-heartbeat.d.ts +2 -2
- package/installer-image-digests.json +3 -3
- package/package.json +5 -5
- package/sbom.spdx.json +53 -53
package/dist/storage/s3.d.ts
CHANGED
|
@@ -47,6 +47,11 @@ export declare class S3CacheStorage implements CacheStorage {
|
|
|
47
47
|
private readonly bucket;
|
|
48
48
|
private readonly prefix;
|
|
49
49
|
private readonly ttlMs;
|
|
50
|
+
/**
|
|
51
|
+
* False once the backend answered a conditional self-copy with
|
|
52
|
+
* `501 NotImplemented`. From then on `updateMeta` copies unconditionally.
|
|
53
|
+
*/
|
|
54
|
+
private conditionalCopySupported;
|
|
50
55
|
constructor(options: S3CacheStorageOptions);
|
|
51
56
|
/** Build the full S3 object key from a cache key. */
|
|
52
57
|
private objectKey;
|
|
@@ -57,11 +62,42 @@ export declare class S3CacheStorage implements CacheStorage {
|
|
|
57
62
|
* Returns null if the object doesn't exist (NoSuchKey/NotFound).
|
|
58
63
|
*/
|
|
59
64
|
private readMeta;
|
|
65
|
+
/**
|
|
66
|
+
* Read an object's TTL metadata together with the ETag of the version it
|
|
67
|
+
* belongs to. Returns null if the object doesn't exist (NoSuchKey/NotFound).
|
|
68
|
+
*/
|
|
69
|
+
private readHead;
|
|
60
70
|
/**
|
|
61
71
|
* Update metadata on an S3 object by copying it to itself.
|
|
62
72
|
* This is the standard S3 pattern for updating metadata without re-uploading data.
|
|
73
|
+
*
|
|
74
|
+
* The copy is conditional on `etag`, the version whose metadata the caller
|
|
75
|
+
* read. A self-copy is a whole-object write: without the condition, one that
|
|
76
|
+
* races a `put()` of the same key can land after it and write the older body
|
|
77
|
+
* back. Cache pointers are rewritten in place while builds publish them, so a
|
|
78
|
+
* touch-on-read racing a publish reverted the pointer to a tarball that no
|
|
79
|
+
* longer exists. `CopySourceIfMatch` pins the bytes copied to that version and
|
|
80
|
+
* `IfMatch` refuses the write unless the key still holds it.
|
|
81
|
+
*
|
|
82
|
+
* Returns false when the object was replaced since the read (`412`), or is
|
|
83
|
+
* being replaced right now (`409 ConditionalRequestConflict`): the newer
|
|
84
|
+
* write stamps metadata of its own, so there is nothing left to refresh.
|
|
85
|
+
*
|
|
86
|
+
* An S3-compatible backend that does not implement conditional copies
|
|
87
|
+
* answers `501 NotImplemented`. The copy then runs again without the
|
|
88
|
+
* condition, and later copies skip it, as on a backend that ignores the
|
|
89
|
+
* headers.
|
|
63
90
|
*/
|
|
64
91
|
private updateMeta;
|
|
92
|
+
/** Copy an object onto itself with new TTL metadata, conditional on `etag` when given. */
|
|
93
|
+
private copyOntoItself;
|
|
94
|
+
/** Check if an error is a failed conditional request (PreconditionFailed, 412). */
|
|
95
|
+
private isPreconditionFailedError;
|
|
96
|
+
/** Check if an error is a conditional write that raced another write (ConditionalRequestConflict, 409). */
|
|
97
|
+
private isConditionalConflictError;
|
|
98
|
+
/** Check if an error is a request the backend does not implement (NotImplemented, 501). */
|
|
99
|
+
private isNotImplementedError;
|
|
100
|
+
private hasErrorShape;
|
|
65
101
|
/** Check if an error is a "not found" error (NoSuchKey, NotFound, 404). */
|
|
66
102
|
private isNotFoundError;
|
|
67
103
|
put(key: string, data: Buffer | string): Promise<void>;
|
|
@@ -80,6 +80,11 @@ export declare class InMemoryExecutionTracker {
|
|
|
80
80
|
* Update step status. Forwards to coordinator callback if configured.
|
|
81
81
|
*/
|
|
82
82
|
onStepStatus(runId: string, jobId: string, stepIndex: number, stepName: string, status: string, timestamp: number, data?: Record<string, unknown>): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Forget a job this worker gave up or was told to stop before it ran. Nothing is
|
|
85
|
+
* forwarded: the coordinator that owns the run already decided what happens to it.
|
|
86
|
+
*/
|
|
87
|
+
dropJob(runId: string, jobId: string): void;
|
|
83
88
|
/**
|
|
84
89
|
* Get the current status of a run.
|
|
85
90
|
*/
|
|
@@ -16,6 +16,25 @@ export declare class InMemoryJobQueue {
|
|
|
16
16
|
private readonly dispatched;
|
|
17
17
|
/** Per-job re-dispatch counter (parity with dispatch_queue.dispatch_attempts). */
|
|
18
18
|
private readonly attempts;
|
|
19
|
+
/**
|
|
20
|
+
* Jobs the owning coordinator stopped (a cancel), or that this worker gave back
|
|
21
|
+
* after its spawn retries ran out. A stopped job is never claimed, listed or
|
|
22
|
+
* re-pended, which is this queue's counterpart of the database queue's
|
|
23
|
+
* stopped-run guard. The mark goes with the job's entry.
|
|
24
|
+
*/
|
|
25
|
+
private readonly stopped;
|
|
26
|
+
/** Stop-mark every job of `runId` (or just `jobId`), pending or dispatched. Synchronous. */
|
|
27
|
+
stop(runId: string, jobId?: string): {
|
|
28
|
+
pending: string[];
|
|
29
|
+
dispatched: string[];
|
|
30
|
+
};
|
|
31
|
+
/** True while the job waits in this queue and has not been stopped. */
|
|
32
|
+
isPending(jobId: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Parity with the database queue, whose dispatcher calls this after a `null`
|
|
35
|
+
* requeue. `requeue` already dropped a stopped job, so nothing is left to settle.
|
|
36
|
+
*/
|
|
37
|
+
settleClaimOfStoppedRun(_jobId: string, _runId: string): Promise<boolean>;
|
|
19
38
|
/** Enqueue a job in memory and return its ID. */
|
|
20
39
|
enqueue(input: QueuedJobInput): Promise<string>;
|
|
21
40
|
/**
|
|
@@ -98,7 +117,8 @@ export declare class InMemoryJobQueue {
|
|
|
98
117
|
* Return a dispatched job to pending for re-dispatch, bumping its attempt
|
|
99
118
|
* counter unless `countAttempt` is false. Mirrors the DB-backed
|
|
100
119
|
* `JobQueue.requeue`. Returns the attempt count, or null when the job is
|
|
101
|
-
* not currently dispatched.
|
|
120
|
+
* not currently dispatched. A stopped job is dropped instead of re-pended, and
|
|
121
|
+
* also returns null.
|
|
102
122
|
*/
|
|
103
123
|
requeue(jobId: string, opts?: {
|
|
104
124
|
countAttempt?: boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove a worker's queued rerouted jobs and stop-mark its dispatched ones.
|
|
3
|
+
*
|
|
4
|
+
* Two triggers reach here: the owning coordinator's `peer.job.cancel`, and this
|
|
5
|
+
* worker exhausting a job's spawn-retry budget. Either way the coordinator owns
|
|
6
|
+
* the job's verdict — it already failed, cancelled or re-dispatched the job — so
|
|
7
|
+
* this reports **nothing** back to it. A worker-side `failed` or `cancelled`
|
|
8
|
+
* terminal would reach the coordinator as a superseded peer's terminal at best,
|
|
9
|
+
* and as a second verdict at worst.
|
|
10
|
+
*
|
|
11
|
+
* A dispatched job keeps its agent, projection and ownership: the agent is
|
|
12
|
+
* running it, and the cancel receiver delivers its `job.cancel`. The stop mark
|
|
13
|
+
* only makes a later requeue drop the job instead of re-pending it.
|
|
14
|
+
*/
|
|
15
|
+
import type { Dispatcher } from '../agent/dispatcher.js';
|
|
16
|
+
import type { InMemoryJobQueue } from './in-memory-job-queue.js';
|
|
17
|
+
import type { InMemoryExecutionTracker } from './in-memory-execution-tracker.js';
|
|
18
|
+
import type { SpawnRetryBudget } from './spawn-retry-budget.js';
|
|
19
|
+
export interface ReleaseDeps {
|
|
20
|
+
queue: Pick<InMemoryJobQueue, 'stop'>;
|
|
21
|
+
dispatcher: Pick<Dispatcher, 'cancelQueuedJob'>;
|
|
22
|
+
executionTracker: Pick<InMemoryExecutionTracker, 'dropJob'>;
|
|
23
|
+
/** jobId → owning coordinator URL. */
|
|
24
|
+
jobOwnership: Map<string, string>;
|
|
25
|
+
budget: Pick<SpawnRetryBudget, 'release'>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Stop every matching job of `runId` (or just `jobId`) and remove the pending
|
|
29
|
+
* ones. The stop mark is set synchronously, before the first `await`, so a
|
|
30
|
+
* requeue racing this call drops the job.
|
|
31
|
+
*
|
|
32
|
+
* @returns the ids of the pending jobs removed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function releaseQueuedRerouteJobs(deps: ReleaseDeps, target: {
|
|
35
|
+
runId: string;
|
|
36
|
+
jobId?: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
}): Promise<string[]>;
|
|
39
|
+
//# sourceMappingURL=queued-reroute-release.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker-side half of the reroute spawn-retry agreement.
|
|
3
|
+
*
|
|
4
|
+
* A coordinator reroutes a job to this worker with a spawn-retry budget
|
|
5
|
+
* (`job.reroute.spawnRetry`). This module applies it:
|
|
6
|
+
*
|
|
7
|
+
* - every scaler request for the job passes a per-job attempt gate, so one spawn
|
|
8
|
+
* is in flight at a time and none starts during the backoff;
|
|
9
|
+
* - each `scaler.failed` is relayed to the owning coordinator with the budget's
|
|
10
|
+
* verdict (`final`), and the last one releases the job from this worker's queue;
|
|
11
|
+
* - a coordinator's cancel releases a queued job and stop-marks a dispatched one.
|
|
12
|
+
*
|
|
13
|
+
* Assembled here so `bootstrapWorker` only wires it.
|
|
14
|
+
*/
|
|
15
|
+
import { type JobReroute, type PeerScalerEvent, type RerouteSpawnRetry } from '@kici-dev/engine';
|
|
16
|
+
import type { Dispatcher } from '../agent/dispatcher.js';
|
|
17
|
+
import type { ScalerManager } from '../scaler/manager.js';
|
|
18
|
+
import type { ScaleResult, ScalerEvent } from '../scaler/types.js';
|
|
19
|
+
import type { InMemoryJobQueue } from './in-memory-job-queue.js';
|
|
20
|
+
import type { InMemoryExecutionTracker } from './in-memory-execution-tracker.js';
|
|
21
|
+
/** The scaler request the dispatcher's `onNoMatchingAgent` makes. */
|
|
22
|
+
export type ScaleRequest = (...args: Parameters<ScalerManager['requestScale']>) => Promise<ScaleResult>;
|
|
23
|
+
export interface RerouteSpawnControlDeps {
|
|
24
|
+
queue: InMemoryJobQueue;
|
|
25
|
+
/** Late-bound: the scaler that emits events is built before the dispatcher. */
|
|
26
|
+
getDispatcher: () => Pick<Dispatcher, 'cancelQueuedJob' | 'redrivePendingJob'>;
|
|
27
|
+
executionTracker: InMemoryExecutionTracker;
|
|
28
|
+
/** jobId → owning coordinator URL. */
|
|
29
|
+
jobOwnership: Map<string, string>;
|
|
30
|
+
/** Relay a message to the coordinator that owns the job. */
|
|
31
|
+
sendToOwningCoord: (jobId: string, msg: PeerScalerEvent) => void;
|
|
32
|
+
/** The worker's configured budget, used when the coordinator sent none. */
|
|
33
|
+
defaults: RerouteSpawnRetry;
|
|
34
|
+
logger: {
|
|
35
|
+
debug(message: string, meta?: Record<string, unknown>): unknown;
|
|
36
|
+
info(message: string, meta?: Record<string, unknown>): unknown;
|
|
37
|
+
warn(message: string, meta?: Record<string, unknown>): unknown;
|
|
38
|
+
error(message: string, meta?: Record<string, unknown>): unknown;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface RerouteSpawnControl {
|
|
42
|
+
/** Start the job's budget. Call before the job is dispatched. */
|
|
43
|
+
registerReroute(msg: JobReroute): void;
|
|
44
|
+
/** Drop the budget of a reroute this worker did not accept. */
|
|
45
|
+
forgetReroute(jobId: string): void;
|
|
46
|
+
/** Gate a scaler request through the job's budget. */
|
|
47
|
+
wrapScaleRequest(fn: ScaleRequest): ScaleRequest;
|
|
48
|
+
/** Relay a scaler event to the owning coordinator, with the budget's verdict. */
|
|
49
|
+
onScalerEvent(runId: string, jobId: string, ev: ScalerEvent): void;
|
|
50
|
+
/** An agent received the job. */
|
|
51
|
+
onDelivered(jobId: string): void;
|
|
52
|
+
/** The job reached a terminal state. */
|
|
53
|
+
onJobTerminal(jobId: string): void;
|
|
54
|
+
/** The owning coordinator cancelled the run, or one job of it. */
|
|
55
|
+
releaseQueued(runId: string, jobId: string | undefined): Promise<string[]>;
|
|
56
|
+
}
|
|
57
|
+
export declare function createRerouteSpawnControl(deps: RerouteSpawnControlDeps): RerouteSpawnControl;
|
|
58
|
+
//# sourceMappingURL=reroute-spawn-control.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The spawn-retry budget a worker applies to each job a coordinator rerouted to it.
|
|
3
|
+
*
|
|
4
|
+
* A worker whose agent spawn fails retries, but only `maxAttempts` times and with
|
|
5
|
+
* `backoffMs` between attempts, so the coordinator that owns the run can tell
|
|
6
|
+
* "still trying" from "gave up". The coordinator sends the budget on `job.reroute`,
|
|
7
|
+
* and the verdict for each failure rides back on the relayed `scaler.failed` as
|
|
8
|
+
* `final`. One entry per rerouted job, created before the job is dispatched and
|
|
9
|
+
* released when the job leaves this worker's queue.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import type { RerouteSpawnRetry } from '@kici-dev/engine';
|
|
13
|
+
/** The answer to "may this job start a spawn now?". */
|
|
14
|
+
export declare const SpawnAttemptGate: z.ZodEnum<{
|
|
15
|
+
backoff: "backoff";
|
|
16
|
+
begun: "begun";
|
|
17
|
+
"in-flight": "in-flight";
|
|
18
|
+
"not-queued": "not-queued";
|
|
19
|
+
untracked: "untracked";
|
|
20
|
+
}>;
|
|
21
|
+
export type SpawnAttemptGate = z.infer<typeof SpawnAttemptGate>;
|
|
22
|
+
/** The budget's verdict on one spawn failure. */
|
|
23
|
+
export type FailureVerdict = {
|
|
24
|
+
tracked: false;
|
|
25
|
+
} | {
|
|
26
|
+
tracked: true;
|
|
27
|
+
final: boolean;
|
|
28
|
+
failures: number;
|
|
29
|
+
maxAttempts: number;
|
|
30
|
+
duplicate: boolean;
|
|
31
|
+
};
|
|
32
|
+
export interface SpawnRetryBudgetDeps {
|
|
33
|
+
/** True while the job waits in the worker's queue (not claimed, not stopped). */
|
|
34
|
+
isQueued: (jobId: string) => boolean;
|
|
35
|
+
/** Offer one pending job to the scaler again. */
|
|
36
|
+
redrive: (jobId: string) => Promise<unknown>;
|
|
37
|
+
logger: {
|
|
38
|
+
debug(message: string, meta?: object): unknown;
|
|
39
|
+
info(message: string, meta?: object): unknown;
|
|
40
|
+
warn(message: string, meta?: object): unknown;
|
|
41
|
+
};
|
|
42
|
+
now?: () => number;
|
|
43
|
+
}
|
|
44
|
+
export declare class SpawnRetryBudget {
|
|
45
|
+
private readonly deps;
|
|
46
|
+
private readonly entries;
|
|
47
|
+
private readonly now;
|
|
48
|
+
constructor(deps: SpawnRetryBudgetDeps);
|
|
49
|
+
/** Start tracking a rerouted job. A second call for the same job keeps the first entry. */
|
|
50
|
+
register(jobId: string, runId: string, policy: RerouteSpawnRetry): void;
|
|
51
|
+
/** Claim the right to start one spawn. Synchronous, so two callers cannot both win. */
|
|
52
|
+
beginAttempt(jobId: string): SpawnAttemptGate;
|
|
53
|
+
/**
|
|
54
|
+
* Record the agent the claimed attempt is spawning. Ignored when the attempt is
|
|
55
|
+
* no longer in flight, or when that agent's failure already arrived.
|
|
56
|
+
*/
|
|
57
|
+
setHolder(jobId: string, agentId: string): void;
|
|
58
|
+
/** The claimed attempt started no spawn (at capacity, no backend, skipped). */
|
|
59
|
+
abortAttempt(jobId: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* The claimed attempt started no spawn because the scaler is deferring for
|
|
62
|
+
* `delayMs`. Nothing is charged; the job is offered to the scaler again when
|
|
63
|
+
* the deferral ends. Without this a worker, which has no periodic pending
|
|
64
|
+
* sweep, would ask once during the deferral and never again, and the
|
|
65
|
+
* coordinator's spawn window would run out on an attempt the budget still
|
|
66
|
+
* holds.
|
|
67
|
+
*/
|
|
68
|
+
deferAttempt(jobId: string, delayMs: number): void;
|
|
69
|
+
/** An agent received the job. The budget stays in place in case the job is requeued. */
|
|
70
|
+
onDelivered(jobId: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Count one failed spawn. A failure for a job no longer queued is not a spawn
|
|
73
|
+
* failure (an agent already took the job), so it is untracked. Only the spawn
|
|
74
|
+
* holding the attempt gate is charged: a failure from any other agent (a
|
|
75
|
+
* superseded spawn failing late) is untracked and leaves the gate held. While
|
|
76
|
+
* the scaler has not yet named the in-flight agent, the in-flight attempt is
|
|
77
|
+
* the only one that can fail, so its failure is charged. On a non-final
|
|
78
|
+
* failure the backoff starts, and its timer offers the job to the scaler again.
|
|
79
|
+
*/
|
|
80
|
+
recordFailure(jobId: string, agentId: string): FailureVerdict;
|
|
81
|
+
/** The job left the queue: terminal, stopped, or given back. */
|
|
82
|
+
release(jobId: string): void;
|
|
83
|
+
private armBackoff;
|
|
84
|
+
/** Gate new attempts for `delayMs`, then offer the job to the scaler again. */
|
|
85
|
+
private scheduleRedrive;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=spawn-retry-budget.d.ts.map
|
package/dist/worker-core.d.ts
CHANGED
|
@@ -64,13 +64,22 @@ interface WorkerSubsystems {
|
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* Resolve the ephemeral agent-token TTL a worker-spawned agent should use:
|
|
67
|
-
* the fleet-wide `agent_token_ttl_ms` pulled from
|
|
68
|
-
* landed, otherwise the boot-time config default. This is the single seam the
|
|
67
|
+
* the fleet-wide `agent_token_ttl_ms` pulled from a coordinator when a snapshot
|
|
68
|
+
* has landed, otherwise the boot-time config default. This is the single seam the
|
|
69
69
|
* worker's three scaler backends read at spawn.
|
|
70
70
|
*/
|
|
71
71
|
export declare function resolveWorkerAgentTokenTtlMs(pushed: {
|
|
72
72
|
settings: WorkerClusterSettings;
|
|
73
73
|
} | null, fallback: number): number;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the Firecracker API-socket wait a worker-spawned VM should use: the
|
|
76
|
+
* fleet-wide `firecracker_api_socket_wait_ms` pulled from a coordinator,
|
|
77
|
+
* otherwise the boot-time config default. A snapshot from a coordinator that
|
|
78
|
+
* predates the knob carries no value, and the default applies.
|
|
79
|
+
*/
|
|
80
|
+
export declare function resolveWorkerFirecrackerApiSocketWaitMs(pushed: {
|
|
81
|
+
settings: WorkerClusterSettings;
|
|
82
|
+
} | null, fallback: number): number;
|
|
74
83
|
/**
|
|
75
84
|
* Bootstrap the orchestrator in worker mode.
|
|
76
85
|
*
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* token, and the agent then registers as normal.
|
|
21
21
|
*/
|
|
22
22
|
import type { WSContext, WSEvents, WSMessageReceive } from 'hono/ws';
|
|
23
|
-
import { LogStream } from '@kici-dev/engine';
|
|
23
|
+
import { LogStream, ScaleDownReason } from '@kici-dev/engine';
|
|
24
24
|
import type { RateLimiterConfig, AttestationVerifyStatus } from '@kici-dev/engine';
|
|
25
25
|
import type { ProvenanceTrustRoot } from '../provenance/trust-root.js';
|
|
26
26
|
import type { AgentRegistry } from '../agent/registry.js';
|
|
@@ -36,6 +36,7 @@ import type { PendingBuildTracker } from '../cache/pending-builds.js';
|
|
|
36
36
|
import type { PendingInitTracker } from '../cache/pending-inits.js';
|
|
37
37
|
import type { PendingDynamicTracker } from '../cache/pending-dynamics.js';
|
|
38
38
|
import type { PendingGlobalEvalTracker } from '../cache/pending-global-evals.js';
|
|
39
|
+
import { type CacheUploadSettle } from '../cache/cache-upload-settle.js';
|
|
39
40
|
import type { CacheStorage } from '../storage/types.js';
|
|
40
41
|
import type { AgentMetricsAggregator } from '../metrics/agent-metrics-aggregator.js';
|
|
41
42
|
import { type AgentApiRegistry } from './agent-api-registry.js';
|
|
@@ -94,6 +95,8 @@ export declare function frameIdentity(data: unknown): FrameIdentity;
|
|
|
94
95
|
export interface AgentWsEvents extends Omit<WSEvents, 'onMessage'> {
|
|
95
96
|
onMessage(evt: MessageEvent<WSMessageReceive>, ws: WSContext): Promise<void>;
|
|
96
97
|
}
|
|
98
|
+
/** `error` frame code sent when a registration is refused because another agent token holds the id. */
|
|
99
|
+
export declare const AGENT_ID_IN_USE = "AGENT_ID_IN_USE";
|
|
97
100
|
export interface AgentWsHandlerDeps {
|
|
98
101
|
registry: AgentRegistry;
|
|
99
102
|
dispatcher: Dispatcher;
|
|
@@ -161,8 +164,11 @@ export interface AgentWsHandlerDeps {
|
|
|
161
164
|
boundJobId?: string;
|
|
162
165
|
mandatoryLabels: string[];
|
|
163
166
|
} | null>;
|
|
164
|
-
/**
|
|
165
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Optional callback when an agent disconnects (for scaler lifecycle). `reason`
|
|
169
|
+
* is the scale-down reason an event scaler emits for it.
|
|
170
|
+
*/
|
|
171
|
+
onScalerAgentDisconnected?: (agentId: string, reason?: ScaleDownReason) => void;
|
|
166
172
|
/** Optional callback when an agent completes a job (for scaler lifecycle). */
|
|
167
173
|
onScalerJobComplete?: (agentId: string) => void;
|
|
168
174
|
/** Optional callback when agent sends per-job heartbeats. */
|
|
@@ -256,6 +262,13 @@ export interface AgentWsHandlerDeps {
|
|
|
256
262
|
artifactStore?: ArtifactStore;
|
|
257
263
|
/** Cache storage for setting metadata after upload completion. */
|
|
258
264
|
cacheStorage?: CacheStorage;
|
|
265
|
+
/**
|
|
266
|
+
* In-flight build-cache publishes and the per-org bound a build's success
|
|
267
|
+
* waits for them. Built once per orchestrator, not per connection, so a
|
|
268
|
+
* success replayed on a reconnected socket still sees a publish started on
|
|
269
|
+
* the old one. Absent: no registration and no wait.
|
|
270
|
+
*/
|
|
271
|
+
cacheUploadSettle?: CacheUploadSettle;
|
|
259
272
|
/**
|
|
260
273
|
* Storage used to mint presigned PUT URLs for provenance bundles. Distinct
|
|
261
274
|
* dep so a deployment can decide whether attestations share the cache bucket.
|
|
@@ -424,6 +437,14 @@ export interface AgentLogChunkInput {
|
|
|
424
437
|
/** Absent when the agent does not report a stream; read as `stdout`. */
|
|
425
438
|
stream?: LogStream;
|
|
426
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Release the in-memory waiters of jobs a disconnect triage failed: pending
|
|
442
|
+
* builds, inits, dynamic evaluations and global eval rounds, and the user-cache
|
|
443
|
+
* refs of the dispatch. The dispatcher runs it inside `onAgentDisconnect`, so
|
|
444
|
+
* every path that drops an agent reaches it, whether or not it waits for the
|
|
445
|
+
* socket to close.
|
|
446
|
+
*/
|
|
447
|
+
export declare function releaseFailedJobWaiters(deps: Pick<AgentWsHandlerDeps, 'pendingBuilds' | 'pendingInits' | 'pendingDynamics' | 'pendingGlobalEvals' | 'dispatchCacheRefs'>, failedJobIds: string[]): void;
|
|
427
448
|
/**
|
|
428
449
|
* Create a Hono WS event handler for agent connections.
|
|
429
450
|
*
|
|
@@ -23,8 +23,8 @@ interface AgentHeartbeatMonitorDeps {
|
|
|
23
23
|
/**
|
|
24
24
|
* Periodically inspects all registered agent connections and:
|
|
25
25
|
* - Marks agents as unhealthy after 90s of silence (log only).
|
|
26
|
-
* -
|
|
27
|
-
*
|
|
26
|
+
* - Triggers dispatcher.onAgentDisconnect for an agent silent for 180s, then
|
|
27
|
+
* unregisters it and closes its socket.
|
|
28
28
|
*/
|
|
29
29
|
export declare class AgentHeartbeatMonitor {
|
|
30
30
|
private readonly registry;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.14.0",
|
|
3
3
|
"images": {
|
|
4
|
-
"kici-agent": "sha256:
|
|
5
|
-
"kici-orchestrator": "sha256:
|
|
4
|
+
"kici-agent": "sha256:c5b2ae281f683b43568c238560aa8bb82d4014a3e3b9e86b857496812eabb493",
|
|
5
|
+
"kici-orchestrator": "sha256:6ae78b978f52e80b2ffafae46cf3d33d1dcae3b48b93ab91ea2bc7dd28412d26"
|
|
6
6
|
}
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kici-dev/orchestrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Customer-deployable orchestrator for the KiCI CI/CD stack. Receives webhook events (direct or via Platform relay), matches triggers against the lock file, and dispatches jobs to connected agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"license": "AGPL-3.0-only",
|
|
26
26
|
"engines": {
|
|
27
|
-
"node": ">=24"
|
|
27
|
+
"node": ">=24.5.0"
|
|
28
28
|
},
|
|
29
29
|
"type": "module",
|
|
30
30
|
"files": [
|
|
@@ -90,8 +90,8 @@
|
|
|
90
90
|
"ws": "^8.21.3",
|
|
91
91
|
"yaml": "^2.9.1",
|
|
92
92
|
"zod": "^4.6.5",
|
|
93
|
-
"@kici-dev/engine": "0.
|
|
94
|
-
"@kici-dev/shared": "0.
|
|
93
|
+
"@kici-dev/engine": "0.14.0",
|
|
94
|
+
"@kici-dev/shared": "0.14.0"
|
|
95
95
|
},
|
|
96
96
|
"kici": {
|
|
97
97
|
"metrics": {
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"@types/dockerode": "^4.0.1",
|
|
107
107
|
"@types/ws": "^8.18.1",
|
|
108
108
|
"kysely-ctl": "^0.21.0",
|
|
109
|
-
"@kici-dev/agent": "0.
|
|
109
|
+
"@kici-dev/agent": "0.14.0"
|
|
110
110
|
},
|
|
111
111
|
"scripts": {
|
|
112
112
|
"build": "node ../../scripts/build-service.mjs && tsc --emitDeclarationOnly",
|