@kici-dev/orchestrator 0.14.1 → 0.15.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/__test-helpers__/peer-mutual-auth.d.ts +42 -0
- package/dist/agent/host-roster.d.ts +100 -37
- package/dist/app.d.ts +4 -2
- package/dist/cli/commands/host.d.ts +3 -0
- package/dist/cli/commands/source-manifest.d.ts +3 -3
- package/dist/cli/commands/source.d.ts +23 -0
- package/dist/cli/join.d.ts +2 -2
- package/dist/cli/kici-admin.d.ts +5 -0
- package/dist/cli/local-github-ingress-url.d.ts +6 -0
- package/dist/cli/root-option-routing.d.ts +46 -0
- package/dist/cli/webhook-url-notes.d.ts +15 -0
- package/dist/cli.js +1429 -885
- package/dist/cluster/coordinator-credential.d.ts +21 -0
- package/dist/cluster/index.d.ts +4 -4
- package/dist/cluster/join-client.d.ts +23 -21
- package/dist/cluster/join-handler.d.ts +24 -16
- package/dist/cluster/join-protocol-v2.d.ts +84 -0
- package/dist/cluster/join-token.d.ts +78 -56
- package/dist/cluster/peer-auth-coordinator.d.ts +16 -2
- package/dist/cluster/peer-client.d.ts +72 -4
- package/dist/cluster/peer-crypto.d.ts +60 -3
- package/dist/cluster/peer-discovery.d.ts +67 -0
- package/dist/cluster/peer-handler.d.ts +17 -5
- package/dist/cluster/peer-registry.d.ts +15 -1
- package/dist/config/schema.d.ts +14 -0
- package/dist/config/types.d.ts +4 -0
- package/dist/config.d.ts +9 -0
- package/dist/db/migrations/157_host_roster_identity_source.d.ts +21 -0
- package/dist/db/types.d.ts +16 -2
- package/dist/github-app-name-refresher/github-app-name-refresher.d.ts +25 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +225 -81
- package/dist/providers/github/manifest.d.ts +58 -7
- package/dist/routes/admin-sources.d.ts +26 -23
- package/dist/routes/admin.d.ts +14 -17
- package/dist/routes/cluster-join.d.ts +13 -0
- package/dist/routes/github-webhook.d.ts +13 -11
- package/dist/scaler/bare-metal-backend.d.ts +42 -1
- package/dist/scaler/types.d.ts +7 -5
- package/dist/server.js +9040 -7631
- package/dist/sources/source-manager.d.ts +0 -24
- package/dist/sources/webhook-url-resolvers.d.ts +60 -0
- package/dist/standalone.js +9944 -8818
- package/dist/ws/dashboard-fleet-handler.d.ts +4 -3
- package/dist/ws/dashboard-fleet-write-handler.d.ts +7 -1
- package/dist/ws/platform-client.d.ts +18 -6
- package/installer-image-digests.json +3 -3
- package/package.json +4 -4
- package/sbom.spdx.json +53 -53
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ECDH key exchange and session encryption for
|
|
2
|
+
* ECDH key exchange, mutual-v2 authentication and session encryption for
|
|
3
|
+
* peer-to-peer channels.
|
|
3
4
|
*
|
|
4
5
|
* Uses X25519 for key agreement and AES-256-GCM for message encryption.
|
|
5
|
-
*
|
|
6
|
-
* and
|
|
6
|
+
* The handshake key (K_hs) is derived via HKDF-SHA256 from the ECDH shared
|
|
7
|
+
* secret and the server nonce; it protects the authentication exchange only.
|
|
8
|
+
*
|
|
9
|
+
* mutual-v2: both sides hash the handshake transcript (TH: both ephemeral keys,
|
|
10
|
+
* the nonce and the advertised schemes). The client proves the shared key (PSK:
|
|
11
|
+
* sha256 of the peer credential, or the join token hash) with an HMAC over TH,
|
|
12
|
+
* and the server answers with its own HMAC over TH and the client proof. Every
|
|
13
|
+
* frame after acceptance uses the application key (K_app), derived from K_hs
|
|
14
|
+
* and the PSK, so a side that skipped the proof check cannot read or write them.
|
|
15
|
+
* Every field is length-prefixed, so field boundaries are unambiguous.
|
|
7
16
|
*
|
|
8
17
|
* Wire format for encrypted messages (base64 encoded):
|
|
9
18
|
* IV (12 bytes) || AuthTag (16 bytes) || Ciphertext
|
|
@@ -54,4 +63,52 @@ export declare function encryptMessage(plaintext: string, sessionKey: Buffer): s
|
|
|
54
63
|
* @throws If the session key is wrong or the ciphertext has been tampered with
|
|
55
64
|
*/
|
|
56
65
|
export declare function decryptMessage(encrypted: string, sessionKey: Buffer): string;
|
|
66
|
+
/** Length of the server nonce in `peer.hello`. */
|
|
67
|
+
export declare const HANDSHAKE_NONCE_BYTES = 32;
|
|
68
|
+
/** Length of a mutual-v2 proof (HMAC-SHA256). */
|
|
69
|
+
export declare const PEER_PROOF_BYTES = 32;
|
|
70
|
+
/** Encode each field as a 4-byte big-endian length followed by its bytes. */
|
|
71
|
+
export declare function lengthPrefixed(...fields: Array<Buffer | string>): Buffer;
|
|
72
|
+
export interface PeerTranscriptInput {
|
|
73
|
+
serverEphemeralPublicKey: Buffer;
|
|
74
|
+
serverNonce: Buffer;
|
|
75
|
+
/** The schemes exactly as the server advertised them, unknown ones included. */
|
|
76
|
+
authSchemes: readonly string[];
|
|
77
|
+
clientEphemeralPublicKey: Buffer;
|
|
78
|
+
}
|
|
79
|
+
/** TH: binds both ephemeral keys, the nonce and the advertised schemes. */
|
|
80
|
+
export declare function peerTranscriptHash(input: PeerTranscriptInput): Buffer;
|
|
81
|
+
export interface ClientProofInput {
|
|
82
|
+
psk: Buffer;
|
|
83
|
+
transcriptHash: Buffer;
|
|
84
|
+
mode: string;
|
|
85
|
+
clientInstanceId: string;
|
|
86
|
+
role: string;
|
|
87
|
+
protocolVersion: number;
|
|
88
|
+
/** The join token's routing segment in token mode; empty in credential mode. */
|
|
89
|
+
tokenRouting: string;
|
|
90
|
+
}
|
|
91
|
+
export declare function computeClientProof(input: ClientProofInput): Buffer;
|
|
92
|
+
export interface ServerProofInput {
|
|
93
|
+
psk: Buffer;
|
|
94
|
+
transcriptHash: Buffer;
|
|
95
|
+
clientProof: Buffer;
|
|
96
|
+
serverInstanceId: string;
|
|
97
|
+
grantedRole: string;
|
|
98
|
+
/** The credential the server issues in this response, or null when it issues none. */
|
|
99
|
+
sessionCredential: string | null;
|
|
100
|
+
}
|
|
101
|
+
export declare function computeServerProof(input: ServerProofInput): Buffer;
|
|
102
|
+
/** A proof as sent on the wire (64 hex characters), or null when malformed. */
|
|
103
|
+
export declare function decodeProof(hex: string): Buffer | null;
|
|
104
|
+
/** Constant-time comparison; a length mismatch is a mismatch, never a throw. */
|
|
105
|
+
export declare function proofMatches(expected: Buffer, presented: Buffer): boolean;
|
|
106
|
+
/** K_app: protects every frame after acceptance, and needs the PSK as well as K_hs. */
|
|
107
|
+
export declare function deriveAppKey(input: {
|
|
108
|
+
handshakeKey: Buffer;
|
|
109
|
+
psk: Buffer;
|
|
110
|
+
transcriptHash: Buffer;
|
|
111
|
+
clientProof: Buffer;
|
|
112
|
+
serverProof: Buffer;
|
|
113
|
+
}): Buffer;
|
|
57
114
|
//# sourceMappingURL=peer-crypto.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { OrchRole } from '@kici-dev/engine';
|
|
2
|
+
import { PeerDiscoveryMode } from '../config/schema.js';
|
|
3
|
+
/** Discovered clients plus pending candidates, far above any real coordinator count. */
|
|
4
|
+
export declare const MAX_DISCOVERED_PEER_CLIENTS = 64;
|
|
5
|
+
/** A discovered client not connected for this long is dropped; a returning peer is announced again. */
|
|
6
|
+
export declare const DISCOVERED_PEER_IDLE_PRUNE_MS: number;
|
|
7
|
+
/** What discovery needs from a peer client. */
|
|
8
|
+
export interface DiscoveryPeerClient {
|
|
9
|
+
readonly state: string;
|
|
10
|
+
connect(): void;
|
|
11
|
+
disconnect(): void;
|
|
12
|
+
}
|
|
13
|
+
/** Hooks a discovered client calls back. */
|
|
14
|
+
export interface DiscoveredClientHooks {
|
|
15
|
+
/** Mutual authentication succeeded with the announced instance id. */
|
|
16
|
+
onAuthenticated: () => void;
|
|
17
|
+
/** Mutual authentication failed; the client closed and will not reconnect. */
|
|
18
|
+
onMutualAuthFailed: () => void;
|
|
19
|
+
}
|
|
20
|
+
/** One peer the Platform announced. */
|
|
21
|
+
export interface PeerAnnouncement {
|
|
22
|
+
instanceId?: string;
|
|
23
|
+
address?: string | null;
|
|
24
|
+
orchRole?: OrchRole;
|
|
25
|
+
}
|
|
26
|
+
export interface PeerDiscoveryDeps {
|
|
27
|
+
mode: PeerDiscoveryMode;
|
|
28
|
+
selfInstanceId: string;
|
|
29
|
+
/** Every outbound peer client. Static clients sit under their URL until they authenticate. */
|
|
30
|
+
peerClients: Map<string, DiscoveryPeerClient>;
|
|
31
|
+
createClient: (address: string, expectedInstanceId: string, hooks: DiscoveredClientHooks) => DiscoveryPeerClient;
|
|
32
|
+
/** Minimum time between dials of an address whose last discovered dial failed mutual authentication. */
|
|
33
|
+
redialAfterFailureMs: number;
|
|
34
|
+
maxClients?: number;
|
|
35
|
+
idlePruneMs?: number;
|
|
36
|
+
now?: () => number;
|
|
37
|
+
}
|
|
38
|
+
export declare class PeerDiscovery {
|
|
39
|
+
private readonly deps;
|
|
40
|
+
private readonly discovered;
|
|
41
|
+
private readonly candidates;
|
|
42
|
+
/** Address -> when its last discovered dial failed mutual authentication. */
|
|
43
|
+
private readonly failedAt;
|
|
44
|
+
private readonly maxClients;
|
|
45
|
+
private readonly idlePruneMs;
|
|
46
|
+
private readonly now;
|
|
47
|
+
private timer;
|
|
48
|
+
constructor(deps: PeerDiscoveryDeps);
|
|
49
|
+
announce(peer: PeerAnnouncement): void;
|
|
50
|
+
/** A static client authenticated as this peer: discovery lets go of it. */
|
|
51
|
+
releaseToStatic(instanceId: string): void;
|
|
52
|
+
/** Drop discovered clients and candidates that stayed unconnected for the idle window. */
|
|
53
|
+
pruneIdle(): void;
|
|
54
|
+
start(): void;
|
|
55
|
+
stop(): void;
|
|
56
|
+
private size;
|
|
57
|
+
private dialDiscovered;
|
|
58
|
+
private dialCandidate;
|
|
59
|
+
/**
|
|
60
|
+
* The candidate authenticated. It runs before the candidate registers the
|
|
61
|
+
* peer, so the replaced client disconnects first.
|
|
62
|
+
*/
|
|
63
|
+
private promote;
|
|
64
|
+
private drop;
|
|
65
|
+
private dropCandidate;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=peer-discovery.d.ts.map
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WebSocket handler for incoming peer connections from other orchestrators.
|
|
3
3
|
*
|
|
4
|
-
* Accepts WS upgrade, performs ECDH key exchange
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Accepts WS upgrade, performs ECDH key exchange, and authenticates the peer
|
|
5
|
+
* with mutual-v2: `peer.hello` advertises the scheme, the peer proves its
|
|
6
|
+
* credential (or, on first join, its join token) with an HMAC over the
|
|
7
|
+
* handshake transcript, and this server answers with its own proof and
|
|
8
|
+
* switches to the application key. A join token never arrives here: the peer
|
|
9
|
+
* sends the token's routing segment, and the role and routing key come from
|
|
10
|
+
* the join_tokens row. A request in the scheme earlier releases used is refused
|
|
11
|
+
* with PEER_MUTUAL_AUTH_REQUIRED_REASON. Registers the peer in PeerRegistry and
|
|
12
|
+
* routes messages bidirectionally. Sends periodic heartbeats to the peer.
|
|
8
13
|
*/
|
|
9
14
|
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
|
-
import type
|
|
15
|
+
import { type PeerRegistry } from './peer-registry.js';
|
|
11
16
|
import { type PEER_FORGET_TIMEOUT, type PeerForgetRequestHandler } from './peer-forget.js';
|
|
12
17
|
import { type SCALER_ORPHANS_TIMEOUT, type ScalerOrphansRequestHandler } from './scaler-orphans-peer.js';
|
|
13
18
|
import type { PeerCredentialStore } from './peer-credentials.js';
|
|
@@ -120,6 +125,13 @@ export interface PeerHandlerDeps {
|
|
|
120
125
|
*/
|
|
121
126
|
onPeerForgetRequest?: PeerForgetRequestHandler;
|
|
122
127
|
}
|
|
128
|
+
/** The schemes this server lists in peer.hello, in order. */
|
|
129
|
+
export declare const ADVERTISED_PEER_AUTH_SCHEMES: readonly string[];
|
|
130
|
+
/**
|
|
131
|
+
* A peer.auth.request from a release before mutual authentication: it carries
|
|
132
|
+
* a proof or a token, or names no scheme.
|
|
133
|
+
*/
|
|
134
|
+
export declare function isLegacyPeerAuthRequest(raw: unknown): boolean;
|
|
123
135
|
/**
|
|
124
136
|
* Create a handler function for incoming peer WebSocket connections.
|
|
125
137
|
*
|
|
@@ -5,7 +5,17 @@
|
|
|
5
5
|
* enabling routing decisions without real-time queries. Follows the
|
|
6
6
|
* same label-matching semantics as agent/registry.ts for consistency.
|
|
7
7
|
*/
|
|
8
|
-
import type { PeerHeartbeat, PeerCapabilities, ScalerCapacitySummary } from '@kici-dev/engine';
|
|
8
|
+
import type { PeerAuthScheme, PeerHeartbeat, PeerCapabilities, ScalerCapacitySummary } from '@kici-dev/engine';
|
|
9
|
+
/** Which side of a peer link authenticated: the peer dialled us, or we dialled it. */
|
|
10
|
+
export declare enum PeerLinkDirection {
|
|
11
|
+
Inbound = "inbound",
|
|
12
|
+
Outbound = "outbound"
|
|
13
|
+
}
|
|
14
|
+
/** The scheme each direction of a peer link authenticated with; null while that direction is down. */
|
|
15
|
+
export interface PeerAuthSchemes {
|
|
16
|
+
inbound: PeerAuthScheme | null;
|
|
17
|
+
outbound: PeerAuthScheme | null;
|
|
18
|
+
}
|
|
9
19
|
export interface PeerAgentInfo {
|
|
10
20
|
agentId: string;
|
|
11
21
|
labels: string[];
|
|
@@ -52,6 +62,8 @@ export interface PeerInfo {
|
|
|
52
62
|
clusterSettingsVersion: number;
|
|
53
63
|
/** Cluster role of the peer. */
|
|
54
64
|
role: 'coordinator' | 'worker';
|
|
65
|
+
/** The authentication scheme of each link direction. */
|
|
66
|
+
authScheme: PeerAuthSchemes;
|
|
55
67
|
hostname?: string;
|
|
56
68
|
osRelease?: string;
|
|
57
69
|
totalMemoryMb?: number;
|
|
@@ -119,6 +131,8 @@ export declare class PeerRegistry {
|
|
|
119
131
|
routingKeys: string[];
|
|
120
132
|
role?: 'coordinator' | 'worker';
|
|
121
133
|
}): void;
|
|
134
|
+
/** Record the scheme one direction of the link authenticated with, or null when it closed. */
|
|
135
|
+
setAuthScheme(instanceId: string, direction: PeerLinkDirection, scheme: PeerAuthScheme | null): void;
|
|
122
136
|
/**
|
|
123
137
|
* Remove a peer entirely from the registry.
|
|
124
138
|
*/
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* and PgSecretStore, not through config schemas.
|
|
11
11
|
*/
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
/** Whether a coordinator dials the peers the Platform announces. */
|
|
14
|
+
export declare const PeerDiscoveryMode: z.ZodEnum<{
|
|
15
|
+
platform: "platform";
|
|
16
|
+
static: "static";
|
|
17
|
+
}>;
|
|
18
|
+
export type PeerDiscoveryMode = z.infer<typeof PeerDiscoveryMode>;
|
|
13
19
|
/**
|
|
14
20
|
* Schema for the local YAML configuration file.
|
|
15
21
|
* All fields optional except database.url.
|
|
@@ -105,6 +111,10 @@ export declare const sharedConfigSchema: z.ZodObject<{
|
|
|
105
111
|
}>>;
|
|
106
112
|
coordinatorUrl: z.ZodOptional<z.ZodString>;
|
|
107
113
|
peerStaleTimeoutMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
114
|
+
peerDiscovery: z.ZodOptional<z.ZodEnum<{
|
|
115
|
+
platform: "platform";
|
|
116
|
+
static: "static";
|
|
117
|
+
}>>;
|
|
108
118
|
}, z.core.$strip>>;
|
|
109
119
|
webhookPayloadDir: z.ZodOptional<z.ZodString>;
|
|
110
120
|
cacheTtlDays: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
@@ -193,6 +203,10 @@ export declare const appConfigSchema: z.ZodObject<{
|
|
|
193
203
|
}>>;
|
|
194
204
|
coordinatorUrl: z.ZodOptional<z.ZodString>;
|
|
195
205
|
peerStaleTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
206
|
+
peerDiscovery: z.ZodDefault<z.ZodEnum<{
|
|
207
|
+
platform: "platform";
|
|
208
|
+
static: "static";
|
|
209
|
+
}>>;
|
|
196
210
|
}, z.core.$strip>>;
|
|
197
211
|
webhookPayloadDir: z.ZodOptional<z.ZodString>;
|
|
198
212
|
cacheTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
package/dist/config/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - AppConfig: merged result type used throughout the codebase
|
|
8
8
|
*/
|
|
9
9
|
import type { OrchestratorMode } from '@kici-dev/engine';
|
|
10
|
+
import type { PeerDiscoveryMode } from './schema.js';
|
|
10
11
|
/**
|
|
11
12
|
* Per-orchestrator settings loaded from YAML file.
|
|
12
13
|
* These are instance-specific and never shared across orchestrators.
|
|
@@ -85,6 +86,7 @@ export interface SharedConfig {
|
|
|
85
86
|
raftHeartbeatMs?: number;
|
|
86
87
|
peerHeartbeatIntervalMs?: number;
|
|
87
88
|
peerMaxReconnectDelayMs?: number;
|
|
89
|
+
peerDiscovery?: PeerDiscoveryMode;
|
|
88
90
|
};
|
|
89
91
|
webhookPayloadDir?: string;
|
|
90
92
|
cacheTtlDays?: number;
|
|
@@ -189,6 +191,8 @@ export interface AppConfig {
|
|
|
189
191
|
coordinatorUrl?: string;
|
|
190
192
|
/** Stale peer timeout in ms. */
|
|
191
193
|
peerStaleTimeoutMs: number;
|
|
194
|
+
/** Whether this coordinator dials Platform-announced peers. */
|
|
195
|
+
peerDiscovery: PeerDiscoveryMode;
|
|
192
196
|
};
|
|
193
197
|
/** Webhook payload storage directory */
|
|
194
198
|
webhookPayloadDir?: string;
|
package/dist/config.d.ts
CHANGED
|
@@ -245,6 +245,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
245
245
|
peerStaleTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
246
246
|
electionGracePeriodMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
247
247
|
singleNode: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>, z.ZodTransform<boolean, string | boolean>>;
|
|
248
|
+
peerDiscovery: z.ZodDefault<z.ZodEnum<{
|
|
249
|
+
platform: "platform";
|
|
250
|
+
static: "static";
|
|
251
|
+
}>>;
|
|
248
252
|
trustedProxies: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string[], string>>;
|
|
249
253
|
}, z.core.$strip>>;
|
|
250
254
|
}, z.core.$strip>;
|
|
@@ -459,6 +463,10 @@ export declare const packagingConfigSchema: z.ZodObject<{
|
|
|
459
463
|
peerStaleTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
460
464
|
electionGracePeriodMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
461
465
|
singleNode: z.ZodPipe<z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>, z.ZodTransform<boolean, string | boolean>>;
|
|
466
|
+
peerDiscovery: z.ZodDefault<z.ZodEnum<{
|
|
467
|
+
platform: "platform";
|
|
468
|
+
static: "static";
|
|
469
|
+
}>>;
|
|
462
470
|
trustedProxies: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string[], string>>;
|
|
463
471
|
}, z.core.$strip>>;
|
|
464
472
|
}, z.core.$strip>;
|
|
@@ -673,6 +681,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
|
|
|
673
681
|
peerStaleTimeoutMs: number;
|
|
674
682
|
electionGracePeriodMs: number;
|
|
675
683
|
singleNode: boolean;
|
|
684
|
+
peerDiscovery: "platform" | "static";
|
|
676
685
|
trustedProxies: string[];
|
|
677
686
|
};
|
|
678
687
|
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Add `host_roster.identity_source text NOT NULL DEFAULT 'operator'`, limited
|
|
4
|
+
* to `agent`, `operator` and `platform`.
|
|
5
|
+
*
|
|
6
|
+
* It records who last set a host's identity (labels, hostname, properties):
|
|
7
|
+
* the host's own agent registration, a local `kici-admin host declare`, or a
|
|
8
|
+
* Platform-relayed dashboard declare. A `platform` row is an unconfirmed
|
|
9
|
+
* placeholder that fan-out, inventory and the unreachable alarm skip.
|
|
10
|
+
*
|
|
11
|
+
* Backfill: a row whose `platform` column is set has registered at least once
|
|
12
|
+
* (only an agent registration writes `platform`), so it becomes `agent`. Every
|
|
13
|
+
* other row stays `operator`: a dashboard-created row from before this column
|
|
14
|
+
* cannot be told apart from a `kici-admin` declare, and treating it as
|
|
15
|
+
* `platform` would drop every declared, never-connected host out of fan-out.
|
|
16
|
+
*
|
|
17
|
+
* Idempotent: a re-run on a database that already has the column is a no-op.
|
|
18
|
+
*/
|
|
19
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
20
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=157_host_roster_identity_source.d.ts.map
|
package/dist/db/types.d.ts
CHANGED
|
@@ -13,6 +13,17 @@ export declare enum JobKind {
|
|
|
13
13
|
Gate = "gate",
|
|
14
14
|
Proxy = "proxy"
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Who last set a roster host's identity (labels, hostname, properties).
|
|
18
|
+
* `platform` marks an unconfirmed placeholder created from the dashboard: it
|
|
19
|
+
* is never a fan-out, inventory, agent-pin or unreachable-alarm target until
|
|
20
|
+
* its agent registers (`agent`) or an operator declares it (`operator`).
|
|
21
|
+
*/
|
|
22
|
+
export declare enum HostIdentitySource {
|
|
23
|
+
agent = "agent",
|
|
24
|
+
operator = "operator",
|
|
25
|
+
platform = "platform"
|
|
26
|
+
}
|
|
16
27
|
/**
|
|
17
28
|
* PostgreSQL-only database types.
|
|
18
29
|
* Column names use snake_case matching the actual database column names.
|
|
@@ -187,8 +198,9 @@ export type ClusterMeta = Selectable<ClusterMetaTable>;
|
|
|
187
198
|
export type NewClusterMeta = Insertable<ClusterMetaTable>;
|
|
188
199
|
/**
|
|
189
200
|
* Join tokens table
|
|
190
|
-
* Stores hashed join tokens
|
|
191
|
-
*
|
|
201
|
+
* Stores hashed join tokens. `kici-admin join` and peer token mode find a row by
|
|
202
|
+
* its hash; a token is consumed on first use and re-usable by the instance that
|
|
203
|
+
* consumed it until it expires.
|
|
192
204
|
*/
|
|
193
205
|
export interface JoinTokenTable {
|
|
194
206
|
/** UUID primary key */
|
|
@@ -2839,6 +2851,8 @@ export interface HostRosterTable {
|
|
|
2839
2851
|
* presigned URL). NULL / false ⇒ the conservative `ssh-push` fallback.
|
|
2840
2852
|
*/
|
|
2841
2853
|
s3_reachable: ColumnType<boolean | null, boolean | null | undefined, boolean | null>;
|
|
2854
|
+
/** Who last set the host's identity. See {@link HostIdentitySource}. */
|
|
2855
|
+
identity_source: ColumnType<HostIdentitySource, HostIdentitySource | undefined, HostIdentitySource>;
|
|
2842
2856
|
created_at: Generated<Date>;
|
|
2843
2857
|
updated_at: ColumnType<Date, Date | string | undefined, Date | string>;
|
|
2844
2858
|
}
|
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* the Platform `webhook_sources` row and the dashboard Sources tab pick up the
|
|
11
11
|
* new name/slug without any extra plumbing here.
|
|
12
12
|
*
|
|
13
|
+
* The same pass checks each App against the events and permissions KiCI needs
|
|
14
|
+
* (`GITHUB_APP_REQUIREMENTS`) and the permissions each installation has
|
|
15
|
+
* accepted, and logs one warning per App with gaps. A gap never blocks the name
|
|
16
|
+
* and slug sync.
|
|
17
|
+
*
|
|
13
18
|
* Lifecycle mirrors `StaleRunDetector`: `start()` runs an immediate refresh
|
|
14
19
|
* (so a rename made while the orchestrator was down propagates on next boot)
|
|
15
20
|
* then a `setInterval`; `stop()` clears it. Per-source errors are logged and
|
|
16
21
|
* never abort the loop.
|
|
17
22
|
*/
|
|
23
|
+
import { type GithubAppIdentity, type GithubAppInstallationGrant, type GithubAppRequirementGaps } from '../providers/github/manifest.js';
|
|
18
24
|
/** A source row as returned by listSources() — the slice this module needs. */
|
|
19
25
|
export type SourceIdentityRow = {
|
|
20
26
|
routing_key: string;
|
|
@@ -39,12 +45,14 @@ export interface RefreshableSourceStore {
|
|
|
39
45
|
export type FetchGithubAppIdentity = (creds: {
|
|
40
46
|
appId: string;
|
|
41
47
|
privateKey: string;
|
|
42
|
-
}) => Promise<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
}) => Promise<GithubAppIdentity>;
|
|
49
|
+
/** List a GitHub App's installations. Matches `listGithubAppInstallations`. */
|
|
50
|
+
export type FetchGithubAppInstallations = (creds: {
|
|
51
|
+
appId: string;
|
|
52
|
+
privateKey: string;
|
|
53
|
+
}) => Promise<GithubAppInstallationGrant[]>;
|
|
54
|
+
/** Outcome of a single source refresh: the name and slug sync plus the requirement gaps. */
|
|
55
|
+
export interface RefreshResult extends GithubAppRequirementGaps {
|
|
48
56
|
routingKey: string;
|
|
49
57
|
changed: boolean;
|
|
50
58
|
oldName: string;
|
|
@@ -54,27 +62,31 @@ export interface RefreshResult {
|
|
|
54
62
|
}
|
|
55
63
|
/**
|
|
56
64
|
* Refresh one already-resolved GitHub source's identity from GitHub and persist
|
|
57
|
-
* it when the name or slug drifted
|
|
58
|
-
*
|
|
59
|
-
*
|
|
65
|
+
* it when the name or slug drifted, then compare the App and its installations
|
|
66
|
+
* with the required events and permissions. The caller supplies the row it
|
|
67
|
+
* already holds, so this never lists the sources table. Throws for a non-GitHub
|
|
68
|
+
* row or missing credentials. A failed installations listing throws after the
|
|
69
|
+
* name and slug sync has been written, so the sync never waits on the gap check.
|
|
60
70
|
*/
|
|
61
|
-
export declare function refreshResolvedGithubSource(sourceStore: Pick<RefreshableSourceStore, 'getSourceWithSecrets' | 'updateSource'>, row: SourceIdentityRow, fetchIdentity: FetchGithubAppIdentity): Promise<RefreshResult>;
|
|
71
|
+
export declare function refreshResolvedGithubSource(sourceStore: Pick<RefreshableSourceStore, 'getSourceWithSecrets' | 'updateSource'>, row: SourceIdentityRow, fetchIdentity: FetchGithubAppIdentity, fetchInstallations: FetchGithubAppInstallations): Promise<RefreshResult>;
|
|
62
72
|
/**
|
|
63
73
|
* Re-fetch one GitHub source's identity from GitHub and persist it when the
|
|
64
|
-
* name or slug drifted. Resolves the row by routing
|
|
65
|
-
* delegates. Shared by `kici-admin source refresh`. Throws for a missing or
|
|
74
|
+
* name or slug drifted, and report requirement gaps. Resolves the row by routing
|
|
75
|
+
* key (one sources read) then delegates. Shared by `kici-admin source refresh`. Throws for a missing or
|
|
66
76
|
* non-GitHub routing key.
|
|
67
77
|
*/
|
|
68
|
-
export declare function refreshGithubSourceIdentity(sourceStore: RefreshableSourceStore, routingKey: string, fetchIdentity: FetchGithubAppIdentity): Promise<RefreshResult>;
|
|
78
|
+
export declare function refreshGithubSourceIdentity(sourceStore: RefreshableSourceStore, routingKey: string, fetchIdentity: FetchGithubAppIdentity, fetchInstallations: FetchGithubAppInstallations): Promise<RefreshResult>;
|
|
69
79
|
export interface GithubAppNameRefresherDeps {
|
|
70
80
|
sourceStore: RefreshableSourceStore;
|
|
71
81
|
fetchIdentity: FetchGithubAppIdentity;
|
|
82
|
+
fetchInstallations: FetchGithubAppInstallations;
|
|
72
83
|
/** Refresh cadence in ms. Default cluster value: 24h (`config.githubAppNameRefreshIntervalMs`). */
|
|
73
84
|
scanIntervalMs: number;
|
|
74
85
|
}
|
|
75
86
|
export declare class GithubAppNameRefresher {
|
|
76
87
|
private readonly sourceStore;
|
|
77
88
|
private readonly fetchIdentity;
|
|
89
|
+
private readonly fetchInstallations;
|
|
78
90
|
private readonly scanIntervalMs;
|
|
79
91
|
private interval;
|
|
80
92
|
constructor(deps: GithubAppNameRefresherDeps);
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { SourceCache } from './cache/source-cache.js';
|
|
|
5
5
|
export { DepCache } from './cache/dep-cache.js';
|
|
6
6
|
export { UserCache, DEFAULT_USER_CACHE_QUOTA_BYTES, DEFAULT_USER_CACHE_TTL_MS, type UserCacheRef, type UserCacheRestoreResult, type UserCacheBeginSaveResult, } from './cache/user-cache.js';
|
|
7
7
|
export { PeerCredentialStore, createPeerCredentialStoreFromUrl, type PeerCredential, type CredentialFileData, } from './cluster/peer-credentials.js';
|
|
8
|
-
export { PeerAuthCoordinator,
|
|
8
|
+
export { PeerAuthCoordinator, RejectionAction, type AuthDecision, } from './cluster/peer-auth-coordinator.js';
|
|
9
9
|
export { JoinTokenManager, createJoinTokenManagerFromUrl } from './cluster/join-token.js';
|
|
10
10
|
export { HeldRunStore, createHeldRunStoreFromUrl, SecurityHoldReason, } from './contexts/held-runs.js';
|
|
11
11
|
export { TrustPolicyStore, createTrustPolicyStoreFromUrl, TrustPolicySource, DEFAULT_TRUST_POLICY, type StoredTrustPolicy, } from './security/trust-policy-store.js';
|