@kici-dev/orchestrator 0.1.2 → 0.1.5
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 +26 -0
- package/dist/agent/ownership-tracker.d.ts +45 -3
- package/dist/audit/access-log.d.ts +7 -0
- package/dist/cli/api-client.d.ts +14 -0
- package/dist/cli/commands/cluster-name.d.ts +18 -0
- package/dist/cli/commands/db.d.ts +9 -6
- package/dist/cli/commands/org-settings.d.ts +2 -1
- package/dist/cli/commands/shared/secret-input.d.ts +46 -0
- package/dist/cli/commands/variable.d.ts +18 -0
- package/dist/cli/service/index.d.ts +1 -0
- package/dist/cli/service/launchd.d.ts +19 -0
- package/dist/cli/service/privilege.d.ts +21 -0
- package/dist/cli/wizard/orchestrator-wizard.d.ts +4 -2
- package/dist/cli.js +1056 -236
- package/dist/cluster/join-token.d.ts +21 -13
- package/dist/cluster/peer-client.d.ts +9 -0
- package/dist/config/cluster-id.d.ts +23 -0
- package/dist/config/cluster-name.d.ts +39 -0
- package/dist/config.d.ts +6 -0
- package/dist/dashboard/handler.d.ts +54 -2
- package/dist/db/migrations/019_generic_sources_change_notify.d.ts +25 -0
- package/dist/db/migrations/020_org_settings_dashboard_write_policy.d.ts +17 -0
- package/dist/db/migrations/021_check_run_tracking.d.ts +26 -0
- package/dist/db/migrations/022_scaler_manager_state.d.ts +29 -0
- package/dist/db/migrations/023_dispatch_queue_recovery_deadline.d.ts +26 -0
- package/dist/db/types.d.ts +137 -0
- package/dist/events/event-store.d.ts +9 -2
- package/dist/events/trust-store.d.ts +7 -0
- package/dist/events/types.d.ts +33 -0
- package/dist/metrics/scheduled-jobs.d.ts +26 -5
- package/dist/orchestrator-core.d.ts +7 -0
- package/dist/policy/dashboard-write-policy.d.ts +115 -0
- package/dist/queue/job-queue.d.ts +54 -1
- package/dist/reporting/check-run-tracking-store.d.ts +133 -0
- package/dist/reporting/commit-status.d.ts +87 -9
- package/dist/routes/admin-access-log.d.ts +1 -0
- package/dist/routes/admin-backends.d.ts +9 -1
- package/dist/routes/admin-cluster-name.d.ts +50 -0
- package/dist/routes/admin-db.d.ts +9 -1
- package/dist/routes/admin-environments.d.ts +10 -6
- package/dist/routes/admin-event-dlq.d.ts +1 -0
- package/dist/routes/admin-event-log.d.ts +1 -0
- package/dist/routes/admin-events.d.ts +19 -0
- package/dist/routes/admin-maintenance.d.ts +9 -1
- package/dist/routes/admin-org-settings.d.ts +10 -1
- package/dist/routes/admin-queue-execution.d.ts +1 -0
- package/dist/routes/admin-registrations.d.ts +1 -0
- package/dist/routes/admin-runs.d.ts +1 -0
- package/dist/routes/admin-scheduled-jobs.d.ts +1 -0
- package/dist/routes/admin-sources.d.ts +9 -1
- package/dist/routes/admin.d.ts +8 -0
- package/dist/scaler/manager.d.ts +38 -0
- package/dist/scaler/scaler-state-store.d.ts +102 -0
- package/dist/secrets/routing-key-scope.d.ts +43 -0
- package/dist/server.js +9846 -6884
- package/dist/stale-detector/stale-run-detector.d.ts +14 -2
- package/dist/standalone.js +6120 -3838
- package/dist/webhook/generic-sources-listener.d.ts +74 -0
- package/dist/webhook/register-source-bundle.d.ts +57 -0
- package/dist/ws/dashboard-backends-handler.d.ts +14 -0
- package/dist/ws/dashboard-env-handler.d.ts +7 -0
- package/dist/ws/dashboard-global-workflows-handler.d.ts +6 -0
- package/dist/ws/dashboard-registrations-handler.d.ts +7 -0
- package/dist/ws/platform-client.d.ts +30 -1
- package/package.json +3 -3
- package/sbom.spdx.json +40 -35
|
@@ -46,25 +46,33 @@ export declare class JoinTokenManager {
|
|
|
46
46
|
expiryMs?: number;
|
|
47
47
|
}): Promise<string>;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* Atomically validate and consume a token in one DB round-trip.
|
|
50
|
+
*
|
|
51
|
+
* Single UPDATE with `WHERE token_hash = ? AND consumed_at IS NULL AND
|
|
52
|
+
* expires_at > NOW()` — only one caller can win the claim across a
|
|
53
|
+
* shared-DB multi-coordinator mesh. The winner gets `{ routing, keys }`;
|
|
54
|
+
* every other concurrent caller gets `TOKEN_ALREADY_USED_MESSAGE` and is
|
|
55
|
+
* expected to fall into the idempotent recovery branch in
|
|
56
|
+
* `peer-handler.ts` (which serialises credential issuance via the
|
|
57
|
+
* `peer_credentials_active_uniq` partial unique index).
|
|
58
|
+
*
|
|
59
|
+
* On a 0-row claim, a follow-up SELECT disambiguates not-found / expired
|
|
60
|
+
* / already-used so callers can branch on the specific reason — the
|
|
61
|
+
* recovery path in peer-handler.ts keys on the "already used" string
|
|
62
|
+
* specifically. The follow-up only fires on the unhappy path.
|
|
51
63
|
*/
|
|
52
|
-
|
|
64
|
+
validateAndConsumeToken(token: string, consumedBy: string): Promise<{
|
|
53
65
|
routing: TokenRouting;
|
|
54
66
|
keys: DerivedKeys;
|
|
55
67
|
}>;
|
|
56
|
-
/**
|
|
57
|
-
* Mark token as consumed (one-time use).
|
|
58
|
-
*/
|
|
59
|
-
consumeToken(validationHash: string, consumedBy: string): Promise<void>;
|
|
60
68
|
}
|
|
61
69
|
/**
|
|
62
|
-
* Narrow detector for the "already been used" error thrown by
|
|
63
|
-
* Used by peer-handler.ts to branch into the
|
|
64
|
-
* legitimate mesh-join races (sibling
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* treated as recoverable.
|
|
70
|
+
* Narrow detector for the "already been used" error thrown by
|
|
71
|
+
* validateAndConsumeToken(). Used by peer-handler.ts to branch into the
|
|
72
|
+
* idempotent recovery path on legitimate mesh-join races (sibling
|
|
73
|
+
* peer-clients on the same peer identity racing on a shared join token
|
|
74
|
+
* across a multi-coordinator shared-DB mesh). Other validation failures
|
|
75
|
+
* (expired, not found, bad parse) MUST NOT be treated as recoverable.
|
|
68
76
|
*/
|
|
69
77
|
export declare function isTokenAlreadyUsedError(err: unknown): boolean;
|
|
70
78
|
/**
|
|
@@ -70,6 +70,14 @@ export interface PeerClientOptions {
|
|
|
70
70
|
restartRequired?: string[];
|
|
71
71
|
fieldsChanged?: string[];
|
|
72
72
|
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Callback invoked once the remote peer accepts our auth handshake,
|
|
75
|
+
* carrying the remote peer's instanceId. Used by callers that initially
|
|
76
|
+
* register this client in `sub.peerClients` keyed by a placeholder (URL
|
|
77
|
+
* or stale id) to re-key the map by the canonical instanceId so later
|
|
78
|
+
* Platform-mediated discovery dedupes against the same client.
|
|
79
|
+
*/
|
|
80
|
+
onAuthenticated?: (targetInstanceId: string) => void;
|
|
73
81
|
}
|
|
74
82
|
export declare class PeerClient {
|
|
75
83
|
private ws;
|
|
@@ -103,6 +111,7 @@ export declare class PeerClient {
|
|
|
103
111
|
private readonly onPeerLeaving?;
|
|
104
112
|
private readonly onAgentTokenRevoke?;
|
|
105
113
|
private readonly onPeerConfigReload?;
|
|
114
|
+
private readonly onAuthenticated?;
|
|
106
115
|
constructor(options: PeerClientOptions);
|
|
107
116
|
/** Current connection state. */
|
|
108
117
|
get state(): PeerConnectionState;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator cluster-id resolution.
|
|
3
|
+
*
|
|
4
|
+
* Every orchestrator DB carries a stable UUID identifier
|
|
5
|
+
* (`cluster_meta.cluster_id`) seeded once on first boot by the initial
|
|
6
|
+
* schema migration. HA-cluster coords share the same orchestrator DB,
|
|
7
|
+
* so they share the same `cluster_id`; two genuinely-different
|
|
8
|
+
* orchestrator clusters carry distinct values.
|
|
9
|
+
*
|
|
10
|
+
* The orchestrator publishes `cluster_id` on `source.register` so
|
|
11
|
+
* Platform can warn when two unrelated clusters in one org accidentally
|
|
12
|
+
* share a `cluster_name`. This module is read-only — the row is seeded
|
|
13
|
+
* by the schema migration and never written from application code.
|
|
14
|
+
*/
|
|
15
|
+
import type { Kysely } from 'kysely';
|
|
16
|
+
import type { Database } from '../db/types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Read the cluster id from `cluster_meta`. Throws when no row exists —
|
|
19
|
+
* the row is seeded by the initial schema migration, so a missing row
|
|
20
|
+
* indicates the DB was opened before migrations ran.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getClusterId(db: Kysely<Database>): Promise<string>;
|
|
23
|
+
//# sourceMappingURL=cluster-id.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import { type ClusterName } from '@kici-dev/engine/protocol/cluster-name';
|
|
3
|
+
import type { Database } from '../db/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Source attribution for the resolved cluster name. Useful for operator
|
|
6
|
+
* tooling (`kici-admin cluster-name get`) to explain where the current
|
|
7
|
+
* value came from.
|
|
8
|
+
*/
|
|
9
|
+
export type ClusterNameSource = 'stored' | 'env-seeded' | 'auto-generated';
|
|
10
|
+
export interface ResolveResult {
|
|
11
|
+
clusterName: ClusterName;
|
|
12
|
+
source: ClusterNameSource;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Read the cluster name from `cluster_meta`. Returns null if no row
|
|
16
|
+
* exists yet (orch has never resolved its name).
|
|
17
|
+
*/
|
|
18
|
+
export declare function readClusterName(db: Kysely<Database>): Promise<ClusterName | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Look up the cluster name. Throws if no row exists — callers that hit
|
|
21
|
+
* this before the boot resolver has run should be considered bugs.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getClusterName(db: Kysely<Database>): Promise<ClusterName>;
|
|
24
|
+
/**
|
|
25
|
+
* Persist a validated cluster name. Upsert semantics: replaces the
|
|
26
|
+
* existing row if one exists. Called by both the boot resolver and the
|
|
27
|
+
* kici-admin CLI's `cluster-name set`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function setClusterName(db: Kysely<Database>, name: string): Promise<ClusterName>;
|
|
30
|
+
/**
|
|
31
|
+
* Boot-time resolution: returns the existing row, seeds from env, or
|
|
32
|
+
* auto-generates. Idempotent: a re-run after first boot just returns
|
|
33
|
+
* the stored value.
|
|
34
|
+
*
|
|
35
|
+
* `randomSource` is dependency-injected so tests can drive deterministic
|
|
36
|
+
* suffixes; production callers pass `node:crypto.randomBytes`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveAndPersistClusterName(db: Kysely<Database>, env?: NodeJS.ProcessEnv, randomSource?: (size: number) => Uint8Array): Promise<ResolveResult>;
|
|
39
|
+
//# sourceMappingURL=cluster-name.d.ts.map
|
package/dist/config.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
85
85
|
eventRouterRetryBaseBackoffMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
86
86
|
eventRouterRetryMaxBackoffMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
87
87
|
eventRouterRetryScanIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
88
|
+
testMode: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
|
|
89
|
+
testEventFailFirstN: z.ZodOptional<z.ZodString>;
|
|
88
90
|
eventLogMaxPayloadBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
89
91
|
logLevel: z.ZodDefault<z.ZodEnum<{
|
|
90
92
|
error: "error";
|
|
@@ -103,6 +105,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
103
105
|
agentMaxReconnectDelayMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
104
106
|
skipS3SentinelValidation: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<boolean, string>>;
|
|
105
107
|
otelExporterOtlpEndpoint: z.ZodOptional<z.ZodString>;
|
|
108
|
+
clusterName: z.ZodOptional<z.ZodString>;
|
|
106
109
|
cluster: z.ZodPrefault<z.ZodObject<{
|
|
107
110
|
instanceId: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
108
111
|
address: z.ZodOptional<z.ZodString>;
|
|
@@ -191,6 +194,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
|
|
|
191
194
|
eventRouterRetryBaseBackoffMs: number;
|
|
192
195
|
eventRouterRetryMaxBackoffMs: number;
|
|
193
196
|
eventRouterRetryScanIntervalMs: number;
|
|
197
|
+
testMode: boolean;
|
|
194
198
|
eventLogMaxPayloadBytes: number;
|
|
195
199
|
logLevel: "error" | "debug" | "info" | "warn";
|
|
196
200
|
nodeEnv: "development" | "production" | "test";
|
|
@@ -240,9 +244,11 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
|
|
|
240
244
|
secretKeyOld?: string | undefined;
|
|
241
245
|
secretKeyFileOld?: string | undefined;
|
|
242
246
|
bootstrapAdminToken?: string | undefined;
|
|
247
|
+
testEventFailFirstN?: string | undefined;
|
|
243
248
|
internalProviderRepoPath?: string | undefined;
|
|
244
249
|
internalProviderCloneUrl?: string | undefined;
|
|
245
250
|
otelExporterOtlpEndpoint?: string | undefined;
|
|
251
|
+
clusterName?: string | undefined;
|
|
246
252
|
}>;
|
|
247
253
|
export declare function loadConfig(): AppConfig;
|
|
248
254
|
export {};
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
* - run.rerun.request: re-runs a completed workflow run
|
|
12
12
|
* - run.cancel.request: cancels a running workflow run
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type Kysely } from 'kysely';
|
|
15
15
|
import { type ColdStore } from '@kici-dev/shared';
|
|
16
|
-
import type { DashboardRunDetailRequest, DashboardStepLogsRequest, DashboardPayloadRequest, DashboardOrchLogsRequest, DashboardEventLogListRequest, DashboardEventLogDetailRequest, DashboardEventLogPayloadStreamRequest, DashboardAccessLogListRequest, RunRerunRequest, RunCancelRequest, ManualScheduleRequest } from '@kici-dev/engine';
|
|
16
|
+
import type { DashboardRunDetailRequest, DashboardStepLogsRequest, DashboardPayloadRequest, DashboardOrchLogsRequest, DashboardEventLogListRequest, DashboardEventLogDetailRequest, DashboardEventLogPayloadStreamRequest, DashboardAccessLogListRequest, DashboardEventDlqListRequest, DashboardEventDlqCountRequest, DashboardEventDlqRetryRequest, DashboardEventDlqDiscardRequest, RunRerunRequest, RunCancelRequest, ManualScheduleRequest } from '@kici-dev/engine';
|
|
17
17
|
import type { Database } from '../db/types.js';
|
|
18
18
|
import type { LogStorage } from '../reporting/log-storage.js';
|
|
19
19
|
import type { AccessLogWriter } from '../audit/access-log.js';
|
|
20
|
+
import type { EventStore } from '../events/event-store.js';
|
|
20
21
|
interface DashboardHandlerDeps {
|
|
21
22
|
db: Kysely<Database>;
|
|
22
23
|
logStorage: LogStorage;
|
|
@@ -66,6 +67,13 @@ interface DashboardHandlerDeps {
|
|
|
66
67
|
onManualSchedule: (registrationId: string, triggeredBy: string | null) => Promise<{
|
|
67
68
|
newRunId: string;
|
|
68
69
|
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Event store for the per-org DLQ surface. Optional — when absent the
|
|
72
|
+
* `handleEventDlq*` methods reply with a structured `error` so the
|
|
73
|
+
* dashboard renders an empty-state without crashing. In real
|
|
74
|
+
* deployments the store is wired by `bootstrapOrchestrator`.
|
|
75
|
+
*/
|
|
76
|
+
eventStore?: EventStore | null;
|
|
69
77
|
}
|
|
70
78
|
export declare class DashboardHandler {
|
|
71
79
|
private readonly db;
|
|
@@ -79,6 +87,7 @@ export declare class DashboardHandler {
|
|
|
79
87
|
private readonly onCancel;
|
|
80
88
|
private readonly onManualSchedule;
|
|
81
89
|
private readonly coldStore;
|
|
90
|
+
private readonly eventStore;
|
|
82
91
|
constructor(deps: DashboardHandlerDeps);
|
|
83
92
|
/**
|
|
84
93
|
* Update the bound orgId + routingKey. Called from server.ts after resolving
|
|
@@ -125,6 +134,13 @@ export declare class DashboardHandler {
|
|
|
125
134
|
* fallback rather than the source of truth.
|
|
126
135
|
*/
|
|
127
136
|
private contextOrFallback;
|
|
137
|
+
/**
|
|
138
|
+
* Defense-in-depth dashboard-write policy gate for the DLQ handlers.
|
|
139
|
+
* Returns true when allowed; false (with a `denied` access_log row and
|
|
140
|
+
* an `operation_disabled` envelope on the wire) when the orch policy
|
|
141
|
+
* has the operation switched off.
|
|
142
|
+
*/
|
|
143
|
+
private enforcePolicy;
|
|
128
144
|
/**
|
|
129
145
|
* Write an access_log row for a handler invocation. The caller resolves
|
|
130
146
|
* the run-owning org via `resolveOrgForRun` (or the registration / event-
|
|
@@ -225,6 +241,42 @@ export declare class DashboardHandler {
|
|
|
225
241
|
* ~80 yields per stream — negligible.
|
|
226
242
|
*/
|
|
227
243
|
handleEventLogPayloadStream(msg: DashboardEventLogPayloadStreamRequest): Promise<void>;
|
|
244
|
+
/**
|
|
245
|
+
* Handle a dashboard.event-dlq.list request.
|
|
246
|
+
*
|
|
247
|
+
* Returns a paginated page of DLQ rows (events whose dispatch attempts
|
|
248
|
+
* exhausted the retry budget). The page is keyed off `dlq_at DESC` and
|
|
249
|
+
* paginates with a single `before` ISO cursor.
|
|
250
|
+
*
|
|
251
|
+
* Records one `event_dlq.list.read` access_log entry using the user
|
|
252
|
+
* actor on the wire — mirrors the HTTP admin route's audit shape but
|
|
253
|
+
* with the calling user's identity instead of the bearer-token role.
|
|
254
|
+
*/
|
|
255
|
+
handleEventDlqList(msg: DashboardEventDlqListRequest): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Handle a dashboard.event-dlq.count request.
|
|
258
|
+
*
|
|
259
|
+
* Returns the DLQ depth for the sidebar badge. Polled ~30s by the
|
|
260
|
+
* dashboard. No access_log row — the count surface is intentionally
|
|
261
|
+
* un-audited (it's a frequent badge poll, not a triage action).
|
|
262
|
+
*/
|
|
263
|
+
handleEventDlqCount(msg: DashboardEventDlqCountRequest): Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* Handle a dashboard.event-dlq.retry request.
|
|
266
|
+
*
|
|
267
|
+
* Clears the DLQ flag on the row and issues a `pg_notify` so a healthy
|
|
268
|
+
* node picks the event up immediately rather than waiting for the
|
|
269
|
+
* leader-only retry scanner's next tick. Notify failure is non-fatal —
|
|
270
|
+
* the scanner will catch up.
|
|
271
|
+
*/
|
|
272
|
+
handleEventDlqRetry(msg: DashboardEventDlqRetryRequest): Promise<void>;
|
|
273
|
+
/**
|
|
274
|
+
* Handle a dashboard.event-dlq.discard request.
|
|
275
|
+
*
|
|
276
|
+
* Permanently deletes the DLQ row. No retry, no archive — used when the
|
|
277
|
+
* payload is corrupt or the routing target has been removed.
|
|
278
|
+
*/
|
|
279
|
+
handleEventDlqDiscard(msg: DashboardEventDlqDiscardRequest): Promise<void>;
|
|
228
280
|
/**
|
|
229
281
|
* Handle a run.manual_schedule.request.
|
|
230
282
|
* Delegates to the onManualSchedule callback which invokes handleManualSchedule.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Add a Postgres trigger on `generic_webhook_sources` that emits
|
|
4
|
+
* `pg_notify('generic_sources_change', routing_key)` on every INSERT,
|
|
5
|
+
* UPDATE, and DELETE. Soft-deletes (UPDATE that sets `deleted_at`) ride
|
|
6
|
+
* the same UPDATE path — the listener treats a row with `deleted_at IS
|
|
7
|
+
* NOT NULL` (or a row that no longer exists for that routing_key) as a
|
|
8
|
+
* de-registration signal.
|
|
9
|
+
*
|
|
10
|
+
* The consumer is `GenericSourcesChangeListener`
|
|
11
|
+
* (`packages/orchestrator/src/webhook/generic-sources-listener.ts`),
|
|
12
|
+
* which runs in every orchestrator peer, LISTENs on the channel, and
|
|
13
|
+
* mutates the local `ProviderRegistry` in place
|
|
14
|
+
* (`registerProviderBundleForSource` for INSERT/UPDATE; `unregister` for
|
|
15
|
+
* DELETE / soft-delete). Without this round-trip, peers other than the
|
|
16
|
+
* one that handled `kici-admin source add generic` would 404 incoming
|
|
17
|
+
* webhooks until restart.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors the existing GitHub-app `sources` trigger
|
|
20
|
+
* (`notify_sources_change()` + `sources_change_trigger`, defined in
|
|
21
|
+
* `001_initial.ts`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
24
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=019_generic_sources_change_notify.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Add `org_settings.dashboard_write_policy jsonb NOT NULL DEFAULT '{}'`.
|
|
4
|
+
*
|
|
5
|
+
* Stores the per-customer policy that decides which Platform-routed
|
|
6
|
+
* dashboard.* write operations the orchestrator accepts. An empty
|
|
7
|
+
* JSONB object means everything is enabled (permissive default). To
|
|
8
|
+
* disable an operation, set its key to `false`, e.g.
|
|
9
|
+
* `{"secrets.set": false, "variables.set": false}`. Unknown / missing
|
|
10
|
+
* keys are treated as `true` by the resolver in
|
|
11
|
+
* `@kici-dev/engine/protocol/dashboard-write-operations`.
|
|
12
|
+
*
|
|
13
|
+
* Idempotent: a re-run on a DB that already has the column is a no-op.
|
|
14
|
+
*/
|
|
15
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
16
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=020_org_settings_dashboard_write_policy.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Add `check_run_tracking` table for HA-safe check-run state persistence.
|
|
4
|
+
*
|
|
5
|
+
* The `CommitStatusReporter` previously held all of its check-run bookkeeping
|
|
6
|
+
* in per-process `Map`s. Under HA (Raft leader switch / coord crash) those
|
|
7
|
+
* maps reset to empty on the replacement coord, leaving GitHub check runs
|
|
8
|
+
* stuck in `queued` forever.
|
|
9
|
+
*
|
|
10
|
+
* Single composite-keyed table per (provider, owner, repo, sha, check_name):
|
|
11
|
+
*
|
|
12
|
+
* - `check_run_id` replaces in-memory `checkRunIds`.
|
|
13
|
+
* - `build_creation_state` replaces `pendingBuildCreations` Promise map.
|
|
14
|
+
* - `step_progress_json` replaces `stepProgress` array map.
|
|
15
|
+
* - `in_progress_sent_at` replaces `inProgressSent` boolean map.
|
|
16
|
+
* - `run_id` indexed; replaces `runIdToKeys` reverse map.
|
|
17
|
+
*
|
|
18
|
+
* Progress-timer debounce state (the `progressTimers` Map) is NOT persisted —
|
|
19
|
+
* timers are recreated on demand when an update arrives and the row's
|
|
20
|
+
* `updated_at` is older than the debounce window.
|
|
21
|
+
*
|
|
22
|
+
* Idempotent: a re-run on a DB that already has the table is a no-op.
|
|
23
|
+
*/
|
|
24
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
25
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=021_check_run_tracking.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Add three tables persisting `ScalerManager` per-coord state:
|
|
4
|
+
*
|
|
5
|
+
* - `scaler_spawning_agents`: agents spawned via Docker/Podman/Firecracker
|
|
6
|
+
* that have not yet registered via WS. Carries the `bound_job_id` so a
|
|
7
|
+
* replacement coord still issues the eager-dispatch hop on register.
|
|
8
|
+
*
|
|
9
|
+
* - `scaler_agent_jobs`: agentId → (runId, jobId) correlation for
|
|
10
|
+
* scaler-lifecycle event routing. Inserted on `correlateAgentToJob`,
|
|
11
|
+
* deleted on disconnect / job completion.
|
|
12
|
+
*
|
|
13
|
+
* - `scaler_reservations`: outstanding resource reservations keyed by
|
|
14
|
+
* agentId. Per-scaler / global usage counters are derived state —
|
|
15
|
+
* recomputed on coord boot as `SUM(...) GROUP BY scaler_name` so the
|
|
16
|
+
* cap-check critical section stays correct.
|
|
17
|
+
*
|
|
18
|
+
* Without these tables, a coord crash mid-spawn orphans the agent (eager
|
|
19
|
+
* dispatch lost), strands the reservation (resource leak until backend
|
|
20
|
+
* GC eventually disconnects the WS, minutes later), and loses every
|
|
21
|
+
* scaler-lifecycle event emitted before correlation (execution-tracker
|
|
22
|
+
* sees a hole in the run timeline).
|
|
23
|
+
*
|
|
24
|
+
* Idempotent: a re-run on a DB that already has any of these tables
|
|
25
|
+
* leaves the existing one alone.
|
|
26
|
+
*/
|
|
27
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
28
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=022_scaler_manager_state.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Add `dispatch_queue.recovery_deadline TIMESTAMPTZ` and
|
|
4
|
+
* `dispatch_queue.recovery_agent_id TEXT` columns persisting per-job
|
|
5
|
+
* recovery state for HA-safe agent-disconnect handling.
|
|
6
|
+
*
|
|
7
|
+
* `Dispatcher.recoveringJobs` previously held the timer handle, agent
|
|
8
|
+
* ID, and deadline entirely in process memory. A coord crash between
|
|
9
|
+
* starting the timer and it firing dropped the timer; the replacement
|
|
10
|
+
* coord saw the row as `status='recovering'` but had no record of when
|
|
11
|
+
* the deadline expired or which agent owned the job. Jobs lingered
|
|
12
|
+
* forever.
|
|
13
|
+
*
|
|
14
|
+
* The new columns make the deadline durable. A leader-gated sweep
|
|
15
|
+
* (`Dispatcher.sweepExpiredRecoveries`) scans
|
|
16
|
+
* `WHERE status='recovering' AND recovery_deadline < now()` and marks
|
|
17
|
+
* each row failed; on coord boot, `Dispatcher.recoverState()`
|
|
18
|
+
* hydrates the in-memory Map by re-creating timers from the persisted
|
|
19
|
+
* deadlines.
|
|
20
|
+
*
|
|
21
|
+
* Idempotent: re-running on a DB that already has either column is a
|
|
22
|
+
* no-op.
|
|
23
|
+
*/
|
|
24
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
25
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=023_dispatch_queue_recovery_deadline.d.ts.map
|
package/dist/db/types.d.ts
CHANGED
|
@@ -41,6 +41,10 @@ export interface Database {
|
|
|
41
41
|
access_log: AccessLogTable;
|
|
42
42
|
cold_store_chunk_counts: ColdStoreChunkCountsTable;
|
|
43
43
|
cold_store_chunks: ColdStoreChunksTable;
|
|
44
|
+
check_run_tracking: CheckRunTrackingTable;
|
|
45
|
+
scaler_spawning_agents: ScalerSpawningAgentsTable;
|
|
46
|
+
scaler_agent_jobs: ScalerAgentJobsTable;
|
|
47
|
+
scaler_reservations: ScalerReservationsTable;
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
46
50
|
* Cluster metadata table
|
|
@@ -132,6 +136,20 @@ export interface DispatchQueueTable {
|
|
|
132
136
|
/** Routing key (e.g. "github:12345") so dispatch can pick the right
|
|
133
137
|
* per-app provider bundle in multi-app setups. Required (NOT NULL). */
|
|
134
138
|
routing_key: string;
|
|
139
|
+
/**
|
|
140
|
+
* For jobs in `status='recovering'`, the moment the recovery grace
|
|
141
|
+
* period elapses. Populated when an agent disconnects with this job
|
|
142
|
+
* in-flight; cleared when the agent reconnects + claims the job, OR
|
|
143
|
+
* when the leader-gated sweep transitions the row to `failed`.
|
|
144
|
+
* NULL for all other statuses.
|
|
145
|
+
*/
|
|
146
|
+
recovery_deadline: ColumnType<Date | null, Date | null | undefined, Date | null>;
|
|
147
|
+
/**
|
|
148
|
+
* Companion to `recovery_deadline`: the agent id that owned the job
|
|
149
|
+
* before disconnect. Used to validate that a reconnecting agent is
|
|
150
|
+
* the rightful claimant. NULL for non-recovering rows.
|
|
151
|
+
*/
|
|
152
|
+
recovery_agent_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
135
153
|
}
|
|
136
154
|
/**
|
|
137
155
|
* Deduplication cache table
|
|
@@ -1108,6 +1126,15 @@ export interface OrgSettingsTable {
|
|
|
1108
1126
|
* URLs are accepted; arbitrary `http://` registries are rejected at dispatch.
|
|
1109
1127
|
*/
|
|
1110
1128
|
allow_http_npm_registries: ColumnType<boolean, boolean | undefined, boolean>;
|
|
1129
|
+
/**
|
|
1130
|
+
* Per-operation policy controlling which dashboard.* writes the orch
|
|
1131
|
+
* accepts when routed through Platform. JSONB shape:
|
|
1132
|
+
* `{ [operation]: boolean }` where operation matches the engine enum
|
|
1133
|
+
* `DashboardWriteOperation`. Empty object = all enabled (permissive).
|
|
1134
|
+
* Resolver in `@kici-dev/engine/protocol/dashboard-write-operations`
|
|
1135
|
+
* treats missing keys as `true`.
|
|
1136
|
+
*/
|
|
1137
|
+
dashboard_write_policy: ColumnType<Record<string, boolean>, Record<string, boolean> | string | undefined, Record<string, boolean> | string>;
|
|
1111
1138
|
/** When this setting was created */
|
|
1112
1139
|
created_at: Generated<Date>;
|
|
1113
1140
|
/** When this setting was last updated */
|
|
@@ -1263,4 +1290,114 @@ export interface ColdStoreChunksTable {
|
|
|
1263
1290
|
export type ColdStoreChunksRow = Selectable<ColdStoreChunksTable>;
|
|
1264
1291
|
export type NewColdStoreChunksRow = Insertable<ColdStoreChunksTable>;
|
|
1265
1292
|
export type ColdStoreChunksUpdate = Updateable<ColdStoreChunksTable>;
|
|
1293
|
+
/**
|
|
1294
|
+
* Check-run tracking table (check_run_tracking).
|
|
1295
|
+
*
|
|
1296
|
+
* HA-safe persistence for the per-coord state previously held in
|
|
1297
|
+
* `CommitStatusReporter`'s six in-memory `Map`s. Replacement coord on a
|
|
1298
|
+
* Raft leader switch reads this table to recover check-run IDs, build
|
|
1299
|
+
* creation state, step-progress entries, in-progress-sent timestamps, and
|
|
1300
|
+
* the run-id reverse index used for cleanup. A coord crash mid-check-run
|
|
1301
|
+
* no longer leaves a GitHub check stuck in `queued` forever.
|
|
1302
|
+
*/
|
|
1303
|
+
export interface CheckRunTrackingTable {
|
|
1304
|
+
/** Provider type (e.g. 'github'). */
|
|
1305
|
+
provider: string;
|
|
1306
|
+
/** Repo owner / namespace. */
|
|
1307
|
+
owner: string;
|
|
1308
|
+
/** Repo name. */
|
|
1309
|
+
repo: string;
|
|
1310
|
+
/** Git commit SHA the check run is anchored to. */
|
|
1311
|
+
sha: string;
|
|
1312
|
+
/** Check-run name (e.g. 'kici/build', 'kici/build/job/test', 'kici/build/setup'). */
|
|
1313
|
+
check_name: string;
|
|
1314
|
+
/**
|
|
1315
|
+
* GitHub Checks API check-run ID. Populated by `checks.create()`; nullable
|
|
1316
|
+
* during the in-flight build-creation window (`build_creation_state =
|
|
1317
|
+
* 'pending'` before the create finishes).
|
|
1318
|
+
*/
|
|
1319
|
+
check_run_id: ColumnType<number | null, number | null | undefined, number | null>;
|
|
1320
|
+
/**
|
|
1321
|
+
* Build check-run creation state: 'pending' while a `setBuildPending`
|
|
1322
|
+
* create is in flight, 'completed' once `setBuildComplete` has reconciled.
|
|
1323
|
+
* Replaces the in-memory `pendingBuildCreations` Promise map.
|
|
1324
|
+
*/
|
|
1325
|
+
build_creation_state: ColumnType<string | null, string | null | undefined, string | null>;
|
|
1326
|
+
/**
|
|
1327
|
+
* Step-progress entries as JSONB array. Each entry shape is
|
|
1328
|
+
* `{ name: string, status: string, durationMs?: number }`. Replaces the
|
|
1329
|
+
* in-memory `stepProgress` map.
|
|
1330
|
+
*/
|
|
1331
|
+
step_progress_json: ColumnType<unknown, string | unknown, string | unknown>;
|
|
1332
|
+
/**
|
|
1333
|
+
* Timestamp the first in-progress transition was sent to GitHub. NULL
|
|
1334
|
+
* before the first running step. Replaces the in-memory `inProgressSent`
|
|
1335
|
+
* boolean map (presence-as-truth).
|
|
1336
|
+
*/
|
|
1337
|
+
in_progress_sent_at: ColumnType<Date | null, Date | null | undefined, Date | null>;
|
|
1338
|
+
/**
|
|
1339
|
+
* KiCI run identifier this check-run belongs to. Indexed (partial, NOT
|
|
1340
|
+
* NULL) to power `cleanupRun(runId)` without scanning the table.
|
|
1341
|
+
*/
|
|
1342
|
+
run_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
1343
|
+
/** When this row was first inserted. */
|
|
1344
|
+
created_at: Generated<Date>;
|
|
1345
|
+
/** When this row was last updated. */
|
|
1346
|
+
updated_at: Generated<Date>;
|
|
1347
|
+
}
|
|
1348
|
+
export type CheckRunTrackingRow = Selectable<CheckRunTrackingTable>;
|
|
1349
|
+
export type NewCheckRunTrackingRow = Insertable<CheckRunTrackingTable>;
|
|
1350
|
+
export type CheckRunTrackingUpdate = Updateable<CheckRunTrackingTable>;
|
|
1351
|
+
/**
|
|
1352
|
+
* Scaler spawning-agents table (scaler_spawning_agents).
|
|
1353
|
+
*
|
|
1354
|
+
* One row per agent that has been spawned via a scaler backend
|
|
1355
|
+
* (container / bare-metal / firecracker) but has not yet registered via
|
|
1356
|
+
* WS. Persists `bound_job_id` so a replacement coord still issues the
|
|
1357
|
+
* eager-dispatch hop when the agent eventually registers. GC'd by a
|
|
1358
|
+
* leader-gated sweep that drops rows older than the spawn-timeout.
|
|
1359
|
+
*/
|
|
1360
|
+
export interface ScalerSpawningAgentsTable {
|
|
1361
|
+
agent_id: string;
|
|
1362
|
+
scaler_name: string;
|
|
1363
|
+
label_set: ColumnType<string[], string | string[], string | string[]>;
|
|
1364
|
+
run_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
1365
|
+
job_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
1366
|
+
bound_job_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
1367
|
+
spawned_at: Generated<Date>;
|
|
1368
|
+
}
|
|
1369
|
+
export type ScalerSpawningAgentRow = Selectable<ScalerSpawningAgentsTable>;
|
|
1370
|
+
export type NewScalerSpawningAgentRow = Insertable<ScalerSpawningAgentsTable>;
|
|
1371
|
+
/**
|
|
1372
|
+
* Scaler agent-jobs table (scaler_agent_jobs).
|
|
1373
|
+
*
|
|
1374
|
+
* agentId → (runId, jobId) correlation used to route scaler-lifecycle
|
|
1375
|
+
* events (spawn / boot / ready / kill) to the execution tracker. Row
|
|
1376
|
+
* inserted in `correlateAgentToJob`, deleted on agent disconnect / job
|
|
1377
|
+
* completion.
|
|
1378
|
+
*/
|
|
1379
|
+
export interface ScalerAgentJobsTable {
|
|
1380
|
+
agent_id: string;
|
|
1381
|
+
run_id: string;
|
|
1382
|
+
job_id: string;
|
|
1383
|
+
correlated_at: Generated<Date>;
|
|
1384
|
+
}
|
|
1385
|
+
export type ScalerAgentJobRow = Selectable<ScalerAgentJobsTable>;
|
|
1386
|
+
export type NewScalerAgentJobRow = Insertable<ScalerAgentJobsTable>;
|
|
1387
|
+
/**
|
|
1388
|
+
* Scaler reservations table (scaler_reservations).
|
|
1389
|
+
*
|
|
1390
|
+
* One row per outstanding resource reservation. `perScalerUsage` /
|
|
1391
|
+
* `globalUsage` are derived state — recomputed from `SUM(...)` on coord
|
|
1392
|
+
* boot so the cap-check critical section is correct under HA.
|
|
1393
|
+
*/
|
|
1394
|
+
export interface ScalerReservationsTable {
|
|
1395
|
+
agent_id: string;
|
|
1396
|
+
scaler_name: string;
|
|
1397
|
+
cpu_units: number;
|
|
1398
|
+
mem_bytes: ColumnType<string, string | number, string | number>;
|
|
1399
|
+
reserved_at: Generated<Date>;
|
|
1400
|
+
}
|
|
1401
|
+
export type ScalerReservationRow = Selectable<ScalerReservationsTable>;
|
|
1402
|
+
export type NewScalerReservationRow = Insertable<ScalerReservationsTable>;
|
|
1266
1403
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -120,13 +120,20 @@ export declare class EventStore {
|
|
|
120
120
|
/**
|
|
121
121
|
* List events currently in the DLQ. Most recent first. Used by the
|
|
122
122
|
* dashboard admin DLQ page.
|
|
123
|
+
*
|
|
124
|
+
* @param sourceRoutingKey - When provided, restrict the result to
|
|
125
|
+
* events whose `source_routing_key` matches. Used by routing-key
|
|
126
|
+
* -scoped admin tokens so the operator only sees their slice.
|
|
123
127
|
*/
|
|
124
|
-
listDlq(limit: number, beforeDlqAt?: Date): Promise<StoredEvent[]>;
|
|
128
|
+
listDlq(limit: number, beforeDlqAt?: Date, sourceRoutingKey?: string): Promise<StoredEvent[]>;
|
|
125
129
|
/**
|
|
126
130
|
* Count events currently in the DLQ. Used by the dashboard summary row
|
|
127
131
|
* and the `kici_orch_event_dlq_depth` gauge.
|
|
132
|
+
*
|
|
133
|
+
* @param sourceRoutingKey - When provided, restrict the count to
|
|
134
|
+
* events whose `source_routing_key` matches.
|
|
128
135
|
*/
|
|
129
|
-
countDlq(): Promise<number>;
|
|
136
|
+
countDlq(sourceRoutingKey?: string): Promise<number>;
|
|
130
137
|
/**
|
|
131
138
|
* Reset a DLQ event so it gets retried on the next scanner tick. Used by
|
|
132
139
|
* the dashboard "Retry" action.
|
|
@@ -41,6 +41,13 @@ export declare class TrustStore {
|
|
|
41
41
|
repo: string;
|
|
42
42
|
routingKey: string;
|
|
43
43
|
}, allowedEvents?: string[]): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Look up a single trust entry by ID. Returns null if the row is
|
|
46
|
+
* missing. Callers (the admin HTTP route, in particular) use this
|
|
47
|
+
* to read a row's source/target routing keys before applying a
|
|
48
|
+
* scope check.
|
|
49
|
+
*/
|
|
50
|
+
getById(id: string): Promise<TrustEntry | null>;
|
|
44
51
|
/**
|
|
45
52
|
* Remove a trust relationship by ID.
|
|
46
53
|
*/
|
package/dist/events/types.d.ts
CHANGED
|
@@ -23,6 +23,20 @@ export interface EventRouterConfig {
|
|
|
23
23
|
retryMaxBackoffMs: number;
|
|
24
24
|
/** Interval at which the leader-only retry scanner ticks (default: 10_000) */
|
|
25
25
|
retryScanIntervalMs: number;
|
|
26
|
+
/**
|
|
27
|
+
* **Test-only.** Per-event-name fault injection: when `attempts <= N`,
|
|
28
|
+
* the EventRouter throws a synthetic dispatch error to drive the retry /
|
|
29
|
+
* DLQ path. Used by the fault-injection E2E to prove the lease + retry
|
|
30
|
+
* loop dispatches a real run when the inner dispatch eventually
|
|
31
|
+
* succeeds, and lands the row in the DLQ when N exceeds
|
|
32
|
+
* `maxDispatchAttempts`.
|
|
33
|
+
*
|
|
34
|
+
* Only honoured when `KICI_TEST_MODE=1` is set at config-load time —
|
|
35
|
+
* production deployments never see this knob even if the env var is
|
|
36
|
+
* planted by accident. Source the value from
|
|
37
|
+
* `KICI_TEST_EVENT_FAIL_FIRST_N` (a JSON object literal).
|
|
38
|
+
*/
|
|
39
|
+
debugFailFirstNAttemptsByEvent?: Record<string, number>;
|
|
26
40
|
}
|
|
27
41
|
/**
|
|
28
42
|
* Reason an event landed in the DLQ.
|
|
@@ -54,6 +68,25 @@ export interface StoredEvent {
|
|
|
54
68
|
dlqAt: Date | null;
|
|
55
69
|
dlqReason: DlqReason | null;
|
|
56
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse the `KICI_TEST_EVENT_FAIL_FIRST_N` JSON payload, gated by
|
|
73
|
+
* `KICI_TEST_MODE`. Returns the parsed map or `undefined` when:
|
|
74
|
+
*
|
|
75
|
+
* - `testMode` is false (the master switch). The JSON value is ignored
|
|
76
|
+
* entirely; production deployments never see fault-injection even if
|
|
77
|
+
* the per-event env var is accidentally planted.
|
|
78
|
+
* - the JSON value is absent / empty.
|
|
79
|
+
* - the JSON value is malformed or carries a wrong-shape entry. We
|
|
80
|
+
* refuse to fall back to a partial map: a typo on one line silently
|
|
81
|
+
* skipping that test would be confusing in CI logs.
|
|
82
|
+
*
|
|
83
|
+
* The accepted shape is `{ "<eventName>": <number> }`; non-string keys
|
|
84
|
+
* and non-number values are rejected. The numeric value is the inclusive
|
|
85
|
+
* upper bound of attempts to fail (so `1` fails the first attempt and
|
|
86
|
+
* succeeds on retry; `99` exceeds `maxDispatchAttempts` and lands the
|
|
87
|
+
* row in the DLQ).
|
|
88
|
+
*/
|
|
89
|
+
export declare function parseFaultInjectionMap(testMode: boolean, raw: string | undefined): Record<string, number> | undefined;
|
|
57
90
|
/**
|
|
58
91
|
* Default event router configuration.
|
|
59
92
|
*/
|