@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.
Files changed (90) hide show
  1. package/dist/agent/dispatcher.d.ts +39 -0
  2. package/dist/agent/registry.d.ts +31 -0
  3. package/dist/app.d.ts +6 -0
  4. package/dist/cache/cache-upload-settle.d.ts +75 -0
  5. package/dist/cache/pending-inits.d.ts +9 -0
  6. package/dist/cancel/cancel-run.d.ts +22 -4
  7. package/dist/cancel/peer-job-cancel.d.ts +62 -0
  8. package/dist/cancel/sweep-stuck-cancelling.d.ts +3 -1
  9. package/dist/cli/api-client.d.ts +73 -0
  10. package/dist/cli/commands/cluster-settings.d.ts +31 -1
  11. package/dist/cli/commands/local-hook.d.ts +3 -2
  12. package/dist/cli/commands/local-trigger.d.ts +7 -0
  13. package/dist/cli/commands/peer.d.ts +27 -1
  14. package/dist/cli/commands/scaler.d.ts +40 -4
  15. package/dist/cli.js +2549 -972
  16. package/dist/cluster/cluster-settings-reader.d.ts +8 -2
  17. package/dist/cluster/coordinator-credential.d.ts +45 -0
  18. package/dist/cluster/coordinator.d.ts +52 -10
  19. package/dist/cluster/index.d.ts +1 -0
  20. package/dist/cluster/instance-heartbeat.d.ts +6 -1
  21. package/dist/cluster/join-token.d.ts +2 -0
  22. package/dist/cluster/peer-auth-coordinator.d.ts +13 -0
  23. package/dist/cluster/peer-client.d.ts +41 -4
  24. package/dist/cluster/peer-credentials.d.ts +31 -0
  25. package/dist/cluster/peer-forget.d.ts +138 -0
  26. package/dist/cluster/peer-handler.d.ts +18 -1
  27. package/dist/cluster/scaler-orphans-peer.d.ts +40 -0
  28. package/dist/cluster/settings-propagation.d.ts +85 -0
  29. package/dist/cluster/worker-cluster-settings.d.ts +29 -0
  30. package/dist/config.d.ts +12 -0
  31. package/dist/db/migrations/152_org_settings_cache_upload_settle_timeout.d.ts +15 -0
  32. package/dist/db/migrations/153_execution_runs_cancel_force.d.ts +16 -0
  33. package/dist/db/migrations/154_cluster_settings_firecracker_api_socket_wait.d.ts +4 -0
  34. package/dist/db/migrations/155_org_settings_reroute_spawn_retry.d.ts +17 -0
  35. package/dist/db/migrations/156_kici_events_match_outcome.d.ts +17 -0
  36. package/dist/db/types.d.ts +31 -0
  37. package/dist/events/event-router.d.ts +7 -1
  38. package/dist/events/event-store.d.ts +20 -3
  39. package/dist/events/types.d.ts +45 -0
  40. package/dist/helpers/windows-batch.d.ts +59 -0
  41. package/dist/index.js +252 -53
  42. package/dist/metrics/prometheus.d.ts +2 -0
  43. package/dist/orchestrator-core.d.ts +7 -0
  44. package/dist/pipeline/changed-files.d.ts +51 -0
  45. package/dist/pipeline/cross-source-tally.d.ts +21 -0
  46. package/dist/pipeline/degraded-reason.d.ts +8 -0
  47. package/dist/pipeline/dispatch-matched-workflow.d.ts +10 -0
  48. package/dist/pipeline/global-eval-round.d.ts +3 -2
  49. package/dist/pipeline/processor.d.ts +9 -1
  50. package/dist/pipeline/rerun-inputs.d.ts +16 -7
  51. package/dist/providers/github/changed-files.d.ts +14 -4
  52. package/dist/providers/universal-git/changed-files.d.ts +6 -0
  53. package/dist/providers/universal-git/config.d.ts +4 -0
  54. package/dist/registration/registration-store.d.ts +10 -0
  55. package/dist/routes/admin-cluster-settings.d.ts +13 -1
  56. package/dist/routes/admin-event-inspect.d.ts +36 -0
  57. package/dist/routes/admin-peer-forget.d.ts +43 -0
  58. package/dist/routes/admin-scaler-orphans.d.ts +64 -0
  59. package/dist/routes/admin.d.ts +23 -0
  60. package/dist/scaler/backend-factory.d.ts +6 -1
  61. package/dist/scaler/bare-metal-backend.d.ts +17 -15
  62. package/dist/scaler/bare-metal-launch.d.ts +90 -0
  63. package/dist/scaler/container-backend.d.ts +2 -0
  64. package/dist/scaler/container-runtime.d.ts +9 -0
  65. package/dist/scaler/firecracker-backend.d.ts +181 -7
  66. package/dist/scaler/live-vms.d.ts +57 -0
  67. package/dist/scaler/manager.d.ts +138 -5
  68. package/dist/scaler/orphan-requests.d.ts +23 -0
  69. package/dist/scaler/overlay-template.d.ts +38 -0
  70. package/dist/scaler/scaler-state-store.d.ts +7 -0
  71. package/dist/scaler/spawn-errors.d.ts +17 -0
  72. package/dist/scaler/startup-orphan-sweep.d.ts +15 -0
  73. package/dist/scaler/types.d.ts +21 -2
  74. package/dist/scaler/vm-job-bindings.d.ts +17 -0
  75. package/dist/secrets/rbac.d.ts +7 -1
  76. package/dist/server.js +7086 -2557
  77. package/dist/stale-detector/workflow-deadline-detector.d.ts +7 -1
  78. package/dist/standalone.js +16473 -11945
  79. package/dist/storage/s3.d.ts +36 -0
  80. package/dist/worker/in-memory-execution-tracker.d.ts +5 -0
  81. package/dist/worker/in-memory-job-queue.d.ts +21 -1
  82. package/dist/worker/queued-reroute-release.d.ts +39 -0
  83. package/dist/worker/reroute-spawn-control.d.ts +58 -0
  84. package/dist/worker/spawn-retry-budget.d.ts +87 -0
  85. package/dist/worker-core.d.ts +11 -2
  86. package/dist/ws/agent-handler.d.ts +24 -3
  87. package/dist/ws/agent-heartbeat.d.ts +2 -2
  88. package/installer-image-digests.json +3 -3
  89. package/package.json +5 -5
  90. package/sbom.spdx.json +53 -53
@@ -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
@@ -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 the leader when a snapshot has
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
- /** Optional callback when an agent disconnects (for scaler lifecycle). */
165
- onScalerAgentDisconnected?: (agentId: string) => void;
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
- * - Closes and unregisters agents after 180s of silence.
27
- * - Triggers dispatcher.onAgentDisconnect for stale agents.
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.12.0",
2
+ "version": "0.14.0",
3
3
  "images": {
4
- "kici-agent": "sha256:eaa1d2270da370f150b149dbf51569b51759d7b62607fe6a1cee8b01babac2e9",
5
- "kici-orchestrator": "sha256:a1a73e458b0cda327e534b74ba02406fb64170d61a3de34bfbfa8c42ab75d087"
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.12.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.12.0",
94
- "@kici-dev/shared": "0.12.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.12.0"
109
+ "@kici-dev/agent": "0.14.0"
110
110
  },
111
111
  "scripts": {
112
112
  "build": "node ../../scripts/build-service.mjs && tsc --emitDeclarationOnly",