@kici-dev/orchestrator 0.1.8 → 0.1.10

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/cli.js CHANGED
@@ -6180,6 +6180,8 @@ var init_systemd = __esmMin((() => {
6180
6180
  const execArgs = config.args?.length ? ` ${config.args.join(" ")}` : "";
6181
6181
  lines.push(`ExecStart=${config.executablePath}${execArgs}`);
6182
6182
  lines.push(`EnvironmentFile=${config.envFilePath}`);
6183
+ const execBinDir = path.dirname(config.executablePath);
6184
+ lines.push(`Environment=PATH=${execBinDir}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`);
6183
6185
  lines.push(`WorkingDirectory=${config.workingDirectory}`);
6184
6186
  if (!config.isUserLevel && config.user) {
6185
6187
  lines.push(`User=${config.user}`);
package/dist/config.d.ts CHANGED
@@ -57,6 +57,7 @@ declare const configSchema: z.ZodObject<{
57
57
  cacheBuildTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
58
58
  cacheMaxTarballBytes: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
59
59
  webhookPayloadDir: z.ZodOptional<z.ZodString>;
60
+ dataDir: z.ZodOptional<z.ZodString>;
60
61
  scalerConfigPath: z.ZodOptional<z.ZodString>;
61
62
  scalerConfigDir: z.ZodOptional<z.ZodString>;
62
63
  machineLedgerDir: z.ZodOptional<z.ZodString>;
@@ -238,6 +239,7 @@ export declare const envDef: import("@kici-dev/shared/env").DefineEnvResult<{
238
239
  cacheStorageFsBaseUrl?: string | undefined;
239
240
  logStorageS3Bucket?: string | undefined;
240
241
  webhookPayloadDir?: string | undefined;
242
+ dataDir?: string | undefined;
241
243
  scalerConfigPath?: string | undefined;
242
244
  scalerConfigDir?: string | undefined;
243
245
  machineLedgerDir?: string | undefined;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Resolve the writable base directory for orchestrator-local data (execution
3
+ * log storage, cache).
4
+ *
5
+ * A system-level orchestrator owns `/var/lib/kici`; a user-level install
6
+ * (e.g. `kici-admin orchestrator install --user-level`) does not and cannot
7
+ * write there. Mirrors the scaler-ledger resolution in machine-ledger.ts so
8
+ * both pieces of orchestrator state degrade the same way: explicit override →
9
+ * `/var/lib/kici` if writable → XDG state dir → tmpdir.
10
+ */
11
+ /**
12
+ * Return the first candidate directory that can be created and written to.
13
+ *
14
+ * Each candidate is `mkdir -p`'d and probed with a sentinel write (removed
15
+ * immediately) so "exists but not writable" is caught the same as "cannot be
16
+ * created". Throws if none are usable.
17
+ */
18
+ export declare function firstWritableDir(candidates: string[]): string;
19
+ /**
20
+ * Resolve the orchestrator data root.
21
+ *
22
+ * 1. `explicit` (KICI_DATA_DIR) wins — created if missing.
23
+ * 2. `/var/lib/kici` if writable (system-level install).
24
+ * 3. `${XDG_STATE_HOME:-$HOME/.local/state}/kici` (user-level install).
25
+ * 4. `${tmpdir}/kici-data` (last resort, e.g. CI sandboxes).
26
+ *
27
+ * Callers append their own subdir (e.g. `${dataDir}/cache/logs`).
28
+ */
29
+ export declare function resolveDataDir(explicit: string | undefined): string;
30
+ //# sourceMappingURL=data-dir.d.ts.map
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { type ToolRequirement } from '@kici-dev/shared';
10
10
  import type { AgentTokenStore } from '../agent/token-store.js';
11
- import type { ScalerBackend, ManagedAgent, LabelSetConfig, LogCapture, ResourceRequest, EffectiveLimits, ScalerEventCallback, ValidationResult } from './types.js';
11
+ import type { ScalerBackend, ManagedAgent, LabelSetConfig, LogCapture, ResourceRequest, EffectiveLimits, ScalerEventCallback, ValidationResult, ScalerEntry } from './types.js';
12
12
  /**
13
13
  * Result of runtime detection.
14
14
  */
@@ -93,10 +93,16 @@ export declare class ContainerScalerBackend implements ScalerBackend {
93
93
  private ensureIsolatedNetwork;
94
94
  /**
95
95
  * Declare required tools for a container scaler entry.
96
- * Container runtime socket and nftables are validated during create() — no
97
- * additional tool declarations needed here.
96
+ *
97
+ * For the auto-detect case (no explicit socketPath / remote host) the
98
+ * orchestrator must have a local container runtime — docker OR podman — on
99
+ * PATH, otherwise the scaler cannot spawn agent containers. Declaring it
100
+ * here lets the startup tool-validation gate fail fast with a clear error
101
+ * instead of the first job hanging. When a socketPath or remote host is
102
+ * configured the binary need not be on PATH (the runtime may be remote), so
103
+ * reachability is validated later in create().
98
104
  */
99
- static getRequiredTools(): ToolRequirement[];
105
+ static getRequiredTools(entry: ScalerEntry): ToolRequirement[];
100
106
  /**
101
107
  * Create a ContainerScalerBackend with auto-detected or configured socket.
102
108
  * Throws if no container runtime is found and no host is configured.
@@ -15,6 +15,19 @@ import type { ScalerStateStore, ScalerStateRecovery } from './scaler-state-store
15
15
  * combined with the scaler's `defaults.resources`, applying the request<->limit
16
16
  * mirroring rule. Caps aggregate `requests`; backends use `limits`.
17
17
  */
18
+ /**
19
+ * Resolve the orchestrator WebSocket URL a scaler-spawned agent should dial.
20
+ *
21
+ * 1. Per-scaler `orchestratorUrl` (scalers.yaml) wins — required for container
22
+ * agents (host.docker.internal / LAN IP) and Firecracker VMs (bridge gateway
23
+ * IP), which cannot reach the orchestrator over the host's loopback.
24
+ * 2. `KICI_ORCHESTRATOR_URL` env override.
25
+ * 3. Default `ws://127.0.0.1:<orchestrator-port>/ws` — for local (bare-metal)
26
+ * agents that share the host. The port is the orchestrator's own bind port
27
+ * (`KICI_PORT`, default 4000), NOT the agent's 8080 default; pointing local
28
+ * agents at 8080 leaves them unable to reach the orchestrator.
29
+ */
30
+ export declare function resolveScalerOrchestratorUrl(configUrl: string | undefined, envUrl: string | undefined, port: string | number | undefined): string;
18
31
  export interface ResolvedResources {
19
32
  requests: {
20
33
  cpus: number;
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@ import { dirname as __cjs_dirname } from "node:path";
3
3
  __cjs_dirname(__cjs_fileURLToPath(import.meta.url));
4
4
  import "node:module";
5
5
  import { BaseColdStore, RingBuffer, createDb, createHealthRoutes, createLogger, createMeter, createMetricsRoutes, createPool, createS3Client, encodeKeySegment, enrichRequestContext, formatDuration, getPrometheusExporter, getReconnectDelay, getRequestContext, guardStartup, initTelemetry, requestContext, serializeError, setServiceName, setupGracefulShutdown, sha256, toErrorMessage, validateRequiredTools } from "@kici-dev/shared";
6
- import { ALLOWED_SYSTEM_VARS, AccessLogAction, AccessLogOutcome, AccessLogSource, AccessLogTargetType, ActorType, CheckRunConclusion, EVENT_LOG_PAYLOAD_CHUNK_BYTES, EventLogPayloadStreamError, EventLogSource, EventLogStatus, ExecutionJobStatus, ExecutionRunStatus, ExecutionStepStatus, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MIN_PROTOCOL_VERSION, ORCH_CAPABILITIES, OrchRole, PROTOCOL_VERSION, PayloadOmittedReason, RegisterableTriggerType, SCHEMA_VERSION, ScalerBackendType, SourceSubtype, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WEBHOOK_RELAY_MAX_BODY_BYTES, WS_CLOSE_AGENT_AUTH_FAILED, WS_CLOSE_AUTH_TIMEOUT, WS_CLOSE_GOING_AWAY, WS_CLOSE_HEARTBEAT_TIMEOUT, WS_CLOSE_INVALID_MESSAGE, WS_CLOSE_PROTOCOL_ERROR, WS_CLOSE_RUN_NOT_FOUND, WS_CLOSE_UNAUTHORIZED, WS_MAX_PAYLOAD_BYTES, WsRateLimiter, accessLogWarmSqlCase, agentAuthRequestSchema, agentToOrchestratorMessageSchema, agentTypeLabel, createWorkflowDecision, dashboardRunDetailApiResponseSchema, dashboardStepLogsApiResponseSchema, deriveOsArchLabels, flattenActor, getAccessLogColdDays, getSecretAuditLogColdDays, isLockDynamicJobFn, isLockInlineValue, isLockStaticJob, joinRequestSchema, logPullPlatformToOrchSchema, matchAllWorkflows, matchScopePattern, minAccessLogWarmDays, minSecretAuditLogWarmDays, observeSubscribeSchema, peerAuthRequestSchema, peerFromPeerMessageSchema, peerHelloResponseSchema, peerHelloSchema, platformToOrchestratorMessageSchema, resolveRoleLabels, resolveSecretsForEnvironment, scalerLabel, secretAuditLogWarmSqlCase, shouldRecordAccess, shouldRecordSecretResolve, stringifyActor, stripScopePrefix, testEventSchema } from "@kici-dev/engine";
6
+ import { ALLOWED_SYSTEM_VARS, AccessLogAction, AccessLogOutcome, AccessLogSource, AccessLogTargetType, ActorType, CheckRunConclusion, EVENT_LOG_PAYLOAD_CHUNK_BYTES, EventLogPayloadStreamError, EventLogSource, EventLogStatus, ExecutionJobStatus, ExecutionRunStatus, ExecutionStepStatus, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MIN_PROTOCOL_VERSION, ORCH_CAPABILITIES, OrchRole, PROTOCOL_VERSION, PayloadOmittedReason, RegisterableTriggerType, SCHEMA_VERSION, ScalerBackendType, SourceSubtype, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WEBHOOK_RELAY_MAX_BODY_BYTES, WS_CLOSE_AGENT_AUTH_FAILED, WS_CLOSE_AUTH_TIMEOUT, WS_CLOSE_GOING_AWAY, WS_CLOSE_HEARTBEAT_TIMEOUT, WS_CLOSE_INVALID_MESSAGE, WS_CLOSE_PROTOCOL_ERROR, WS_CLOSE_RUN_NOT_FOUND, WS_CLOSE_UNAUTHORIZED, WS_MAX_PAYLOAD_BYTES, WsRateLimiter, accessLogWarmSqlCase, agentAuthRequestSchema, agentToOrchestratorMessageSchema, agentTypeLabel, createWorkflowDecision, dashboardRunDetailApiResponseSchema, dashboardStepLogsApiResponseSchema, deriveOsArchLabels, flattenActor, getAccessLogColdDays, getSecretAuditLogColdDays, isLockDynamicJobFn, isLockInlineValue, isLockStaticJob, isSelfReportedLabel, joinRequestSchema, logPullPlatformToOrchSchema, matchAllWorkflows, matchScopePattern, minAccessLogWarmDays, minSecretAuditLogWarmDays, observeSubscribeSchema, peerAuthRequestSchema, peerFromPeerMessageSchema, peerHelloResponseSchema, peerHelloSchema, platformToOrchestratorMessageSchema, resolveRoleLabels, resolveSecretsForEnvironment, scalerAgentLabels, scalerLabel, secretAuditLogWarmSqlCase, shouldRecordAccess, shouldRecordSecretResolve, stringifyActor, stripScopePrefix, testEventSchema } from "@kici-dev/engine";
7
7
  import { verifySignature } from "@kici-dev/engine/webhook/signature";
8
8
  import { X509Certificate, createCipheriv, createDecipheriv, createHmac, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
9
9
  import os, { cpus, freemem, homedir, hostname, release, tmpdir, totalmem, uptime, userInfo, version } from "node:os";
@@ -18,7 +18,7 @@ import picomatch from "picomatch";
18
18
  import vm from "node:vm";
19
19
  import { EventEmitter } from "node:events";
20
20
  import { DASHBOARD_WRITE_OPERATIONS_BY_NAME, dashboardWritePolicyMapSchema, isDashboardWriteOperationEnabled, resolveFullPolicyView, resolveFullPolicyView as resolveFullPolicyView$1 } from "@kici-dev/engine/protocol/dashboard-write-operations";
21
- import { chmodSync, closeSync, createReadStream, createWriteStream, mkdtempSync, openSync, promises, readFileSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
21
+ import { chmodSync, closeSync, createReadStream, createWriteStream, mkdirSync, mkdtempSync, openSync, promises, readFileSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
22
22
  import { Cron } from "croner";
23
23
  import Vault from "hashi-vault-js";
24
24
  import "pg";
@@ -421,6 +421,7 @@ var init_config$5 = __esmMin((() => {
421
421
  cacheBuildTimeoutMs: z.coerce.number().default(6e5),
422
422
  cacheMaxTarballBytes: z.coerce.number().default(524288e3),
423
423
  webhookPayloadDir: z.string().optional(),
424
+ dataDir: z.string().optional(),
424
425
  scalerConfigPath: z.string().optional(),
425
426
  scalerConfigDir: z.string().optional(),
426
427
  machineLedgerDir: z.string().optional(),
@@ -605,6 +606,7 @@ var init_config$5 = __esmMin((() => {
605
606
  workerConcurrency: "KICI_WORKER_CONCURRENCY",
606
607
  concurrencyWaitTimeoutMs: "KICI_CONCURRENCY_WAIT_TIMEOUT_MS",
607
608
  webhookPayloadDir: "KICI_WEBHOOK_PAYLOAD_DIR",
609
+ dataDir: "KICI_DATA_DIR",
608
610
  scalerConfigPath: "KICI_SCALER_CONFIG_PATH",
609
611
  scalerConfigDir: "KICI_SCALER_CONFIG_DIR",
610
612
  machineLedgerDir: "KICI_MACHINE_LEDGER_DIR",
@@ -15149,7 +15151,7 @@ var init_peer_client = __esmMin((() => {
15149
15151
  init_peer_crypto();
15150
15152
  init_peer_credentials();
15151
15153
  logger$73 = createLogger({ prefix: "peer-client" });
15152
- SOFTWARE_VERSION$1 = "0.1.8";
15154
+ SOFTWARE_VERSION$1 = "0.1.10";
15153
15155
  PeerClient$1 = class {
15154
15156
  ws = null;
15155
15157
  _state = "disconnected";
@@ -16612,7 +16614,7 @@ var init_peer_handler = __esmMin((() => {
16612
16614
  init_peer_crypto();
16613
16615
  init_join_token();
16614
16616
  logger$72 = createLogger({ prefix: "peer-handler" });
16615
- SOFTWARE_VERSION = "0.1.8";
16617
+ SOFTWARE_VERSION = "0.1.10";
16616
16618
  RATE_LIMIT_MAX = 5;
16617
16619
  RATE_LIMIT_WINDOW_MS = 6e4;
16618
16620
  }));
@@ -18172,6 +18174,59 @@ var init_global_workflow_policy = __esmMin((() => {
18172
18174
  };
18173
18175
  }));
18174
18176
  //#endregion
18177
+ //#region src/data-dir.ts
18178
+ /**
18179
+ * Resolve the writable base directory for orchestrator-local data (execution
18180
+ * log storage, cache).
18181
+ *
18182
+ * A system-level orchestrator owns `/var/lib/kici`; a user-level install
18183
+ * (e.g. `kici-admin orchestrator install --user-level`) does not and cannot
18184
+ * write there. Mirrors the scaler-ledger resolution in machine-ledger.ts so
18185
+ * both pieces of orchestrator state degrade the same way: explicit override →
18186
+ * `/var/lib/kici` if writable → XDG state dir → tmpdir.
18187
+ */
18188
+ /**
18189
+ * Return the first candidate directory that can be created and written to.
18190
+ *
18191
+ * Each candidate is `mkdir -p`'d and probed with a sentinel write (removed
18192
+ * immediately) so "exists but not writable" is caught the same as "cannot be
18193
+ * created". Throws if none are usable.
18194
+ */
18195
+ function firstWritableDir(candidates) {
18196
+ for (const dir of candidates) try {
18197
+ mkdirSync(dir, { recursive: true });
18198
+ const sentinel = join(dir, `.write-probe-${process.pid}`);
18199
+ writeFileSync(sentinel, "probe");
18200
+ rmSync(sentinel, { force: true });
18201
+ return dir;
18202
+ } catch {
18203
+ continue;
18204
+ }
18205
+ throw new Error(`data-dir: no writable directory among candidates: ${candidates.join(", ")}`);
18206
+ }
18207
+ /**
18208
+ * Resolve the orchestrator data root.
18209
+ *
18210
+ * 1. `explicit` (KICI_DATA_DIR) wins — created if missing.
18211
+ * 2. `/var/lib/kici` if writable (system-level install).
18212
+ * 3. `${XDG_STATE_HOME:-$HOME/.local/state}/kici` (user-level install).
18213
+ * 4. `${tmpdir}/kici-data` (last resort, e.g. CI sandboxes).
18214
+ *
18215
+ * Callers append their own subdir (e.g. `${dataDir}/cache/logs`).
18216
+ */
18217
+ function resolveDataDir(explicit) {
18218
+ if (explicit) {
18219
+ mkdirSync(explicit, { recursive: true });
18220
+ return explicit;
18221
+ }
18222
+ return firstWritableDir([
18223
+ "/var/lib/kici",
18224
+ join(process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state"), "kici"),
18225
+ join(tmpdir(), "kici-data")
18226
+ ]);
18227
+ }
18228
+ var init_data_dir = __esmMin((() => {}));
18229
+ //#endregion
18175
18230
  //#region src/config/reload.ts
18176
18231
  /**
18177
18232
  * Compute a list of top-level config fields that differ between old and new config.
@@ -24719,13 +24774,25 @@ var init_check_run_reporter = __esmMin((() => {
24719
24774
  * returns `false` after closing the WS on the first violation, in which
24720
24775
  * case the caller MUST stop processing.
24721
24776
  */
24777
+ /**
24778
+ * WebSocket close reasons are capped at 123 UTF-8 bytes (RFC 6455 §5.5.1).
24779
+ * A reason longer than that makes `ws.close()` throw a RangeError, which —
24780
+ * thrown from an async message handler — surfaces as an unhandled rejection
24781
+ * and skips the close entirely. Truncate on a byte boundary and drop any
24782
+ * trailing partial multi-byte char.
24783
+ */
24784
+ function truncateCloseReason(reason) {
24785
+ const bytes = Buffer.from(reason, "utf-8");
24786
+ if (bytes.length <= 123) return reason;
24787
+ return bytes.subarray(0, 123).toString("utf-8").replace(/�+$/, "");
24788
+ }
24722
24789
  function enforceRegisterAuthGates(authState, payload, ws, agentIdToTokenId) {
24723
24790
  if (authState === void 0) return true;
24724
24791
  const { tokenId, tokenLabels, tokenAgentType, tokenCreatedBy } = authState;
24725
24792
  const { agentId, labels } = payload;
24726
24793
  if (tokenLabels !== void 0 && tokenLabels !== null) {
24727
24794
  const allowedSet = new Set(tokenLabels);
24728
- const elevated = labels.filter((l) => !allowedSet.has(l));
24795
+ const elevated = labels.filter((l) => !allowedSet.has(l) && !isSelfReportedLabel(l));
24729
24796
  if (elevated.length > 0) {
24730
24797
  logger$59.warn("Agent register-time label-scope violation: wire labels exceed token-bound set", {
24731
24798
  agentId,
@@ -24733,7 +24800,7 @@ function enforceRegisterAuthGates(authState, payload, ws, agentIdToTokenId) {
24733
24800
  wireLabels: labels,
24734
24801
  elevated
24735
24802
  });
24736
- ws.close(WS_CLOSE_AGENT_AUTH_FAILED, `Agent labels exceed token-bound scope: ${elevated.join(",")}`);
24803
+ ws.close(WS_CLOSE_AGENT_AUTH_FAILED, truncateCloseReason(`Agent labels exceed token-bound scope: ${elevated.join(",")}`));
24737
24804
  return false;
24738
24805
  }
24739
24806
  }
@@ -24743,7 +24810,7 @@ function enforceRegisterAuthGates(authState, payload, ws, agentIdToTokenId) {
24743
24810
  tokenCreatedBy,
24744
24811
  tokenId
24745
24812
  });
24746
- ws.close(WS_CLOSE_AGENT_AUTH_FAILED, `Ephemeral token bound to a different agentId: expected ${tokenCreatedBy}, got ${agentId}`);
24813
+ ws.close(WS_CLOSE_AGENT_AUTH_FAILED, truncateCloseReason(`Ephemeral token bound to a different agentId: expected ${tokenCreatedBy}, got ${agentId}`));
24747
24814
  return false;
24748
24815
  }
24749
24816
  if (tokenId !== void 0) {
@@ -34049,15 +34116,15 @@ var init_admin_config = __esmMin((() => {
34049
34116
  function createHealthRoutes$1(deps = {}) {
34050
34117
  return createHealthRoutes({
34051
34118
  livenessInfo: () => ({
34052
- version: "0.1.8",
34053
- buildDate: "2026-05-26T14:42:32.591Z",
34054
- buildCommit: "5f626dda9",
34055
- sdkVersion: "0.1.8",
34056
- sdkBundleHash: "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128",
34057
- sharedVersion: "0.1.8",
34119
+ version: "0.1.10",
34120
+ buildDate: "2026-05-26T17:19:08.888Z",
34121
+ buildCommit: "6bf528b56",
34122
+ sdkVersion: "0.1.10",
34123
+ sdkBundleHash: "unknown",
34124
+ sharedVersion: "0.1.10",
34058
34125
  sharedBundleHash: "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae",
34059
- engineVersion: "0.1.8",
34060
- engineBundleHash: "b704538612ec796a5668765bd55e0eaeaf1bad8a2afa508f9e20ad7b9eb61d21"
34126
+ engineVersion: "0.1.10",
34127
+ engineBundleHash: "dff25bbd7602e853cec6e6e985c6c956ad773e245830273cd52950fff958d4bb"
34061
34128
  }),
34062
34129
  readinessCheck: deps.db ? async () => {
34063
34130
  const checks = {};
@@ -34091,7 +34158,7 @@ function createCapabilitiesRoutes() {
34091
34158
  const app = new Hono();
34092
34159
  app.get("/api/v1/capabilities", (c) => {
34093
34160
  const manifest = {
34094
- orchestratorVersion: "0.1.8",
34161
+ orchestratorVersion: "0.1.10",
34095
34162
  protocolVersion: PROTOCOL_VERSION,
34096
34163
  minProtocolVersion: MIN_PROTOCOL_VERSION
34097
34164
  };
@@ -36452,11 +36519,22 @@ var init_container_backend = __esmMin((() => {
36452
36519
  }
36453
36520
  /**
36454
36521
  * Declare required tools for a container scaler entry.
36455
- * Container runtime socket and nftables are validated during create() — no
36456
- * additional tool declarations needed here.
36522
+ *
36523
+ * For the auto-detect case (no explicit socketPath / remote host) the
36524
+ * orchestrator must have a local container runtime — docker OR podman — on
36525
+ * PATH, otherwise the scaler cannot spawn agent containers. Declaring it
36526
+ * here lets the startup tool-validation gate fail fast with a clear error
36527
+ * instead of the first job hanging. When a socketPath or remote host is
36528
+ * configured the binary need not be on PATH (the runtime may be remote), so
36529
+ * reachability is validated later in create().
36457
36530
  */
36458
- static getRequiredTools() {
36459
- return [];
36531
+ static getRequiredTools(entry) {
36532
+ if (entry.host || entry.socketPath) return [];
36533
+ return [{
36534
+ type: "any-path-binary",
36535
+ names: ["docker", "podman"],
36536
+ reason: `container scaler "${entry.name}" needs a local container runtime to spawn agents. Install Docker or Podman, or set socketPath / host in scalers.yaml for a remote runtime.`
36537
+ }];
36460
36538
  }
36461
36539
  /**
36462
36540
  * Create a ContainerScalerBackend with auto-detected or configured socket.
@@ -36517,17 +36595,13 @@ var init_container_backend = __esmMin((() => {
36517
36595
  const stripped = key.slice(KICI_AGENT_ENV_PREFIX.length);
36518
36596
  if (stripped.length > 0) agentEnvForwarded.push(`${stripped}=${value}`);
36519
36597
  }
36598
+ const fullLabels = scalerAgentLabels(labelSet, this.type, this.name, this.roles);
36520
36599
  let agentToken;
36521
- if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, labelSet, this.tokenTtlMs);
36600
+ if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, fullLabels, this.tokenTtlMs);
36522
36601
  const env = [
36523
36602
  `KICI_ORCHESTRATOR_URL=${orchestratorUrl}`,
36524
36603
  `KICI_AGENT_ID=${agentId}`,
36525
- `KICI_LABELS=${[
36526
- ...labelSet,
36527
- `kici:agent:${this.type}`,
36528
- `kici:scaler:${this.name}`,
36529
- ...resolveRoleLabels(this.roles)
36530
- ].join(",")}`,
36604
+ `KICI_LABELS=${fullLabels.join(",")}`,
36531
36605
  `KICI_SCALER_MANAGED=1`,
36532
36606
  `KICI_EXECUTION_MODE=bare-metal`,
36533
36607
  ...agentToken ? [`KICI_AGENT_TOKEN=${agentToken}`] : [],
@@ -36780,6 +36854,11 @@ var init_bare_metal_backend = __esmMin((() => {
36780
36854
  mode: "executable",
36781
36855
  reason: `agent binary for bare-metal scaler "${entry.name}"`
36782
36856
  }));
36857
+ requirements.push({
36858
+ type: "path-binary",
36859
+ name: "node",
36860
+ reason: `bare-metal scaler "${entry.name}" spawns the kici-agent node script. node must be on the orchestrator's PATH (it is forwarded to spawned agents). Ensure the orchestrator service's PATH includes your node install (e.g. the systemd unit's Environment=PATH covers the mise/nvm node bin dir).`
36861
+ });
36783
36862
  const sandboxViaGlobalEnv = process.env.KICI_AGENT_ENV_KICI_SANDBOX === "true";
36784
36863
  const sandboxViaLabelSet = entry.labelSets.some((ls) => ls.env && ls.env.KICI_SANDBOX === "true");
36785
36864
  if (sandboxViaGlobalEnv || sandboxViaLabelSet) requirements.push({
@@ -36818,15 +36897,11 @@ var init_bare_metal_backend = __esmMin((() => {
36818
36897
  const strippedKey = key.slice(KICI_AGENT_ENV_PREFIX.length);
36819
36898
  if (strippedKey.length > 0) env[strippedKey] = value;
36820
36899
  }
36821
- if (this.tokenStore) env.KICI_AGENT_TOKEN = await this.tokenStore.createEphemeral(agentId, labelSet, this.tokenTtlMs);
36900
+ const fullLabels = scalerAgentLabels(labelSet, this.type, this.name, this.roles);
36901
+ if (this.tokenStore) env.KICI_AGENT_TOKEN = await this.tokenStore.createEphemeral(agentId, fullLabels, this.tokenTtlMs);
36822
36902
  env.KICI_ORCHESTRATOR_URL = orchestratorUrl;
36823
36903
  env.KICI_AGENT_ID = agentId;
36824
- env.KICI_LABELS = [
36825
- ...labelSet,
36826
- `kici:agent:${this.type}`,
36827
- `kici:scaler:${this.name}`,
36828
- ...resolveRoleLabels(this.roles)
36829
- ].join(",");
36904
+ env.KICI_LABELS = fullLabels.join(",");
36830
36905
  env.KICI_SCALER_MANAGED = "1";
36831
36906
  env.KICI_EXECUTION_MODE = "bare-metal";
36832
36907
  env.KICI_PORT = "0";
@@ -37576,18 +37651,14 @@ var init_firecracker_backend = __esmMin((() => {
37576
37651
  if (chmodLoosenInterval) clearInterval(chmodLoosenInterval);
37577
37652
  }
37578
37653
  if (!ready) throw new Error(`Firecracker API socket not ready within 5s for agent ${agentId}`);
37654
+ const fullLabels = scalerAgentLabels(labelSet, this.type, this.name, this.roles);
37579
37655
  let agentToken;
37580
- if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, labelSet, this.tokenTtlMs);
37656
+ if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, fullLabels, this.tokenTtlMs);
37581
37657
  const acceptedEnv = this.buildForwardedEnv(matchedLabelSet, agentId);
37582
37658
  await api.putMmds({ latest: { "meta-data": {
37583
37659
  "kici-orchestrator-url": orchestratorUrl,
37584
37660
  "kici-agent-id": agentId,
37585
- "kici-labels": [
37586
- ...labelSet,
37587
- `kici:agent:${this.type}`,
37588
- `kici:scaler:${this.name}`,
37589
- ...resolveRoleLabels(this.roles)
37590
- ].join(","),
37661
+ "kici-labels": fullLabels.join(","),
37591
37662
  "kici-scaler-managed": "1",
37592
37663
  "kici-gateway-ip": this.gateway,
37593
37664
  ...agentToken ? { "kici-agent-token": agentToken } : {},
@@ -38664,6 +38735,29 @@ var init_machine_ledger = __esmMin((() => {
38664
38735
  * and manages the agent lifecycle from spawn to destroy.
38665
38736
  */
38666
38737
  /**
38738
+ * Resolved per-job resource amounts (cpus + bytes) for both `requests` and
38739
+ * `limits`. The scaler manager produces this from the job's declared resources
38740
+ * combined with the scaler's `defaults.resources`, applying the request<->limit
38741
+ * mirroring rule. Caps aggregate `requests`; backends use `limits`.
38742
+ */
38743
+ /**
38744
+ * Resolve the orchestrator WebSocket URL a scaler-spawned agent should dial.
38745
+ *
38746
+ * 1. Per-scaler `orchestratorUrl` (scalers.yaml) wins — required for container
38747
+ * agents (host.docker.internal / LAN IP) and Firecracker VMs (bridge gateway
38748
+ * IP), which cannot reach the orchestrator over the host's loopback.
38749
+ * 2. `KICI_ORCHESTRATOR_URL` env override.
38750
+ * 3. Default `ws://127.0.0.1:<orchestrator-port>/ws` — for local (bare-metal)
38751
+ * agents that share the host. The port is the orchestrator's own bind port
38752
+ * (`KICI_PORT`, default 4000), NOT the agent's 8080 default; pointing local
38753
+ * agents at 8080 leaves them unable to reach the orchestrator.
38754
+ */
38755
+ function resolveScalerOrchestratorUrl(configUrl, envUrl, port) {
38756
+ if (configUrl) return configUrl;
38757
+ if (envUrl) return envUrl;
38758
+ return `ws://127.0.0.1:${port ?? "4000"}/ws`;
38759
+ }
38760
+ /**
38667
38761
  * Apply the request<->limit mirroring rule: if only one side is set, copy it
38668
38762
  * to the other; if neither is set, return undefined; if both are set, leave them.
38669
38763
  *
@@ -39575,9 +39669,7 @@ var init_manager = __esmMin((() => {
39575
39669
  });
39576
39670
  }
39577
39671
  getOrchestratorUrl(backendName) {
39578
- const configUrl = this.scalerUrls.get(backendName);
39579
- if (configUrl) return configUrl;
39580
- return process.env.KICI_ORCHESTRATOR_URL ?? "ws://localhost:8080/ws";
39672
+ return resolveScalerOrchestratorUrl(this.scalerUrls.get(backendName), process.env.KICI_ORCHESTRATOR_URL, process.env.KICI_PORT);
39581
39673
  }
39582
39674
  /**
39583
39675
  * Create a per-agent event emitter closure to pass to backend.spawn().
@@ -47162,7 +47254,7 @@ async function initializeScaler(config, db, tokenStore) {
47162
47254
  }
47163
47255
  const toolErrors = validateRequiredTools(scalerConfig.scalers.flatMap((s) => {
47164
47256
  switch (s.type) {
47165
- case "container": return ContainerScalerBackend.getRequiredTools();
47257
+ case "container": return ContainerScalerBackend.getRequiredTools(s);
47166
47258
  case "bare-metal": return BareMetalScalerBackend.getRequiredTools(s);
47167
47259
  case "firecracker": return FirecrackerScalerBackend.getRequiredTools(s);
47168
47260
  default: return [];
@@ -48154,7 +48246,7 @@ async function bootstrapOrchestrator$1(config, hooks, options) {
48154
48246
  forcePathStyle: config.storage.forcePathStyle
48155
48247
  } : {
48156
48248
  type: "filesystem",
48157
- basePath: (config.webhookPayloadDir ?? "/var/lib/kici/cache") + "/logs"
48249
+ basePath: (config.webhookPayloadDir ?? resolveDataDir(config.dataDir) + "/cache") + "/logs"
48158
48250
  });
48159
48251
  const observerRegistry = new ObserverRegistry();
48160
48252
  let executionTrackerRef = null;
@@ -48954,6 +49046,7 @@ async function bootstrapOrchestrator$1(config, hooks, options) {
48954
49046
  }
48955
49047
  var logger$2;
48956
49048
  var init_orchestrator_core = __esmMin((() => {
49049
+ init_data_dir();
48957
49050
  init_reload();
48958
49051
  init_resolver();
48959
49052
  init_client();
@@ -50226,14 +50319,14 @@ var init_worker_core = __esmMin((() => {
50226
50319
  init_agent_handler();
50227
50320
  init_worker_status();
50228
50321
  init_agent_heartbeat();
50229
- ORCHESTRATOR_VERSION$1 = "0.1.8";
50230
- WORKER_BUILD_COMMIT = "5f626dda9";
50231
- WORKER_SDK_VERSION = "0.1.8";
50232
- WORKER_SDK_BUNDLE_HASH = "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128";
50233
- WORKER_SHARED_VERSION = "0.1.8";
50322
+ ORCHESTRATOR_VERSION$1 = "0.1.10";
50323
+ WORKER_BUILD_COMMIT = "6bf528b56";
50324
+ WORKER_SDK_VERSION = "0.1.10";
50325
+ WORKER_SDK_BUNDLE_HASH = "unknown";
50326
+ WORKER_SHARED_VERSION = "0.1.10";
50234
50327
  WORKER_SHARED_BUNDLE_HASH = "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae";
50235
- WORKER_ENGINE_VERSION = "0.1.8";
50236
- WORKER_ENGINE_BUNDLE_HASH = "b704538612ec796a5668765bd55e0eaeaf1bad8a2afa508f9e20ad7b9eb61d21";
50328
+ WORKER_ENGINE_VERSION = "0.1.10";
50329
+ WORKER_ENGINE_BUNDLE_HASH = "dff25bbd7602e853cec6e6e985c6c956ad773e245830273cd52950fff958d4bb";
50237
50330
  logger$1 = createLogger({ prefix: "worker" });
50238
50331
  DRAIN_TIMEOUT_MS = 3e5;
50239
50332
  }));
@@ -50254,14 +50347,14 @@ var init_worker_core = __esmMin((() => {
50254
50347
  * Graceful shutdown in reverse order:
50255
50348
  * Platform client -> agent WS -> heartbeat -> HTTP -> DB
50256
50349
  */
50257
- const ORCHESTRATOR_VERSION = "0.1.8";
50258
- const BUILD_COMMIT = "5f626dda9";
50259
- const SDK_VERSION = "0.1.8";
50260
- const SDK_BUNDLE_HASH = "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128";
50261
- const SHARED_VERSION = "0.1.8";
50350
+ const ORCHESTRATOR_VERSION = "0.1.10";
50351
+ const BUILD_COMMIT = "6bf528b56";
50352
+ const SDK_VERSION = "0.1.10";
50353
+ const SDK_BUNDLE_HASH = "unknown";
50354
+ const SHARED_VERSION = "0.1.10";
50262
50355
  const SHARED_BUNDLE_HASH = "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae";
50263
- const ENGINE_VERSION = "0.1.8";
50264
- const ENGINE_BUNDLE_HASH = "b704538612ec796a5668765bd55e0eaeaf1bad8a2afa508f9e20ad7b9eb61d21";
50356
+ const ENGINE_VERSION = "0.1.10";
50357
+ const ENGINE_BUNDLE_HASH = "dff25bbd7602e853cec6e6e985c6c956ad773e245830273cd52950fff958d4bb";
50265
50358
  const otelSdk = initTelemetry({
50266
50359
  serviceName: "kici-orchestrator",
50267
50360
  otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
@@ -8,8 +8,8 @@ import { X509Certificate, createCipheriv, createDecipheriv, createHmac, createPr
8
8
  import { z } from "zod";
9
9
  import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
10
10
  import WebSocket from "ws";
11
- import { chmodSync, closeSync, createReadStream, createWriteStream, mkdtempSync, openSync, promises, readFileSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
12
- import { ALLOWED_SYSTEM_VARS, AccessLogAction, AccessLogOutcome, AccessLogSource, AccessLogTargetType, ActorType, CheckRunConclusion, EventLogSource, EventLogStatus, ExecutionJobStatus, ExecutionRunStatus, ExecutionStepStatus, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MIN_PROTOCOL_VERSION, PROTOCOL_VERSION, PayloadOmittedReason, RegisterableTriggerType, SCHEMA_VERSION, ScalerBackendType, SourceSubtype, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_CLOSE_AGENT_AUTH_FAILED, WS_CLOSE_AUTH_TIMEOUT, WS_CLOSE_GOING_AWAY, WS_CLOSE_HEARTBEAT_TIMEOUT, WS_CLOSE_INVALID_MESSAGE, WS_CLOSE_PROTOCOL_ERROR, WS_CLOSE_RUN_NOT_FOUND, WS_CLOSE_UNAUTHORIZED, WS_MAX_PAYLOAD_BYTES, WsRateLimiter, accessLogWarmSqlCase, agentAuthRequestSchema, agentToOrchestratorMessageSchema, agentTypeLabel, createWorkflowDecision, deriveOsArchLabels, flattenActor, getAccessLogColdDays, getSecretAuditLogColdDays, isLockDynamicJobFn, isLockInlineValue, isLockStaticJob, matchAllWorkflows, minAccessLogWarmDays, minSecretAuditLogWarmDays, observeSubscribeSchema, peerAuthRequestSchema, peerFromPeerMessageSchema, peerHelloResponseSchema, peerHelloSchema, resolveRoleLabels, scalerLabel, secretAuditLogWarmSqlCase, shouldRecordAccess, shouldRecordSecretResolve, testEventSchema } from "@kici-dev/engine";
11
+ import { chmodSync, closeSync, createReadStream, createWriteStream, mkdirSync, mkdtempSync, openSync, promises, readFileSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
12
+ import { ALLOWED_SYSTEM_VARS, AccessLogAction, AccessLogOutcome, AccessLogSource, AccessLogTargetType, ActorType, CheckRunConclusion, EventLogSource, EventLogStatus, ExecutionJobStatus, ExecutionRunStatus, ExecutionStepStatus, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MIN_PROTOCOL_VERSION, PROTOCOL_VERSION, PayloadOmittedReason, RegisterableTriggerType, SCHEMA_VERSION, ScalerBackendType, SourceSubtype, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_CLOSE_AGENT_AUTH_FAILED, WS_CLOSE_AUTH_TIMEOUT, WS_CLOSE_GOING_AWAY, WS_CLOSE_HEARTBEAT_TIMEOUT, WS_CLOSE_INVALID_MESSAGE, WS_CLOSE_PROTOCOL_ERROR, WS_CLOSE_RUN_NOT_FOUND, WS_CLOSE_UNAUTHORIZED, WS_MAX_PAYLOAD_BYTES, WsRateLimiter, accessLogWarmSqlCase, agentAuthRequestSchema, agentToOrchestratorMessageSchema, agentTypeLabel, createWorkflowDecision, deriveOsArchLabels, flattenActor, getAccessLogColdDays, getSecretAuditLogColdDays, isLockDynamicJobFn, isLockInlineValue, isLockStaticJob, isSelfReportedLabel, matchAllWorkflows, minAccessLogWarmDays, minSecretAuditLogWarmDays, observeSubscribeSchema, peerAuthRequestSchema, peerFromPeerMessageSchema, peerHelloResponseSchema, peerHelloSchema, resolveRoleLabels, scalerAgentLabels, scalerLabel, secretAuditLogWarmSqlCase, shouldRecordAccess, shouldRecordSecretResolve, testEventSchema } from "@kici-dev/engine";
13
13
  import { access, appendFile, chmod, constants, copyFile, link, mkdir, mkdtemp, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
14
14
  import { dirname, join, relative, resolve } from "node:path";
15
15
  import { sql } from "kysely";
@@ -175,6 +175,7 @@ var init_config$5 = __esmMin((() => {
175
175
  cacheBuildTimeoutMs: z.coerce.number().default(6e5),
176
176
  cacheMaxTarballBytes: z.coerce.number().default(524288e3),
177
177
  webhookPayloadDir: z.string().optional(),
178
+ dataDir: z.string().optional(),
178
179
  scalerConfigPath: z.string().optional(),
179
180
  scalerConfigDir: z.string().optional(),
180
181
  machineLedgerDir: z.string().optional(),
@@ -359,6 +360,7 @@ var init_config$5 = __esmMin((() => {
359
360
  workerConcurrency: "KICI_WORKER_CONCURRENCY",
360
361
  concurrencyWaitTimeoutMs: "KICI_CONCURRENCY_WAIT_TIMEOUT_MS",
361
362
  webhookPayloadDir: "KICI_WEBHOOK_PAYLOAD_DIR",
363
+ dataDir: "KICI_DATA_DIR",
362
364
  scalerConfigPath: "KICI_SCALER_CONFIG_PATH",
363
365
  scalerConfigDir: "KICI_SCALER_CONFIG_DIR",
364
366
  machineLedgerDir: "KICI_MACHINE_LEDGER_DIR",
@@ -1038,7 +1040,7 @@ var init_peer_client = __esmMin((() => {
1038
1040
  init_peer_crypto();
1039
1041
  init_peer_credentials();
1040
1042
  logger$85 = createLogger({ prefix: "peer-client" });
1041
- SOFTWARE_VERSION$1 = "0.1.8";
1043
+ SOFTWARE_VERSION$1 = "0.1.10";
1042
1044
  PeerClient$1 = class {
1043
1045
  ws = null;
1044
1046
  _state = "disconnected";
@@ -2501,7 +2503,7 @@ var init_peer_handler = __esmMin((() => {
2501
2503
  init_peer_crypto();
2502
2504
  init_join_token();
2503
2505
  logger$84 = createLogger({ prefix: "peer-handler" });
2504
- SOFTWARE_VERSION = "0.1.8";
2506
+ SOFTWARE_VERSION = "0.1.10";
2505
2507
  RATE_LIMIT_MAX = 5;
2506
2508
  RATE_LIMIT_WINDOW_MS = 6e4;
2507
2509
  }));
@@ -3739,6 +3741,59 @@ var init_cluster = __esmMin((() => {
3739
3741
  init_health_api();
3740
3742
  }));
3741
3743
  //#endregion
3744
+ //#region src/data-dir.ts
3745
+ /**
3746
+ * Resolve the writable base directory for orchestrator-local data (execution
3747
+ * log storage, cache).
3748
+ *
3749
+ * A system-level orchestrator owns `/var/lib/kici`; a user-level install
3750
+ * (e.g. `kici-admin orchestrator install --user-level`) does not and cannot
3751
+ * write there. Mirrors the scaler-ledger resolution in machine-ledger.ts so
3752
+ * both pieces of orchestrator state degrade the same way: explicit override →
3753
+ * `/var/lib/kici` if writable → XDG state dir → tmpdir.
3754
+ */
3755
+ /**
3756
+ * Return the first candidate directory that can be created and written to.
3757
+ *
3758
+ * Each candidate is `mkdir -p`'d and probed with a sentinel write (removed
3759
+ * immediately) so "exists but not writable" is caught the same as "cannot be
3760
+ * created". Throws if none are usable.
3761
+ */
3762
+ function firstWritableDir(candidates) {
3763
+ for (const dir of candidates) try {
3764
+ mkdirSync(dir, { recursive: true });
3765
+ const sentinel = join(dir, `.write-probe-${process.pid}`);
3766
+ writeFileSync(sentinel, "probe");
3767
+ rmSync(sentinel, { force: true });
3768
+ return dir;
3769
+ } catch {
3770
+ continue;
3771
+ }
3772
+ throw new Error(`data-dir: no writable directory among candidates: ${candidates.join(", ")}`);
3773
+ }
3774
+ /**
3775
+ * Resolve the orchestrator data root.
3776
+ *
3777
+ * 1. `explicit` (KICI_DATA_DIR) wins — created if missing.
3778
+ * 2. `/var/lib/kici` if writable (system-level install).
3779
+ * 3. `${XDG_STATE_HOME:-$HOME/.local/state}/kici` (user-level install).
3780
+ * 4. `${tmpdir}/kici-data` (last resort, e.g. CI sandboxes).
3781
+ *
3782
+ * Callers append their own subdir (e.g. `${dataDir}/cache/logs`).
3783
+ */
3784
+ function resolveDataDir(explicit) {
3785
+ if (explicit) {
3786
+ mkdirSync(explicit, { recursive: true });
3787
+ return explicit;
3788
+ }
3789
+ return firstWritableDir([
3790
+ "/var/lib/kici",
3791
+ join(process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state"), "kici"),
3792
+ join(tmpdir(), "kici-data")
3793
+ ]);
3794
+ }
3795
+ var init_data_dir = __esmMin((() => {}));
3796
+ //#endregion
3742
3797
  //#region src/metrics/prometheus.ts
3743
3798
  /** Set the current number of active agents. */
3744
3799
  function setAgentsActive(value) {
@@ -10845,13 +10900,25 @@ var init_check_run_reporter = __esmMin((() => {
10845
10900
  * returns `false` after closing the WS on the first violation, in which
10846
10901
  * case the caller MUST stop processing.
10847
10902
  */
10903
+ /**
10904
+ * WebSocket close reasons are capped at 123 UTF-8 bytes (RFC 6455 §5.5.1).
10905
+ * A reason longer than that makes `ws.close()` throw a RangeError, which —
10906
+ * thrown from an async message handler — surfaces as an unhandled rejection
10907
+ * and skips the close entirely. Truncate on a byte boundary and drop any
10908
+ * trailing partial multi-byte char.
10909
+ */
10910
+ function truncateCloseReason(reason) {
10911
+ const bytes = Buffer.from(reason, "utf-8");
10912
+ if (bytes.length <= 123) return reason;
10913
+ return bytes.subarray(0, 123).toString("utf-8").replace(/�+$/, "");
10914
+ }
10848
10915
  function enforceRegisterAuthGates(authState, payload, ws, agentIdToTokenId) {
10849
10916
  if (authState === void 0) return true;
10850
10917
  const { tokenId, tokenLabels, tokenAgentType, tokenCreatedBy } = authState;
10851
10918
  const { agentId, labels } = payload;
10852
10919
  if (tokenLabels !== void 0 && tokenLabels !== null) {
10853
10920
  const allowedSet = new Set(tokenLabels);
10854
- const elevated = labels.filter((l) => !allowedSet.has(l));
10921
+ const elevated = labels.filter((l) => !allowedSet.has(l) && !isSelfReportedLabel(l));
10855
10922
  if (elevated.length > 0) {
10856
10923
  logger$69.warn("Agent register-time label-scope violation: wire labels exceed token-bound set", {
10857
10924
  agentId,
@@ -10859,7 +10926,7 @@ function enforceRegisterAuthGates(authState, payload, ws, agentIdToTokenId) {
10859
10926
  wireLabels: labels,
10860
10927
  elevated
10861
10928
  });
10862
- ws.close(WS_CLOSE_AGENT_AUTH_FAILED, `Agent labels exceed token-bound scope: ${elevated.join(",")}`);
10929
+ ws.close(WS_CLOSE_AGENT_AUTH_FAILED, truncateCloseReason(`Agent labels exceed token-bound scope: ${elevated.join(",")}`));
10863
10930
  return false;
10864
10931
  }
10865
10932
  }
@@ -10869,7 +10936,7 @@ function enforceRegisterAuthGates(authState, payload, ws, agentIdToTokenId) {
10869
10936
  tokenCreatedBy,
10870
10937
  tokenId
10871
10938
  });
10872
- ws.close(WS_CLOSE_AGENT_AUTH_FAILED, `Ephemeral token bound to a different agentId: expected ${tokenCreatedBy}, got ${agentId}`);
10939
+ ws.close(WS_CLOSE_AGENT_AUTH_FAILED, truncateCloseReason(`Ephemeral token bound to a different agentId: expected ${tokenCreatedBy}, got ${agentId}`));
10873
10940
  return false;
10874
10941
  }
10875
10942
  if (tokenId !== void 0) {
@@ -21536,15 +21603,15 @@ var init_admin_config = __esmMin((() => {
21536
21603
  function createHealthRoutes$1(deps = {}) {
21537
21604
  return createHealthRoutes({
21538
21605
  livenessInfo: () => ({
21539
- version: "0.1.8",
21540
- buildDate: "2026-05-26T14:42:32.591Z",
21541
- buildCommit: "5f626dda9",
21542
- sdkVersion: "0.1.8",
21543
- sdkBundleHash: "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128",
21544
- sharedVersion: "0.1.8",
21606
+ version: "0.1.10",
21607
+ buildDate: "2026-05-26T17:19:08.888Z",
21608
+ buildCommit: "6bf528b56",
21609
+ sdkVersion: "0.1.10",
21610
+ sdkBundleHash: "unknown",
21611
+ sharedVersion: "0.1.10",
21545
21612
  sharedBundleHash: "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae",
21546
- engineVersion: "0.1.8",
21547
- engineBundleHash: "b704538612ec796a5668765bd55e0eaeaf1bad8a2afa508f9e20ad7b9eb61d21"
21613
+ engineVersion: "0.1.10",
21614
+ engineBundleHash: "dff25bbd7602e853cec6e6e985c6c956ad773e245830273cd52950fff958d4bb"
21548
21615
  }),
21549
21616
  readinessCheck: deps.db ? async () => {
21550
21617
  const checks = {};
@@ -21578,7 +21645,7 @@ function createCapabilitiesRoutes() {
21578
21645
  const app = new Hono();
21579
21646
  app.get("/api/v1/capabilities", (c) => {
21580
21647
  const manifest = {
21581
- orchestratorVersion: "0.1.8",
21648
+ orchestratorVersion: "0.1.10",
21582
21649
  protocolVersion: PROTOCOL_VERSION,
21583
21650
  minProtocolVersion: MIN_PROTOCOL_VERSION
21584
21651
  };
@@ -28795,11 +28862,22 @@ var init_container_backend = __esmMin((() => {
28795
28862
  }
28796
28863
  /**
28797
28864
  * Declare required tools for a container scaler entry.
28798
- * Container runtime socket and nftables are validated during create() — no
28799
- * additional tool declarations needed here.
28865
+ *
28866
+ * For the auto-detect case (no explicit socketPath / remote host) the
28867
+ * orchestrator must have a local container runtime — docker OR podman — on
28868
+ * PATH, otherwise the scaler cannot spawn agent containers. Declaring it
28869
+ * here lets the startup tool-validation gate fail fast with a clear error
28870
+ * instead of the first job hanging. When a socketPath or remote host is
28871
+ * configured the binary need not be on PATH (the runtime may be remote), so
28872
+ * reachability is validated later in create().
28800
28873
  */
28801
- static getRequiredTools() {
28802
- return [];
28874
+ static getRequiredTools(entry) {
28875
+ if (entry.host || entry.socketPath) return [];
28876
+ return [{
28877
+ type: "any-path-binary",
28878
+ names: ["docker", "podman"],
28879
+ reason: `container scaler "${entry.name}" needs a local container runtime to spawn agents. Install Docker or Podman, or set socketPath / host in scalers.yaml for a remote runtime.`
28880
+ }];
28803
28881
  }
28804
28882
  /**
28805
28883
  * Create a ContainerScalerBackend with auto-detected or configured socket.
@@ -28860,17 +28938,13 @@ var init_container_backend = __esmMin((() => {
28860
28938
  const stripped = key.slice(KICI_AGENT_ENV_PREFIX.length);
28861
28939
  if (stripped.length > 0) agentEnvForwarded.push(`${stripped}=${value}`);
28862
28940
  }
28941
+ const fullLabels = scalerAgentLabels(labelSet, this.type, this.name, this.roles);
28863
28942
  let agentToken;
28864
- if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, labelSet, this.tokenTtlMs);
28943
+ if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, fullLabels, this.tokenTtlMs);
28865
28944
  const env = [
28866
28945
  `KICI_ORCHESTRATOR_URL=${orchestratorUrl}`,
28867
28946
  `KICI_AGENT_ID=${agentId}`,
28868
- `KICI_LABELS=${[
28869
- ...labelSet,
28870
- `kici:agent:${this.type}`,
28871
- `kici:scaler:${this.name}`,
28872
- ...resolveRoleLabels(this.roles)
28873
- ].join(",")}`,
28947
+ `KICI_LABELS=${fullLabels.join(",")}`,
28874
28948
  `KICI_SCALER_MANAGED=1`,
28875
28949
  `KICI_EXECUTION_MODE=bare-metal`,
28876
28950
  ...agentToken ? [`KICI_AGENT_TOKEN=${agentToken}`] : [],
@@ -29123,6 +29197,11 @@ var init_bare_metal_backend = __esmMin((() => {
29123
29197
  mode: "executable",
29124
29198
  reason: `agent binary for bare-metal scaler "${entry.name}"`
29125
29199
  }));
29200
+ requirements.push({
29201
+ type: "path-binary",
29202
+ name: "node",
29203
+ reason: `bare-metal scaler "${entry.name}" spawns the kici-agent node script. node must be on the orchestrator's PATH (it is forwarded to spawned agents). Ensure the orchestrator service's PATH includes your node install (e.g. the systemd unit's Environment=PATH covers the mise/nvm node bin dir).`
29204
+ });
29126
29205
  const sandboxViaGlobalEnv = process.env.KICI_AGENT_ENV_KICI_SANDBOX === "true";
29127
29206
  const sandboxViaLabelSet = entry.labelSets.some((ls) => ls.env && ls.env.KICI_SANDBOX === "true");
29128
29207
  if (sandboxViaGlobalEnv || sandboxViaLabelSet) requirements.push({
@@ -29161,15 +29240,11 @@ var init_bare_metal_backend = __esmMin((() => {
29161
29240
  const strippedKey = key.slice(KICI_AGENT_ENV_PREFIX.length);
29162
29241
  if (strippedKey.length > 0) env[strippedKey] = value;
29163
29242
  }
29164
- if (this.tokenStore) env.KICI_AGENT_TOKEN = await this.tokenStore.createEphemeral(agentId, labelSet, this.tokenTtlMs);
29243
+ const fullLabels = scalerAgentLabels(labelSet, this.type, this.name, this.roles);
29244
+ if (this.tokenStore) env.KICI_AGENT_TOKEN = await this.tokenStore.createEphemeral(agentId, fullLabels, this.tokenTtlMs);
29165
29245
  env.KICI_ORCHESTRATOR_URL = orchestratorUrl;
29166
29246
  env.KICI_AGENT_ID = agentId;
29167
- env.KICI_LABELS = [
29168
- ...labelSet,
29169
- `kici:agent:${this.type}`,
29170
- `kici:scaler:${this.name}`,
29171
- ...resolveRoleLabels(this.roles)
29172
- ].join(",");
29247
+ env.KICI_LABELS = fullLabels.join(",");
29173
29248
  env.KICI_SCALER_MANAGED = "1";
29174
29249
  env.KICI_EXECUTION_MODE = "bare-metal";
29175
29250
  env.KICI_PORT = "0";
@@ -29919,18 +29994,14 @@ var init_firecracker_backend = __esmMin((() => {
29919
29994
  if (chmodLoosenInterval) clearInterval(chmodLoosenInterval);
29920
29995
  }
29921
29996
  if (!ready) throw new Error(`Firecracker API socket not ready within 5s for agent ${agentId}`);
29997
+ const fullLabels = scalerAgentLabels(labelSet, this.type, this.name, this.roles);
29922
29998
  let agentToken;
29923
- if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, labelSet, this.tokenTtlMs);
29999
+ if (this.tokenStore) agentToken = await this.tokenStore.createEphemeral(agentId, fullLabels, this.tokenTtlMs);
29924
30000
  const acceptedEnv = this.buildForwardedEnv(matchedLabelSet, agentId);
29925
30001
  await api.putMmds({ latest: { "meta-data": {
29926
30002
  "kici-orchestrator-url": orchestratorUrl,
29927
30003
  "kici-agent-id": agentId,
29928
- "kici-labels": [
29929
- ...labelSet,
29930
- `kici:agent:${this.type}`,
29931
- `kici:scaler:${this.name}`,
29932
- ...resolveRoleLabels(this.roles)
29933
- ].join(","),
30004
+ "kici-labels": fullLabels.join(","),
29934
30005
  "kici-scaler-managed": "1",
29935
30006
  "kici-gateway-ip": this.gateway,
29936
30007
  ...agentToken ? { "kici-agent-token": agentToken } : {},
@@ -31007,6 +31078,29 @@ var init_machine_ledger = __esmMin((() => {
31007
31078
  * and manages the agent lifecycle from spawn to destroy.
31008
31079
  */
31009
31080
  /**
31081
+ * Resolved per-job resource amounts (cpus + bytes) for both `requests` and
31082
+ * `limits`. The scaler manager produces this from the job's declared resources
31083
+ * combined with the scaler's `defaults.resources`, applying the request<->limit
31084
+ * mirroring rule. Caps aggregate `requests`; backends use `limits`.
31085
+ */
31086
+ /**
31087
+ * Resolve the orchestrator WebSocket URL a scaler-spawned agent should dial.
31088
+ *
31089
+ * 1. Per-scaler `orchestratorUrl` (scalers.yaml) wins — required for container
31090
+ * agents (host.docker.internal / LAN IP) and Firecracker VMs (bridge gateway
31091
+ * IP), which cannot reach the orchestrator over the host's loopback.
31092
+ * 2. `KICI_ORCHESTRATOR_URL` env override.
31093
+ * 3. Default `ws://127.0.0.1:<orchestrator-port>/ws` — for local (bare-metal)
31094
+ * agents that share the host. The port is the orchestrator's own bind port
31095
+ * (`KICI_PORT`, default 4000), NOT the agent's 8080 default; pointing local
31096
+ * agents at 8080 leaves them unable to reach the orchestrator.
31097
+ */
31098
+ function resolveScalerOrchestratorUrl(configUrl, envUrl, port) {
31099
+ if (configUrl) return configUrl;
31100
+ if (envUrl) return envUrl;
31101
+ return `ws://127.0.0.1:${port ?? "4000"}/ws`;
31102
+ }
31103
+ /**
31010
31104
  * Apply the request<->limit mirroring rule: if only one side is set, copy it
31011
31105
  * to the other; if neither is set, return undefined; if both are set, leave them.
31012
31106
  *
@@ -31918,9 +32012,7 @@ var init_manager = __esmMin((() => {
31918
32012
  });
31919
32013
  }
31920
32014
  getOrchestratorUrl(backendName) {
31921
- const configUrl = this.scalerUrls.get(backendName);
31922
- if (configUrl) return configUrl;
31923
- return process.env.KICI_ORCHESTRATOR_URL ?? "ws://localhost:8080/ws";
32015
+ return resolveScalerOrchestratorUrl(this.scalerUrls.get(backendName), process.env.KICI_ORCHESTRATOR_URL, process.env.KICI_PORT);
31924
32016
  }
31925
32017
  /**
31926
32018
  * Create a per-agent event emitter closure to pass to backend.spawn().
@@ -40755,7 +40847,7 @@ async function initializeScaler(config, db, tokenStore) {
40755
40847
  }
40756
40848
  const toolErrors = validateRequiredTools(scalerConfig.scalers.flatMap((s) => {
40757
40849
  switch (s.type) {
40758
- case "container": return ContainerScalerBackend.getRequiredTools();
40850
+ case "container": return ContainerScalerBackend.getRequiredTools(s);
40759
40851
  case "bare-metal": return BareMetalScalerBackend.getRequiredTools(s);
40760
40852
  case "firecracker": return FirecrackerScalerBackend.getRequiredTools(s);
40761
40853
  default: return [];
@@ -41747,7 +41839,7 @@ async function bootstrapOrchestrator$1(config, hooks, options) {
41747
41839
  forcePathStyle: config.storage.forcePathStyle
41748
41840
  } : {
41749
41841
  type: "filesystem",
41750
- basePath: (config.webhookPayloadDir ?? "/var/lib/kici/cache") + "/logs"
41842
+ basePath: (config.webhookPayloadDir ?? resolveDataDir(config.dataDir) + "/cache") + "/logs"
41751
41843
  });
41752
41844
  const observerRegistry = new ObserverRegistry();
41753
41845
  let executionTrackerRef = null;
@@ -42547,6 +42639,7 @@ async function bootstrapOrchestrator$1(config, hooks, options) {
42547
42639
  }
42548
42640
  var logger$2;
42549
42641
  var init_orchestrator_core = __esmMin((() => {
42642
+ init_data_dir();
42550
42643
  init_reload();
42551
42644
  init_resolver();
42552
42645
  init_client();
@@ -43705,14 +43798,14 @@ var init_worker_core = __esmMin((() => {
43705
43798
  init_agent_handler();
43706
43799
  init_worker_status();
43707
43800
  init_agent_heartbeat();
43708
- ORCHESTRATOR_VERSION$1 = "0.1.8";
43709
- WORKER_BUILD_COMMIT = "5f626dda9";
43710
- WORKER_SDK_VERSION = "0.1.8";
43711
- WORKER_SDK_BUNDLE_HASH = "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128";
43712
- WORKER_SHARED_VERSION = "0.1.8";
43801
+ ORCHESTRATOR_VERSION$1 = "0.1.10";
43802
+ WORKER_BUILD_COMMIT = "6bf528b56";
43803
+ WORKER_SDK_VERSION = "0.1.10";
43804
+ WORKER_SDK_BUNDLE_HASH = "unknown";
43805
+ WORKER_SHARED_VERSION = "0.1.10";
43713
43806
  WORKER_SHARED_BUNDLE_HASH = "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae";
43714
- WORKER_ENGINE_VERSION = "0.1.8";
43715
- WORKER_ENGINE_BUNDLE_HASH = "b704538612ec796a5668765bd55e0eaeaf1bad8a2afa508f9e20ad7b9eb61d21";
43807
+ WORKER_ENGINE_VERSION = "0.1.10";
43808
+ WORKER_ENGINE_BUNDLE_HASH = "dff25bbd7602e853cec6e6e985c6c956ad773e245830273cd52950fff958d4bb";
43716
43809
  logger$1 = createLogger({ prefix: "worker" });
43717
43810
  DRAIN_TIMEOUT_MS = 3e5;
43718
43811
  }));
@@ -43736,14 +43829,14 @@ var init_worker_core = __esmMin((() => {
43736
43829
  * Graceful shutdown:
43737
43830
  * agent WS -> heartbeat -> HTTP -> DB
43738
43831
  */
43739
- const ORCHESTRATOR_VERSION = "0.1.8";
43740
- const BUILD_COMMIT = "5f626dda9";
43741
- const SDK_VERSION = "0.1.8";
43742
- const SDK_BUNDLE_HASH = "675aafe4de03e677785ef2794d8f34951b14e2bd1e6f0ea825953d6c2abfe128";
43743
- const SHARED_VERSION = "0.1.8";
43832
+ const ORCHESTRATOR_VERSION = "0.1.10";
43833
+ const BUILD_COMMIT = "6bf528b56";
43834
+ const SDK_VERSION = "0.1.10";
43835
+ const SDK_BUNDLE_HASH = "unknown";
43836
+ const SHARED_VERSION = "0.1.10";
43744
43837
  const SHARED_BUNDLE_HASH = "36a23a454fd75ec35f258d39f254543ec0451c6b543fe3618ce427e6e6369aae";
43745
- const ENGINE_VERSION = "0.1.8";
43746
- const ENGINE_BUNDLE_HASH = "b704538612ec796a5668765bd55e0eaeaf1bad8a2afa508f9e20ad7b9eb61d21";
43838
+ const ENGINE_VERSION = "0.1.10";
43839
+ const ENGINE_BUNDLE_HASH = "dff25bbd7602e853cec6e6e985c6c956ad773e245830273cd52950fff958d4bb";
43747
43840
  const otelSdk = initTelemetry({
43748
43841
  serviceName: "kici-orchestrator",
43749
43842
  otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
@@ -29,6 +29,39 @@ import type { PendingDynamicTracker } from '../cache/pending-dynamics.js';
29
29
  import type { CacheStorage } from '../storage/types.js';
30
30
  import type { AgentMetricsAggregator } from '../metrics/agent-metrics-aggregator.js';
31
31
  import type { AgentApiRegistry } from './agent-api-registry.js';
32
+ /**
33
+ * Run the three token-bound authorization gates against a wire-supplied
34
+ * `agent.register` payload. The gates are identical at first register
35
+ * (Phase 2 / `pendingRegistration`) and on every subsequent re-register
36
+ * arriving on the same registered WS — every code path that mutates the
37
+ * registry's `agentId → entry` mapping for an authenticated WS MUST run
38
+ * them, otherwise the §5.3 / §5.1 invariants only hold for the very first
39
+ * register and a re-register can silently overwrite the authority.
40
+ *
41
+ * Gates:
42
+ * 1. Token-scope subset — wire labels MUST be a subset of
43
+ * `agent_tokens.labels` when that set is non-null. Closes the WS with
44
+ * `WS_CLOSE_AGENT_AUTH_FAILED` listing the elevated labels.
45
+ * 2. Ephemeral identity-binding — for `agent_type === 'ephemeral'`,
46
+ * wire `agentId` MUST equal `tokenCreatedBy` (the scaler-spawned
47
+ * agentId the token was issued for). Closes the WS with
48
+ * `WS_CLOSE_AGENT_AUTH_FAILED`.
49
+ * 3. Static-token agentId-collision — a different `tokenId` must not
50
+ * already claim the wire `agentId`. Closes the WS with
51
+ * `WS_CLOSE_INVALID_MESSAGE`.
52
+ *
53
+ * Returns `true` when every gate passed and the caller may proceed;
54
+ * returns `false` after closing the WS on the first violation, in which
55
+ * case the caller MUST stop processing.
56
+ */
57
+ /**
58
+ * WebSocket close reasons are capped at 123 UTF-8 bytes (RFC 6455 §5.5.1).
59
+ * A reason longer than that makes `ws.close()` throw a RangeError, which —
60
+ * thrown from an async message handler — surfaces as an unhandled rejection
61
+ * and skips the close entirely. Truncate on a byte boundary and drop any
62
+ * trailing partial multi-byte char.
63
+ */
64
+ export declare function truncateCloseReason(reason: string): string;
32
65
  export interface AgentWsHandlerDeps {
33
66
  registry: AgentRegistry;
34
67
  dispatcher: Dispatcher;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kici-dev/orchestrator",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Customer-deployable orchestrator for the KiCI CI/CD stack. Receives webhook events (direct or via Platform relay), matches triggers against the lock file, and dispatches jobs to connected agents.",
5
5
  "keywords": [
6
6
  "kici",
@@ -82,8 +82,8 @@
82
82
  "ws": "^8.20.0",
83
83
  "yaml": "^2.8.3",
84
84
  "zod": "^4.3.6",
85
- "@kici-dev/engine": "0.1.8",
86
- "@kici-dev/shared": "0.1.8"
85
+ "@kici-dev/shared": "0.1.10",
86
+ "@kici-dev/engine": "0.1.10"
87
87
  },
88
88
  "kici": {
89
89
  "metrics": {
package/sbom.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@kici-dev/orchestrator@0.1.8",
6
- "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Forchestrator/0.1.8/e0cb98b7-4fb4-4ac7-acf3-a268022d05fc",
5
+ "name": "@kici-dev/orchestrator@0.1.10",
6
+ "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Forchestrator/0.1.10/1e7943f5-2300-4aee-b905-0121c011771c",
7
7
  "creationInfo": {
8
- "created": "2026-05-26T14:55:34Z",
8
+ "created": "2026-05-26T17:26:36Z",
9
9
  "creators": [
10
10
  "Tool: kici-sbom-generator"
11
11
  ]
@@ -1477,9 +1477,9 @@
1477
1477
  "homepage": "https://ericsmekens.github.io/jsep/tree/master/packages/regex#readme"
1478
1478
  },
1479
1479
  {
1480
- "SPDXID": "SPDXRef-Package--kici-dev-engine-0.1.8",
1480
+ "SPDXID": "SPDXRef-Package--kici-dev-engine-0.1.10",
1481
1481
  "name": "@kici-dev/engine",
1482
- "versionInfo": "0.1.8",
1482
+ "versionInfo": "0.1.10",
1483
1483
  "downloadLocation": "NOASSERTION",
1484
1484
  "filesAnalyzed": false,
1485
1485
  "licenseConcluded": "NOASSERTION",
@@ -1490,7 +1490,7 @@
1490
1490
  {
1491
1491
  "referenceCategory": "PACKAGE-MANAGER",
1492
1492
  "referenceType": "purl",
1493
- "referenceLocator": "pkg:npm/%40kici-dev/engine@0.1.8"
1493
+ "referenceLocator": "pkg:npm/%40kici-dev/engine@0.1.10"
1494
1494
  }
1495
1495
  ],
1496
1496
  "description": "Shared business logic for the KiCI CI/CD stack: protocol, triggers, state machine, and provider interfaces used by the Platform relay, orchestrator, and compiler.",
@@ -1499,7 +1499,7 @@
1499
1499
  {
1500
1500
  "SPDXID": "SPDXRef-RootPackage",
1501
1501
  "name": "@kici-dev/orchestrator",
1502
- "versionInfo": "0.1.8",
1502
+ "versionInfo": "0.1.10",
1503
1503
  "downloadLocation": "NOASSERTION",
1504
1504
  "filesAnalyzed": false,
1505
1505
  "licenseConcluded": "NOASSERTION",
@@ -1510,16 +1510,16 @@
1510
1510
  {
1511
1511
  "referenceCategory": "PACKAGE-MANAGER",
1512
1512
  "referenceType": "purl",
1513
- "referenceLocator": "pkg:npm/%40kici-dev/orchestrator@0.1.8"
1513
+ "referenceLocator": "pkg:npm/%40kici-dev/orchestrator@0.1.10"
1514
1514
  }
1515
1515
  ],
1516
1516
  "description": "Customer-deployable orchestrator for the KiCI CI/CD stack. Receives webhook events (direct or via Platform relay), matches triggers against the lock file, and dispatches jobs to connected agents.",
1517
1517
  "homepage": "https://kici.dev"
1518
1518
  },
1519
1519
  {
1520
- "SPDXID": "SPDXRef-Package--kici-dev-shared-0.1.8",
1520
+ "SPDXID": "SPDXRef-Package--kici-dev-shared-0.1.10",
1521
1521
  "name": "@kici-dev/shared",
1522
- "versionInfo": "0.1.8",
1522
+ "versionInfo": "0.1.10",
1523
1523
  "downloadLocation": "NOASSERTION",
1524
1524
  "filesAnalyzed": false,
1525
1525
  "licenseConcluded": "NOASSERTION",
@@ -1530,7 +1530,7 @@
1530
1530
  {
1531
1531
  "referenceCategory": "PACKAGE-MANAGER",
1532
1532
  "referenceType": "purl",
1533
- "referenceLocator": "pkg:npm/%40kici-dev/shared@0.1.8"
1533
+ "referenceLocator": "pkg:npm/%40kici-dev/shared@0.1.10"
1534
1534
  }
1535
1535
  ],
1536
1536
  "description": "Shared utilities for the KiCI CI/CD stack — logging, zx setup, crypto, telemetry, health and metrics routes. No business logic.",
@@ -10682,17 +10682,17 @@
10682
10682
  "relationshipType": "DEPENDS_ON"
10683
10683
  },
10684
10684
  {
10685
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.8",
10685
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.10",
10686
10686
  "relatedSpdxElement": "SPDXRef-Package-jsonpath-plus-10.4.0",
10687
10687
  "relationshipType": "DEPENDS_ON"
10688
10688
  },
10689
10689
  {
10690
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.8",
10690
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.10",
10691
10691
  "relatedSpdxElement": "SPDXRef-Package-picomatch-4.0.4",
10692
10692
  "relationshipType": "DEPENDS_ON"
10693
10693
  },
10694
10694
  {
10695
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.8",
10695
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.10",
10696
10696
  "relatedSpdxElement": "SPDXRef-Package-zod-4.3.6",
10697
10697
  "relationshipType": "DEPENDS_ON"
10698
10698
  },
@@ -10728,12 +10728,12 @@
10728
10728
  },
10729
10729
  {
10730
10730
  "spdxElementId": "SPDXRef-RootPackage",
10731
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.1.8",
10731
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.1.10",
10732
10732
  "relationshipType": "DEPENDS_ON"
10733
10733
  },
10734
10734
  {
10735
10735
  "spdxElementId": "SPDXRef-RootPackage",
10736
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-shared-0.1.8",
10736
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-shared-0.1.10",
10737
10737
  "relationshipType": "DEPENDS_ON"
10738
10738
  },
10739
10739
  {
@@ -10837,102 +10837,102 @@
10837
10837
  "relationshipType": "DEPENDS_ON"
10838
10838
  },
10839
10839
  {
10840
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10840
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10841
10841
  "relatedSpdxElement": "SPDXRef-Package--aws-sdk-client-s3-3.1038.0",
10842
10842
  "relationshipType": "DEPENDS_ON"
10843
10843
  },
10844
10844
  {
10845
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10845
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10846
10846
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-api-1.9.1",
10847
10847
  "relationshipType": "DEPENDS_ON"
10848
10848
  },
10849
10849
  {
10850
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10850
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10851
10851
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-exporter-metrics-otlp-http-0.217.0",
10852
10852
  "relationshipType": "DEPENDS_ON"
10853
10853
  },
10854
10854
  {
10855
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10855
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10856
10856
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-exporter-prometheus-0.217.0",
10857
10857
  "relationshipType": "DEPENDS_ON"
10858
10858
  },
10859
10859
  {
10860
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10860
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10861
10861
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-exporter-trace-otlp-http-0.217.0",
10862
10862
  "relationshipType": "DEPENDS_ON"
10863
10863
  },
10864
10864
  {
10865
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10865
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10866
10866
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-instrumentation-runtime-node-0.28.0",
10867
10867
  "relationshipType": "DEPENDS_ON"
10868
10868
  },
10869
10869
  {
10870
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10870
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10871
10871
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-resources-2.7.0",
10872
10872
  "relationshipType": "DEPENDS_ON"
10873
10873
  },
10874
10874
  {
10875
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10875
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10876
10876
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-sdk-node-0.217.0",
10877
10877
  "relationshipType": "DEPENDS_ON"
10878
10878
  },
10879
10879
  {
10880
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10880
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10881
10881
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-semantic-conventions-1.40.0",
10882
10882
  "relationshipType": "DEPENDS_ON"
10883
10883
  },
10884
10884
  {
10885
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10885
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10886
10886
  "relatedSpdxElement": "SPDXRef-Package-diff-7.0.0",
10887
10887
  "relationshipType": "DEPENDS_ON"
10888
10888
  },
10889
10889
  {
10890
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10890
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10891
10891
  "relatedSpdxElement": "SPDXRef-Package-hono-4.12.18",
10892
10892
  "relationshipType": "DEPENDS_ON"
10893
10893
  },
10894
10894
  {
10895
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10895
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10896
10896
  "relatedSpdxElement": "SPDXRef-Package-kysely-0.29.0",
10897
10897
  "relationshipType": "DEPENDS_ON"
10898
10898
  },
10899
10899
  {
10900
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10900
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10901
10901
  "relatedSpdxElement": "SPDXRef-Package-oxc-transform-0.128.0",
10902
10902
  "relationshipType": "DEPENDS_ON"
10903
10903
  },
10904
10904
  {
10905
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10905
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10906
10906
  "relatedSpdxElement": "SPDXRef-Package-pg-8.20.0",
10907
10907
  "relationshipType": "DEPENDS_ON"
10908
10908
  },
10909
10909
  {
10910
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10910
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10911
10911
  "relatedSpdxElement": "SPDXRef-Package-picocolors-1.1.1",
10912
10912
  "relationshipType": "DEPENDS_ON"
10913
10913
  },
10914
10914
  {
10915
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10915
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10916
10916
  "relatedSpdxElement": "SPDXRef-Package-winston-daily-rotate-file-5.0.0",
10917
10917
  "relationshipType": "DEPENDS_ON"
10918
10918
  },
10919
10919
  {
10920
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10920
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10921
10921
  "relatedSpdxElement": "SPDXRef-Package-winston-3.19.0",
10922
10922
  "relationshipType": "DEPENDS_ON"
10923
10923
  },
10924
10924
  {
10925
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10925
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10926
10926
  "relatedSpdxElement": "SPDXRef-Package-yaml-2.8.3",
10927
10927
  "relationshipType": "DEPENDS_ON"
10928
10928
  },
10929
10929
  {
10930
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10930
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10931
10931
  "relatedSpdxElement": "SPDXRef-Package-zod-4.3.6",
10932
10932
  "relationshipType": "DEPENDS_ON"
10933
10933
  },
10934
10934
  {
10935
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.8",
10935
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.10",
10936
10936
  "relatedSpdxElement": "SPDXRef-Package-zx-8.8.5",
10937
10937
  "relationshipType": "DEPENDS_ON"
10938
10938
  },