@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Kysely } from 'kysely';
|
|
2
|
-
import type { Database } from '../db/types.js';
|
|
2
|
+
import type { Database, ClusterSettings } from '../db/types.js';
|
|
3
3
|
/** Numeric columns on cluster_settings readable via {@link ClusterSettingsReader}. */
|
|
4
|
-
export type ClusterNumberColumn = 'max_github_payload_bytes' | 'event_log_max_payload_bytes' | 'lock_file_max_bytes' | 'webhook_dedup_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'lockfile_cache_max' | 'lockfile_cache_max_bytes' | 'lockfile_cache_ttl_ms' | 'content_cache_max' | 'content_cache_max_bytes' | 'content_cache_ttl_ms' | 'global_eval_round_timeout_ms' | 'global_eval_candidate_timeout_ms' | 'global_eval_cache_max' | 'global_eval_wait_timeout_ms' | 'check_run_tracking_ttl_days' | 'run_retention_days' | 'audit_retention_days' | 'provenance_retention_days' | 'held_run_retention_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'sealed_secrets_retry_backoff_ms' | 'unroutable_grace_ms' | 'ingest_overflow_claim_timeout_ms' | 'scaler_reap_interval_ms' | 'scaler_reap_stranded_timeout_ms' | 'scaler_reap_reattempt_interval_ms' | 'scaler_claim_retention_ms' | 'scaler_provision_backoff_base_ms' | 'scaler_provision_backoff_max_ms' | 'scaler_provision_max_consecutive_failures';
|
|
4
|
+
export type ClusterNumberColumn = 'max_github_payload_bytes' | 'event_log_max_payload_bytes' | 'lock_file_max_bytes' | 'webhook_dedup_ttl_ms' | 'event_router_event_ttl_seconds' | 'event_router_max_dispatch_attempts' | 'queue_max_depth' | 'reroute_flap_grace_ms' | 'max_fanout_hosts' | 'event_router_rate_limit_per_workflow_per_minute' | 'cache_max_tarball_bytes' | 'cache_ttl_days' | 'lockfile_cache_max' | 'lockfile_cache_max_bytes' | 'lockfile_cache_ttl_ms' | 'content_cache_max' | 'content_cache_max_bytes' | 'content_cache_ttl_ms' | 'global_eval_round_timeout_ms' | 'global_eval_candidate_timeout_ms' | 'global_eval_cache_max' | 'global_eval_wait_timeout_ms' | 'firecracker_api_socket_wait_ms' | 'check_run_tracking_ttl_days' | 'run_retention_days' | 'audit_retention_days' | 'provenance_retention_days' | 'held_run_retention_days' | 'concurrency_wait_timeout_ms' | 'agent_token_ttl_ms' | 'ownership_db_check_timeout_ms' | 'sealed_secrets_retry_backoff_ms' | 'unroutable_grace_ms' | 'ingest_overflow_claim_timeout_ms' | 'scaler_reap_interval_ms' | 'scaler_reap_stranded_timeout_ms' | 'scaler_reap_reattempt_interval_ms' | 'scaler_claim_retention_ms' | 'scaler_provision_backoff_base_ms' | 'scaler_provision_backoff_max_ms' | 'scaler_provision_max_consecutive_failures';
|
|
5
5
|
/** Text columns on cluster_settings readable via {@link ClusterSettingsReader}. */
|
|
6
6
|
export type ClusterStringColumn = 'dashboard_verified_issuer';
|
|
7
7
|
/** Boolean columns on cluster_settings readable via {@link ClusterSettingsReader}. */
|
|
@@ -88,6 +88,12 @@ export declare class ClusterSettingsReader {
|
|
|
88
88
|
*/
|
|
89
89
|
private loadSnapshot;
|
|
90
90
|
private loadRow;
|
|
91
|
+
/**
|
|
92
|
+
* The whole row from one read (undefined when there is no row or the read
|
|
93
|
+
* failed). For a caller that needs several columns that agree with each
|
|
94
|
+
* other, such as a value and the row version it belongs to.
|
|
95
|
+
*/
|
|
96
|
+
getRow(): Promise<ClusterSettings | undefined>;
|
|
91
97
|
/** Resolve a fleet-wide numeric knob; `fallback` is the config.ts cluster default. */
|
|
92
98
|
getNumber(column: ClusterNumberColumn, fallback: number): Promise<number>;
|
|
93
99
|
/**
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import { type Logger } from '@kici-dev/shared';
|
|
3
|
+
import type { Database } from '../db/types.js';
|
|
4
|
+
import { PeerCredentialStore } from './peer-credentials.js';
|
|
5
|
+
/** What one reconciliation did. */
|
|
6
|
+
export declare enum CoordinatorCredentialOutcome {
|
|
7
|
+
Valid = "valid",
|
|
8
|
+
Issued = "issued",
|
|
9
|
+
Replaced = "replaced",
|
|
10
|
+
Revoked = "revoked",
|
|
11
|
+
Failed = "failed"
|
|
12
|
+
}
|
|
13
|
+
/** Why a new credential was issued, recorded on the issue log line. */
|
|
14
|
+
export declare enum CredentialIssueReason {
|
|
15
|
+
NoCredential = "no-credential",
|
|
16
|
+
Expired = "expired",
|
|
17
|
+
FileMissing = "credential-file-missing",
|
|
18
|
+
FileMismatch = "credential-file-mismatch"
|
|
19
|
+
}
|
|
20
|
+
export declare const COORDINATOR_CREDENTIAL_REVOKED_MESSAGE = "Peer credential for this coordinator was revoked; not issuing a new one";
|
|
21
|
+
export type CoordinatorCredentialStore = Pick<PeerCredentialStore, 'save' | 'findUnrevokedByInstanceId' | 'findLatestRevokedByInstanceId' | 'retireSelfIssued'>;
|
|
22
|
+
export interface EnsureCoordinatorCredentialOptions {
|
|
23
|
+
store: CoordinatorCredentialStore;
|
|
24
|
+
/** Absolute path of the credential file, with `~` already expanded. */
|
|
25
|
+
credentialFile: string;
|
|
26
|
+
instanceId: string;
|
|
27
|
+
/** Whether an instance has a live `cluster_instances` heartbeat. */
|
|
28
|
+
isInstanceLive: (instanceId: string) => Promise<boolean>;
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
now?: () => Date;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Make sure this coordinator holds a credential its peers accept, issuing one
|
|
34
|
+
* when it has none. Never throws: a failure is logged and reported as `Failed`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensureCoordinatorCredential(opts: EnsureCoordinatorCredentialOptions): Promise<CoordinatorCredentialOutcome>;
|
|
37
|
+
/** The `selfIssue` callback the coordinator's `PeerAuthCoordinator` runs. */
|
|
38
|
+
export declare function coordinatorSelfIssuer(opts: {
|
|
39
|
+
db: Kysely<Database>;
|
|
40
|
+
credentialFile: string;
|
|
41
|
+
instanceId: string;
|
|
42
|
+
agentMaxReconnectDelayMs: number;
|
|
43
|
+
clusterInstanceHeartbeatMs: number;
|
|
44
|
+
}): () => Promise<CoordinatorCredentialOutcome>;
|
|
45
|
+
//# sourceMappingURL=coordinator-credential.d.ts.map
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - "Peers report step-by-step progress back to coordinator"
|
|
11
11
|
* - "Cancel mode: graceful -- finish current step, cancel remaining"
|
|
12
12
|
*/
|
|
13
|
-
import type { JobReroute, JobProgress, JobProgressAck, PeerScalerEvent, PeerToPeerMessage, ResourceRequest, LabelMatcher } from '@kici-dev/engine';
|
|
13
|
+
import type { JobReroute, JobProgress, JobProgressAck, PeerScalerEvent, PeerToPeerMessage, RerouteSpawnRetry, ResourceRequest, LabelMatcher } from '@kici-dev/engine';
|
|
14
14
|
import type { PeerRegistry } from './peer-registry.js';
|
|
15
15
|
import type { PeerClient } from './peer-client.js';
|
|
16
16
|
import type { Dispatcher } from '../agent/dispatcher.js';
|
|
@@ -114,6 +114,14 @@ export interface RunCoordinatorDeps {
|
|
|
114
114
|
* {@link DEFAULT_MAX_HOPS}.
|
|
115
115
|
*/
|
|
116
116
|
getRerouteMaxHops?: (job: JobToRoute) => Promise<number>;
|
|
117
|
+
/**
|
|
118
|
+
* Per-job spawn-retry budget sent to the receiving peer on `job.reroute` and
|
|
119
|
+
* enforced here on the peer's relayed spawn failures. Resolves the job's org
|
|
120
|
+
* overrides from `org_settings.reroute_spawn_max_attempts` /
|
|
121
|
+
* `reroute_spawn_retry_backoff_ms`, else the cluster defaults. When absent,
|
|
122
|
+
* {@link DEFAULT_REROUTE_SPAWN_RETRY} is used.
|
|
123
|
+
*/
|
|
124
|
+
getRerouteSpawnRetry?: (job: JobToRoute) => Promise<RerouteSpawnRetry>;
|
|
117
125
|
}
|
|
118
126
|
export declare class RunCoordinator {
|
|
119
127
|
private readonly instanceId;
|
|
@@ -128,6 +136,7 @@ export declare class RunCoordinator {
|
|
|
128
136
|
private readonly getRerouteSpawnWindowMs;
|
|
129
137
|
private readonly getRerouteAckTimeoutMs;
|
|
130
138
|
private readonly getRerouteMaxHops;
|
|
139
|
+
private readonly getRerouteSpawnRetry;
|
|
131
140
|
/**
|
|
132
141
|
* Tracks which jobs have been rerouted to which peers, keyed by runId.
|
|
133
142
|
* Used for cancel propagation, progress tracking, and the post-ACK spawn
|
|
@@ -193,12 +202,18 @@ export declare class RunCoordinator {
|
|
|
193
202
|
* line + dispatch-queue last-error column) just as it would for a
|
|
194
203
|
* locally-emitted scaler event.
|
|
195
204
|
*
|
|
196
|
-
* Layer B
|
|
197
|
-
*
|
|
198
|
-
* the reroute
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
205
|
+
* Layer B: a `scaler.failed` for a tracked rerouted job whose spawn window is
|
|
206
|
+
* still armed is the worker reporting that an agent spawn failed after it
|
|
207
|
+
* accepted the reroute. The worker retries within the spawn-retry budget sent on
|
|
208
|
+
* `job.reroute`, and the relay carries its verdict:
|
|
209
|
+
*
|
|
210
|
+
* - `final: true` — the worker gave the job back. Re-dispatch immediately (the
|
|
211
|
+
* same routine the spawn-window timer runs). Idempotent with the Layer A
|
|
212
|
+
* timer: whichever fires first removes the tracking entry.
|
|
213
|
+
* - `final: false` — the worker is retrying. Re-arm the window for one more
|
|
214
|
+
* attempt plus the backoff; treat the `maxAttempts`-th such relay as final.
|
|
215
|
+
* - no verdict — an older worker that retries without a bound. The window
|
|
216
|
+
* armed at the reroute keeps running unchanged.
|
|
202
217
|
*
|
|
203
218
|
* Two guards make the re-dispatch safe. First, source provenance: the failure
|
|
204
219
|
* must come from `fromPeerId === tracked.peerId` (the authenticated connection
|
|
@@ -211,6 +226,16 @@ export declare class RunCoordinator {
|
|
|
211
226
|
* correct regardless of which peer relayed the event.
|
|
212
227
|
*/
|
|
213
228
|
onPeerScalerEvent(msg: PeerScalerEvent, fromPeerId: string): void;
|
|
229
|
+
/**
|
|
230
|
+
* A worker's verdict on a failed spawn for a job it holds. `final: true` means it
|
|
231
|
+
* gave the job back, so re-dispatch now. `final: false` means it is retrying, so
|
|
232
|
+
* restart the window for one more attempt plus the backoff this coordinator told it
|
|
233
|
+
* to wait. No verdict means a worker that retries without a bound, so leave the
|
|
234
|
+
* window as it is. The count caps a worker that never says `final`.
|
|
235
|
+
*/
|
|
236
|
+
private onRelayedSpawnFailure;
|
|
237
|
+
/** Restart a rerouted job's spawn window for one more worker attempt plus its backoff. */
|
|
238
|
+
private rearmAfterSpawnRetry;
|
|
214
239
|
/**
|
|
215
240
|
* Handle peer job completion. Updates ExecutionTracker.
|
|
216
241
|
* The execution tracker's onJobStatus fires the needs-aware scheduler hook,
|
|
@@ -317,7 +342,8 @@ export declare class RunCoordinator {
|
|
|
317
342
|
/**
|
|
318
343
|
* Post-ACK spawn-window backstop. Fires when a rerouted job's peer accepted
|
|
319
344
|
* but produced no progress within the window (Layer A), or immediately when a
|
|
320
|
-
* worker relays
|
|
345
|
+
* worker relays its final spawn-failure verdict for the job (Layer B, via
|
|
346
|
+
* onPeerScalerEvent).
|
|
321
347
|
* Best-effort cancels the original peer (double-execution guard against a
|
|
322
348
|
* slow-but-healthy spawn), then re-runs routing to another peer, then a local
|
|
323
349
|
* fallback, and finally records the job failed rather than leaving it pending.
|
|
@@ -334,9 +360,25 @@ export declare class RunCoordinator {
|
|
|
334
360
|
* The run-cancel path needs this for a job this coordinator never routed:
|
|
335
361
|
* the Platform picks any pool member to receive `run.cancel.request`, and only
|
|
336
362
|
* the coordinator holding the agent's socket can put a cancel frame on it.
|
|
337
|
-
*
|
|
363
|
+
* `force` rides on the message, so a force cancel stays forced on the peer.
|
|
364
|
+
* {@link RunCoordinator.cancelRun} covers rerouted jobs.
|
|
365
|
+
*/
|
|
366
|
+
cancelJobOnPeer(peerId: string, runId: string, jobId: string, reason: string, force?: boolean): boolean;
|
|
367
|
+
/**
|
|
368
|
+
* Send a fire-and-forget message to a peer. The outbound client carries it
|
|
369
|
+
* when connected; otherwise the peer's own connection to this process does.
|
|
370
|
+
* The outbound client is down while it reconnects, and for good on a
|
|
371
|
+
* coordinator whose credential an operator revoked.
|
|
372
|
+
* `PeerClient.send` returns false only when it sent nothing, so the fallback
|
|
373
|
+
* never delivers a message twice.
|
|
374
|
+
*/
|
|
375
|
+
private sendToPeer;
|
|
376
|
+
/**
|
|
377
|
+
* The link a reroute uses, chosen before sending. A reroute is never retried
|
|
378
|
+
* over the second link: `false` from a connected client is a NAK or an ack
|
|
379
|
+
* timeout, and resending would deliver the job twice.
|
|
338
380
|
*/
|
|
339
|
-
|
|
381
|
+
private rerouteTransport;
|
|
340
382
|
/**
|
|
341
383
|
* Local fallback for a re-dispatch: dispatch the job on this coordinator under
|
|
342
384
|
* the existing jobId. Returns true when the local dispatcher accepts it.
|
package/dist/cluster/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type { PeerInfo, PeerAgentInfo, PeerRegistryOptions } from './peer-regist
|
|
|
9
9
|
export { PeerClient } from './peer-client.js';
|
|
10
10
|
export type { PeerClientOptions } from './peer-client.js';
|
|
11
11
|
export { PeerAuthCoordinator } from './peer-auth-coordinator.js';
|
|
12
|
+
export { coordinatorSelfIssuer } from './coordinator-credential.js';
|
|
12
13
|
export type { AuthDecision, RejectionAction } from './peer-auth-coordinator.js';
|
|
13
14
|
export { createPeerHandler } from './peer-handler.js';
|
|
14
15
|
export type { PeerHandlerDeps } from './peer-handler.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
2
|
import type { Database } from '../db/types.js';
|
|
3
3
|
/** Default interval between `cluster_instances` heartbeat writes. */
|
|
4
4
|
export declare const DEFAULT_INSTANCE_HEARTBEAT_MS = 10000;
|
|
@@ -23,6 +23,11 @@ export declare const DEFAULT_RECOVERY_GRACE_MS = 120000;
|
|
|
23
23
|
* recovery machinery would have given it to reconnect.
|
|
24
24
|
*/
|
|
25
25
|
export declare function instanceLivenessGraceMs(gracePeriodMs: number, heartbeatMs?: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* Whether `instanceId` has a `cluster_instances` heartbeat inside `graceMs`:
|
|
28
|
+
* the same liveness the dispatch plane's ownership predicate reads.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isInstanceLive(db: Kysely<Database>, instanceId: string, graceMs: number): Promise<boolean>;
|
|
26
31
|
export interface InstanceHeartbeatOptions {
|
|
27
32
|
db: Kysely<Database>;
|
|
28
33
|
instanceId: string;
|
|
@@ -18,6 +18,8 @@ import { Kysely } from 'kysely';
|
|
|
18
18
|
* and a stray log line would break the contract).
|
|
19
19
|
*/
|
|
20
20
|
export declare function silenceJoinTokenLogger(): void;
|
|
21
|
+
export declare const TOKEN_ALREADY_USED_MESSAGE = "Join token has already been used. Create a new token with: kici-admin peer create-token";
|
|
22
|
+
export declare const TOKEN_EXPIRED_MESSAGE = "Join token has expired. Create a new token with: kici-admin peer create-token";
|
|
21
23
|
interface TokenRouting {
|
|
22
24
|
orgId: string;
|
|
23
25
|
routingKey: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CredentialFileData } from './peer-credentials.js';
|
|
2
|
+
import { CoordinatorCredentialOutcome } from './coordinator-credential.js';
|
|
2
3
|
export type AuthDecision = {
|
|
3
4
|
mode: 'credential';
|
|
4
5
|
credential: CredentialFileData;
|
|
@@ -15,6 +16,11 @@ export declare class PeerAuthCoordinator {
|
|
|
15
16
|
private readonly instanceId;
|
|
16
17
|
private readonly joinToken?;
|
|
17
18
|
private readonly joinWaitTimeoutMs;
|
|
19
|
+
private readonly selfIssue?;
|
|
20
|
+
/** The issuance in progress; concurrent sibling clients share it. */
|
|
21
|
+
private selfIssueInFlight;
|
|
22
|
+
/** An operator revoked this coordinator's credential: never issue again in this process. */
|
|
23
|
+
private selfIssueRevoked;
|
|
18
24
|
/** Promise-chain mutex tail; every file op awaits the prior one. */
|
|
19
25
|
private lock;
|
|
20
26
|
/** Set while one peer-client is mid token-join; siblings await it. */
|
|
@@ -24,11 +30,18 @@ export declare class PeerAuthCoordinator {
|
|
|
24
30
|
instanceId: string;
|
|
25
31
|
joinToken?: string;
|
|
26
32
|
joinWaitTimeoutMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Issues this coordinator its own credential when a decision finds no
|
|
35
|
+
* credential and no join token. Only a coordinator wires it.
|
|
36
|
+
*/
|
|
37
|
+
selfIssue?: () => Promise<CoordinatorCredentialOutcome>;
|
|
27
38
|
});
|
|
28
39
|
/** Run `fn` exclusively against the credential file. */
|
|
29
40
|
private withLock;
|
|
30
41
|
private readValidCredential;
|
|
31
42
|
decideAuth(): Promise<AuthDecision>;
|
|
43
|
+
/** Run the issuer under the mutex, once for every concurrent caller. */
|
|
44
|
+
private runSelfIssue;
|
|
32
45
|
private makeComplete;
|
|
33
46
|
private awaitInFlightJoin;
|
|
34
47
|
reportRejection(provedCredential: string | null, reason: string): Promise<RejectionAction>;
|
|
@@ -10,9 +10,13 @@
|
|
|
10
10
|
* Authentication uses ECDH key exchange followed by join token (first connect)
|
|
11
11
|
* or HMAC credential proof (reconnection).
|
|
12
12
|
*/
|
|
13
|
-
import { type PeerHeartbeat, type PeerToPeerMessage, type JobReroute, type JobProgress, type JobProgressAck, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, type PeerClusterSettingsRequest, type PeerClusterSettingsResponse, type PeerLogsCollectRequest, type PeerLeaving, type PeerAgentTokenRevoke, type RaftVoteRequest, type RaftVoteResponse, type RaftAppendEntries } from '@kici-dev/engine';
|
|
13
|
+
import { type PeerHeartbeat, type PeerToPeerMessage, type JobReroute, type JobProgress, type JobProgressAck, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, type PeerScalerOrphansRequest, type PeerForgetRequest, type PeerForgetResponse, type PeerScalerOrphansResponse, type PeerClusterSettingsRequest, type PeerClusterSettingsResponse, type PeerLogsCollectRequest, type PeerLeaving, type PeerAgentTokenRevoke, type RaftVoteRequest, type RaftVoteResponse, type RaftAppendEntries } from '@kici-dev/engine';
|
|
14
14
|
import type { PeerRegistry } from './peer-registry.js';
|
|
15
|
+
import { type PEER_FORGET_TIMEOUT, type PeerForgetRequestHandler } from './peer-forget.js';
|
|
16
|
+
import { type SCALER_ORPHANS_TIMEOUT, type ScalerOrphansRequestHandler } from './scaler-orphans-peer.js';
|
|
15
17
|
import type { PeerAuthCoordinator } from './peer-auth-coordinator.js';
|
|
18
|
+
/** Logged when this instance has neither a credential file nor a join token. */
|
|
19
|
+
export declare const NO_AUTH_METHOD_MESSAGE = "No peer auth method: this instance has no credential file and no join token";
|
|
16
20
|
type PeerConnectionState = 'disconnected' | 'connecting' | 'handshaking' | 'authenticating' | 'connected';
|
|
17
21
|
export interface PeerClientOptions {
|
|
18
22
|
/** WebSocket URL of the remote peer orchestrator. */
|
|
@@ -95,6 +99,17 @@ export interface PeerClientOptions {
|
|
|
95
99
|
* If undefined, incoming collect requests are ignored.
|
|
96
100
|
*/
|
|
97
101
|
onLogsCollectRequest?: PeerLogsCollectResponder;
|
|
102
|
+
/**
|
|
103
|
+
* Answers a peer.scaler.orphans.request the connected coordinator forwarded
|
|
104
|
+
* to this node (`kici-admin scaler orphans --target`). If undefined,
|
|
105
|
+
* requests are answered with ok=false.
|
|
106
|
+
*/
|
|
107
|
+
onScalerOrphansRequest?: ScalerOrphansRequestHandler;
|
|
108
|
+
/**
|
|
109
|
+
* Forgets a departed peer the connected coordinator forgot (`kici-admin peer
|
|
110
|
+
* forget`). If undefined, requests are answered with outcome `error`.
|
|
111
|
+
*/
|
|
112
|
+
onPeerForgetRequest?: PeerForgetRequestHandler;
|
|
98
113
|
}
|
|
99
114
|
/**
|
|
100
115
|
* Builds a node's subtree bundle in response to a peer.logs.collect.request and
|
|
@@ -108,12 +123,16 @@ export declare class PeerClient {
|
|
|
108
123
|
private heartbeatTimer;
|
|
109
124
|
private reconnectTimer;
|
|
110
125
|
private reconnectAttempts;
|
|
126
|
+
/** Whether this client already warned that it has no auth method; re-armed by an accepted auth. */
|
|
127
|
+
private noAuthWarned;
|
|
111
128
|
private intentionalDisconnect;
|
|
112
129
|
private _targetInstanceId;
|
|
113
130
|
private sessionKey;
|
|
114
131
|
private readonly ackWaiters;
|
|
115
132
|
private readonly cacheWaiters;
|
|
116
133
|
private readonly configReloadWaiters;
|
|
134
|
+
private readonly scalerOrphansWaiters;
|
|
135
|
+
private readonly peerForgetWaiters;
|
|
117
136
|
private readonly clusterSettingsWaiters;
|
|
118
137
|
/** Correlates peer.logs.collect.request with the peer's chunked subtree response. */
|
|
119
138
|
private readonly logsCollectWaiters;
|
|
@@ -146,6 +165,8 @@ export declare class PeerClient {
|
|
|
146
165
|
private readonly onPeerConfigReload?;
|
|
147
166
|
private readonly onAuthenticated?;
|
|
148
167
|
private readonly onLogsCollectRequest?;
|
|
168
|
+
private readonly onScalerOrphansRequest?;
|
|
169
|
+
private readonly onPeerForgetRequest?;
|
|
149
170
|
constructor(options: PeerClientOptions);
|
|
150
171
|
/** Current connection state. */
|
|
151
172
|
get state(): PeerConnectionState;
|
|
@@ -185,10 +206,26 @@ export declare class PeerClient {
|
|
|
185
206
|
*/
|
|
186
207
|
sendConfigReloadAndWait(msg: PeerConfigReload, timeoutMs?: number): Promise<PeerConfigReloadResponse | null>;
|
|
187
208
|
/**
|
|
188
|
-
* Send a peer.
|
|
189
|
-
*
|
|
209
|
+
* Send a peer.forget.request to the connected peer and wait for the matching
|
|
210
|
+
* response.
|
|
211
|
+
*
|
|
212
|
+
* @returns the response; null when not connected; `'timeout'` when no
|
|
213
|
+
* response arrived within `timeoutMs`.
|
|
214
|
+
*/
|
|
215
|
+
sendPeerForgetAndWait(msg: PeerForgetRequest, timeoutMs: number): Promise<PeerForgetResponse | null | typeof PEER_FORGET_TIMEOUT>;
|
|
216
|
+
/**
|
|
217
|
+
* Send a peer.scaler.orphans.request to the connected peer and wait for the
|
|
218
|
+
* matching response.
|
|
219
|
+
*
|
|
220
|
+
* @returns the response; null when not connected; `'timeout'` when no
|
|
221
|
+
* response arrived within `timeoutMs`.
|
|
222
|
+
*/
|
|
223
|
+
sendScalerOrphansAndWait(msg: PeerScalerOrphansRequest, timeoutMs: number): Promise<PeerScalerOrphansResponse | null | typeof SCALER_ORPHANS_TIMEOUT>;
|
|
224
|
+
/**
|
|
225
|
+
* Send a peer.clusterSettings.request to the connected coordinator and wait for
|
|
226
|
+
* the matching peer.clusterSettings.response carrying the worker-settings snapshot.
|
|
190
227
|
*
|
|
191
|
-
* @returns The response, or null if not connected or the
|
|
228
|
+
* @returns The response, or null if not connected or the coordinator doesn't reply
|
|
192
229
|
* within the timeout — the caller keeps its current (config-default) settings.
|
|
193
230
|
*/
|
|
194
231
|
sendClusterSettingsRequestAndWait(msg: PeerClusterSettingsRequest, timeoutMs?: number): Promise<PeerClusterSettingsResponse | null>;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { Kysely } from 'kysely';
|
|
2
|
+
/** How a credential row was issued, recorded in `peer_credentials.metadata.issuance`. */
|
|
3
|
+
export declare enum PeerCredentialIssuance {
|
|
4
|
+
/** A coordinator issued the credential to itself (`coordinator-credential.ts`). */
|
|
5
|
+
Self = "self"
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* `metadata` key set when a coordinator retires the self-issued credential its
|
|
9
|
+
* previous run left behind. An operator revoke never sets it, which is how a
|
|
10
|
+
* retirement is told apart from a revoke.
|
|
11
|
+
*/
|
|
12
|
+
export declare const RETIRED_BY_INSTANCE_KEY = "retiredByInstance";
|
|
2
13
|
/**
|
|
3
14
|
* A peer credential record stored in the coordinator's database.
|
|
4
15
|
*/
|
|
@@ -14,6 +25,7 @@ export interface PeerCredential {
|
|
|
14
25
|
lastValidatedBy: string | null;
|
|
15
26
|
expiresAt: Date;
|
|
16
27
|
revokedAt: Date | null;
|
|
28
|
+
metadata: Record<string, unknown>;
|
|
17
29
|
}
|
|
18
30
|
/**
|
|
19
31
|
* Data persisted in this orchestrator's local credential file
|
|
@@ -55,6 +67,7 @@ export declare class PeerCredentialStore {
|
|
|
55
67
|
routingKeys: string[];
|
|
56
68
|
sourceTokenHash?: string;
|
|
57
69
|
expiryDays?: number;
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
58
71
|
}): Promise<{
|
|
59
72
|
revokedCount: number;
|
|
60
73
|
}>;
|
|
@@ -66,6 +79,24 @@ export declare class PeerCredentialStore {
|
|
|
66
79
|
* Find a non-revoked, non-expired credential by instance ID.
|
|
67
80
|
*/
|
|
68
81
|
findByInstanceId(instanceId: string): Promise<PeerCredential | null>;
|
|
82
|
+
/**
|
|
83
|
+
* The credential row for `instanceId` that nobody revoked, expired or not.
|
|
84
|
+
* `peer_credentials_active_uniq` allows at most one.
|
|
85
|
+
*/
|
|
86
|
+
findUnrevokedByInstanceId(instanceId: string): Promise<PeerCredential | null>;
|
|
87
|
+
/** The most recently revoked credential row for `instanceId`, or null. */
|
|
88
|
+
findLatestRevokedByInstanceId(instanceId: string): Promise<PeerCredential | null>;
|
|
89
|
+
/**
|
|
90
|
+
* Revoke `instanceId`'s unrevoked, self-issued row whose hash is
|
|
91
|
+
* `credentialHash`, recording who retired it. Every condition sits in the
|
|
92
|
+
* WHERE clause, so a row that changed meanwhile is left alone.
|
|
93
|
+
* Returns whether a row was retired.
|
|
94
|
+
*/
|
|
95
|
+
retireSelfIssued(opts: {
|
|
96
|
+
instanceId: string;
|
|
97
|
+
credentialHash: string;
|
|
98
|
+
retiredByInstance: string;
|
|
99
|
+
}): Promise<boolean>;
|
|
69
100
|
/**
|
|
70
101
|
* Update the last_seen_at timestamp for a credential.
|
|
71
102
|
* Optionally records which coordinator validated it.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { PeerForgetOutcome, type PeerForgetRequest, type PeerForgetResponse } from '@kici-dev/engine';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { PeerRegistry } from './peer-registry.js';
|
|
4
|
+
/** What forgetting did on one coordinator. */
|
|
5
|
+
export interface PeerForgetResult {
|
|
6
|
+
/** The coordinator that answered. */
|
|
7
|
+
coordinator: string;
|
|
8
|
+
outcome: PeerForgetOutcome;
|
|
9
|
+
detail: string;
|
|
10
|
+
}
|
|
11
|
+
/** Local outcomes that stop a forget before it reaches any sibling. */
|
|
12
|
+
export declare const LOCAL_REFUSALS: ReadonlySet<PeerForgetOutcome>;
|
|
13
|
+
/** A sibling's answer that never arrived, or a sibling no connection reached. */
|
|
14
|
+
export declare const PEER_FORGET_TIMEOUT = "timeout";
|
|
15
|
+
/** The answer to a forget request a peer that is not a coordinator sent. */
|
|
16
|
+
export declare const PEER_FORGET_COORDINATORS_ONLY = "peer forget requests are accepted from coordinators only";
|
|
17
|
+
/** The answer of a peer with no forget handler wired. */
|
|
18
|
+
export declare const PEER_FORGET_UNHANDLED = "peer forget requests are not handled by this peer";
|
|
19
|
+
/** Which window a coordinator keeps treating a silent peer as alive for. */
|
|
20
|
+
export declare const PeerLivenessWindowKind: z.ZodEnum<{
|
|
21
|
+
"adopter-grace": "adopter-grace";
|
|
22
|
+
"reroute-grace": "reroute-grace";
|
|
23
|
+
"stale-window": "stale-window";
|
|
24
|
+
}>;
|
|
25
|
+
export type PeerLivenessWindowKind = z.infer<typeof PeerLivenessWindowKind>;
|
|
26
|
+
/** How long after its last heartbeat a coordinator still treats a peer as alive. */
|
|
27
|
+
export interface PeerLivenessWindow {
|
|
28
|
+
kind: PeerLivenessWindowKind;
|
|
29
|
+
windowMs: number;
|
|
30
|
+
}
|
|
31
|
+
/** What a coordinator reads to choose a peer's liveness window. */
|
|
32
|
+
export interface PeerLivenessSource {
|
|
33
|
+
/** The peer stale window (`KICI_CLUSTER_PEER_STALE_TIMEOUT_MS`). */
|
|
34
|
+
staleTimeoutMs: number;
|
|
35
|
+
/** Reads the live `reroute_flap_grace_ms` setting. */
|
|
36
|
+
rerouteFlapGraceMs: () => Promise<number>;
|
|
37
|
+
/**
|
|
38
|
+
* This coordinator's event-provision backstop, or null when it runs none.
|
|
39
|
+
* `holdsAdoptedProvisions` reads whether a peer adopted provisions the
|
|
40
|
+
* backstop judges by that peer's liveness.
|
|
41
|
+
*/
|
|
42
|
+
backstop: {
|
|
43
|
+
holdsAdoptedProvisions: (instanceId: string) => Promise<boolean>;
|
|
44
|
+
} | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The longest window in which this coordinator still treats `instanceId` as
|
|
48
|
+
* alive after its last heartbeat. Forgetting the peer inside it would cut that
|
|
49
|
+
* grace short: every reader of the registry treats a peer it no longer knows
|
|
50
|
+
* as gone at once.
|
|
51
|
+
*
|
|
52
|
+
* - A peer that adopted provisions gets the event-provision backstop's own
|
|
53
|
+
* grace (`reaperFlapGraceMs`), or the backstop tears its instances down.
|
|
54
|
+
* - Any other peer gets the longer of the stale window and the reroute flap
|
|
55
|
+
* grace, which the rerouted-job guard and the master-key requeue give every
|
|
56
|
+
* disconnected peer whether or not this coordinator runs a backstop.
|
|
57
|
+
*/
|
|
58
|
+
export declare function peerLivenessWindow(instanceId: string, source: PeerLivenessSource): Promise<PeerLivenessWindow>;
|
|
59
|
+
/** The checks a coordinator runs before it forgets a peer. */
|
|
60
|
+
export interface PeerForgetGuards {
|
|
61
|
+
/**
|
|
62
|
+
* How long after its last heartbeat the peer is still treated as alive. A
|
|
63
|
+
* peer heard from inside it may be partitioned rather than gone, so it is kept.
|
|
64
|
+
*/
|
|
65
|
+
liveness: PeerLivenessWindow;
|
|
66
|
+
/**
|
|
67
|
+
* This coordinator's event-provision backstop, or null when it runs none.
|
|
68
|
+
* `configuredPeerCount` is the `KICI_CLUSTER_PEERS` count the backstop's
|
|
69
|
+
* cluster-view check reads beside the live registry.
|
|
70
|
+
*/
|
|
71
|
+
backstop: {
|
|
72
|
+
configuredPeerCount: number;
|
|
73
|
+
} | null;
|
|
74
|
+
/** The operator acknowledged that the forget may switch the backstop back on. */
|
|
75
|
+
acknowledgeBackstop: boolean;
|
|
76
|
+
/** The current time; tests pin it. */
|
|
77
|
+
nowMs?: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The guards for forgetting `instanceId` on one coordinator, built from its
|
|
81
|
+
* cluster config, the live reroute flap grace and its event-provision
|
|
82
|
+
* backstop. The backstop runs on every coordinator that runs a scaler manager.
|
|
83
|
+
*/
|
|
84
|
+
export declare function peerForgetGuards(instanceId: string, cluster: {
|
|
85
|
+
peerStaleTimeoutMs: number;
|
|
86
|
+
peers: readonly unknown[];
|
|
87
|
+
}, liveness: Omit<PeerLivenessSource, 'staleTimeoutMs'>, acknowledgeBackstop: boolean): Promise<PeerForgetGuards>;
|
|
88
|
+
/** What forgetting a peer that would switch the backstop back on does. */
|
|
89
|
+
export declare function backstopConsequence(instanceId: string): string;
|
|
90
|
+
/**
|
|
91
|
+
* Drop `instanceId` from `registry` when it names a peer that left the
|
|
92
|
+
* cluster. Kept, with the reason: a connected peer (its next heartbeat would
|
|
93
|
+
* register it again), a peer heard from inside its liveness window (it may be
|
|
94
|
+
* partitioned rather than gone; see `peerLivenessWindow`), and a peer whose removal would switch the
|
|
95
|
+
* event-provision backstop back on, unless the request acknowledged that.
|
|
96
|
+
*/
|
|
97
|
+
export declare function forgetDepartedPeer(registry: PeerRegistry, selfInstanceId: string, instanceId: string, guards: PeerForgetGuards): Omit<PeerForgetResult, 'coordinator'>;
|
|
98
|
+
/** Answers a forget request a sibling coordinator fanned out. */
|
|
99
|
+
export type PeerForgetRequestHandler = (msg: PeerForgetRequest) => Promise<{
|
|
100
|
+
outcome: PeerForgetOutcome;
|
|
101
|
+
detail: string;
|
|
102
|
+
}>;
|
|
103
|
+
/**
|
|
104
|
+
* Answer one forget request with `handler`. A coordinator with no handler
|
|
105
|
+
* answers {@link PEER_FORGET_UNHANDLED}, and a handler that fails (its guards
|
|
106
|
+
* read the database) answers `error`, so the peer is kept and the operator
|
|
107
|
+
* sees why.
|
|
108
|
+
*/
|
|
109
|
+
export declare function replyToPeerForgetRequest(msg: PeerForgetRequest, handler: PeerForgetRequestHandler | undefined, send: (response: PeerForgetResponse) => void, logFields?: Record<string, unknown>): void;
|
|
110
|
+
/**
|
|
111
|
+
* Sends one forget request to a sibling coordinator and waits for its answer.
|
|
112
|
+
* Resolves null when no connection reached the sibling, and
|
|
113
|
+
* {@link PEER_FORGET_TIMEOUT} when it did not answer within `timeoutMs`.
|
|
114
|
+
*/
|
|
115
|
+
export type SendPeerForget = (siblingInstanceId: string, msg: PeerForgetRequest, timeoutMs: number) => Promise<PeerForgetResponse | null | typeof PEER_FORGET_TIMEOUT>;
|
|
116
|
+
/**
|
|
117
|
+
* Forget `instanceId` on this coordinator, then on every connected sibling
|
|
118
|
+
* coordinator, each of which holds its own registry. A peer this coordinator
|
|
119
|
+
* still has connected is refused and not fanned out. The first result is this
|
|
120
|
+
* coordinator's.
|
|
121
|
+
*/
|
|
122
|
+
export declare function forgetAcrossCoordinators(registry: PeerRegistry, selfInstanceId: string, instanceId: string, timeoutMs: number, guards: PeerForgetGuards, send: SendPeerForget): Promise<PeerForgetResult[]>;
|
|
123
|
+
/**
|
|
124
|
+
* The coordinator peers the cluster expects but this coordinator has no live
|
|
125
|
+
* link to: registered coordinators whose connection is down.
|
|
126
|
+
*/
|
|
127
|
+
export declare function disconnectedCoordinatorIds(registry: PeerRegistry, selfInstanceId: string): string[];
|
|
128
|
+
/** Pending forget requests sent to siblings, keyed by message id. */
|
|
129
|
+
export declare class PeerForgetWaiters {
|
|
130
|
+
private readonly waiters;
|
|
131
|
+
/** Wait for the response to `messageId`, or {@link PEER_FORGET_TIMEOUT}. */
|
|
132
|
+
wait(messageId: string, timeoutMs: number): Promise<PeerForgetResponse | typeof PEER_FORGET_TIMEOUT>;
|
|
133
|
+
/** Hand a response to the request waiting for it. An unknown id is dropped. */
|
|
134
|
+
resolve(response: PeerForgetResponse): void;
|
|
135
|
+
/** End every pending wait with an `error` response naming `reason`. */
|
|
136
|
+
rejectAll(reason: string): void;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=peer-forget.d.ts.map
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* proof (reconnection), registers the peer in PeerRegistry, and routes
|
|
7
7
|
* messages bidirectionally. Sends periodic heartbeats to the connecting peer.
|
|
8
8
|
*/
|
|
9
|
-
import { type PeerHeartbeat, type PeerToPeerMessage, type JobReroute, type JobProgress, type JobProgressAck, type PeerScalerEvent, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, type PeerClusterSettingsRequest, type WorkerClusterSettings, type PeerLogsCollectRequest, type PeerLeaving, type PeerAgentTokenRevoke, type RaftVoteRequest, type RaftVoteResponse, type RaftAppendEntries } from '@kici-dev/engine';
|
|
9
|
+
import { type PeerHeartbeat, type PeerToPeerMessage, type JobReroute, type JobProgress, type JobProgressAck, type PeerScalerEvent, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, type PeerScalerOrphansRequest, type PeerForgetRequest, type PeerForgetResponse, type PeerScalerOrphansResponse, type PeerClusterSettingsRequest, type WorkerClusterSettings, type PeerLogsCollectRequest, type PeerLeaving, type PeerAgentTokenRevoke, type RaftVoteRequest, type RaftVoteResponse, type RaftAppendEntries } from '@kici-dev/engine';
|
|
10
10
|
import type { PeerRegistry } from './peer-registry.js';
|
|
11
|
+
import { type PEER_FORGET_TIMEOUT, type PeerForgetRequestHandler } from './peer-forget.js';
|
|
12
|
+
import { type SCALER_ORPHANS_TIMEOUT, type ScalerOrphansRequestHandler } from './scaler-orphans-peer.js';
|
|
11
13
|
import type { PeerCredentialStore } from './peer-credentials.js';
|
|
12
14
|
import { type JoinTokenManager } from './join-token.js';
|
|
13
15
|
/**
|
|
@@ -104,6 +106,19 @@ export interface PeerHandlerDeps {
|
|
|
104
106
|
* If undefined, incoming collect requests are ignored.
|
|
105
107
|
*/
|
|
106
108
|
onLogsCollectRequest?: (msg: PeerLogsCollectRequest, send: (out: PeerToPeerMessage) => boolean) => Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Answers a peer.scaler.orphans.request a coordinator forwarded to this node
|
|
111
|
+
* (`kici-admin scaler orphans --target`). A request from a peer that is not
|
|
112
|
+
* a coordinator is refused before it reaches the handler. If undefined,
|
|
113
|
+
* requests are answered with ok=false.
|
|
114
|
+
*/
|
|
115
|
+
onScalerOrphansRequest?: ScalerOrphansRequestHandler;
|
|
116
|
+
/**
|
|
117
|
+
* Forgets a departed peer a sibling coordinator forgot (`kici-admin peer
|
|
118
|
+
* forget`). A request from a peer that is not a coordinator is refused. If
|
|
119
|
+
* undefined, requests are answered with outcome `error`.
|
|
120
|
+
*/
|
|
121
|
+
onPeerForgetRequest?: PeerForgetRequestHandler;
|
|
107
122
|
}
|
|
108
123
|
/**
|
|
109
124
|
* Create a handler function for incoming peer WebSocket connections.
|
|
@@ -127,6 +142,8 @@ export declare function createPeerHandler(deps: PeerHandlerDeps): {
|
|
|
127
142
|
closePeer: (targetInstanceId: string, code: number, reason: string) => void;
|
|
128
143
|
sendAndWaitAck: (targetInstanceId: string, msg: JobReroute, timeoutMs?: number) => Promise<boolean>;
|
|
129
144
|
sendConfigReloadAndWait: (targetInstanceId: string, msg: PeerConfigReload, timeoutMs?: number) => Promise<PeerConfigReloadResponse | null>;
|
|
145
|
+
sendScalerOrphansAndWait: (targetInstanceId: string, msg: PeerScalerOrphansRequest, timeoutMs: number) => Promise<PeerScalerOrphansResponse | null | typeof SCALER_ORPHANS_TIMEOUT>;
|
|
146
|
+
sendPeerForgetAndWait: (targetInstanceId: string, msg: PeerForgetRequest, timeoutMs: number) => Promise<PeerForgetResponse | null | typeof PEER_FORGET_TIMEOUT>;
|
|
130
147
|
sendLogsCollectAndWait: (targetInstanceId: string, msg: PeerLogsCollectRequest, timeoutMs: number) => Promise<Buffer>;
|
|
131
148
|
getConnectionCount: () => number;
|
|
132
149
|
broadcastHeartbeat: (inventory: Omit<PeerHeartbeat, 'type'>) => void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The peer-channel half of `kici-admin scaler orphans --target`: answering a
|
|
3
|
+
* forwarded peer.scaler.orphans.request, and correlating the response with
|
|
4
|
+
* the request that is waiting for it. Shared by the outgoing `PeerClient` and
|
|
5
|
+
* the incoming peer handler, which carry the same message pair.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { PeerScalerOrphansRequest, PeerScalerOrphansResponse } from '@kici-dev/engine';
|
|
9
|
+
import type { ScalerOrphansAnswer } from '../scaler/orphan-requests.js';
|
|
10
|
+
/** Answers a forwarded request from this node's own host and tracking. */
|
|
11
|
+
export type ScalerOrphansRequestHandler = (msg: PeerScalerOrphansRequest) => Promise<ScalerOrphansAnswer>;
|
|
12
|
+
/** The answer of a peer that has no handler wired. */
|
|
13
|
+
export declare const SCALER_ORPHANS_UNHANDLED_ERROR = "scaler orphan requests are not handled by this peer";
|
|
14
|
+
/** The answer to a request a worker sent: a worker never stops another node's VMs. */
|
|
15
|
+
export declare const SCALER_ORPHANS_COORDINATORS_ONLY_ERROR = "scaler orphan requests are accepted from coordinators only";
|
|
16
|
+
/** A forward that reached no peer, or a peer that did not answer in time. */
|
|
17
|
+
export declare const ScalerOrphansForwardFailure: z.ZodEnum<{
|
|
18
|
+
"not-connected": "not-connected";
|
|
19
|
+
timeout: "timeout";
|
|
20
|
+
}>;
|
|
21
|
+
export type ScalerOrphansForwardFailure = z.infer<typeof ScalerOrphansForwardFailure>;
|
|
22
|
+
/** A wait that ended without a response. */
|
|
23
|
+
export declare const SCALER_ORPHANS_TIMEOUT: "timeout";
|
|
24
|
+
/**
|
|
25
|
+
* Answer one forwarded request through `send`. A missing handler and a
|
|
26
|
+
* handler failure are both answered `ok: false`, so the requester never waits
|
|
27
|
+
* out its timeout for a peer that cannot answer.
|
|
28
|
+
*/
|
|
29
|
+
export declare function replyToScalerOrphansRequest(msg: PeerScalerOrphansRequest, handler: ScalerOrphansRequestHandler | undefined, send: (response: PeerScalerOrphansResponse) => void, logFields?: Record<string, unknown>): void;
|
|
30
|
+
/** Pending scaler orphan requests, keyed by message id. */
|
|
31
|
+
export declare class ScalerOrphansWaiters {
|
|
32
|
+
private readonly waiters;
|
|
33
|
+
/** Wait for the response to `messageId`, or {@link SCALER_ORPHANS_TIMEOUT}. */
|
|
34
|
+
wait(messageId: string, timeoutMs: number): Promise<PeerScalerOrphansResponse | typeof SCALER_ORPHANS_TIMEOUT>;
|
|
35
|
+
/** Hand a response to the request waiting for it. An unknown id is dropped. */
|
|
36
|
+
resolve(response: PeerScalerOrphansResponse): void;
|
|
37
|
+
/** End every pending wait with an `ok: false` response naming `reason`. */
|
|
38
|
+
rejectAll(reason: string): void;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=scaler-orphans-peer.d.ts.map
|