@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
|
@@ -258,6 +258,17 @@ export declare class Dispatcher {
|
|
|
258
258
|
*/
|
|
259
259
|
hasPeerCoordinators?: () => boolean | Promise<boolean>;
|
|
260
260
|
});
|
|
261
|
+
/** Runs with the ids of the jobs each disconnect triage failed; see {@link setDisconnectCleanup}. */
|
|
262
|
+
private disconnectCleanup?;
|
|
263
|
+
/**
|
|
264
|
+
* Install what runs with the ids of the jobs a disconnect triage failed. The
|
|
265
|
+
* agent WebSocket handler installs the release of their in-memory waiters
|
|
266
|
+
* (pending builds, inits, dynamic evaluations, global eval rounds). It runs
|
|
267
|
+
* inside {@link onAgentDisconnect}, so a path that drops an agent without
|
|
268
|
+
* waiting for its socket (heartbeat timeout, ack timeout, token revocation)
|
|
269
|
+
* reaches it too.
|
|
270
|
+
*/
|
|
271
|
+
setDisconnectCleanup(cleanup: (failedJobIds: string[]) => void): void;
|
|
261
272
|
/**
|
|
262
273
|
* Dispatch a job to a matching agent, or queue it if none available.
|
|
263
274
|
*
|
|
@@ -289,6 +300,23 @@ export declare class Dispatcher {
|
|
|
289
300
|
* @returns the number of jobs that entered `spawning`.
|
|
290
301
|
*/
|
|
291
302
|
retryPendingScaleRequests(maxJobs?: number, trigger?: ScalerRedispatchTrigger): Promise<number>;
|
|
303
|
+
/**
|
|
304
|
+
* Re-offer ONE pending job to the scaler: a worker's spawn-retry backoff ending.
|
|
305
|
+
*
|
|
306
|
+
* Outside the batch's single-flight on purpose: a wake-up that lands during a
|
|
307
|
+
* batch pass would otherwise be dropped, and a worker runs no pending-scale
|
|
308
|
+
* sweep to pick it up. The worker's per-job attempt gate is what keeps this
|
|
309
|
+
* from starting a second spawn for the same job.
|
|
310
|
+
*
|
|
311
|
+
* @returns true when a spawn started.
|
|
312
|
+
*/
|
|
313
|
+
redrivePendingJob(jobId: string): Promise<boolean>;
|
|
314
|
+
/**
|
|
315
|
+
* Offer one pending job to the scaler through the same `onNoMatchingAgent` →
|
|
316
|
+
* `requestScale` path a dispatch uses, or settle it when its sealed secrets
|
|
317
|
+
* cannot be opened here.
|
|
318
|
+
*/
|
|
319
|
+
private offerPendingToScaler;
|
|
292
320
|
/**
|
|
293
321
|
* Release the slot claimed for a direct dispatch the queue refused because
|
|
294
322
|
* the job's run has stopped. Nothing is sent to the agent. The queue already
|
|
@@ -508,6 +536,15 @@ export declare class Dispatcher {
|
|
|
508
536
|
* triage finds nothing to double-handle.
|
|
509
537
|
*/
|
|
510
538
|
private handleAckExpiry;
|
|
539
|
+
/**
|
|
540
|
+
* Drop an agent that did not acknowledge a dispatch. Its other in-flight jobs
|
|
541
|
+
* (a multi-slot agent may run more than the one that timed out) go through
|
|
542
|
+
* {@link onAgentDisconnect}, which reads the registration before its first
|
|
543
|
+
* await to tell a scaler-managed agent from a static one, so the triage
|
|
544
|
+
* starts first. The unregister right after removes the agent from routing at
|
|
545
|
+
* once, so a redispatch cannot re-select it while the triage awaits.
|
|
546
|
+
*/
|
|
547
|
+
private dropUnacknowledgingAgent;
|
|
511
548
|
/**
|
|
512
549
|
* Handle an explicit dispatch rejection (`job.reject`) from an agent.
|
|
513
550
|
* Undoes the dispatch accounting and requeues the job for another agent.
|
|
@@ -634,6 +671,8 @@ export declare class Dispatcher {
|
|
|
634
671
|
* Used by the cancel-run API. Returns null if the job is not tracked (e.g. already completed).
|
|
635
672
|
*/
|
|
636
673
|
getAgentIdForJob(jobId: string): string | null;
|
|
674
|
+
/** The jobs of `runId` this dispatcher currently tracks to an agent. */
|
|
675
|
+
getTrackedJobIdsForRun(runId: string): string[];
|
|
637
676
|
/**
|
|
638
677
|
* Start periodic cleanup of expired grace window entries.
|
|
639
678
|
* Should be called during server startup.
|
package/dist/agent/registry.d.ts
CHANGED
|
@@ -263,6 +263,37 @@ export declare class AgentRegistry {
|
|
|
263
263
|
* registry behaves exactly as before.
|
|
264
264
|
*/
|
|
265
265
|
constructor(deps?: AgentRegistryDeps);
|
|
266
|
+
/**
|
|
267
|
+
* Triages the in-flight jobs of an agent this registry drops on its own (a
|
|
268
|
+
* revoked or expired token): fails or requeues a scaler-managed agent's
|
|
269
|
+
* jobs, starts recovery for a static agent's. Installed by the dispatcher,
|
|
270
|
+
* which is built after the registry.
|
|
271
|
+
*/
|
|
272
|
+
private disconnectTriage?;
|
|
273
|
+
/** Install the job triage {@link disconnectByTokenId} runs for each agent it drops. */
|
|
274
|
+
setDisconnectTriage(triage: (agentId: string) => Promise<unknown>): void;
|
|
275
|
+
/**
|
|
276
|
+
* Sockets whose `agent.register` is in flight (awaiting the scaler lookup),
|
|
277
|
+
* per agent id, with the id of the agent token each authenticated with
|
|
278
|
+
* (`null` without token auth). Kept here rather than in the WebSocket
|
|
279
|
+
* handler because the coordinator builds one handler per connection: a
|
|
280
|
+
* socket's close, and the check a registration makes against another agent
|
|
281
|
+
* token, must see a registration in flight on another connection.
|
|
282
|
+
*/
|
|
283
|
+
private readonly registeringSockets;
|
|
284
|
+
/** Record that `ws`, authenticated with `tokenId`, has started registering `agentId`. */
|
|
285
|
+
beginRegistering(agentId: string, ws: WsLike, tokenId: string | null): void;
|
|
286
|
+
/** Record that `ws` has finished registering `agentId`, however it ended. */
|
|
287
|
+
endRegistering(agentId: string, ws: WsLike): void;
|
|
288
|
+
/** Whether a socket other than `ws` is registering `agentId` right now. */
|
|
289
|
+
isRegisteringElsewhere(agentId: string, ws: WsLike): boolean;
|
|
290
|
+
/**
|
|
291
|
+
* Whether another connection holds `agentId` under an agent token other than
|
|
292
|
+
* `tokenId`: the live registration, or a registration in flight on another
|
|
293
|
+
* socket. A registration without token auth (`null`) holds no token identity
|
|
294
|
+
* and conflicts with nothing.
|
|
295
|
+
*/
|
|
296
|
+
isAgentIdHeldByOtherToken(agentId: string, tokenId: string, ws: WsLike): boolean;
|
|
266
297
|
/**
|
|
267
298
|
* Register an agent. If the agentId already exists, updates the existing
|
|
268
299
|
* entry (agent reconnection scenario).
|
package/dist/app.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ import type { PendingInitTracker } from './cache/index.js';
|
|
|
41
41
|
import type { PendingDynamicTracker, PendingGlobalEvalTracker } from './cache/index.js';
|
|
42
42
|
import type { GlobalEvalRoundCache } from './cache/index.js';
|
|
43
43
|
import type { CacheStorage } from './storage/types.js';
|
|
44
|
+
import type { CacheUploadSettle } from './cache/cache-upload-settle.js';
|
|
44
45
|
import type { ProvenanceTrustRoot } from './provenance/trust-root.js';
|
|
45
46
|
import type { LocalSigner } from './oidc/local-dev-signer.js';
|
|
46
47
|
import type { CheckRunReporter } from './reporting/check-run-reporter.js';
|
|
@@ -178,6 +179,11 @@ export interface AppDependencies {
|
|
|
178
179
|
dispatchCacheRefs?: DispatchCacheRefTracker;
|
|
179
180
|
/** Cache storage backend (S3) for metadata operations on upload completion. */
|
|
180
181
|
cacheStorage?: CacheStorage;
|
|
182
|
+
/**
|
|
183
|
+
* In-flight build-cache publishes plus the per-org settle bound. Constructed
|
|
184
|
+
* once by orchestrator-core: the agent-WS factory below runs per connection.
|
|
185
|
+
*/
|
|
186
|
+
cacheUploadSettle?: CacheUploadSettle;
|
|
181
187
|
/** Provenance trust root used to verify build-provenance bundles at ingest. */
|
|
182
188
|
provenanceTrustRoot?: ProvenanceTrustRoot;
|
|
183
189
|
/**
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holding a build job's success until the cache publish its agent reported
|
|
3
|
+
* just before it has finished.
|
|
4
|
+
*
|
|
5
|
+
* A build agent sends `cache.upload.complete` and then, moments later, the
|
|
6
|
+
* build's terminal `job.status success`. The agent WebSocket handler runs
|
|
7
|
+
* frames concurrently, so without this record the success can release the
|
|
8
|
+
* jobs that wait on the build while the pointer naming the uploaded tarball is
|
|
9
|
+
* still being written — and they dispatch without the cache URL.
|
|
10
|
+
*
|
|
11
|
+
* The upload frame registers here before its first `await`. Frames enter the
|
|
12
|
+
* handler in wire order, so the registration is in place by the time the
|
|
13
|
+
* success frame is handled. The success then waits, bounded by the org's
|
|
14
|
+
* `cache_upload_settle_timeout_ms` (cluster default
|
|
15
|
+
* `KICI_CACHE_UPLOAD_SETTLE_TIMEOUT_MS`).
|
|
16
|
+
*/
|
|
17
|
+
import type { Kysely } from 'kysely';
|
|
18
|
+
import type { Database } from '../db/types.js';
|
|
19
|
+
/** How a build success's wait for its cache publish ended. */
|
|
20
|
+
export declare enum CacheUploadSettleOutcome {
|
|
21
|
+
/** No publish was in flight for the job. */
|
|
22
|
+
None = "none",
|
|
23
|
+
/** The effective timeout is 0: the wait is turned off. */
|
|
24
|
+
Disabled = "disabled",
|
|
25
|
+
/** Every in-flight publish finished, successfully or not. */
|
|
26
|
+
Settled = "settled",
|
|
27
|
+
/** The bound passed first. The publish may still finish afterwards. */
|
|
28
|
+
TimedOut = "timed-out"
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The cache publishes in flight, per job. A full build registers two (the
|
|
32
|
+
* dependency tarball and the source tarball); a publish always settles —
|
|
33
|
+
* success or failure — so a waiter never sees a rejection.
|
|
34
|
+
*/
|
|
35
|
+
export declare class InFlightCacheUploads {
|
|
36
|
+
private readonly inFlight;
|
|
37
|
+
/** Register one publish for `jobId`; the returned function settles it (idempotent). */
|
|
38
|
+
begin(jobId: string): () => void;
|
|
39
|
+
has(jobId: string): boolean;
|
|
40
|
+
/** Number of jobs with a publish in flight. */
|
|
41
|
+
get size(): number;
|
|
42
|
+
/**
|
|
43
|
+
* Wait for every publish registered for `jobId` at call time, bounded by
|
|
44
|
+
* `timeoutMs`. A timeout leaves the records in place: they clear when their
|
|
45
|
+
* publishes settle.
|
|
46
|
+
*/
|
|
47
|
+
waitFor(jobId: string, timeoutMs: number): Promise<CacheUploadSettleOutcome>;
|
|
48
|
+
}
|
|
49
|
+
/** What the agent WebSocket handler needs to hold a build's success. */
|
|
50
|
+
export interface CacheUploadSettle {
|
|
51
|
+
uploads: InFlightCacheUploads;
|
|
52
|
+
/** The effective bound for the job's org (org override, else cluster default). */
|
|
53
|
+
timeoutMsFor: (orgId: string | undefined) => Promise<number>;
|
|
54
|
+
}
|
|
55
|
+
export interface CacheUploadSettleResult {
|
|
56
|
+
outcome: CacheUploadSettleOutcome;
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
waitedMs?: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Wait for a job's in-flight cache publishes. Reads the setting only when a
|
|
62
|
+
* publish is in flight, so an ordinary job's success costs no query.
|
|
63
|
+
*/
|
|
64
|
+
export declare function awaitCacheUploadSettle(settle: CacheUploadSettle | undefined, job: {
|
|
65
|
+
jobId: string;
|
|
66
|
+
orgId: string | undefined;
|
|
67
|
+
}): Promise<CacheUploadSettleResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the settle bound for an org: `org_settings.cache_upload_settle_timeout_ms`
|
|
70
|
+
* when set (0 included), otherwise `fallbackMs`. A missing DB, org, row or
|
|
71
|
+
* value, or a failed query, falls back — the caller is a frame handler that
|
|
72
|
+
* must not reject.
|
|
73
|
+
*/
|
|
74
|
+
export declare function createCacheUploadSettleTimeoutReader(db: Kysely<Database> | undefined, fallbackMs: number): (orgId: string | undefined) => Promise<number>;
|
|
75
|
+
//# sourceMappingURL=cache-upload-settle.d.ts.map
|
|
@@ -28,6 +28,15 @@ export interface InitResult {
|
|
|
28
28
|
* explicit `false` suppresses.
|
|
29
29
|
*/
|
|
30
30
|
filterPassed?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Verdict on the workflow's deferred `paths`, reported only when the init job
|
|
33
|
+
* was asked to decide them. `false` means the agent's clone diff matched none
|
|
34
|
+
* of them and the job must not be dispatched.
|
|
35
|
+
*
|
|
36
|
+
* Optional on purpose: an agent that predates deferred paths never sends it,
|
|
37
|
+
* so absence means "no verdict" and dispatch proceeds.
|
|
38
|
+
*/
|
|
39
|
+
pathsPassed?: boolean;
|
|
31
40
|
}
|
|
32
41
|
export declare class PendingInitTracker extends PendingTracker<InitResult> {
|
|
33
42
|
constructor();
|
|
@@ -33,11 +33,12 @@ export interface CancelRunDeps {
|
|
|
33
33
|
*/
|
|
34
34
|
instanceId?: string;
|
|
35
35
|
/**
|
|
36
|
-
* Forward `job.cancel` to the coordinator holding the agent's socket
|
|
37
|
-
*
|
|
38
|
-
* so a job owned by a sibling is
|
|
36
|
+
* Forward `job.cancel` to the coordinator holding the agent's socket, with
|
|
37
|
+
* `force` when the cancel is forced. Returns whether the send left this
|
|
38
|
+
* process. Undefined ⇒ no peer transport, so a job owned by a sibling is
|
|
39
|
+
* unreachable rather than orphaned.
|
|
39
40
|
*/
|
|
40
|
-
cancelJobOnPeer?: (peerId: string, runId: string, jobId: string, reason: string) => boolean;
|
|
41
|
+
cancelJobOnPeer?: (peerId: string, runId: string, jobId: string, reason: string, force?: boolean) => boolean;
|
|
41
42
|
/** How stale a `cluster_instances` heartbeat may be and still read as live. */
|
|
42
43
|
ownershipGraceMs?: number;
|
|
43
44
|
/**
|
|
@@ -112,4 +113,21 @@ export declare class HeldRunCancelRefusedError extends Error {
|
|
|
112
113
|
* returns a structured already-terminal result).
|
|
113
114
|
*/
|
|
114
115
|
export declare function cancelRunWithReason(deps: CancelRunDeps, runId: string, reason: string, options?: CancelRunOptions): Promise<CancelRunResult>;
|
|
116
|
+
/**
|
|
117
|
+
* Send `job.cancel` down this coordinator's own socket for the agent.
|
|
118
|
+
*
|
|
119
|
+
* Only an OPEN socket can receive it. A socket mid-close is still in the
|
|
120
|
+
* registry until its close handler unregisters it, and sending to it throws, so
|
|
121
|
+
* a non-OPEN socket leaves the job un-notified and the orphan sweep takes it.
|
|
122
|
+
* The send is wrapped so one bad socket cannot abort the whole cancellation and
|
|
123
|
+
* strand the run in `cancelling`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function sendLocalCancel(args: {
|
|
126
|
+
registry: Pick<AgentRegistry, 'get'>;
|
|
127
|
+
runId: string;
|
|
128
|
+
jobId: string;
|
|
129
|
+
agentId: string | null;
|
|
130
|
+
reason: string;
|
|
131
|
+
force: boolean;
|
|
132
|
+
}): boolean;
|
|
115
133
|
//# sourceMappingURL=cancel-run.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The receiving end of `peer.job.cancel`.
|
|
3
|
+
*
|
|
4
|
+
* A sibling forwards a cancel to the coordinator its agent is connected to,
|
|
5
|
+
* which it reads from the host roster. That coordinator's dispatcher tracks the
|
|
6
|
+
* job only when it dispatched or re-adopted it: an agent that reconnected here
|
|
7
|
+
* from a sibling, or one not yet back after this process restarted, is known
|
|
8
|
+
* only to `dispatch_queue.agent_id`. So the agent is resolved from the
|
|
9
|
+
* dispatcher first and from the dispatch record second, and a cancel that
|
|
10
|
+
* cannot be delivered is logged with the reason.
|
|
11
|
+
*
|
|
12
|
+
* On a worker the receiver also removes the jobs of the cancel that are still
|
|
13
|
+
* queued (waiting for an agent spawn), and stop-marks the dispatched ones so a
|
|
14
|
+
* requeue cannot bring them back. A removed job is reported `dequeued`.
|
|
15
|
+
*/
|
|
16
|
+
import type { Kysely } from 'kysely';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import type { PeerJobCancel } from '@kici-dev/engine';
|
|
19
|
+
import type { Database } from '../db/types.js';
|
|
20
|
+
import type { AgentRegistry } from '../agent/registry.js';
|
|
21
|
+
import type { Dispatcher } from '../agent/dispatcher.js';
|
|
22
|
+
export declare const PeerCancelOutcome: z.ZodEnum<{
|
|
23
|
+
"agent-not-connected": "agent-not-connected";
|
|
24
|
+
delivered: "delivered";
|
|
25
|
+
dequeued: "dequeued";
|
|
26
|
+
"lookup-failed": "lookup-failed";
|
|
27
|
+
"not-tracked": "not-tracked";
|
|
28
|
+
}>;
|
|
29
|
+
export type PeerCancelOutcome = z.infer<typeof PeerCancelOutcome>;
|
|
30
|
+
/** A live dispatch row: the job and the agent it was handed to. */
|
|
31
|
+
export interface DispatchedAgent {
|
|
32
|
+
jobId: string;
|
|
33
|
+
agentId: string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface PeerCancelDelivery extends DispatchedAgent {
|
|
36
|
+
outcome: PeerCancelOutcome;
|
|
37
|
+
}
|
|
38
|
+
export interface PeerJobCancelDeps {
|
|
39
|
+
dispatcher: Pick<Dispatcher, 'getAgentIdForJob' | 'getTrackedJobIdsForRun'>;
|
|
40
|
+
registry: Pick<AgentRegistry, 'get'>;
|
|
41
|
+
/** The durable dispatch record. Absent on a worker, which has no database. */
|
|
42
|
+
lookupDispatched?: (runId: string, jobId: string | undefined) => Promise<DispatchedAgent[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Remove this process's queued (not yet dispatched) jobs of the cancel, and
|
|
45
|
+
* stop-mark its dispatched ones so a requeue cannot bring them back. Wired only
|
|
46
|
+
* on a worker, whose queue lives in memory; a coordinator's queue rows are
|
|
47
|
+
* settled by its own run-cancel path.
|
|
48
|
+
*/
|
|
49
|
+
releaseQueued?: (runId: string, jobId: string | undefined) => Promise<string[]>;
|
|
50
|
+
logger: {
|
|
51
|
+
info(message: string, meta?: object): unknown;
|
|
52
|
+
warn(message: string, meta?: object): unknown;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Read the live dispatch rows of a run, or of one job of it. */
|
|
56
|
+
export declare function readDispatchedAgents(db: Kysely<Database>, runId: string, jobId: string | undefined): Promise<DispatchedAgent[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Deliver a `peer.job.cancel` to the agents connected here. Resolves; never
|
|
59
|
+
* rejects: every failure is an outcome and a warn line.
|
|
60
|
+
*/
|
|
61
|
+
export declare function deliverPeerJobCancel(deps: PeerJobCancelDeps, msg: PeerJobCancel): Promise<PeerCancelDelivery[]>;
|
|
62
|
+
//# sourceMappingURL=peer-job-cancel.d.ts.map
|
|
@@ -3,7 +3,9 @@ import type { Database } from '../db/types.js';
|
|
|
3
3
|
export interface SweepStuckCancellingDeps {
|
|
4
4
|
db: Kysely<Database>;
|
|
5
5
|
/** The canonical cancel path, bound to this orchestrator's deps. */
|
|
6
|
-
cancelRun: (runId: string, reason: string
|
|
6
|
+
cancelRun: (runId: string, reason: string, options: {
|
|
7
|
+
force: boolean;
|
|
8
|
+
}) => Promise<unknown>;
|
|
7
9
|
/**
|
|
8
10
|
* How long a run may sit in `cancelling` before it is re-driven. Twice the
|
|
9
11
|
* recovery grace period: long enough that a graceful agent teardown finishes
|
package/dist/cli/api-client.d.ts
CHANGED
|
@@ -5,6 +5,42 @@
|
|
|
5
5
|
* All methods are thin wrappers around fetch() that handle JSON serialization,
|
|
6
6
|
* error formatting, and URL construction.
|
|
7
7
|
*/
|
|
8
|
+
import type { PeerForgetOutcome, ScalerLiveVm, ScalerVmStopResult } from '@kici-dev/engine';
|
|
9
|
+
/** `POST /api/v1/admin/peers/forget`: one result per coordinator, this one first. */
|
|
10
|
+
export interface PeerForgetResponseBody {
|
|
11
|
+
instanceId: string;
|
|
12
|
+
results: Array<{
|
|
13
|
+
coordinator: string;
|
|
14
|
+
outcome: PeerForgetOutcome;
|
|
15
|
+
detail: string;
|
|
16
|
+
}>;
|
|
17
|
+
/** Set when the coordinator kept the peer only for want of the backstop acknowledgement. */
|
|
18
|
+
acknowledgementRequired?: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/** The node a scaler orphans request answered for. */
|
|
22
|
+
export interface ScalerOrphansNode {
|
|
23
|
+
instanceId: string;
|
|
24
|
+
/** The node's cluster role; `unknown` for a peer the coordinator has no role for. */
|
|
25
|
+
role: 'coordinator' | 'worker' | 'unknown';
|
|
26
|
+
/** The node's Firecracker scaler names; empty when it runs none. */
|
|
27
|
+
firecrackerScalers: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Coordinator peers the cluster expects but the answering coordinator has no
|
|
30
|
+
* live link to. While any is listed, no VM is `orphaned`.
|
|
31
|
+
*/
|
|
32
|
+
disconnectedCoordinators?: string[];
|
|
33
|
+
}
|
|
34
|
+
/** `GET /api/v1/admin/scaler/orphans`: every live Firecracker VM on the node. */
|
|
35
|
+
export interface ScalerOrphansListResponse {
|
|
36
|
+
node: ScalerOrphansNode;
|
|
37
|
+
vms: ScalerLiveVm[];
|
|
38
|
+
}
|
|
39
|
+
/** `POST /api/v1/admin/scaler/orphans/stop`: one result per requested VM. */
|
|
40
|
+
export interface ScalerOrphansStopResponse {
|
|
41
|
+
node: ScalerOrphansNode;
|
|
42
|
+
results: ScalerVmStopResult[];
|
|
43
|
+
}
|
|
8
44
|
/** A node in the fleet topology returned by GET /admin/fleet-topology. */
|
|
9
45
|
export interface FleetTopologyNodeResponse {
|
|
10
46
|
kind: 'orchestrator' | 'agent';
|
|
@@ -384,6 +420,29 @@ export declare class AdminApiClient {
|
|
|
384
420
|
draining: boolean;
|
|
385
421
|
jobsRunning: number;
|
|
386
422
|
}>;
|
|
423
|
+
/** Forget a departed peer on this coordinator and every connected sibling. */
|
|
424
|
+
/**
|
|
425
|
+
* Forget a departed peer on this coordinator and every connected sibling. A
|
|
426
|
+
* refusal that only needs the backstop acknowledgement comes back as a body
|
|
427
|
+
* with `acknowledgementRequired`, so the caller can ask and retry; every
|
|
428
|
+
* other failure throws.
|
|
429
|
+
*/
|
|
430
|
+
forgetPeer(body: {
|
|
431
|
+
instanceId: string;
|
|
432
|
+
timeoutMs?: number;
|
|
433
|
+
acknowledgeBackstop?: boolean;
|
|
434
|
+
}): Promise<PeerForgetResponseBody>;
|
|
435
|
+
/** List the live Firecracker VMs on this coordinator, or on the `target` peer. */
|
|
436
|
+
listScalerOrphans(opts?: {
|
|
437
|
+
target?: string;
|
|
438
|
+
timeoutMs?: number;
|
|
439
|
+
}): Promise<ScalerOrphansListResponse>;
|
|
440
|
+
/** Stop the named VMs on this coordinator, or on the `target` peer. */
|
|
441
|
+
stopScalerOrphans(body: {
|
|
442
|
+
target?: string;
|
|
443
|
+
vmIds: string[];
|
|
444
|
+
timeoutMs?: number;
|
|
445
|
+
}): Promise<ScalerOrphansStopResponse>;
|
|
387
446
|
createApiKey(opts: {
|
|
388
447
|
label?: string;
|
|
389
448
|
routingKeys?: string[];
|
|
@@ -547,6 +606,20 @@ export declare class AdminApiClient {
|
|
|
547
606
|
includePayload?: boolean;
|
|
548
607
|
routingKey?: string;
|
|
549
608
|
}): Promise<Record<string, unknown>>;
|
|
609
|
+
/** List internal events, newest first (`GET /api/v1/admin/events`). */
|
|
610
|
+
listEvents(opts?: {
|
|
611
|
+
name?: string;
|
|
612
|
+
outcome?: string;
|
|
613
|
+
since?: string;
|
|
614
|
+
before?: string;
|
|
615
|
+
limit?: number;
|
|
616
|
+
}): Promise<{
|
|
617
|
+
events: Array<Record<string, unknown>>;
|
|
618
|
+
limit: number;
|
|
619
|
+
nextCursor: string | null;
|
|
620
|
+
}>;
|
|
621
|
+
/** One internal event with its redacted payload and dispatched runs. */
|
|
622
|
+
getEvent(id: string): Promise<Record<string, unknown>>;
|
|
550
623
|
listAccessLog(opts?: {
|
|
551
624
|
orgId?: string;
|
|
552
625
|
actorType?: string;
|
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Talks to the orchestrator admin API directly (not the Platform dashboard
|
|
7
7
|
* proxy), so the CLI stays operable even when Platform is unavailable. Backed
|
|
8
|
-
* by `packages/orchestrator/src/routes/admin-cluster-settings.ts`.
|
|
8
|
+
* by `packages/orchestrator/src/routes/admin-cluster-settings.ts`. `show` also
|
|
9
|
+
* lists which settings version each coordinator and worker applied.
|
|
9
10
|
*
|
|
10
11
|
* These knobs are fleet-wide (a single `cluster_settings` row), not per-tenant.
|
|
11
12
|
* For per-tenant knobs use `kici-admin org-settings`.
|
|
12
13
|
*/
|
|
13
14
|
import type { Command } from 'commander';
|
|
14
15
|
import type { AdminApiClient } from '../api-client.js';
|
|
16
|
+
import { type SettingsPropagationReport } from '../../cluster/settings-propagation.js';
|
|
15
17
|
/**
|
|
16
18
|
* One text knob. Kept separate from {@link KnobSpec} because the value parser,
|
|
17
19
|
* the PATCH body type, and the `show` formatter all differ — a string knob has
|
|
@@ -36,8 +38,36 @@ interface BooleanKnobSpec {
|
|
|
36
38
|
flag: string;
|
|
37
39
|
label: string;
|
|
38
40
|
}
|
|
41
|
+
type ClusterSettings = Record<string, number | string | boolean | null>;
|
|
39
42
|
/** camelCase → value (null clears the override). */
|
|
40
43
|
type PatchBody = Record<string, number | string | boolean | null>;
|
|
44
|
+
/** The outcome of the propagation request `show` makes after the settings read. */
|
|
45
|
+
export type PropagationFetch = {
|
|
46
|
+
kind: 'ok';
|
|
47
|
+
report: SettingsPropagationReport;
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'unsupported';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'error';
|
|
52
|
+
reason: string;
|
|
53
|
+
};
|
|
54
|
+
export declare const PROPAGATION_UNSUPPORTED_NOTE: string;
|
|
55
|
+
/**
|
|
56
|
+
* Fetch the propagation report. A 404 means the orchestrator predates the
|
|
57
|
+
* route; `show` then prints the settings alone, because this CLI may be newer
|
|
58
|
+
* than the orchestrator it talks to.
|
|
59
|
+
*/
|
|
60
|
+
export declare function fetchPropagation(client: AdminApiClient): Promise<PropagationFetch>;
|
|
61
|
+
/** The table section: one row per orchestrator, lagging ones in upper case. */
|
|
62
|
+
export declare function formatPropagation(report: SettingsPropagationReport): string;
|
|
63
|
+
/**
|
|
64
|
+
* What `show` prints. The JSON keeps the flat settings map exactly as before
|
|
65
|
+
* and adds one `propagation` key, so scripts that read knob keys keep working.
|
|
66
|
+
*/
|
|
67
|
+
export declare function renderShow(settings: ClusterSettings, propagation: PropagationFetch, format: string): {
|
|
68
|
+
stdout: string;
|
|
69
|
+
stderr: string | null;
|
|
70
|
+
};
|
|
41
71
|
/**
|
|
42
72
|
* Parse an integer CLI flag against its knob's bounds, exiting on failure.
|
|
43
73
|
*
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Render the post-receive hook script. For each pushed ref the hook calls
|
|
3
|
-
* `kici-admin source trigger-local <id> --ref <ref> --sha <new> --base-url <url>`,
|
|
4
|
-
* so a push to any branch dispatches a run for that ref
|
|
3
|
+
* `kici-admin source trigger-local <id> --ref <ref> --before <old> --sha <new> --base-url <url>`,
|
|
4
|
+
* so a push to any branch dispatches a run for that ref, and its changed files
|
|
5
|
+
* are the commits the push added (`<old>..<new>`).
|
|
5
6
|
*/
|
|
6
7
|
export declare function renderPostReceiveHook(opts: {
|
|
7
8
|
sourceId: string;
|
|
@@ -5,6 +5,11 @@ export interface LocalTriggerInput {
|
|
|
5
5
|
event: 'push' | 'pull_request';
|
|
6
6
|
ref: string;
|
|
7
7
|
sha: string;
|
|
8
|
+
/**
|
|
9
|
+
* The commit before the push. With it the changed files are `before..sha`;
|
|
10
|
+
* without it the push has no range and path filters match conservatively.
|
|
11
|
+
*/
|
|
12
|
+
before?: string;
|
|
8
13
|
defaultBranch: string;
|
|
9
14
|
}
|
|
10
15
|
export interface LocalTriggerRequest {
|
|
@@ -19,6 +24,8 @@ export declare function readRepoHead(repoPath: string): {
|
|
|
19
24
|
ref: string;
|
|
20
25
|
sha: string;
|
|
21
26
|
};
|
|
27
|
+
/** The parent of `sha` in a local repo, or undefined for a root commit or an unreadable repo. */
|
|
28
|
+
export declare function readParentSha(repoPath: string, sha: string): string | undefined;
|
|
22
29
|
/** POST the trigger request to the orchestrator base URL. Returns the HTTP status. */
|
|
23
30
|
export declare function sendLocalTrigger(baseUrl: string, req: LocalTriggerRequest): Promise<number>;
|
|
24
31
|
//# sourceMappingURL=local-trigger.d.ts.map
|
|
@@ -5,9 +5,35 @@
|
|
|
5
5
|
* peer create-token Create a join token for a new peer
|
|
6
6
|
* peer list List active peer credentials
|
|
7
7
|
* peer revoke Revoke a specific peer's credential
|
|
8
|
+
* peer forget Drop a departed peer from every coordinator's live
|
|
9
|
+
* peer registry (admin HTTP API; credential untouched)
|
|
8
10
|
* peer revoke-all Revoke all peer credentials
|
|
9
11
|
*/
|
|
10
12
|
import type { Command } from 'commander';
|
|
11
13
|
import type { AdminApiClient } from '../api-client.js';
|
|
12
|
-
|
|
14
|
+
/** Where `peer forget` writes its results. */
|
|
15
|
+
export interface PeerForgetIo {
|
|
16
|
+
out: (line: string) => void;
|
|
17
|
+
/** Asks the operator; resolves true on yes. */
|
|
18
|
+
confirm: (prompt: string) => Promise<boolean>;
|
|
19
|
+
/** Whether an operator can answer a prompt (stdin is a terminal). */
|
|
20
|
+
interactive: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `kici-admin peer forget`: drop a departed peer from this coordinator's live
|
|
24
|
+
* peer registry and every connected sibling's. Exits 1 when any coordinator
|
|
25
|
+
* kept it (connected, heard from recently, or no answer).
|
|
26
|
+
*
|
|
27
|
+
* When the forget would switch a coordinator's event-provision backstop back
|
|
28
|
+
* on, the operator confirms that consequence first; `--yes` acknowledges it up
|
|
29
|
+
* front, and without a terminal the command refuses instead of asking.
|
|
30
|
+
*
|
|
31
|
+
* @returns the process exit code
|
|
32
|
+
*/
|
|
33
|
+
export declare function runPeerForget(client: Pick<AdminApiClient, 'forgetPeer'>, instanceId: string, opts: {
|
|
34
|
+
json: boolean;
|
|
35
|
+
timeout: string;
|
|
36
|
+
yes: boolean;
|
|
37
|
+
}, io: PeerForgetIo): Promise<number>;
|
|
38
|
+
export declare function registerPeerCommands(program: Command, getClient: () => AdminApiClient): void;
|
|
13
39
|
//# sourceMappingURL=peer.d.ts.map
|
|
@@ -1,18 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Scaler maintenance commands for kici-admin.
|
|
3
3
|
*
|
|
4
|
+
* scaler orphans List the live Firecracker VMs a node's running
|
|
5
|
+
* orchestrator does not track and, with --stop, stop
|
|
6
|
+
* them. Goes through the admin HTTP API; `--target`
|
|
7
|
+
* reaches a worker through the coordinator.
|
|
4
8
|
* scaler reap-orphans Free leaked Firecracker/container resources WITHOUT a
|
|
5
9
|
* running orchestrator (recovery for a wedged node whose
|
|
6
10
|
* data disk is full).
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
* and runs the liveness-driven reaper. It is the sanctioned operator path for
|
|
12
|
+
* `reap-orphans` loads the orchestrator's LOCAL config (no HTTP admin API, no
|
|
13
|
+
* DB) and runs the liveness-driven reaper, which spares every live VM. It is the sanctioned operator path for
|
|
10
14
|
* the ENOSPC bootstrap deadlock: when the data disk is 100% full the
|
|
11
15
|
* orchestrator crash-loops before its in-process orphan sweep can run, so a
|
|
12
16
|
* tool that frees disk WITHOUT the orchestrator is required.
|
|
13
17
|
*/
|
|
14
18
|
import type { Command } from 'commander';
|
|
19
|
+
import type { AdminApiClient } from '../api-client.js';
|
|
15
20
|
/** Probe the local orchestrator /health endpoint. Healthy => it reaps itself. */
|
|
16
21
|
export declare function isOrchestratorHealthy(port: number, basePath: string): Promise<boolean>;
|
|
17
|
-
|
|
22
|
+
/** The `scaler orphans` options as Commander parses them. */
|
|
23
|
+
export interface ScalerOrphansOptions {
|
|
24
|
+
target?: string;
|
|
25
|
+
all: boolean;
|
|
26
|
+
stop: boolean;
|
|
27
|
+
vm: string[];
|
|
28
|
+
yes: boolean;
|
|
29
|
+
dryRun: boolean;
|
|
30
|
+
/** Seconds to wait for a `--target` node. */
|
|
31
|
+
timeout: string;
|
|
32
|
+
json: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Where `scaler orphans` reads its confirmation and writes its output. */
|
|
35
|
+
export interface ScalerOrphansIo {
|
|
36
|
+
confirm: (prompt: string) => Promise<boolean>;
|
|
37
|
+
/** The listing, the results, or the JSON body. */
|
|
38
|
+
out: (line: string) => void;
|
|
39
|
+
/** Progress and the confirmation context; stderr, so `--json` output stays parseable. */
|
|
40
|
+
err: (line: string) => void;
|
|
41
|
+
}
|
|
42
|
+
type OrphansClient = Pick<AdminApiClient, 'listScalerOrphans' | 'stopScalerOrphans'>;
|
|
43
|
+
/** `45s`, `12m3s`, `1h2m`, `3d4h`. */
|
|
44
|
+
export declare function formatAge(seconds: number): string;
|
|
45
|
+
/**
|
|
46
|
+
* `kici-admin scaler orphans`: list the live Firecracker VMs a node's
|
|
47
|
+
* orchestrator does not track, and with `--stop` stop the orphaned ones.
|
|
48
|
+
*
|
|
49
|
+
* @returns the process exit code
|
|
50
|
+
*/
|
|
51
|
+
export declare function runScalerOrphans(client: OrphansClient, opts: ScalerOrphansOptions, io: ScalerOrphansIo): Promise<number>;
|
|
52
|
+
export declare function registerScalerCommands(program: Command, getClient: () => AdminApiClient): void;
|
|
53
|
+
export {};
|
|
18
54
|
//# sourceMappingURL=scaler.d.ts.map
|