@kici-dev/orchestrator 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +13 -1
  2. package/dist/__test-helpers__/mock-db.d.ts +2 -0
  3. package/dist/agent/dispatcher.d.ts +110 -6
  4. package/dist/agent/registry.d.ts +14 -0
  5. package/dist/app.d.ts +36 -0
  6. package/dist/approvals/apply-decision.d.ts +60 -0
  7. package/dist/approvals/approval-resolver.d.ts +66 -0
  8. package/dist/approvals/step-approval-bridge.d.ts +72 -0
  9. package/dist/approvals/team-membership-lookup.d.ts +13 -0
  10. package/dist/cache/agent-job-failed-error.d.ts +13 -0
  11. package/dist/cache/dispatch-cache-ref-tracker.d.ts +45 -0
  12. package/dist/cache/index.d.ts +2 -0
  13. package/dist/cache/user-cache.d.ts +116 -0
  14. package/dist/cancel/cancel-run.d.ts +56 -0
  15. package/dist/cli/api-client.d.ts +24 -0
  16. package/dist/cli/commands/environment.d.ts +1 -0
  17. package/dist/cli/commands/firecracker/index.d.ts +11 -0
  18. package/dist/cli/commands/firecracker/provision.d.ts +13 -0
  19. package/dist/cli/commands/firecracker/teardown.d.ts +3 -0
  20. package/dist/cli/commands/firecracker/verify.d.ts +3 -0
  21. package/dist/cli/commands/scaler.d.ts +18 -0
  22. package/dist/cli/kici-admin.d.ts +10 -1
  23. package/dist/cli/service/image-digests.d.ts +21 -0
  24. package/dist/cli.js +4148 -455
  25. package/dist/cluster/peer-client.d.ts +23 -1
  26. package/dist/cluster/peer-handler.d.ts +9 -1
  27. package/dist/cluster/peer-registry.d.ts +6 -0
  28. package/dist/config/schema.d.ts +4 -0
  29. package/dist/config.d.ts +13 -0
  30. package/dist/dashboard/handler.d.ts +92 -1
  31. package/dist/db/migrations/026_event_log_lockfile_corrupt.d.ts +11 -0
  32. package/dist/db/migrations/027_workflow_timeout.d.ts +20 -0
  33. package/dist/db/migrations/028_org_settings_user_cache.d.ts +4 -0
  34. package/dist/db/migrations/029_dispatch_queue_attempts.d.ts +16 -0
  35. package/dist/db/migrations/030_held_runs_env_set_null.d.ts +13 -0
  36. package/dist/db/migrations/031_dispatch_queue_ack_deadline.d.ts +19 -0
  37. package/dist/db/migrations/032_org_settings_dispatch_ack_timeout.d.ts +14 -0
  38. package/dist/db/migrations/033_org_settings_approval.d.ts +19 -0
  39. package/dist/db/migrations/034_held_runs_generalize.d.ts +24 -0
  40. package/dist/db/types.d.ts +89 -3
  41. package/dist/diagnostics/bundle-writer.d.ts +1 -17
  42. package/dist/diagnostics/checks/firecracker-network.d.ts +13 -0
  43. package/dist/diagnostics/checks/index.d.ts +2 -1
  44. package/dist/diagnostics/fleet-collector.d.ts +52 -0
  45. package/dist/diagnostics/fleet-constants.d.ts +8 -0
  46. package/dist/diagnostics/fleet-selection.d.ts +15 -0
  47. package/dist/diagnostics/fleet-topology.d.ts +47 -0
  48. package/dist/diagnostics/fleet-wiring.d.ts +60 -0
  49. package/dist/environments/environment-store.d.ts +14 -1
  50. package/dist/environments/held-runs.d.ts +69 -1
  51. package/dist/firecracker/host-network.d.ts +83 -0
  52. package/dist/firecracker/persist.d.ts +14 -0
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.js +380 -40
  55. package/dist/lockfile-cache.d.ts +1 -1
  56. package/dist/metrics/prometheus.d.ts +8 -0
  57. package/dist/orchestrator-core.d.ts +13 -2
  58. package/dist/pipeline/dispatch-matched-workflow.d.ts +9 -0
  59. package/dist/pipeline/inline-eval.d.ts +17 -2
  60. package/dist/pipeline/process-webhook.d.ts +19 -0
  61. package/dist/pipeline/processor.d.ts +28 -1
  62. package/dist/pipeline/test-pipeline.d.ts +10 -0
  63. package/dist/providers/github/lock-file.d.ts +1 -1
  64. package/dist/providers/internal/lock-file-fetcher.d.ts +3 -2
  65. package/dist/queue/job-queue.d.ts +53 -1
  66. package/dist/reporting/execution-tracker.d.ts +3 -1
  67. package/dist/routes/admin-environments.d.ts +1 -0
  68. package/dist/routes/fleet.d.ts +20 -0
  69. package/dist/scaler/bare-metal-backend.d.ts +1 -0
  70. package/dist/scaler/config.d.ts +4 -2
  71. package/dist/scaler/container-backend.d.ts +3 -2
  72. package/dist/scaler/disk-guard.d.ts +27 -0
  73. package/dist/scaler/firecracker-backend.d.ts +35 -0
  74. package/dist/scaler/manager.d.ts +8 -0
  75. package/dist/scaler/nftables.d.ts +29 -3
  76. package/dist/scaler/reap-orphans.d.ts +27 -0
  77. package/dist/scaler/types.d.ts +28 -1
  78. package/dist/server.js +6138 -1587
  79. package/dist/stale-detector/stale-run-detector.d.ts +18 -0
  80. package/dist/stale-detector/workflow-deadline-detector.d.ts +49 -0
  81. package/dist/standalone.js +18499 -14925
  82. package/dist/storage/filesystem.d.ts +14 -4
  83. package/dist/storage/s3.d.ts +19 -5
  84. package/dist/storage/types.d.ts +32 -5
  85. package/dist/worker/in-memory-job-queue.d.ts +40 -7
  86. package/dist/ws/agent-handler.d.ts +45 -0
  87. package/dist/ws/dashboard-env-handler.d.ts +36 -0
  88. package/dist/ws/fleet-agent-collector.d.ts +23 -0
  89. package/dist/ws/platform-client.d.ts +10 -1
  90. package/installer-image-digests.json +7 -0
  91. package/package.json +15 -11
  92. package/sbom.spdx.json +62 -57
@@ -10,7 +10,7 @@
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 PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, 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 PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, 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
15
  type PeerConnectionState = 'disconnected' | 'connecting' | 'handshaking' | 'authenticating' | 'connected';
16
16
  export interface PeerClientOptions {
@@ -78,7 +78,20 @@ export interface PeerClientOptions {
78
78
  * Platform-mediated discovery dedupes against the same client.
79
79
  */
80
80
  onAuthenticated?: (targetInstanceId: string) => void;
81
+ /**
82
+ * Callback when a peer.logs.collect.request is received from the peer. Builds
83
+ * this node's subtree bundle and streams it back via the supplied `send`
84
+ * (peer.logs.collect.chunk frames, or a peer.logs.collect.error on failure).
85
+ * If undefined, incoming collect requests are ignored.
86
+ */
87
+ onLogsCollectRequest?: PeerLogsCollectResponder;
81
88
  }
89
+ /**
90
+ * Builds a node's subtree bundle in response to a peer.logs.collect.request and
91
+ * streams it back through `send`. Shared by PeerClient (outgoing-dialed peers)
92
+ * and the peer handler (incoming-dialed peers).
93
+ */
94
+ export type PeerLogsCollectResponder = (msg: PeerLogsCollectRequest, send: (out: PeerToPeerMessage) => boolean) => Promise<void>;
82
95
  export declare class PeerClient {
83
96
  private ws;
84
97
  private _state;
@@ -91,6 +104,8 @@ export declare class PeerClient {
91
104
  private readonly ackWaiters;
92
105
  private readonly cacheWaiters;
93
106
  private readonly configReloadWaiters;
107
+ /** Correlates peer.logs.collect.request with the peer's chunked subtree response. */
108
+ private readonly logsCollectWaiters;
94
109
  private readonly url;
95
110
  private readonly joinToken?;
96
111
  private readonly credentialFile;
@@ -112,6 +127,7 @@ export declare class PeerClient {
112
127
  private readonly onAgentTokenRevoke?;
113
128
  private readonly onPeerConfigReload?;
114
129
  private readonly onAuthenticated?;
130
+ private readonly onLogsCollectRequest?;
115
131
  constructor(options: PeerClientOptions);
116
132
  /** Current connection state. */
117
133
  get state(): PeerConnectionState;
@@ -150,6 +166,12 @@ export declare class PeerClient {
150
166
  * if the peer doesn't reply within the timeout.
151
167
  */
152
168
  sendConfigReloadAndWait(msg: PeerConfigReload, timeoutMs?: number): Promise<PeerConfigReloadResponse | null>;
169
+ /**
170
+ * Send a peer.logs.collect.request to the connected peer and await its
171
+ * reassembled subtree-bundle ZIP. Rejects on timeout, an error frame, or
172
+ * peer disconnect.
173
+ */
174
+ sendLogsCollectAndWait(msg: PeerLogsCollectRequest, timeoutMs: number): Promise<Buffer>;
153
175
  /**
154
176
  * Send a cache upload request to the coordinator and wait for a response
155
177
  * with a pre-signed URL.
@@ -6,7 +6,7 @@
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 PeerScalerEvent, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, 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 PeerScalerEvent, type PeerJobCancel, type PeerLogChunk, type PeerCacheUploadRequest, type PeerCacheUploadResponse, type PeerConfigReload, type PeerConfigReloadResponse, 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
11
  import type { PeerCredentialStore } from './peer-credentials.js';
12
12
  import { type JoinTokenManager } from './join-token.js';
@@ -78,6 +78,13 @@ export interface PeerHandlerDeps {
78
78
  restartRequired?: string[];
79
79
  fieldsChanged?: string[];
80
80
  }>;
81
+ /**
82
+ * Callback when a peer.logs.collect.request arrives from an incoming-dialed
83
+ * peer. Builds this node's subtree bundle and streams it back through `send`
84
+ * (peer.logs.collect.chunk frames, or a peer.logs.collect.error on failure).
85
+ * If undefined, incoming collect requests are ignored.
86
+ */
87
+ onLogsCollectRequest?: (msg: PeerLogsCollectRequest, send: (out: PeerToPeerMessage) => boolean) => Promise<void>;
81
88
  }
82
89
  /**
83
90
  * Create a handler function for incoming peer WebSocket connections.
@@ -90,6 +97,7 @@ export declare function createPeerHandler(deps: PeerHandlerDeps): {
90
97
  sendToPeer: (targetInstanceId: string, msg: PeerToPeerMessage) => boolean;
91
98
  sendAndWaitAck: (targetInstanceId: string, msg: JobReroute, timeoutMs?: number) => Promise<boolean>;
92
99
  sendConfigReloadAndWait: (targetInstanceId: string, msg: PeerConfigReload, timeoutMs?: number) => Promise<PeerConfigReloadResponse | null>;
100
+ sendLogsCollectAndWait: (targetInstanceId: string, msg: PeerLogsCollectRequest, timeoutMs: number) => Promise<Buffer>;
93
101
  getConnectionCount: () => number;
94
102
  broadcastHeartbeat: (inventory: Omit<PeerHeartbeat, "type">) => void;
95
103
  broadcastAgentTokenRevoke: (msg: PeerAgentTokenRevoke) => void;
@@ -21,6 +21,12 @@ export interface PeerAgentInfo {
21
21
  * in the required label set.
22
22
  */
23
23
  mandatoryLabels: string[];
24
+ /**
25
+ * Name of the scaler backend that spawned this agent, or null for static
26
+ * (stateful) agents not bound to any scaler. Surfaced in diagnostics so the
27
+ * dashboard can group a peer's agents under the correct scaler row.
28
+ */
29
+ scalerName?: string | null;
24
30
  }
25
31
  export interface PeerInfo {
26
32
  instanceId: string;
@@ -107,6 +107,8 @@ export declare const sharedConfigSchema: z.ZodObject<{
107
107
  cacheTtlDays: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
108
108
  cacheBuildTimeoutMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
109
109
  cacheMaxTarballBytes: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
110
+ userCacheQuotaBytes: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
111
+ userCacheTtlMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
110
112
  eventRouter: z.ZodOptional<z.ZodObject<{
111
113
  maxChainDepth: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
112
114
  rateLimitPerWorkflowPerMinute: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
@@ -189,6 +191,8 @@ export declare const appConfigSchema: z.ZodObject<{
189
191
  cacheTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
190
192
  cacheBuildTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
191
193
  cacheMaxTarballBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
194
+ userCacheQuotaBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
195
+ userCacheTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
192
196
  eventRouterMaxChainDepth: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
193
197
  eventRouterRateLimitPerWorkflowPerMinute: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
194
198
  eventRouterEventTtlSeconds: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
package/dist/config.d.ts CHANGED
@@ -36,6 +36,7 @@ declare const configSchema: z.ZodObject<{
36
36
  queueBackpressureThreshold: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
37
37
  workerConcurrency: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
38
38
  concurrencyWaitTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
39
+ dispatchAckTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
39
40
  cacheStorageType: z.ZodOptional<z.ZodEnum<{
40
41
  s3: "s3";
41
42
  filesystem: "filesystem";
@@ -46,6 +47,7 @@ declare const configSchema: z.ZodObject<{
46
47
  cacheStorageS3Region: z.ZodOptional<z.ZodString>;
47
48
  cacheStorageS3Endpoint: z.ZodOptional<z.ZodString>;
48
49
  cacheStorageS3ExternalEndpoint: z.ZodOptional<z.ZodString>;
50
+ cacheStorageS3UploadEndpoint: z.ZodOptional<z.ZodString>;
49
51
  cacheStorageS3ForcePathStyle: z.ZodOptional<z.ZodPipe<z.ZodEnum<{
50
52
  true: "true";
51
53
  false: "false";
@@ -56,6 +58,8 @@ declare const configSchema: z.ZodObject<{
56
58
  cacheTtlDays: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
57
59
  cacheBuildTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
58
60
  cacheMaxTarballBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
61
+ userCacheQuotaBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
62
+ userCacheTtlMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
59
63
  webhookPayloadDir: z.ZodOptional<z.ZodString>;
60
64
  dataDir: z.ZodOptional<z.ZodString>;
61
65
  scalerConfigPath: z.ZodOptional<z.ZodString>;
@@ -149,12 +153,17 @@ export type AppConfig = z.infer<typeof configSchema> & {
149
153
  region?: string;
150
154
  endpoint?: string;
151
155
  externalEndpoint?: string;
156
+ uploadEndpoint?: string;
152
157
  forcePathStyle?: boolean;
153
158
  logBucket?: string;
154
159
  /** Filesystem backend: absolute base directory for cached blobs. */
155
160
  fsBasePath?: string;
156
161
  /** Filesystem backend: base URL the agent uses to reach this orchestrator. */
157
162
  fsBaseUrl?: string;
163
+ /** Per-org byte quota for the user-facing cache (UserCache). */
164
+ userCacheQuotaBytes?: number;
165
+ /** Per-entry TTL (ms) for the user-facing cache (UserCache). */
166
+ userCacheTtlMs?: number;
158
167
  };
159
168
  };
160
169
  /**
@@ -177,10 +186,13 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
177
186
  queueBackpressureThreshold: number;
178
187
  workerConcurrency: number;
179
188
  concurrencyWaitTimeoutMs: number;
189
+ dispatchAckTimeoutMs: number;
180
190
  cacheStorageS3Prefix: string;
181
191
  cacheTtlDays: number;
182
192
  cacheBuildTimeoutMs: number;
183
193
  cacheMaxTarballBytes: number;
194
+ userCacheQuotaBytes: number;
195
+ userCacheTtlMs: number;
184
196
  staleDetectorScanIntervalMs: number;
185
197
  staleDetectorThresholdMultiplier: number;
186
198
  jobHeartbeatIntervalMs: number;
@@ -234,6 +246,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
234
246
  cacheStorageS3Region?: string | undefined;
235
247
  cacheStorageS3Endpoint?: string | undefined;
236
248
  cacheStorageS3ExternalEndpoint?: string | undefined;
249
+ cacheStorageS3UploadEndpoint?: string | undefined;
237
250
  cacheStorageS3ForcePathStyle?: boolean | undefined;
238
251
  cacheStorageFsPath?: string | undefined;
239
252
  cacheStorageFsBaseUrl?: string | undefined;
@@ -13,7 +13,7 @@
13
13
  */
14
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, DashboardEventDlqListRequest, DashboardEventDlqCountRequest, DashboardEventDlqRetryRequest, DashboardEventDlqDiscardRequest, RunRerunRequest, RunCancelRequest, ManualScheduleRequest } from '@kici-dev/engine';
16
+ import type { DashboardRunDetailRequest, DashboardRunsListRequest, DashboardRunsListResponse, DashboardRunsFiltersRequest, DashboardRunsFiltersResponse, DashboardSourcesListRequest, DashboardSourcesListResponse, 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';
@@ -155,6 +155,97 @@ export declare class DashboardHandler {
155
155
  * builds a nested job/step tree, and sends the response.
156
156
  */
157
157
  handleRunDetail(msg: DashboardRunDetailRequest): Promise<void>;
158
+ /**
159
+ * Resolve every routing key owned by an org by unioning both source
160
+ * tables. The orchestrator is single-org but multi-routing-key: one org
161
+ * owns one GitHub-app source plus N generic sources, each with its own
162
+ * `routing_key`. `execution_runs` has no `org_id` column — org ownership
163
+ * is expressed purely through `routing_key → sources.customer_id` /
164
+ * `generic_webhook_sources.customer_id`, so scoping a run query to an org
165
+ * means scoping it to the union of that org's routing keys.
166
+ *
167
+ * Predicates mirror the org-context resolver in `server.ts` and
168
+ * `resolveOrgForRoutingKey` above:
169
+ * - `sources`: `customer_id = orgId` excluding the `__default__` sentinel.
170
+ * - `generic_webhook_sources`: `customer_id = orgId` and not soft-deleted
171
+ * (`deleted_at is null`).
172
+ */
173
+ private resolveOrgRoutingKeys;
174
+ /**
175
+ * Resolve a per-routing-key source identity map for a set of routing keys,
176
+ * unioning both source tables. Uses the identical name/subtype/provider
177
+ * derivation as `handleSourcesList` so a run summary's `source` block
178
+ * matches what the sources page shows for the same routing key. Routing
179
+ * keys with no live source row are simply absent from the map (the caller
180
+ * falls back to the run's own provider).
181
+ */
182
+ private resolveSourceIdentities;
183
+ /**
184
+ * Look up the page's jobs in one query to derive per-run `jobCount` plus the
185
+ * compile-job markers (`hadCompileJob` / `compileJobId`). The compile job is
186
+ * the synthetic `__build__*` job KiCI inserts for the compile phase — the
187
+ * same definition the Platform run-list route uses
188
+ * (`job_name LIKE '__build__%'`). Returns a per-run aggregate keyed by
189
+ * `run_id`; runs with no job rows are absent (the caller omits the fields).
190
+ */
191
+ private resolveRunJobAggregates;
192
+ /**
193
+ * Handle a dashboard.runs.list request.
194
+ *
195
+ * Returns a page of run summaries from `execution_runs`, scoped to ALL
196
+ * routing keys owned by this orchestrator's bound org (`this.orgId`). The
197
+ * orchestrator is single-org but multi-routing-key, and the bound
198
+ * `this.routingKey` is just ONE of the org's keys (resolved
199
+ * non-deterministically at startup), so filtering on it alone would drop
200
+ * runs that arrived under a sibling routing key. We resolve the org's full
201
+ * routing-key set from `sources` + `generic_webhook_sources` and filter on
202
+ * the union. Attribution still uses the handler-bound `orgId` /
203
+ * `routingKey` directly — the same inline-ctx pattern as the org-scoped
204
+ * event-log / access-log handlers.
205
+ *
206
+ * Used by the operator console (`support-read` break-glass) and the
207
+ * dashboard's run-list view. The access_log row carries the wire actor
208
+ * (including the platform_operator reason) so the customer can audit any
209
+ * operator read.
210
+ */
211
+ handleRunsList(msg: DashboardRunsListRequest): Promise<DashboardRunsListResponse>;
212
+ /**
213
+ * Handle a dashboard.runs.filters request.
214
+ *
215
+ * Returns the distinct filter-option values the customer runs page renders
216
+ * in its filter controls — statuses / workflows / branches / repositories /
217
+ * triggerTypes / sources — scoped to ALL routing keys owned by this
218
+ * orchestrator's bound org (same scoping as `handleRunsList`). Distinct
219
+ * values are derived in-memory from a single column projection over
220
+ * `execution_runs`; `sources` reuses `resolveSourceIdentities` so the list
221
+ * matches what the sources page shows for the same routing key.
222
+ *
223
+ * `triggerTypes` is populated from `execution_runs.provider` — the
224
+ * orchestrator has no dedicated trigger-event column, so `provider`
225
+ * (`github` / `generic` / `internal`) is the closest available
226
+ * discriminator the customer filter can offer.
227
+ *
228
+ * Attribution uses the handler-bound `orgId` / `routingKey` directly — the
229
+ * same inline-ctx pattern as `handleRunsList`. The access_log row carries
230
+ * the wire actor (including the platform_operator reason) so the customer
231
+ * can audit any operator read.
232
+ */
233
+ handleRunsFilters(msg: DashboardRunsFiltersRequest): Promise<DashboardRunsFiltersResponse>;
234
+ /**
235
+ * Handle a dashboard.sources.list request.
236
+ *
237
+ * Returns the org's webhook source summaries by unioning both source
238
+ * tables (GitHub-app `sources` + live `generic_webhook_sources`), scoped
239
+ * to this orchestrator's bound org (`this.orgId`). Source counts are tiny
240
+ * so the page is returned unpaginated (`msg.limit` is ignored). Secret-
241
+ * bearing columns (`config`, `git_config`, `verification_config`) are
242
+ * never projected — `git_config` is read only to derive the subtype.
243
+ *
244
+ * Attribution uses the handler-bound `orgId` / `routingKey` directly —
245
+ * the same inline-ctx pattern as `handleRunsList`. The access_log row
246
+ * carries the wire actor so the customer can audit any operator read.
247
+ */
248
+ handleSourcesList(msg: DashboardSourcesListRequest): Promise<DashboardSourcesListResponse>;
158
249
  /**
159
250
  * Handle a dashboard.step.logs request.
160
251
  * Looks up the step's log_path and reads content from LogStorage.
@@ -0,0 +1,11 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Extend the event_log.status CHECK constraint with 'lockfile_corrupt' so the
4
+ * orchestrator can log a delivery whose lock file was present but unparseable.
5
+ * Orchestrator-only: the Platform event_log uses a separate, narrower status set.
6
+ *
7
+ * Idempotent: the DROP ... IF EXISTS / re-ADD pair re-runs cleanly.
8
+ */
9
+ export declare function up(db: Kysely<unknown>): Promise<void>;
10
+ export declare function down(db: Kysely<unknown>): Promise<void>;
11
+ //# sourceMappingURL=026_event_log_lockfile_corrupt.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `workflow_timeout_ms integer` to `execution_runs`.
4
+ *
5
+ * Persists the workflow-level wall-clock timeout (in milliseconds) read from
6
+ * the lock workflow at run creation. NULL means no workflow-level cap is
7
+ * configured. The WorkflowDeadlineDetector reads this column to find runs
8
+ * whose `started_at + workflow_timeout_ms` has passed and cancels them with
9
+ * the distinct workflow-timeout reason.
10
+ *
11
+ * Typed INTEGER to match the existing `*_ms` columns (`duration_ms`,
12
+ * `concurrency_timeout_ms`), which keeps the pg read representation a plain
13
+ * number rather than the BIGINT-as-string shape. INTEGER caps the timeout at
14
+ * ~24.8 days, far beyond any sane workflow wall-clock budget.
15
+ *
16
+ * Idempotent: re-running on a DB that already has the column is a no-op.
17
+ */
18
+ export declare function up(db: Kysely<unknown>): Promise<void>;
19
+ export declare function down(db: Kysely<unknown>): Promise<void>;
20
+ //# sourceMappingURL=027_workflow_timeout.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { type Kysely } from 'kysely';
2
+ export declare function up(db: Kysely<unknown>): Promise<void>;
3
+ export declare function down(db: Kysely<unknown>): Promise<void>;
4
+ //# sourceMappingURL=028_org_settings_user_cache.d.ts.map
@@ -0,0 +1,16 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `dispatch_queue.dispatch_attempts INT NOT NULL DEFAULT 0`.
4
+ *
5
+ * Counts how many times a job has been returned to `pending` for
6
+ * re-dispatch after a failed delivery attempt (agent sent job.reject, or
7
+ * a scaler-managed agent disconnected before the job started). The
8
+ * dispatcher fails the job permanently once the counter reaches
9
+ * MAX_DISPATCH_ATTEMPTS, bounding requeue loops; `expires_at` remains the
10
+ * time-based backstop.
11
+ *
12
+ * Idempotent: re-running on a DB that already has the column is a no-op.
13
+ */
14
+ export declare function up(db: Kysely<unknown>): Promise<void>;
15
+ export declare function down(db: Kysely<unknown>): Promise<void>;
16
+ //# sourceMappingURL=029_dispatch_queue_attempts.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * held_runs.environment_id becomes nullable with ON DELETE SET NULL so
4
+ * terminal held-run history survives environment deletion (a null
5
+ * environment_id means the environment was since deleted). Pending held
6
+ * runs still block deletion — enforced in EnvironmentStore.delete.
7
+ *
8
+ * Idempotent: dropping the NOT NULL and the constraint are both no-ops on a
9
+ * re-run, and the constraint is re-created with the SET NULL action.
10
+ */
11
+ export declare function up(db: Kysely<unknown>): Promise<void>;
12
+ export declare function down(db: Kysely<unknown>): Promise<void>;
13
+ //# sourceMappingURL=030_held_runs_env_set_null.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `dispatch_queue.ack_deadline TIMESTAMPTZ` and
4
+ * `dispatch_queue.ack_agent_id TEXT` persisting the per-dispatch
5
+ * acknowledgment deadline for HA-safe lost-dispatch detection.
6
+ *
7
+ * The dispatcher stamps both when a job.dispatch is sent and clears them
8
+ * when the agent answers (job.ack / job.reject / job.status running). A
9
+ * `dispatched` row past its deadline is requeued — by the owning coord's
10
+ * in-memory timer, or by the leader-gated sweep
11
+ * (`Dispatcher.sweepExpiredAckDeadlines`) when the owning coord crashed.
12
+ * On coord boot, `Dispatcher.recoverState()` re-arms timers from the
13
+ * persisted deadlines.
14
+ *
15
+ * Idempotent: re-running on a DB that already has either column is a no-op.
16
+ */
17
+ export declare function up(db: Kysely<unknown>): Promise<void>;
18
+ export declare function down(db: Kysely<unknown>): Promise<void>;
19
+ //# sourceMappingURL=031_dispatch_queue_ack_deadline.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add `org_settings.dispatch_ack_timeout_ms BIGINT` (nullable).
4
+ *
5
+ * Per-org override of the dispatch-acknowledgment deadline. NULL falls
6
+ * back to the cluster-wide default (`KICI_DISPATCH_ACK_TIMEOUT_MS`,
7
+ * default 10s). Operators raise it on high-latency networks via
8
+ * `kici-admin org-settings`.
9
+ *
10
+ * Idempotent: a re-run on a DB that already has the column is a no-op.
11
+ */
12
+ export declare function up(db: Kysely<unknown>): Promise<void>;
13
+ export declare function down(db: Kysely<unknown>): Promise<void>;
14
+ //# sourceMappingURL=032_org_settings_dispatch_ack_timeout.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Add the two approval-policy columns to `org_settings`:
4
+ *
5
+ * - `approval_expiry_seconds INTEGER NOT NULL DEFAULT 86400` — how long a held
6
+ * approval element waits before it expires (and its run/job/step is
7
+ * rejected). One day by default. An SDK `requireApproval` `timeout` overrides
8
+ * this per element; otherwise this per-org value applies.
9
+ * - `allow_self_approval BOOLEAN NOT NULL DEFAULT true` — whether the user who
10
+ * triggered a run may also approve its held elements. Operators turn it off
11
+ * to enforce four-eyes review.
12
+ *
13
+ * Both are cluster-configurable per org via `kici-admin org-settings approval`
14
+ * and the orchestrator admin route. Idempotent: a re-run on a DB that already
15
+ * has the columns is a no-op (each column is guarded independently).
16
+ */
17
+ export declare function up(db: Kysely<unknown>): Promise<void>;
18
+ export declare function down(db: Kysely<unknown>): Promise<void>;
19
+ //# sourceMappingURL=033_org_settings_approval.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { type Kysely } from 'kysely';
2
+ /**
3
+ * Generalize `held_runs` from an environment-only hold into the unified
4
+ * "held element" model that backs per-element approvals, and add the
5
+ * `held_run_approvals` table that records each approver's decision.
6
+ *
7
+ * New `held_runs` columns (all idempotent, column-exists guarded):
8
+ * - `hold_scope text NOT NULL DEFAULT 'job'` — 'workflow' | 'job' | 'step'
9
+ * (engine `HoldScope`). Existing rows held a single job, so they default to
10
+ * 'job'.
11
+ * - `step_index integer` — nullable; set only for step-scoped holds.
12
+ * - `trigger_source text NOT NULL DEFAULT 'environment'` — 'environment' |
13
+ * 'explicit' (engine `TriggerSource`). Existing holds came from environment
14
+ * protection, so they default to 'environment'.
15
+ * - `approval_requirement jsonb` — the normalized `ApprovalRequirement`
16
+ * (clauses + expiresAt + reason) the hold must satisfy. Nullable for legacy
17
+ * rows that predate the approval model.
18
+ *
19
+ * New `held_run_approvals` table: one row per approver decision, FK to
20
+ * `held_runs.id` (uuid) with ON DELETE CASCADE.
21
+ */
22
+ export declare function up(db: Kysely<unknown>): Promise<void>;
23
+ export declare function down(db: Kysely<unknown>): Promise<void>;
24
+ //# sourceMappingURL=034_held_runs_generalize.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely';
2
- import type { InitFailure } from '@kici-dev/engine';
2
+ import type { ApprovalRequirement, ApproverClause, InitFailure } from '@kici-dev/engine';
3
3
  /**
4
4
  * PostgreSQL-only database types.
5
5
  * Column names use snake_case matching the actual database column names.
@@ -19,6 +19,7 @@ export interface Database {
19
19
  environment_variables: EnvironmentVariablesTable;
20
20
  environment_source_overrides: EnvironmentSourceOverridesTable;
21
21
  held_runs: HeldRunsTable;
22
+ held_run_approvals: HeldRunApprovalsTable;
22
23
  admin_tokens: AdminTokenTable;
23
24
  agent_tokens: AgentTokenTable;
24
25
  config_versions: ConfigVersionTable;
@@ -158,6 +159,17 @@ export interface DispatchQueueTable {
158
159
  * real provisioning cause; cleared on dispatch. NULL when none recorded.
159
160
  */
160
161
  last_provisioning_error: ColumnType<string | null, string | null | undefined, string | null>;
162
+ /** Times this job was returned to pending for re-dispatch (job.reject / pre-start agent loss). */
163
+ dispatch_attempts: Generated<number>;
164
+ /**
165
+ * Deadline by which the dispatched job's agent must answer the
166
+ * job.dispatch (job.ack / job.reject / job.status running). Stamped when
167
+ * the dispatch is sent, cleared on any answer; `dispatched` rows past the
168
+ * deadline are requeued by the owning coord's timer or the leader sweep.
169
+ */
170
+ ack_deadline: ColumnType<Date | null, Date | null | undefined, Date | null>;
171
+ /** Agent the dispatch was sent to (for ack-timeout disconnect + logging). */
172
+ ack_agent_id: ColumnType<string | null, string | null | undefined, string | null>;
161
173
  }
162
174
  /**
163
175
  * Deduplication cache table
@@ -266,6 +278,12 @@ export interface ExecutionRunTable {
266
278
  * provider context, agent spawn). NULL for normal runs.
267
279
  */
268
280
  init_failure: ColumnType<InitFailure | null, unknown, unknown>;
281
+ /**
282
+ * Whole-run wall-clock timeout in ms from the workflow lock; null when
283
+ * unset. Read by the WorkflowDeadlineDetector. INTEGER in Postgres, matching
284
+ * the other `*_ms` columns, so pg returns a plain number.
285
+ */
286
+ workflow_timeout_ms: ColumnType<number | null, number | null | undefined, number | null>;
269
287
  /** When this record was created */
270
288
  created_at: Generated<Date>;
271
289
  /**
@@ -595,8 +613,8 @@ export interface HeldRunsTable {
595
613
  run_id: string;
596
614
  /** Job ID within the run */
597
615
  job_id: string;
598
- /** Environment ID (FK to environments.id) */
599
- environment_id: string;
616
+ /** Environment ID (FK to environments.id); null once the environment is deleted */
617
+ environment_id: string | null;
600
618
  /** Hold type: 'approval' | 'wait_timer' | 'concurrency' */
601
619
  hold_type: string;
602
620
  /** Hold status: 'pending' | 'approved' | 'rejected' | 'expired' | 'released' */
@@ -613,10 +631,49 @@ export interface HeldRunsTable {
613
631
  expires_at: Date;
614
632
  /** When this hold was resolved */
615
633
  resolved_at: Date | null;
634
+ /**
635
+ * Hold granularity: 'workflow' | 'job' | 'step' (engine `HoldScope`).
636
+ * Existing environment holds are job-scoped, hence the 'job' default.
637
+ */
638
+ hold_scope: Generated<string>;
639
+ /** Step index within the job for step-scoped holds; null otherwise. */
640
+ step_index: number | null;
641
+ /**
642
+ * What created the hold: 'environment' (mandatory env policy) | 'explicit'
643
+ * (SDK `requireApproval`). Engine `TriggerSource`.
644
+ */
645
+ trigger_source: Generated<string>;
646
+ /**
647
+ * Normalized `ApprovalRequirement` (clauses + expiresAt + reason) the hold
648
+ * must satisfy. Null for legacy rows that predate the approval model.
649
+ */
650
+ approval_requirement: ColumnType<ApprovalRequirement | null, ApprovalRequirement | string | null | undefined, ApprovalRequirement | string | null>;
616
651
  }
617
652
  export type HeldRun = Selectable<HeldRunsTable>;
618
653
  export type NewHeldRun = Insertable<HeldRunsTable>;
619
654
  export type HeldRunUpdate = Updateable<HeldRunsTable>;
655
+ /**
656
+ * One approver's recorded decision on a held element. Multiple rows accumulate
657
+ * until the hold's `ApprovalRequirement` clauses are all satisfied (approve) or
658
+ * any single reject lands.
659
+ */
660
+ export interface HeldRunApprovalsTable {
661
+ /** UUID primary key */
662
+ id: Generated<string>;
663
+ /** FK to held_runs.id (ON DELETE CASCADE) */
664
+ held_run_id: string;
665
+ /** The approver's user id (Keycloak sub) */
666
+ approver_user_id: string;
667
+ /** 'approve' | 'reject' (engine `ApprovalDecision`) */
668
+ decision: string;
669
+ /** Which requirement clauses this decision satisfied (for attribution). */
670
+ clauses_satisfied: ColumnType<ApproverClause[] | null, ApproverClause[] | string | null | undefined, ApproverClause[] | string | null>;
671
+ /** When the decision was recorded */
672
+ created_at: Generated<Date>;
673
+ }
674
+ export type HeldRunApproval = Selectable<HeldRunApprovalsTable>;
675
+ export type NewHeldRunApproval = Insertable<HeldRunApprovalsTable>;
676
+ export type HeldRunApprovalUpdate = Updateable<HeldRunApprovalsTable>;
620
677
  /**
621
678
  * Secret audit log table
622
679
  * Immutable log of secret access and denial events.
@@ -1157,6 +1214,35 @@ export interface OrgSettingsTable {
1157
1214
  * treats missing keys as `true`.
1158
1215
  */
1159
1216
  dashboard_write_policy: ColumnType<Record<string, boolean>, Record<string, boolean> | string | undefined, Record<string, boolean> | string>;
1217
+ /**
1218
+ * Per-org byte quota for the user-facing cache (UserCache). NULL = use the
1219
+ * cluster-wide default (`KICI_USER_CACHE_QUOTA_BYTES`, 5 GiB). Postgres
1220
+ * BIGINT — pg returns a string on select; accept a number on insert/update.
1221
+ */
1222
+ user_cache_quota_bytes: ColumnType<string | null, number | null | undefined, number | null>;
1223
+ /**
1224
+ * Per-entry TTL (ms) for the user-facing cache (UserCache). NULL = use the
1225
+ * cluster-wide default (`KICI_USER_CACHE_TTL_MS`, 7 days). Postgres BIGINT —
1226
+ * pg returns a string on select; accept a number on insert/update.
1227
+ */
1228
+ user_cache_ttl_ms: ColumnType<string | null, number | null | undefined, number | null>;
1229
+ /**
1230
+ * Per-org dispatch-acknowledgment deadline (ms); null = cluster default
1231
+ * (config.dispatchAckTimeoutMs / KICI_DISPATCH_ACK_TIMEOUT_MS). Postgres
1232
+ * BIGINT — pg returns a string on select; accept a number on insert/update.
1233
+ */
1234
+ dispatch_ack_timeout_ms: ColumnType<string | null, number | null | undefined, number | null>;
1235
+ /**
1236
+ * Per-org expiry (seconds) for a held approval element before it is rejected
1237
+ * and its run/job/step fails. NOT NULL, default 86400 (one day). An SDK
1238
+ * `requireApproval` `timeout` overrides this per element.
1239
+ */
1240
+ approval_expiry_seconds: ColumnType<number, number | undefined, number>;
1241
+ /**
1242
+ * Whether the user who triggered a run may also approve its held elements.
1243
+ * NOT NULL, default true. Operators turn it off to enforce four-eyes review.
1244
+ */
1245
+ allow_self_approval: ColumnType<boolean, boolean | undefined, boolean>;
1160
1246
  /** When this setting was created */
1161
1247
  created_at: Generated<Date>;
1162
1248
  /** When this setting was last updated */
@@ -6,8 +6,8 @@
6
6
  * This is the primary support tool -- operators run debug-bundle and share
7
7
  * the ZIP for troubleshooting.
8
8
  */
9
- import archiver from 'archiver';
10
9
  import type { DiagnosticDeps } from './types.js';
10
+ export { redactConfig, addLogsToArchive } from '@kici-dev/shared';
11
11
  export interface BundleOptions {
12
12
  /** Where to write the ZIP. */
13
13
  outputPath: string;
@@ -26,11 +26,6 @@ export interface BundleOptions {
26
26
  /** URL for recent runs endpoint. */
27
27
  recentRunsUrl?: string;
28
28
  }
29
- /**
30
- * Redact config values using allowlist approach.
31
- * Only known-safe fields are preserved; everything else becomes "****".
32
- */
33
- export declare function redactConfig(obj: unknown, parentKey?: string): unknown;
34
29
  /**
35
30
  * Create a debug bundle ZIP file.
36
31
  *
@@ -43,15 +38,4 @@ export declare function redactConfig(obj: unknown, parentKey?: string): unknown;
43
38
  * - logs/summary.json: log statistics
44
39
  */
45
40
  export declare function createDebugBundle(options: BundleOptions): Promise<string>;
46
- /**
47
- * Add log files from logDir to the archive, respecting MAX_LOG_BYTES cap
48
- * and the logWindow time filter. Matches any `*.log` file in the directory,
49
- * so the per-instance filename pattern produced by
50
- * `buildLogFilename()` is picked up without additional configuration.
51
- *
52
- * Exported for reuse by the `kici-admin debug-bundle` CLI command, which
53
- * runs outside the orchestrator process but still needs to include the
54
- * same log files in its locally-assembled bundle.
55
- */
56
- export declare function addLogsToArchive(archive: archiver.Archiver, logDir: string, logWindowHours: number): Promise<void>;
57
41
  //# sourceMappingURL=bundle-writer.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Firecracker host-bridge diagnostic. Emits one row per configured Firecracker
3
+ * scaler backend: the bridge interface + its gateway addr + its nft table must
4
+ * all be present, else the scaler cannot spawn microVMs.
5
+ */
6
+ import type { DiagnosticDeps, DiagnosticResult } from '../types.js';
7
+ import { type BridgeHealth, type FirecrackerBridgeConfig } from '../../firecracker/host-network.js';
8
+ interface CheckOpts {
9
+ verify?: (cfg: FirecrackerBridgeConfig) => Promise<BridgeHealth>;
10
+ }
11
+ export declare function checkFirecrackerNetwork(deps: DiagnosticDeps, opts?: CheckOpts): Promise<DiagnosticResult[]>;
12
+ export {};
13
+ //# sourceMappingURL=firecracker-network.d.ts.map