@kici-dev/orchestrator 0.1.13 → 0.1.15

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 (65) 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 +11 -0
  6. package/dist/cache/agent-job-failed-error.d.ts +13 -0
  7. package/dist/cache/dispatch-cache-ref-tracker.d.ts +45 -0
  8. package/dist/cache/index.d.ts +2 -0
  9. package/dist/cache/user-cache.d.ts +116 -0
  10. package/dist/cancel/cancel-run.d.ts +56 -0
  11. package/dist/cli/commands/environment.d.ts +1 -0
  12. package/dist/cli/service/index.d.ts +1 -1
  13. package/dist/cli/service/instance/manifest.d.ts +9 -0
  14. package/dist/cli.js +601 -113
  15. package/dist/cluster/peer-registry.d.ts +6 -0
  16. package/dist/config/schema.d.ts +4 -0
  17. package/dist/config.d.ts +13 -0
  18. package/dist/dashboard/handler.d.ts +92 -1
  19. package/dist/db/migrations/025_init_failure.d.ts +16 -0
  20. package/dist/db/migrations/026_event_log_lockfile_corrupt.d.ts +11 -0
  21. package/dist/db/migrations/027_workflow_timeout.d.ts +20 -0
  22. package/dist/db/migrations/028_org_settings_user_cache.d.ts +4 -0
  23. package/dist/db/migrations/029_dispatch_queue_attempts.d.ts +16 -0
  24. package/dist/db/migrations/030_held_runs_env_set_null.d.ts +13 -0
  25. package/dist/db/migrations/031_dispatch_queue_ack_deadline.d.ts +19 -0
  26. package/dist/db/migrations/032_org_settings_dispatch_ack_timeout.d.ts +14 -0
  27. package/dist/db/types.d.ts +52 -2
  28. package/dist/diagnostics/checks/index.d.ts +2 -1
  29. package/dist/diagnostics/checks/scaler.d.ts +13 -0
  30. package/dist/diagnostics/types.d.ts +5 -2
  31. package/dist/environments/environment-store.d.ts +14 -1
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +362 -40
  34. package/dist/lockfile-cache.d.ts +1 -1
  35. package/dist/metrics/prometheus.d.ts +8 -0
  36. package/dist/orchestrator-core.d.ts +4 -1
  37. package/dist/pipeline/dispatch-matched-workflow.d.ts +9 -0
  38. package/dist/pipeline/inline-eval.d.ts +17 -2
  39. package/dist/pipeline/process-webhook.d.ts +19 -0
  40. package/dist/pipeline/processor.d.ts +6 -1
  41. package/dist/pipeline/test-pipeline.d.ts +10 -0
  42. package/dist/providers/github/lock-file.d.ts +1 -1
  43. package/dist/providers/internal/lock-file-fetcher.d.ts +3 -2
  44. package/dist/queue/job-queue.d.ts +53 -1
  45. package/dist/reporting/execution-tracker.d.ts +80 -7
  46. package/dist/routes/admin-environments.d.ts +1 -0
  47. package/dist/scaler/bare-metal-backend.d.ts +1 -0
  48. package/dist/scaler/container-backend.d.ts +3 -2
  49. package/dist/scaler/failure-tracker.d.ts +46 -0
  50. package/dist/scaler/firecracker-backend.d.ts +17 -0
  51. package/dist/scaler/manager.d.ts +10 -0
  52. package/dist/scaler/nftables.d.ts +25 -3
  53. package/dist/scaler/types.d.ts +26 -1
  54. package/dist/server.js +4421 -1459
  55. package/dist/stale-detector/workflow-deadline-detector.d.ts +49 -0
  56. package/dist/standalone.js +17003 -14561
  57. package/dist/storage/filesystem.d.ts +12 -3
  58. package/dist/storage/s3.d.ts +17 -4
  59. package/dist/storage/types.d.ts +25 -5
  60. package/dist/worker/in-memory-job-queue.d.ts +40 -7
  61. package/dist/ws/agent-handler.d.ts +13 -0
  62. package/dist/ws/dashboard-env-handler.d.ts +1 -0
  63. package/dist/ws/platform-client.d.ts +10 -1
  64. package/package.json +13 -10
  65. package/sbom.spdx.json +91 -36
package/README.md CHANGED
@@ -1 +1,13 @@
1
- TBD
1
+ # @kici-dev/orchestrator
2
+
3
+ Customer-deployable orchestrator for the KiCI CI/CD stack. Receives webhook events (directly or via the hosted relay), matches triggers against the lock file, and dispatches jobs to connected agents — including auto-scaled ephemeral agents (containers, bare-metal, micro-VMs).
4
+
5
+ You normally don't install this package directly: deploy the orchestrator with the [`kici-admin`](https://www.npmjs.com/package/kici-admin) CLI (`kici-admin orchestrator install`) or run the published container image `quay.io/kici-dev/kici-orchestrator`.
6
+
7
+ Part of [KiCI](https://kici.dev) — CI/CD workflows as TypeScript code: author them with full language power, dry-run them locally, and run them on your own infrastructure.
8
+
9
+ ## Links
10
+
11
+ - Documentation: <https://docs.kici.dev/operator/orchestrator/getting-started/>
12
+ - Source: <https://github.com/kici-dev/kici-public/tree/main/packages/orchestrator>
13
+ - License: AGPL-3.0-only
@@ -28,6 +28,8 @@ export interface MockDbOptions {
28
28
  insertReturning?: unknown;
29
29
  /** Row returned by update chains ending in .returningAll().executeTakeFirstOrThrow() or executeTakeFirst(). Default: undefined */
30
30
  updatedRow?: unknown;
31
+ /** Row returned by update chains ending in .returning(...).executeTakeFirst(). Default: undefined */
32
+ updateReturning?: unknown;
31
33
  /** Result for update .execute() calls. Default: { numUpdatedRows: 0n } */
32
34
  updateResult?: {
33
35
  numUpdatedRows: bigint;
@@ -71,10 +71,40 @@ export declare class Dispatcher {
71
71
  private graceCleanupTimer;
72
72
  /** Max reconnection delay from agent config, used to derive grace period. */
73
73
  private readonly maxReconnectDelayMs;
74
- /** Callback when a recovery timer expires and the job is permanently failed. */
75
- private readonly onRecoveryTimeout?;
74
+ /**
75
+ * Callback fired when a job reaches a permanent dispatch-side failure:
76
+ * recovery window expired, re-dispatch attempts exhausted, or a
77
+ * scaler-managed agent was destroyed mid-execution. The receiver marks
78
+ * the execution-tracker job failed with the given reason.
79
+ */
80
+ private readonly onJobFailedPermanently?;
76
81
  /** Callback when a job enters recovery (starts timer). */
77
82
  private readonly onRecoveryStarted?;
83
+ /** Fallback ack deadline when no getAckTimeoutMs dep is wired (tests). */
84
+ private static readonly DEFAULT_ACK_TIMEOUT_MS;
85
+ /**
86
+ * Pending dispatch acknowledgments: armed when a job.dispatch is sent,
87
+ * resolved by job.ack / job.reject / job.status running / disconnect
88
+ * triage. On expiry the dispatch is treated as lost: requeue + disconnect.
89
+ */
90
+ private readonly pendingAcks;
91
+ /**
92
+ * Acks that arrived before their pending-ack entry was armed (the agent
93
+ * answered faster than the orchestrator could resolve the timeout + stamp
94
+ * the deadline). Keyed by jobId -> the agent that acked. Consumed by
95
+ * `armAckDeadline`, which then skips arming.
96
+ */
97
+ private readonly earlyAcks;
98
+ /** Resolve the per-job ack deadline (per-org override / cluster default). */
99
+ private readonly getAckTimeoutMs;
100
+ /** Cancel + disconnect an agent whose dispatch ack deadline expired. */
101
+ private readonly onAckTimeout?;
102
+ /**
103
+ * Jobs that have reached agent-side execution (a `job.status: running`
104
+ * arrived). Disconnect triage treats never-started jobs as safely
105
+ * re-dispatchable; started jobs on destroyed agents fail fast.
106
+ */
107
+ private readonly startedJobs;
78
108
  /**
79
109
  * Per-job recovery timers for jobs in 'recovering' state.
80
110
  * When an agent disconnects with in-flight jobs, each job gets a timer.
@@ -94,10 +124,14 @@ export declare class Dispatcher {
94
124
  onNoMatchingAgent?: (labels: string[], jobId: string, runId: string, excludeLabels: string[], resources?: ResourceRequest) => Promise<ScaleResult>;
95
125
  /** Max reconnection delay from agent config (default 60s). Used to derive grace period. */
96
126
  maxReconnectDelayMs?: number;
97
- /** Callback when a recovery timer expires and the job is permanently failed. */
98
- onRecoveryTimeout?: (agentId: string, jobId: string, runId: string) => void;
127
+ /** Callback fired when a job is permanently failed before/outside agent execution. */
128
+ onJobFailedPermanently?: (agentId: string, jobId: string, runId: string, reason: string) => void;
99
129
  /** Callback when a job enters recovery (starts timer). */
100
130
  onRecoveryStarted?: (agentId: string, jobId: string) => void;
131
+ /** Resolve the per-job dispatch-ack deadline (ms). Defaults to 10s. */
132
+ getAckTimeoutMs?: (job: QueuedJob) => Promise<number>;
133
+ /** Cancel + disconnect an agent whose dispatch ack deadline expired. */
134
+ onAckTimeout?: (agentId: string, jobId: string, runId: string) => void;
101
135
  });
102
136
  /**
103
137
  * Dispatch a job to a matching agent, or queue it if none available.
@@ -132,6 +166,54 @@ export declare class Dispatcher {
132
166
  * calling onDispatch for each.
133
167
  */
134
168
  onAgentAvailable(agentId: string): Promise<void>;
169
+ /** Record that a job began executing on its agent. */
170
+ markJobStarted(jobId: string): void;
171
+ /**
172
+ * Arm the ack deadline for a just-sent dispatch (timer + persisted row).
173
+ *
174
+ * Resolving the timeout and persisting the deadline both touch the DB, so
175
+ * a fast agent's job.ack can arrive before this method registers its
176
+ * in-memory entry. `earlyAcks` records such an ack so this method skips
177
+ * arming entirely instead of starting a timer that has nothing to resolve
178
+ * it (which would expire a dispatch the agent actually accepted).
179
+ */
180
+ private armAckDeadline;
181
+ /**
182
+ * Consume a recorded early ack for `jobId` from `agentId`. Returns true if
183
+ * one was pending (and removes it).
184
+ */
185
+ private consumeEarlyAck;
186
+ /** Clear a pending ack (answered or otherwise settled). Idempotent. */
187
+ private resolvePendingAck;
188
+ /** Handle an explicit dispatch acknowledgment (`job.ack`) from an agent. */
189
+ onJobAcked(agentId: string, jobId: string): void;
190
+ /**
191
+ * The ack deadline expired: the dispatch is lost. Untrack the job, then
192
+ * remove the unresponsive agent from the registry BEFORE requeueing, so the
193
+ * redispatch cannot land the job back on the very agent we are tearing down.
194
+ * The job is already untracked when the WS close fires, so the disconnect
195
+ * triage finds nothing to double-handle.
196
+ */
197
+ private handleAckExpiry;
198
+ /**
199
+ * Handle an explicit dispatch rejection (`job.reject`) from an agent.
200
+ * Undoes the dispatch accounting and requeues the job for another agent.
201
+ */
202
+ onJobRejected(agentId: string, jobId: string, reason: string): Promise<void>;
203
+ /** Remove a job from all per-agent in-memory tracking. */
204
+ private untrackJob;
205
+ /**
206
+ * Requeue a dispatched job for re-delivery, or fail it permanently when
207
+ * its attempt budget is exhausted. Returns the outcome so disconnect
208
+ * triage can surface failed job IDs to the caller.
209
+ */
210
+ private requeueOrFail;
211
+ /**
212
+ * Try to deliver a requeued pending job: prefer an idle matching agent
213
+ * (atomic claim via dispatchBoundJob), otherwise consult the scaler so a
214
+ * fresh agent is spawned bound to this job.
215
+ */
216
+ private redispatch;
135
217
  /**
136
218
  * Handle agent disconnect. Starts per-job recovery timers instead of
137
219
  * immediately failing jobs, giving the agent a grace period to reconnect.
@@ -141,6 +223,19 @@ export declare class Dispatcher {
141
223
  * asynchronously when recovery timers expire).
142
224
  */
143
225
  onAgentDisconnect(agentId: string): Promise<string[]>;
226
+ /** Drop grace-window entries owned by an agent. */
227
+ private cleanupGraceEntriesForAgent;
228
+ /**
229
+ * Scaler-managed agent disconnected: requeue never-started jobs, fail
230
+ * started ones. Returns the permanently failed job IDs.
231
+ */
232
+ private triageScalerManagedDisconnect;
233
+ /**
234
+ * Static agent disconnected: start per-job recovery timers (the agent
235
+ * may reconnect and reclaim). Always returns [] — failures happen
236
+ * asynchronously when recovery timers expire.
237
+ */
238
+ private startRecoveryForDisconnect;
144
239
  /**
145
240
  * Called when an agent completes a job (success or failure).
146
241
  * Decrements active jobs and moves the job to the grace window
@@ -197,7 +292,9 @@ export declare class Dispatcher {
197
292
  getRecoveringJobsForAgent(agentId: string): string[];
198
293
  /**
199
294
  * Restore a reconciled job into the dispatcher's in-memory tracking.
200
- * Called when an agent reconnects and claims a recovering job.
295
+ * Called when an agent reconnects and claims a recovering job. A
296
+ * reconnecting agent reporting the job in-flight means it had started,
297
+ * so mark it started for disconnect triage.
201
298
  */
202
299
  restoreJobForAgent(agentId: string, jobId: string): void;
203
300
  /**
@@ -221,10 +318,17 @@ export declare class Dispatcher {
221
318
  * fast-failed by the sweep on its first run.
222
319
  */
223
320
  recoverState(): Promise<void>;
321
+ /**
322
+ * Leader-gated sweep: requeue every `dispatched` row whose ack deadline
323
+ * passed while no coord was watching (owning coord crashed before its
324
+ * in-memory timer fired). Requeue is atomic (WHERE status='dispatched'),
325
+ * so racing coords cannot double-requeue.
326
+ */
327
+ sweepExpiredAckDeadlines(): Promise<number>;
224
328
  /**
225
329
  * Leader-gated sweep: mark every `recovering` row whose
226
330
  * `recovery_deadline < now` as `failed` and fire the per-job
227
- * `onRecoveryTimeout` hook. Intended to run on the Raft leader at a
331
+ * `onJobFailedPermanently` hook. Intended to run on the Raft leader at a
228
332
  * fixed interval so jobs whose owning coord crashed mid-recovery
229
333
  * still reach a terminal state.
230
334
  *
@@ -36,6 +36,14 @@ export interface AgentEntry {
36
36
  * agent's labels but does not satisfy the gate).
37
37
  */
38
38
  mandatoryLabels: Set<string>;
39
+ /**
40
+ * True when this agent was spawned by a scaler backend. Scaler-managed
41
+ * agents are single-use: the scaler destroys them on disconnect, so they
42
+ * can never reconnect to reclaim recovering jobs. The dispatcher uses
43
+ * this to triage disconnects (requeue / fail-fast) instead of starting a
44
+ * recovery window that cannot succeed.
45
+ */
46
+ scalerManaged: boolean;
39
47
  /** Number of jobs currently executing on this agent. */
40
48
  activeJobs: number;
41
49
  /** Maximum concurrent jobs this agent can handle (default 1). */
@@ -92,6 +100,12 @@ interface AgentMetadata {
92
100
  * `onAgentRegistered(...)` return value.
93
101
  */
94
102
  mandatoryLabels?: string[];
103
+ /**
104
+ * True when a scaler backend spawned this agent (single-use; destroyed
105
+ * on disconnect). Set by `agent-handler.ts` from the presence of the
106
+ * scaler registration metadata.
107
+ */
108
+ scalerManaged?: boolean;
95
109
  }
96
110
  export declare class AgentRegistry {
97
111
  /** Primary: agentId -> AgentEntry */
package/dist/app.d.ts CHANGED
@@ -30,6 +30,8 @@ import type { ProviderRegistry } from './provider-registry.js';
30
30
  import type { SourceCache } from './cache/index.js';
31
31
  import type { BuildCoordinator } from './cache/index.js';
32
32
  import type { DepCache } from './cache/index.js';
33
+ import type { UserCache } from './cache/index.js';
34
+ import type { DispatchCacheRefTracker } from './cache/index.js';
33
35
  import type { PendingBuildTracker } from './cache/index.js';
34
36
  import type { PendingInitTracker } from './cache/index.js';
35
37
  import type { PendingDynamicTracker } from './cache/index.js';
@@ -89,6 +91,15 @@ export interface AppDependencies {
89
91
  buildCoordinator?: BuildCoordinator;
90
92
  /** Dep cache for dependency tarballs. Optional — requires S3 storage. */
91
93
  depCache?: DepCache;
94
+ /** User-facing cache (ctx.cache / declarative job-step cache). Optional — requires cache storage. */
95
+ userCache?: UserCache;
96
+ /**
97
+ * Server-side jobId -> user-cache-namespace store. Written at dispatch time
98
+ * (orchestrator-core's buildOnDispatch); read by the agent-WS handler to
99
+ * resolve the cache ref for `cache.user.*` requests WITHOUT trusting the wire
100
+ * message. Optional — absent for modes that never serve the user cache.
101
+ */
102
+ dispatchCacheRefs?: DispatchCacheRefTracker;
92
103
  /** Cache storage backend (S3) for metadata operations on upload completion. */
93
104
  cacheStorage?: CacheStorage;
94
105
  /**
@@ -0,0 +1,13 @@
1
+ import type { InitFailure } from '@kici-dev/engine';
2
+ /**
3
+ * Reject reason for a pending init / dynamic-eval job the agent reported as
4
+ * failed. Carries the optional structured initFailure the agent attached to
5
+ * job.status.data so the orchestrator catch can record the correct category
6
+ * instead of collapsing everything to a string.
7
+ */
8
+ export declare class AgentJobFailedError extends Error {
9
+ readonly initFailure?: InitFailure | undefined;
10
+ readonly name = "AgentJobFailedError";
11
+ constructor(message: string, initFailure?: InitFailure | undefined);
12
+ }
13
+ //# sourceMappingURL=agent-job-failed-error.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Server-side record of the user-cache namespacing for each dispatched job.
3
+ *
4
+ * The user-facing cache is namespaced by `{orgId, repoId, cacheRefScope, runId}`.
5
+ * Those values are decided by the orchestrator at dispatch time (lifted onto the
6
+ * `job.dispatch` message). The agent later sends `cache.user.*` requests that
7
+ * carry ONLY a `jobId` + `key` — never the namespacing — so the WS handler must
8
+ * resolve the namespace from a trusted server-side store keyed by jobId. This
9
+ * tracker is that store: a wire `cache.user.*` message can name a `jobId`, but it
10
+ * can never influence the org/repo/scope the orchestrator resolves for it.
11
+ *
12
+ * The map is written when a job is dispatched, read on every `cache.user.*`
13
+ * request, and deleted when the job completes or its agent disconnects (mirroring
14
+ * the dispatcher's own per-job cleanup lifecycle) so it cannot leak.
15
+ */
16
+ import type { CacheRefScope } from '@kici-dev/engine';
17
+ /** Cache namespacing recorded for a dispatched job. */
18
+ export interface DispatchCacheRef {
19
+ /** Org that owns the run — the per-tenant cache isolation boundary. Absent for sourceless deploys. */
20
+ orgId?: string;
21
+ /** Repo identifier (e.g. "owner/repo") — second namespacing level. Absent for sourceless deploys. */
22
+ repoId?: string;
23
+ /** Write scope: `shared` (trusted ref) or `isolated` (untrusted ref, per-run scope). */
24
+ cacheRefScope?: CacheRefScope;
25
+ /** Run id — the per-run isolation namespace for `isolated`-scope writes. */
26
+ runId: string;
27
+ }
28
+ /**
29
+ * In-memory `jobId -> DispatchCacheRef` map populated at dispatch time and
30
+ * consumed by the agent WS handler to resolve a `UserCacheRef` server-side.
31
+ */
32
+ export declare class DispatchCacheRefTracker {
33
+ private readonly refs;
34
+ /** Record the cache namespacing for a dispatched job. */
35
+ record(jobId: string, ref: DispatchCacheRef): void;
36
+ /** Resolve the cache namespacing for a job, or `undefined` if it was never dispatched / already cleaned up. */
37
+ get(jobId: string): DispatchCacheRef | undefined;
38
+ /** Drop a job's recorded ref (on completion or agent disconnect). */
39
+ delete(jobId: string): void;
40
+ /** Drop every recorded ref (test/teardown helper). */
41
+ clear(): void;
42
+ /** Number of currently-tracked dispatches (leak assertions in tests). */
43
+ get size(): number;
44
+ }
45
+ //# sourceMappingURL=dispatch-cache-ref-tracker.d.ts.map
@@ -9,6 +9,8 @@
9
9
  export { SourceCache } from './source-cache.js';
10
10
  export { BuildCoordinator } from './build-coordinator.js';
11
11
  export { DepCache } from './dep-cache.js';
12
+ export { UserCache, DEFAULT_USER_CACHE_QUOTA_BYTES, DEFAULT_USER_CACHE_TTL_MS, type UserCacheRef, type UserCacheRestoreResult, type UserCacheBeginSaveResult, type UserCacheOrgLimits, type UserCacheOrgLimitsReader, } from './user-cache.js';
13
+ export { DispatchCacheRefTracker, type DispatchCacheRef } from './dispatch-cache-ref-tracker.js';
12
14
  export { PendingBuildTracker } from './pending-builds.js';
13
15
  export { PendingInitTracker } from './pending-inits.js';
14
16
  export type { InitResult } from './pending-inits.js';
@@ -0,0 +1,116 @@
1
+ import type { CacheRefScope } from '@kici-dev/engine';
2
+ import type { CacheStorage } from '../storage/types.js';
3
+ /**
4
+ * Cluster-wide default quota: 5 GiB. Serves as the fallback when an org has no
5
+ * per-org override in `org_settings.user_cache_quota_bytes`. The cluster-wide
6
+ * value is itself operator-configurable via KICI_USER_CACHE_QUOTA_BYTES.
7
+ */
8
+ export declare const DEFAULT_USER_CACHE_QUOTA_BYTES: number;
9
+ /**
10
+ * Cluster-wide default entry TTL: 7 days. Fallback when an org has no per-org
11
+ * override in `org_settings.user_cache_ttl_ms`. The cluster-wide value is
12
+ * operator-configurable via KICI_USER_CACHE_TTL_MS.
13
+ */
14
+ export declare const DEFAULT_USER_CACHE_TTL_MS: number;
15
+ /**
16
+ * Per-org override of the cache quota + TTL, read from `org_settings` at
17
+ * operation time. A field left `undefined` means "no per-org override" — the
18
+ * cluster-wide default applies. Injected into `UserCache` so the cache stays
19
+ * decoupled from the DB layer (and unit-testable with a stub reader).
20
+ */
21
+ export interface UserCacheOrgLimits {
22
+ quotaBytes?: number;
23
+ ttlMs?: number;
24
+ }
25
+ /** Resolves the per-org cache limits for an org id (e.g. an `org_settings` read). */
26
+ export type UserCacheOrgLimitsReader = (orgId: string) => Promise<UserCacheOrgLimits>;
27
+ /** Identifies the org + repo + write scope a cache operation targets. */
28
+ export interface UserCacheRef {
29
+ org: string;
30
+ repo: string;
31
+ scope: CacheRefScope;
32
+ /** Required when scope === 'isolated' — the per-run isolation namespace. */
33
+ runId?: string;
34
+ }
35
+ /** Outcome of a restore: whether an entry matched and how to fetch it. */
36
+ export interface UserCacheRestoreResult {
37
+ hit: boolean;
38
+ matchedKey?: string;
39
+ downloadUrl?: string;
40
+ tarHash?: string;
41
+ }
42
+ /** Outcome of begin-save: a presigned PUT to a temp key, or skip when the key already exists. */
43
+ export interface UserCacheBeginSaveResult {
44
+ skip: boolean;
45
+ uploadUrl?: string;
46
+ tempKey?: string;
47
+ }
48
+ export declare class UserCache {
49
+ private readonly storage;
50
+ /** Cluster-wide default quota (the `KICI_USER_CACHE_QUOTA_BYTES` value). */
51
+ private readonly defaultQuotaBytes;
52
+ /** Cluster-wide default TTL (the `KICI_USER_CACHE_TTL_MS` value). */
53
+ private readonly defaultTtlMs;
54
+ /** Optional per-org override reader; absent = always use the cluster defaults. */
55
+ private readonly orgLimitsReader?;
56
+ constructor(opts: {
57
+ storage: CacheStorage;
58
+ /** Cluster-wide default quota (env-var default). */
59
+ quotaBytes?: number;
60
+ /** Cluster-wide default TTL (env-var default). */
61
+ ttlMs?: number;
62
+ /** Per-org override reader (org_settings). When unset, defaults apply. */
63
+ orgLimitsReader?: UserCacheOrgLimitsReader;
64
+ });
65
+ /**
66
+ * Resolve the effective quota + TTL for an org: the per-org override from
67
+ * `org_settings` when present, otherwise the cluster-wide default. A reader
68
+ * failure falls back to the defaults (logged) — the cache must never fail a
69
+ * restore/save because the settings lookup hiccupped.
70
+ */
71
+ private resolveLimits;
72
+ /**
73
+ * Sanitize a path segment so a key can never escape its org/repo/scope
74
+ * namespace. Beyond stripping disallowed characters, a segment consisting
75
+ * only of dots (`.`, `..`, …) is replaced wholesale: such a segment is a
76
+ * dot-segment that HTTP/S3 path canonicalization collapses (`a/./b` → `a/b`,
77
+ * `a/../b` → `b`), which both corrupts the namespace and breaks the SigV4
78
+ * signature on a pre-signed PUT/GET. Repo identifiers like `.` (the internal
79
+ * provider's repo id) hit exactly this case, so the all-dots guard keeps the
80
+ * object key canonical and the namespace boundary intact.
81
+ */
82
+ private seg;
83
+ /** Org-level prefix: the per-tenant isolation boundary and quota scope. */
84
+ private orgPrefix;
85
+ /** Org + repo prefix shared by every scope of a repo. */
86
+ private repoPrefix;
87
+ /** Namespace prefix for the WRITE scope of a ref (shared OR per-run isolated). */
88
+ private writePrefix;
89
+ /** Namespace prefixes the ref may READ, in priority order. Isolated reads its own run scope, then shared. */
90
+ private readPrefixes;
91
+ private finalKey;
92
+ /** Restore: try the exact key across read prefixes, then restoreKeys prefix scan (newest wins). */
93
+ restore(ref: UserCacheRef & {
94
+ key: string;
95
+ restoreKeys?: string[];
96
+ }): Promise<UserCacheRestoreResult>;
97
+ /** Try the exact key in read-prefix priority order. */
98
+ private restoreExact;
99
+ /** restoreKeys prefix fallback (ordered); within a prefix, list() returns newest-first. */
100
+ private restoreByPrefix;
101
+ /** Begin a save: presigned PUT to a temp key, or skip=true when the immutable key exists. */
102
+ beginSave(ref: UserCacheRef & {
103
+ key: string;
104
+ }): Promise<UserCacheBeginSaveResult>;
105
+ /** Commit a save: copy temp -> final, init metadata, store companion hash/size, delete temp, enforce quota. */
106
+ commitSave(ref: UserCacheRef & {
107
+ key: string;
108
+ tarHash: string;
109
+ sizeBytes: number;
110
+ tempKey?: string;
111
+ }): Promise<void>;
112
+ private readHash;
113
+ /** Evict oldest entries for the org until total tarball size <= the per-org quota. */
114
+ private enforceQuota;
115
+ }
116
+ //# sourceMappingURL=user-cache.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Shared run-cancellation path.
3
+ *
4
+ * `cancelRunWithReason` is the single canonical implementation of "cancel
5
+ * this whole run, recording a reason". It is invoked by:
6
+ *
7
+ * - the operator-facing `POST /api/v1/admin/runs/:runId/cancel` route
8
+ * (user-initiated `kici cancel`), and
9
+ * - the WorkflowDeadlineDetector, which cancels runs that exceeded their
10
+ * workflow-level wall-clock timeout (TimeoutReason.workflow_timeout).
11
+ *
12
+ * Keeping one implementation means the deadline enforcer and the user cancel
13
+ * follow identical mechanics: send `job.cancel` to the agents running the
14
+ * run's jobs, cancel queued dispatch rows, mark pending/queued jobs cancelled,
15
+ * stamp the failure reason, and drive the run terminal when no agent work is
16
+ * outstanding.
17
+ */
18
+ import type { Kysely } from 'kysely';
19
+ import type { Database } from '../db/types.js';
20
+ import type { JobQueue } from '../queue/job-queue.js';
21
+ import type { Dispatcher } from '../agent/dispatcher.js';
22
+ import type { AgentRegistry } from '../agent/registry.js';
23
+ import type { ExecutionTracker } from '../reporting/execution-tracker.js';
24
+ export interface CancelRunDeps {
25
+ db: Kysely<Database>;
26
+ jobQueue: JobQueue;
27
+ dispatcher: Dispatcher;
28
+ registry: AgentRegistry;
29
+ executionTracker: ExecutionTracker;
30
+ }
31
+ export interface CancelRunOptions {
32
+ /** Force immediate SIGKILL on the agent (skip graceful hooks). */
33
+ force?: boolean;
34
+ /** Attribution stamped into execution_runs.cancelled_by. */
35
+ cancelledBy?: string;
36
+ }
37
+ export interface CancelRunResult {
38
+ /** Number of `job.cancel` messages dispatched to agents (running jobs). */
39
+ agentsNotified: number;
40
+ /** Number of pending/queued execution_jobs rows marked cancelled. */
41
+ pendingCancelled: number;
42
+ }
43
+ /**
44
+ * Cancel a whole run, recording `reason` on its cancelled jobs and (if not
45
+ * already set) on the run's failure_reason. Sends `job.cancel` to every agent
46
+ * holding a dispatched job for the run; cancels queued dispatch rows; marks
47
+ * pending/queued jobs cancelled. When no agent had outstanding work, drives
48
+ * the run to its terminal status immediately so the run does not linger in
49
+ * `cancelling`.
50
+ *
51
+ * Idempotent against terminal runs: callers should pre-check terminal state
52
+ * (the cancel route returns 409); the underlying UPDATEs are status-guarded so
53
+ * a double-call is harmless.
54
+ */
55
+ export declare function cancelRunWithReason(deps: CancelRunDeps, runId: string, reason: string, options?: CancelRunOptions): Promise<CancelRunResult>;
56
+ //# sourceMappingURL=cancel-run.d.ts.map
@@ -6,6 +6,7 @@
6
6
  * environment set-policy — update policy fields (branch, reviewers, timers, trust)
7
7
  * environment list — list environments for an org
8
8
  * environment show — show a single environment with variables + bindings
9
+ * environment delete — delete an environment (cascades bindings, variables, overrides; held-run history survives; pending held runs block with a clear error, resolved holds do not)
9
10
  * environment create-template — create/update an environment template + its seed variables
10
11
  *
11
12
  * Each command supports two modes (stage-4 pattern from `maintenance.ts`):
@@ -12,7 +12,7 @@ export { detectPlatform, isRoot, kiciConfigRoot, getConfigDir, getLogDir, getCac
12
12
  export { resolveUserLevel, type PrivilegeOpts } from './privilege.js';
13
13
  export type { Component, InstanceManifest, IndexEntry, ResolveOptions, ResolvedInstance, } from './instance/types.js';
14
14
  export { COMPONENTS, isComponent } from './instance/types.js';
15
- export { manifestFilename, manifestPath, readManifest, writeManifest, } from './instance/manifest.js';
15
+ export { manifestFilename, manifestPath, readManifest, writeManifest, readKiciVersion, } from './instance/manifest.js';
16
16
  export { indexPath, readIndex, writeIndex, appendIndexEntry, removeIndexEntry, } from './instance/index-file.js';
17
17
  export { listInstances, resolveInstance, formatRefusal } from './instance/resolve.js';
18
18
  export type { ListedInstance, ResolveArgs, ListInstancesArgs } from './instance/resolve.js';
@@ -18,4 +18,13 @@ export declare function readManifest(instanceDir: string, component: Component):
18
18
  * Returns the full path written.
19
19
  */
20
20
  export declare function writeManifest(instanceDir: string, manifest: InstanceManifest): string;
21
+ /**
22
+ * Read the running kici-admin's version from the orchestrator package.json.
23
+ *
24
+ * `process.env.npm_package_version` is only populated under `npm run` and is
25
+ * undefined when kici-admin runs as a globally-installed binary, which is the
26
+ * actual install path. Reading from the package.json on disk is the only
27
+ * reliable source.
28
+ */
29
+ export declare function readKiciVersion(): string;
21
30
  //# sourceMappingURL=manifest.d.ts.map