@kici-dev/agent 0.1.22 → 0.1.23

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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Agent-process interception of the bootstrap bring-up API methods.
3
+ *
4
+ * `ctx.kici.bootstrap.ensureInitRunner` / `preBootSend` are relayed from the
5
+ * workflow sandbox as `agent.api.request` IPC. The agent process intercepts
6
+ * those two methods HERE (rather than relaying them straight to the
7
+ * orchestrator) so the SSH transport — and the bring-up key / bootstrap token
8
+ * the orchestrator hands back — stay in the agent process, never reaching user
9
+ * workflow code. Every other method falls through to the orchestrator relay
10
+ * unchanged.
11
+ */
12
+ import { type ApiTransport, type EnsureInitRunnerDeps } from './ensure-init-runner.js';
13
+ /**
14
+ * Wrap the orchestrator API transport so the two bootstrap methods are handled
15
+ * in-process (SSH transport here; privileged resolve relayed to the
16
+ * orchestrator). `relay` is the raw orchestrator transport (the WS
17
+ * `sendApiRequest`).
18
+ */
19
+ export declare function withBootstrapInterception(relay: ApiTransport, deps?: EnsureInitRunnerDeps): (method: string, params?: Record<string, unknown>) => Promise<unknown>;
20
+ //# sourceMappingURL=api-intercept.d.ts.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Agent-side init-runner bring-up.
3
+ *
4
+ * Runs in the AGENT process (never the workflow sandbox), so the bring-up SSH
5
+ * key and bootstrap token the orchestrator hands back never reach user
6
+ * workflow code. The flow:
7
+ *
8
+ * 1. Call the orchestrator's privileged `kici.ensureInitRunner` handler — it
9
+ * gates on this agent's `kici:capability:ssh-transport` capability,
10
+ * resolves the target's reach + SSH key, mints a single-use bootstrap
11
+ * token, audits, and returns the material (or `{ broughtUp: false }` when
12
+ * the target already has a live agent).
13
+ * 2. Over SSH (ephemeral key, never on disk): drop a launcher onto the target
14
+ * that starts `kici-agent` with the bootstrap env (token + agent id +
15
+ * orchestrator URL + labels), and start it detached.
16
+ *
17
+ * The init-runner then connects → `auth.request` (bootstrap token) →
18
+ * `agent.register` auto-enroll as a temporary `kici:init` agent.
19
+ */
20
+ import { type SshDeps } from './ssh-exec.js';
21
+ /** Transport that relays an API request to the orchestrator and awaits the result. */
22
+ export type ApiTransport = (method: string, params: Record<string, unknown>) => Promise<unknown>;
23
+ export interface EnsureInitRunnerDeps extends SshDeps {
24
+ /**
25
+ * The command used to start the init-runner on the target. Defaults to
26
+ * `kici-agent` (resolved on the target's PATH). Override for a rescue env
27
+ * that stages the binary at a fixed path.
28
+ */
29
+ agentCommand?: string;
30
+ }
31
+ /**
32
+ * Bring up a temporary init-runner on `targetAgentId`. Returns `{ broughtUp }`:
33
+ * false when the target already had a live agent (the orchestrator no-op'd),
34
+ * true when this call dropped + started the init-runner.
35
+ */
36
+ export declare function ensureInitRunner(transport: ApiTransport, targetAgentId: string, deps?: EnsureInitRunnerDeps): Promise<{
37
+ broughtUp: boolean;
38
+ }>;
39
+ //# sourceMappingURL=ensure-init-runner.d.ts.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Agent-side pre-boot input send (dropbear / initramfs unlock).
3
+ *
4
+ * Runs in the AGENT process. Asks the orchestrator's privileged
5
+ * `kici.preBootSend` handler to gate on the `kici:capability:ssh-transport`
6
+ * capability, resolve the input secret (e.g. a LUKS passphrase), and audit;
7
+ * then pipes the resolved input to the target's pre-boot SSH endpoint
8
+ * (default dropbear port 2222, forced `cryptroot-unlock`). The unlock drops
9
+ * the session as the box boots — success is the send completing, not the SSH
10
+ * exit code; the caller composes a host-alive wait to confirm boot.
11
+ */
12
+ import { type SshDeps } from './ssh-exec.js';
13
+ import type { ApiTransport } from './ensure-init-runner.js';
14
+ export interface PreBootSendOpts {
15
+ inputSecret: string;
16
+ port?: number;
17
+ command?: string;
18
+ }
19
+ /**
20
+ * Ship a pre-boot input to the target's dropbear/initramfs SSH channel. The
21
+ * input plaintext is resolved server-side and never logged. Resolves once the
22
+ * send completes (the SSH session legitimately drops as the box boots).
23
+ */
24
+ export declare function preBootSend(transport: ApiTransport, targetAgentId: string, opts: PreBootSendOpts, deps?: SshDeps): Promise<void>;
25
+ //# sourceMappingURL=pre-boot-send.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Agent-facing host reach metadata: how to SSH to a target for bootstrap.
3
+ *
4
+ * This is the subset the agent's SSH helper needs — the connection
5
+ * coordinates only. It deliberately does NOT carry the `sshKeySecret` ref:
6
+ * the orchestrator resolves the scoped secret server-side and hands the agent
7
+ * the already-resolved private key separately, so the secret ref never reaches
8
+ * the agent. `sshPort` is the OS-sshd port for bring-up; a pre-boot dropbear
9
+ * port is supplied per-call via `SshExecOpts.port`.
10
+ */
11
+ export interface HostReach {
12
+ agentId: string;
13
+ address: string | null;
14
+ sshUser: string | null;
15
+ sshPort: number | null;
16
+ }
17
+ //# sourceMappingURL=reach.d.ts.map
@@ -0,0 +1,58 @@
1
+ import type { HostReach } from './reach.js';
2
+ /** Host-key verification mode for the SSH connection. */
3
+ export type SshHostKeyMode = 'accept-new' | 'strict';
4
+ /** Result of an SSH invocation: exit code is reported, never thrown away. */
5
+ export interface SshResult {
6
+ exitCode: number;
7
+ stdout: string;
8
+ stderr: string;
9
+ }
10
+ export interface SshExecOpts {
11
+ /** Piped to the remote command's stdin (e.g. a LUKS passphrase to cryptroot-unlock). */
12
+ stdin?: string;
13
+ /** Override the default SSH port (22). E.g. 2222 for a dropbear initramfs prompt. */
14
+ port?: number;
15
+ /**
16
+ * Host-key verification. Defaults to `accept-new` — the dropbear initramfs
17
+ * host key differs from the OS sshd key, so a pinned OS entry must not be
18
+ * reused. `strict` enforces a known_hosts match.
19
+ */
20
+ hostKeyMode?: SshHostKeyMode;
21
+ }
22
+ /**
23
+ * Low-level process spawn boundary, injectable so unit tests can assert the
24
+ * exact argv / env / stdin without running a real `ssh`. Mirrors the relevant
25
+ * subset of `child_process.spawn`'s contract.
26
+ */
27
+ export interface SpawnFn {
28
+ (command: string, args: string[], opts: {
29
+ env: NodeJS.ProcessEnv;
30
+ stdin?: string;
31
+ }): Promise<SshResult>;
32
+ }
33
+ /** Default spawn boundary backed by `node:child_process.spawn`. */
34
+ export declare const defaultSpawn: SpawnFn;
35
+ /** Deps for the SSH helper — the spawn boundary is injectable for tests. */
36
+ export interface SshDeps {
37
+ spawnFn?: SpawnFn;
38
+ }
39
+ /**
40
+ * Run a command on the target over SSH using an ephemeral, in-memory key.
41
+ *
42
+ * The key is loaded into a per-call ssh-agent (never written to disk) and the
43
+ * agent is killed in `finally`. The remote command's exit code / stdout /
44
+ * stderr are returned verbatim — a non-zero exit is reported, not swallowed
45
+ * (a pre-boot unlock legitimately drops the session, so the caller decides
46
+ * what a "success" looks like).
47
+ */
48
+ export declare function sshExec(reach: HostReach, privateKey: string, command: string, opts?: SshExecOpts, deps?: SshDeps): Promise<SshResult>;
49
+ /**
50
+ * Ship local bytes to a remote path over SSH (`ssh 'cat > path'`), using the
51
+ * same ephemeral-key discipline. Throws on a non-zero exit (a push must
52
+ * succeed end-to-end, unlike a pre-boot unlock).
53
+ */
54
+ export declare function sshPush(reach: HostReach, privateKey: string, localBytes: string, remotePath: string, opts?: {
55
+ port?: number;
56
+ hostKeyMode?: SshHostKeyMode;
57
+ }, deps?: SshDeps): Promise<void>;
58
+ //# sourceMappingURL=ssh-exec.d.ts.map
@@ -215,6 +215,18 @@ export declare class JobRunner {
215
215
  * failure diagnostics, and send the terminal `job.status` message.
216
216
  */
217
217
  private reportExecutionResult;
218
+ /**
219
+ * Handle a bring-up job: bring up a temporary init-runner on a declared-but-
220
+ * un-agented host over SSH (fresh-box bootstrap convergence). The orchestrator
221
+ * dispatches this synthetic `__bringup__` job to an agent holding the
222
+ * `kici:capability:ssh-transport` capability; here we run the agent-side
223
+ * `ensureInitRunner` helper (the privileged resolve is relayed to the
224
+ * orchestrator, the SSH transport happens in this agent process — never
225
+ * reaching workflow code). No clone, no sandbox — the init-runner then
226
+ * connects under the target's agent id and the orchestrator's pinned-hold
227
+ * drains the target's bootstrap steps onto it.
228
+ */
229
+ private handleBringupJob;
218
230
  /**
219
231
  * Handle a build-only job.
220
232
  *
@@ -1,11 +1,13 @@
1
- import type { RuleContext } from '@kici-dev/sdk';
1
+ import type { RuleContext, FanoutPosition } from '@kici-dev/sdk';
2
2
  /**
3
3
  * Create RuleContext for agent-side rule evaluation.
4
4
  *
5
5
  * @param event - Event payload from the dispatch message
6
6
  * @param changedFiles - List of files changed in this event
7
7
  * @param env - Merged environment variables
8
+ * @param dispatchInputs - Operator dispatch inputs (`ctx.dispatchInputs`)
9
+ * @param fanout - Fan-out position (`ctx.fanout`); undefined on a non-fan-out job
8
10
  */
9
- export declare function createRuleContext(event: Record<string, unknown>, changedFiles?: string[], env?: Record<string, string | undefined>): RuleContext;
11
+ export declare function createRuleContext(event: Record<string, unknown>, changedFiles?: string[], env?: Record<string, string | undefined>, dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>, fanout?: FanoutPosition): RuleContext;
10
12
  export { evaluateRules, type RuleEvaluationResult } from '@kici-dev/sdk';
11
13
  //# sourceMappingURL=rule-evaluator.d.ts.map
@@ -410,6 +410,20 @@ export interface JobExecutionRequest {
410
410
  platform?: string;
411
411
  arch?: string;
412
412
  };
413
+ /**
414
+ * Operator-supplied, validated + coerced + defaulted workflow-dispatch inputs
415
+ * (from `kici run --input`), exposed to steps + rules as `ctx.dispatchInputs`.
416
+ * Absent for webhook runs.
417
+ */
418
+ dispatchInputs?: Record<string, unknown>;
419
+ /**
420
+ * For a fan-out child (`runsOnAll` host or matrix combination): the 0-based
421
+ * deterministic position in the fan-out, assembled into `ctx.fanout`. Absent
422
+ * for non-fan-out jobs.
423
+ */
424
+ fanoutIndex?: number;
425
+ /** For a fan-out child: the number of children in this fan-out. */
426
+ fanoutTotal?: number;
413
427
  /** Secrets to merge into step environment (highest precedence). */
414
428
  secrets?: Record<string, string>;
415
429
  /** Namespaced secrets by context name for ctx.secrets['context-name'].KEY access. */
@@ -5,7 +5,7 @@
5
5
  * Extracted for testability: the workflow-runner's main() handles IPC, clone, deps,
6
6
  * module loading, and calls this loop for step execution with hooks.
7
7
  */
8
- import type { Step, StepContext, HookInput, OutputsMap, StepSecretMountRecord } from '@kici-dev/sdk';
8
+ import type { Step, StepContext, HookInput, OutputsMap, StepSecretMountRecord, FanoutPosition } from '@kici-dev/sdk';
9
9
  import { CheckMode } from '@kici-dev/engine';
10
10
  import type { RunnerToAgentMessage } from './ipc-protocol.js';
11
11
  import type { SandboxStepResult } from './types.js';
@@ -36,6 +36,10 @@ export interface StepLoopOptions {
36
36
  event: Record<string, unknown>;
37
37
  /** Environment variables for rule context. */
38
38
  env: Record<string, string | undefined>;
39
+ /** Operator dispatch inputs for the rule context (`ctx.dispatchInputs`). */
40
+ dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>;
41
+ /** Fan-out position for the rule context (`ctx.fanout`); undefined on a non-fan-out job. */
42
+ fanout?: FanoutPosition;
39
43
  /** Job-level hooks. */
40
44
  jobHooks?: JobHooks;
41
45
  /**
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { ExecutionJobStatus } from '@kici-dev/engine';
17
17
  import type { StepContext } from '@kici-dev/sdk';
18
- import type { NeedsContext } from '@kici-dev/sdk';
18
+ import type { NeedsContext, FanoutPosition } from '@kici-dev/sdk';
19
19
  import type { OutputsMap, StepRefMap, TrackedStepSecrets } from '@kici-dev/sdk';
20
20
  import type { RunnerToAgentMessage, JobExecutionRequest } from './ipc-protocol.js';
21
21
  import { LogMasker } from './log-masker.js';
@@ -36,6 +36,12 @@ export declare function buildStepNeedsContext(declaredNeeds: readonly unknown[]
36
36
  * inside this process with full shell access.
37
37
  */
38
38
  export declare function createSandboxStepContext(workDir: string, stepIndex: number, stepName: string, request: JobExecutionRequest, maskedSendFn: (msg: RunnerToAgentMessage) => void, outputsMap: OutputsMap, refMap: StepRefMap, operatorSecretKeys: Set<string>, secretOutputs: Map<string, string>, jobOutputsMap: OutputsMap, secrets: TrackedStepSecrets, masker: LogMasker): StepContext;
39
+ /**
40
+ * Derive the fan-out position (`ctx.fanout`) from a dispatch request. Returns
41
+ * `undefined` for a non-fan-out job (no `fanoutTotal`), so `ctx.fanout` is only
42
+ * set for `runsOnAll` host children and matrix combinations.
43
+ */
44
+ export declare function deriveFanout(request: JobExecutionRequest): FanoutPosition | undefined;
39
45
  /** Raw provider webhook body for ctx.rawPayload — nested in the envelope. */
40
46
  export declare function rawPayloadFromEvent(event: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
41
47
  //# sourceMappingURL=workflow-runner.d.ts.map
package/dist/server.js CHANGED
@@ -1310,14 +1310,14 @@ var init_console_capture = __esmMin((() => {
1310
1310
  init_console_capture();
1311
1311
  function safe(name, fallback = "unknown") {
1312
1312
  switch (name) {
1313
- case "version": return "0.1.22";
1314
- case "buildCommit": return "5afd16303";
1315
- case "sdkVersion": return "0.1.22";
1316
- case "sdkBundleHash": return "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
1317
- case "sharedVersion": return "0.1.22";
1313
+ case "version": return "0.1.23";
1314
+ case "buildCommit": return "4465935bb";
1315
+ case "sdkVersion": return "0.1.23";
1316
+ case "sdkBundleHash": return "3a83610d2d122b9f9b0f924b225a47050d0b35f00a8f3256a3e15f3ff3bf7ddc";
1317
+ case "sharedVersion": return "0.1.23";
1318
1318
  case "sharedBundleHash": return "991385c024392c395d3eb8a68946ef8ed3fcba96f2652f21c3164a54eafa1b1b";
1319
- case "engineVersion": return "0.1.22";
1320
- case "engineBundleHash": return "ff74a3afb2b1db2870c03d47543642f64d04b660bebdcde6681f76d874f26757";
1319
+ case "engineVersion": return "0.1.23";
1320
+ case "engineBundleHash": return "4a0566c709180a6a8744ff3281640e596b8e661ddceb81327cd37d232d9768d5";
1321
1321
  default: return fallback;
1322
1322
  }
1323
1323
  }
@@ -2080,8 +2080,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
2080
2080
  }
2081
2081
  var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
2082
2082
  var init_workflow_loader = __esmMin((() => {
2083
- AGENT_SDK_VERSION = "0.1.22";
2084
- AGENT_SDK_BUNDLE_HASH = "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
2083
+ AGENT_SDK_VERSION = "0.1.23";
2084
+ AGENT_SDK_BUNDLE_HASH = "3a83610d2d122b9f9b0f924b225a47050d0b35f00a8f3256a3e15f3ff3bf7ddc";
2085
2085
  hookRegistered = false;
2086
2086
  }));
2087
2087
  //#endregion
@@ -2566,6 +2566,274 @@ var init_init_runner = __esmMin((() => {
2566
2566
  init_timeout_util();
2567
2567
  }));
2568
2568
  //#endregion
2569
+ //#region src/bootstrap/ssh-exec.ts
2570
+ /**
2571
+ * Agent-side SSH helper for bootstrap bring-up.
2572
+ *
2573
+ * Lifts the discipline from `infra/prod/hw/ssh.sh` + the deploy-prod
2574
+ * `runOnBox` helper into the agent: the bring-up private key is piped into an
2575
+ * **ephemeral ssh-agent** (`ssh-add -` reads it from stdin) and the agent is
2576
+ * torn down in a `finally`, so the key never lands on disk and never enters a
2577
+ * long-lived process environment. The agent only holds the key for the
2578
+ * lifetime of the one `ssh` / `scp` invocation.
2579
+ *
2580
+ * `sshExec` runs a remote command; `sshPush` ships local bytes to a remote
2581
+ * path (`ssh 'cat > path'`). Both accept a resolved private key string
2582
+ * (supplied by the caller — the orchestrator resolves the scoped secret and
2583
+ * hands the key down). `sshExec` supports `{ stdin, port, hostKeyMode }` so it
2584
+ * can also drive a pre-boot dropbear / initramfs prompt (a forced-command
2585
+ * endpoint such as `cryptroot-unlock` on port 2222, which accepts the unlock
2586
+ * input on stdin and uses a transient host key distinct from the OS sshd key).
2587
+ */
2588
+ /** Common `-o` flags every bootstrap SSH connection carries. */
2589
+ function baseSshOptions(hostKeyMode) {
2590
+ return [
2591
+ "-o",
2592
+ `StrictHostKeyChecking=${HOST_KEY_FLAG[hostKeyMode]}`,
2593
+ "-o",
2594
+ "ConnectTimeout=10",
2595
+ "-o",
2596
+ "BatchMode=yes"
2597
+ ];
2598
+ }
2599
+ /** Resolve the `user@address` target + port from reach metadata. */
2600
+ function resolveTarget(reach, portOverride) {
2601
+ if (!reach.address) throw new Error(`host ${reach.agentId} has no SSH reach address declared`);
2602
+ const user = reach.sshUser ?? SSH_USER_DEFAULT;
2603
+ const port = portOverride ?? reach.sshPort ?? SSH_PORT_DEFAULT;
2604
+ return {
2605
+ dest: `${user}@${reach.address}`,
2606
+ port
2607
+ };
2608
+ }
2609
+ /**
2610
+ * Run a command on the target over SSH using an ephemeral, in-memory key.
2611
+ *
2612
+ * The key is loaded into a per-call ssh-agent (never written to disk) and the
2613
+ * agent is killed in `finally`. The remote command's exit code / stdout /
2614
+ * stderr are returned verbatim — a non-zero exit is reported, not swallowed
2615
+ * (a pre-boot unlock legitimately drops the session, so the caller decides
2616
+ * what a "success" looks like).
2617
+ */
2618
+ async function sshExec(reach, privateKey, command, opts = {}, deps = {}) {
2619
+ const spawnFn = deps.spawnFn ?? defaultSpawn;
2620
+ const hostKeyMode = opts.hostKeyMode ?? "accept-new";
2621
+ const { dest, port } = resolveTarget(reach, opts.port);
2622
+ return withEphemeralAgent(privateKey, spawnFn, async (env) => {
2623
+ return spawnFn("ssh", [
2624
+ ...baseSshOptions(hostKeyMode),
2625
+ "-p",
2626
+ String(port),
2627
+ dest,
2628
+ command
2629
+ ], {
2630
+ env,
2631
+ stdin: opts.stdin
2632
+ });
2633
+ });
2634
+ }
2635
+ /**
2636
+ * Ship local bytes to a remote path over SSH (`ssh 'cat > path'`), using the
2637
+ * same ephemeral-key discipline. Throws on a non-zero exit (a push must
2638
+ * succeed end-to-end, unlike a pre-boot unlock).
2639
+ */
2640
+ async function sshPush(reach, privateKey, localBytes, remotePath, opts = {}, deps = {}) {
2641
+ const spawnFn = deps.spawnFn ?? defaultSpawn;
2642
+ const hostKeyMode = opts.hostKeyMode ?? "accept-new";
2643
+ const { dest, port } = resolveTarget(reach, opts.port);
2644
+ const result = await withEphemeralAgent(privateKey, spawnFn, async (env) => {
2645
+ return spawnFn("ssh", [
2646
+ ...baseSshOptions(hostKeyMode),
2647
+ "-p",
2648
+ String(port),
2649
+ dest,
2650
+ `cat > '${remotePath.replace(/'/g, `'\\''`)}'`
2651
+ ], {
2652
+ env,
2653
+ stdin: localBytes
2654
+ });
2655
+ });
2656
+ if (result.exitCode !== 0) throw new Error(`sshPush(${reach.agentId}:${remotePath}): exit ${result.exitCode}${result.stderr ? `\n${result.stderr}` : ""}`);
2657
+ }
2658
+ /**
2659
+ * Start a per-call ephemeral ssh-agent, load the key via stdin (never a file),
2660
+ * run `body` with `SSH_AUTH_SOCK` in env, and kill the agent in `finally`.
2661
+ */
2662
+ async function withEphemeralAgent(privateKey, spawnFn, body) {
2663
+ const baseEnv = { ...process.env };
2664
+ const start = await spawnFn("ssh-agent", ["-s"], { env: baseEnv });
2665
+ if (start.exitCode !== 0) throw new Error(`ssh-agent start failed: exit ${start.exitCode}\n${start.stderr}`);
2666
+ const sock = parseAgentSocket(start.stdout);
2667
+ const pid = parseAgentPid(start.stdout);
2668
+ const agentEnv = {
2669
+ ...baseEnv,
2670
+ SSH_AUTH_SOCK: sock,
2671
+ ...pid ? { SSH_AGENT_PID: pid } : {},
2672
+ SSH_ASKPASS: "/bin/false",
2673
+ DISPLAY: ""
2674
+ };
2675
+ try {
2676
+ const add = await spawnFn("ssh-add", ["-"], {
2677
+ env: agentEnv,
2678
+ stdin: privateKey.endsWith("\n") ? privateKey : `${privateKey}\n`
2679
+ });
2680
+ if (add.exitCode !== 0) throw new Error(`ssh-add failed: exit ${add.exitCode}\n${add.stderr}`);
2681
+ return await body(agentEnv);
2682
+ } finally {
2683
+ await spawnFn("ssh-agent", ["-k"], { env: agentEnv }).catch(() => {});
2684
+ }
2685
+ }
2686
+ /** Extract `SSH_AUTH_SOCK=<path>;` from `ssh-agent -s` output. */
2687
+ function parseAgentSocket(out) {
2688
+ const m = out.match(/SSH_AUTH_SOCK=([^;\n]+)/);
2689
+ if (!m) throw new Error("ssh-agent -s did not emit SSH_AUTH_SOCK");
2690
+ return m[1];
2691
+ }
2692
+ /** Extract `SSH_AGENT_PID=<n>;` from `ssh-agent -s` output (best-effort). */
2693
+ function parseAgentPid(out) {
2694
+ return out.match(/SSH_AGENT_PID=([^;\n]+)/)?.[1];
2695
+ }
2696
+ var defaultSpawn, SSH_USER_DEFAULT, SSH_PORT_DEFAULT, HOST_KEY_FLAG;
2697
+ var init_ssh_exec = __esmMin((() => {
2698
+ defaultSpawn = (command, args, opts) => new Promise((resolve, reject) => {
2699
+ const child = spawn(command, args, {
2700
+ env: opts.env,
2701
+ stdio: [
2702
+ opts.stdin !== void 0 ? "pipe" : "ignore",
2703
+ "pipe",
2704
+ "pipe"
2705
+ ]
2706
+ });
2707
+ let stdout = "";
2708
+ let stderr = "";
2709
+ child.stdout?.on("data", (b) => {
2710
+ stdout += b.toString();
2711
+ });
2712
+ child.stderr?.on("data", (b) => {
2713
+ stderr += b.toString();
2714
+ });
2715
+ child.on("error", reject);
2716
+ child.on("close", (code) => resolve({
2717
+ exitCode: code ?? -1,
2718
+ stdout,
2719
+ stderr
2720
+ }));
2721
+ if (opts.stdin !== void 0) child.stdin?.end(opts.stdin);
2722
+ });
2723
+ SSH_USER_DEFAULT = "root";
2724
+ SSH_PORT_DEFAULT = 22;
2725
+ HOST_KEY_FLAG = {
2726
+ "accept-new": "accept-new",
2727
+ strict: "yes"
2728
+ };
2729
+ }));
2730
+ //#endregion
2731
+ //#region src/bootstrap/ensure-init-runner.ts
2732
+ /**
2733
+ * Build the launcher script that starts the init-runner on the target with its
2734
+ * bootstrap env. Detached (`setsid … &`) so the SSH session can return while
2735
+ * the agent keeps running and dials the orchestrator.
2736
+ */
2737
+ function buildLauncher(material, agentCommand) {
2738
+ return [
2739
+ "#!/usr/bin/env bash",
2740
+ "set -euo pipefail",
2741
+ `setsid env ${[
2742
+ `KICI_AGENT_TOKEN=${shQuote(material.bootstrapToken)}`,
2743
+ `KICI_AGENT_ID=${shQuote(material.targetAgentId)}`,
2744
+ `KICI_ORCHESTRATOR_URL=${shQuote(material.orchestratorUrl)}`,
2745
+ `KICI_LABELS=${shQuote(material.labels.join(","))}`,
2746
+ "KICI_EXECUTION_MODE=bare-metal",
2747
+ "KICI_PORT=0"
2748
+ ].join(" \\\n ")} \\`,
2749
+ ` ${agentCommand} >/tmp/kici-init-runner.log 2>&1 &`,
2750
+ "echo \"init-runner started pid=$!\""
2751
+ ].join("\n");
2752
+ }
2753
+ /** Single-quote a value for safe embedding in the launcher's env assignment. */
2754
+ function shQuote(v) {
2755
+ return `'${v.replace(/'/g, `'\\''`)}'`;
2756
+ }
2757
+ /**
2758
+ * Bring up a temporary init-runner on `targetAgentId`. Returns `{ broughtUp }`:
2759
+ * false when the target already had a live agent (the orchestrator no-op'd),
2760
+ * true when this call dropped + started the init-runner.
2761
+ */
2762
+ async function ensureInitRunner(transport, targetAgentId, deps = {}) {
2763
+ const material = await transport("kici.ensureInitRunner", { targetAgentId });
2764
+ if (!material.broughtUp) return { broughtUp: false };
2765
+ const { reach, privateKey, bootstrapToken, orchestratorUrl, labels } = material;
2766
+ if (!reach || !privateKey || !bootstrapToken || !orchestratorUrl || !labels) throw new Error(`orchestrator returned incomplete bring-up material for ${targetAgentId}`);
2767
+ const agentCommand = deps.agentCommand ?? DEFAULT_AGENT_COMMAND;
2768
+ await sshPush(reach, privateKey, buildLauncher({
2769
+ bootstrapToken,
2770
+ targetAgentId,
2771
+ orchestratorUrl,
2772
+ labels
2773
+ }, agentCommand), LAUNCHER_REMOTE_PATH, {}, deps);
2774
+ const run = await sshExec(reach, privateKey, `chmod 0700 ${LAUNCHER_REMOTE_PATH} && ${LAUNCHER_REMOTE_PATH}`, {}, deps);
2775
+ if (run.exitCode !== 0) throw new Error(`init-runner launch on ${targetAgentId} failed: exit ${run.exitCode}${run.stderr ? `\n${run.stderr}` : ""}`);
2776
+ return { broughtUp: true };
2777
+ }
2778
+ var DEFAULT_AGENT_COMMAND, LAUNCHER_REMOTE_PATH;
2779
+ var init_ensure_init_runner = __esmMin((() => {
2780
+ init_ssh_exec();
2781
+ DEFAULT_AGENT_COMMAND = "kici-agent";
2782
+ LAUNCHER_REMOTE_PATH = "/tmp/kici-init-runner.sh";
2783
+ }));
2784
+ //#endregion
2785
+ //#region src/bootstrap/pre-boot-send.ts
2786
+ /**
2787
+ * Ship a pre-boot input to the target's dropbear/initramfs SSH channel. The
2788
+ * input plaintext is resolved server-side and never logged. Resolves once the
2789
+ * send completes (the SSH session legitimately drops as the box boots).
2790
+ */
2791
+ async function preBootSend(transport, targetAgentId, opts, deps = {}) {
2792
+ const material = await transport("kici.preBootSend", {
2793
+ targetAgentId,
2794
+ inputSecret: opts.inputSecret,
2795
+ ...opts.port !== void 0 ? { port: opts.port } : {},
2796
+ ...opts.command !== void 0 ? { command: opts.command } : {}
2797
+ });
2798
+ await sshExec(material.reach, material.privateKey, material.command, {
2799
+ stdin: material.input,
2800
+ port: material.port,
2801
+ hostKeyMode: "accept-new"
2802
+ }, deps);
2803
+ }
2804
+ var init_pre_boot_send = __esmMin((() => {
2805
+ init_ssh_exec();
2806
+ }));
2807
+ //#endregion
2808
+ //#region src/bootstrap/api-intercept.ts
2809
+ /**
2810
+ * Wrap the orchestrator API transport so the two bootstrap methods are handled
2811
+ * in-process (SSH transport here; privileged resolve relayed to the
2812
+ * orchestrator). `relay` is the raw orchestrator transport (the WS
2813
+ * `sendApiRequest`).
2814
+ */
2815
+ function withBootstrapInterception(relay, deps = {}) {
2816
+ return async (method, params = {}) => {
2817
+ if (method === ENSURE_INIT_RUNNER) return ensureInitRunner(relay, String(params.targetAgentId ?? ""), deps);
2818
+ if (method === PRE_BOOT_SEND) {
2819
+ await preBootSend(relay, String(params.targetAgentId ?? ""), {
2820
+ inputSecret: String(params.inputSecret ?? ""),
2821
+ ...typeof params.port === "number" ? { port: params.port } : {},
2822
+ ...typeof params.command === "string" ? { command: params.command } : {}
2823
+ }, deps);
2824
+ return;
2825
+ }
2826
+ return relay(method, params);
2827
+ };
2828
+ }
2829
+ var ENSURE_INIT_RUNNER, PRE_BOOT_SEND;
2830
+ var init_api_intercept = __esmMin((() => {
2831
+ init_ensure_init_runner();
2832
+ init_pre_boot_send();
2833
+ ENSURE_INIT_RUNNER = "kici.ensureInitRunner";
2834
+ PRE_BOOT_SEND = "kici.preBootSend";
2835
+ }));
2836
+ //#endregion
2569
2837
  //#region src/execution/dynamic-job-serializer.ts
2570
2838
  /**
2571
2839
  * Convert an array of SDK Job objects into LockJob format for the orchestrator.
@@ -2687,7 +2955,13 @@ function serializeSteps(steps) {
2687
2955
  name: step.name || `step-${index}`,
2688
2956
  hasOutputs: !!step.outputs,
2689
2957
  ...step.continueOnError ? { continueOnError: true } : {},
2690
- ...step.timeout ? { timeout: step.timeout } : {}
2958
+ ...step.timeout ? { timeout: step.timeout } : {},
2959
+ ...step.retry ? { retry: {
2960
+ maxAttempts: step.retry.maxAttempts,
2961
+ delayMs: step.retry.delayMs,
2962
+ backoff: step.retry.backoff,
2963
+ maxDelayMs: step.retry.maxDelayMs
2964
+ } } : {}
2691
2965
  };
2692
2966
  });
2693
2967
  }
@@ -4263,6 +4537,9 @@ function buildRequest(dispatch, workDir) {
4263
4537
  matrixValues: jobConfig.matrixValues,
4264
4538
  host: jobConfig.host,
4265
4539
  agent: jobConfig.agent,
4540
+ dispatchInputs: jobConfig.dispatchInputs,
4541
+ fanoutIndex: jobConfig.fanoutIndex,
4542
+ fanoutTotal: jobConfig.fanoutTotal,
4266
4543
  secrets: dispatch.secrets,
4267
4544
  namespacedSecrets: dispatch.namespacedSecrets,
4268
4545
  sourceFile: jobConfig.source?.file,
@@ -5543,6 +5820,8 @@ var init_job_runner = __esmMin((() => {
5543
5820
  init_source_packer();
5544
5821
  init_source_restore();
5545
5822
  init_init_runner();
5823
+ init_api_intercept();
5824
+ init_ensure_init_runner();
5546
5825
  init_timeout_util();
5547
5826
  init_dynamic_job_serializer();
5548
5827
  init_log_streamer();
@@ -5655,6 +5934,10 @@ var init_job_runner = __esmMin((() => {
5655
5934
  await this.handleDynamicJobFn(dispatch, workDir, abortController);
5656
5935
  return true;
5657
5936
  }
5937
+ if (jobConfig.bringupOnly === true) {
5938
+ await this.handleBringupJob(dispatch);
5939
+ return true;
5940
+ }
5658
5941
  if (jobConfig.buildOnly === true) {
5659
5942
  if (jobConfig.fullRepo) {
5660
5943
  logger$2.warn("Build job received for fullRepo run -- skipping (should not happen)", {
@@ -5827,7 +6110,7 @@ var init_job_runner = __esmMin((() => {
5827
6110
  reason: ack.reason
5828
6111
  };
5829
6112
  },
5830
- onApiRequest: this._sendApiRequest ? async (method, params) => this._sendApiRequest(method, params) : void 0,
6113
+ onApiRequest: this._sendApiRequest ? withBootstrapInterception(async (method, params) => this._sendApiRequest(method, params)) : void 0,
5831
6114
  onCacheRequest: this._requestUserCache ? async (request) => this._requestUserCache(jobId, request) : void 0,
5832
6115
  onProvenanceRequest: this._relayProvenance ? async (request) => this._relayProvenance(jobId, request) : void 0,
5833
6116
  onApprovalRequest: this._sendStepApproval ? async (request) => this._sendStepApproval(dispatch.runId, dispatch.jobId, request) : void 0,
@@ -5889,6 +6172,53 @@ var init_job_runner = __esmMin((() => {
5889
6172
  }, result.secretOutputs);
5890
6173
  }
5891
6174
  /**
6175
+ * Handle a bring-up job: bring up a temporary init-runner on a declared-but-
6176
+ * un-agented host over SSH (fresh-box bootstrap convergence). The orchestrator
6177
+ * dispatches this synthetic `__bringup__` job to an agent holding the
6178
+ * `kici:capability:ssh-transport` capability; here we run the agent-side
6179
+ * `ensureInitRunner` helper (the privileged resolve is relayed to the
6180
+ * orchestrator, the SSH transport happens in this agent process — never
6181
+ * reaching workflow code). No clone, no sandbox — the init-runner then
6182
+ * connects under the target's agent id and the orchestrator's pinned-hold
6183
+ * drains the target's bootstrap steps onto it.
6184
+ */
6185
+ async handleBringupJob(dispatch) {
6186
+ const { runId, jobId, jobConfig } = dispatch;
6187
+ const targetAgentId = String(jobConfig.bringupTarget ?? "");
6188
+ logger$2.info("Starting bring-up job", {
6189
+ jobId,
6190
+ runId,
6191
+ targetAgentId
6192
+ });
6193
+ this.sendJobStatus(dispatch, ExecutionJobStatus.enum.running);
6194
+ const streamer = this.createStepStreamer(dispatch, 0);
6195
+ this.sendStepStatus(dispatch, 0, "bring-up", ExecutionStepStatus.enum.running);
6196
+ try {
6197
+ if (!targetAgentId) throw new Error("bring-up job missing bringupTarget");
6198
+ if (!this._sendApiRequest) throw new Error("bring-up job requires an orchestrator API transport");
6199
+ streamer.addLine(`Bringing up init-runner on ${targetAgentId}…`);
6200
+ const result = await ensureInitRunner(async (method, params) => this._sendApiRequest(method, params), targetAgentId);
6201
+ streamer.addLine(result.broughtUp ? `Init-runner brought up on ${targetAgentId}.` : `${targetAgentId} already has a live agent — no bring-up needed.`);
6202
+ await streamer.flush();
6203
+ this.sendStepStatus(dispatch, 0, "bring-up", ExecutionStepStatus.enum.success, void 0, streamer.getTotalBytes());
6204
+ streamer.destroy();
6205
+ this.sendJobStatus(dispatch, ExecutionJobStatus.enum.success);
6206
+ } catch (err) {
6207
+ const message = err instanceof Error ? err.message : String(err);
6208
+ streamer.addLine(`Bring-up failed: ${message}`);
6209
+ await streamer.flush();
6210
+ this.sendStepStatus(dispatch, 0, "bring-up", ExecutionStepStatus.enum.failed, void 0, streamer.getTotalBytes());
6211
+ streamer.destroy();
6212
+ this.sendJobStatus(dispatch, ExecutionJobStatus.enum.failed, { error: message });
6213
+ logger$2.warn("Bring-up job failed", {
6214
+ jobId,
6215
+ runId,
6216
+ targetAgentId,
6217
+ error: message
6218
+ });
6219
+ }
6220
+ }
6221
+ /**
5892
6222
  * Handle a build-only job.
5893
6223
  *
5894
6224
  * Build jobs install dependencies, pack them into a tarball,
@@ -6281,7 +6611,7 @@ var init_job_runner = __esmMin((() => {
6281
6611
  error: (msg, ..._args) => evalLog(`ERROR: ${msg}`),
6282
6612
  debug: (msg, ..._args) => evalLog(`DEBUG: ${msg}`)
6283
6613
  };
6284
- const kici = buildKiciApi(this._sendApiRequest ? (method, params) => this._sendApiRequest(method, params ?? {}) : () => Promise.reject(/* @__PURE__ */ new Error("Agent API not available")));
6614
+ const kici = buildKiciApi(this._sendApiRequest ? withBootstrapInterception((method, params) => this._sendApiRequest(method, params ?? {})) : () => Promise.reject(/* @__PURE__ */ new Error("Agent API not available")));
6285
6615
  const lockJobs = await runCaptured(evalSink, async () => {
6286
6616
  const { module } = await loadWorkflowSource(workDir, config.source.file, config.contentHash, config.resolvedHashFiles);
6287
6617
  evalLog("Workflow loaded");
@@ -6511,14 +6841,14 @@ var init_job_runner = __esmMin((() => {
6511
6841
  */
6512
6842
  init_console_capture();
6513
6843
  init_npm_resolver();
6514
- const AGENT_VERSION = "0.1.22";
6515
- const BUILD_COMMIT = "5afd16303";
6516
- const SDK_VERSION = "0.1.22";
6517
- const SDK_BUNDLE_HASH = "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
6518
- const SHARED_VERSION = "0.1.22";
6844
+ const AGENT_VERSION = "0.1.23";
6845
+ const BUILD_COMMIT = "4465935bb";
6846
+ const SDK_VERSION = "0.1.23";
6847
+ const SDK_BUNDLE_HASH = "3a83610d2d122b9f9b0f924b225a47050d0b35f00a8f3256a3e15f3ff3bf7ddc";
6848
+ const SHARED_VERSION = "0.1.23";
6519
6849
  const SHARED_BUNDLE_HASH = "991385c024392c395d3eb8a68946ef8ed3fcba96f2652f21c3164a54eafa1b1b";
6520
- const ENGINE_VERSION = "0.1.22";
6521
- const ENGINE_BUNDLE_HASH = "ff74a3afb2b1db2870c03d47543642f64d04b660bebdcde6681f76d874f26757";
6850
+ const ENGINE_VERSION = "0.1.23";
6851
+ const ENGINE_BUNDLE_HASH = "4a0566c709180a6a8744ff3281640e596b8e661ddceb81327cd37d232d9768d5";
6522
6852
  initTelemetry({
6523
6853
  serviceName: "kici-agent",
6524
6854
  otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
@@ -10,7 +10,7 @@ import { createLogger, deriveSharedSecret, initZx, normalizeLineEndings, sha256,
10
10
  import { CacheOutcome, CacheStepType, CheckMode, CheckStepOutcome, ExecutionJobStatus, ExecutionStepStatus, TimeoutReason } from "@kici-dev/engine";
11
11
  import { buildKiciApi, buildNeedsContext, createStepSecrets, evaluateRules, isDynamicJobFn, normalizeApproval, normalizeCacheSpecs, provenanceSubjectIsPath, resolveJobOutputs, resolveStepOutputs, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
12
12
  import { OIDC_TOKEN_REQUEST_METHOD } from "@kici-dev/engine/protocol/messages/oidc-token-relay";
13
- import { sha256File as sha256File$1 } from "@kici-dev/core";
13
+ import { computeBackoffDelay, sha256File as sha256File$1 } from "@kici-dev/core";
14
14
  import { calculateJwkThumbprint, decodeJwt, exportJWK, generateKeyPair } from "jose";
15
15
  import { IN_TOTO_PAYLOAD_TYPE, KICI_PROVENANCE_AUDIENCE, KICI_PROVENANCE_BUNDLE_MEDIA_TYPE } from "@kici-dev/engine/provenance/bundle";
16
16
  import { IN_TOTO_STATEMENT_TYPE, KICI_WORKFLOW_BUILD_TYPE, SLSA_PROVENANCE_PREDICATE_TYPE } from "@kici-dev/engine/provenance/schema";
@@ -1106,12 +1106,16 @@ initZx();
1106
1106
  * @param event - Event payload from the dispatch message
1107
1107
  * @param changedFiles - List of files changed in this event
1108
1108
  * @param env - Merged environment variables
1109
+ * @param dispatchInputs - Operator dispatch inputs (`ctx.dispatchInputs`)
1110
+ * @param fanout - Fan-out position (`ctx.fanout`); undefined on a non-fan-out job
1109
1111
  */
1110
- function createRuleContext(event, changedFiles = [], env = {}) {
1112
+ function createRuleContext(event, changedFiles = [], env = {}, dispatchInputs = {}, fanout) {
1111
1113
  return {
1112
1114
  event,
1113
1115
  changedFiles,
1114
1116
  env,
1117
+ dispatchInputs,
1118
+ ...fanout && { fanout },
1115
1119
  $
1116
1120
  };
1117
1121
  }
@@ -1316,7 +1320,7 @@ function extractSignal(error) {
1316
1320
  */
1317
1321
  async function evaluateStepRulesAndMaybeSkip(step, stepIndex, opts) {
1318
1322
  if (!step.rules || step.rules.length === 0) return null;
1319
- const ruleCtx = createRuleContext(opts.event, [], opts.env);
1323
+ const ruleCtx = createRuleContext(opts.event, [], opts.env, opts.dispatchInputs ?? {}, opts.fanout);
1320
1324
  const ruleResult = await evaluateRules(step.rules, ruleCtx, step.name);
1321
1325
  if (ruleResult.allPassed) return null;
1322
1326
  opts.sendIpc({
@@ -1442,6 +1446,39 @@ async function runObserverHook(args) {
1442
1446
  * `ctx.secrets.mountFile` tmpdir, any env vars set via `exposeFile`) is
1443
1447
  * removed even when the step throws, times out, or rule-skips.
1444
1448
  */
1449
+ /**
1450
+ * Run a step through its retry policy. Each call to `executeStepInLoop` is one
1451
+ * attempt: it sets up its own per-attempt timeout from `step.timeout` and returns
1452
+ * a `SandboxStepResult` (it never throws — a failed attempt is reported as a
1453
+ * `failed` status with an `error`). A failed attempt is retried while attempts
1454
+ * remain AND `retryIf(reconstructedError)` is true; backoff sleeps between
1455
+ * attempts. The retry loop runs to completion BEFORE the caller applies
1456
+ * `continueOnError` to the final outcome.
1457
+ */
1458
+ async function runStepWithRetry(step, stepIndex, ctx, timeoutMs, opts) {
1459
+ const retry = step.retry;
1460
+ const max = retry?.maxAttempts ?? 1;
1461
+ let result;
1462
+ for (let n = 1; n <= max; n++) {
1463
+ result = await executeStepInLoop(step, stepIndex, ctx, timeoutMs, opts.sendIpc, opts.outputsMap, opts.getSecretsAccessLog, opts.getSecretMountRecords, opts.jobDeadlineSignal, opts.checkMode ?? CheckMode.enum.apply, opts);
1464
+ if (result.status !== ExecutionStepStatus.enum.failed) return result;
1465
+ const err = new Error(result.error?.message ?? `Step '${step.name}' failed`);
1466
+ if (!(n < max && (retry?.retryIf?.(err) ?? true))) break;
1467
+ const delay = computeBackoffDelay(n, {
1468
+ maxAttempts: max,
1469
+ delayMs: retry.delayMs,
1470
+ backoff: retry.backoff,
1471
+ maxDelayMs: retry.maxDelayMs
1472
+ });
1473
+ opts.sendIpc({
1474
+ type: "log.line",
1475
+ stepIndex,
1476
+ line: `[kici] Step '${step.name}' attempt ${n}/${max} failed: ${err.message}; retrying in ${delay}ms`
1477
+ });
1478
+ await new Promise((r) => setTimeout(r, delay));
1479
+ }
1480
+ return result;
1481
+ }
1445
1482
  async function runStepIteration(step, stepIndex, opts) {
1446
1483
  const skippedResult = await evaluateStepRulesAndMaybeSkip(step, stepIndex, opts);
1447
1484
  if (skippedResult) {
@@ -1469,7 +1506,7 @@ async function runStepIteration(step, stepIndex, opts) {
1469
1506
  const timeoutMs = step.timeout ?? opts.defaultTimeoutMs;
1470
1507
  let result;
1471
1508
  try {
1472
- result = await executeStepInLoop(step, stepIndex, ctx, timeoutMs, opts.sendIpc, opts.outputsMap, opts.getSecretsAccessLog, opts.getSecretMountRecords, opts.jobDeadlineSignal, opts.checkMode ?? CheckMode.enum.apply, opts);
1509
+ result = await runStepWithRetry(step, stepIndex, ctx, timeoutMs, opts);
1473
1510
  } finally {
1474
1511
  await opts.afterStepApplyEnvFiles?.();
1475
1512
  }
@@ -3111,8 +3148,8 @@ function logSubprocessStreams(e, tokens) {
3111
3148
  * no Rolldown step at runtime. `@kici-dev/sdk` and host-repo deps resolve via
3112
3149
  * Node's normal ESM lookup against `.kici/node_modules/`.
3113
3150
  */
3114
- const AGENT_SDK_VERSION = "0.1.22";
3115
- const AGENT_SDK_BUNDLE_HASH = "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
3151
+ const AGENT_SDK_VERSION = "0.1.23";
3152
+ const AGENT_SDK_BUNDLE_HASH = "3a83610d2d122b9f9b0f924b225a47050d0b35f00a8f3256a3e15f3ff3bf7ddc";
3116
3153
  /**
3117
3154
  * Register the `@kici-dev/core/ts-loader-hook` oxc-transform ESM loader hook so
3118
3155
  * subsequent dynamic `import()` calls for `.ts` / `.tsx` files transform on the
@@ -3474,7 +3511,7 @@ async function applyOverlay(config) {
3474
3511
  */
3475
3512
  init_download();
3476
3513
  init_dep_restore();
3477
- const AGENT_VERSION = "0.1.22";
3514
+ const AGENT_VERSION = "0.1.23";
3478
3515
  process.on("uncaughtException", (err) => {
3479
3516
  process.stderr.write(`[workflow-runner] UNCAUGHT EXCEPTION: ${err.message}\n`);
3480
3517
  if (err.stack) process.stderr.write(`[workflow-runner] Stack: ${err.stack}\n`);
@@ -4407,12 +4444,32 @@ function createSandboxStepContext(workDir, stepIndex, stepName, request, maskedS
4407
4444
  ...request.matrixValues && { matrix: request.matrixValues },
4408
4445
  ...request.host && { host: request.host },
4409
4446
  ...request.agent && { agent: request.agent },
4447
+ ...(() => {
4448
+ const fanout = deriveFanout(request);
4449
+ return fanout ? { fanout } : {};
4450
+ })(),
4451
+ dispatchInputs: request.dispatchInputs ?? {},
4410
4452
  ...(() => {
4411
4453
  const needs = buildStepNeedsContext(request.jobNeeds, request.upstreamJobOutputs, request.upstreamJobStatuses);
4412
4454
  return needs ? { needs } : {};
4413
4455
  })()
4414
4456
  };
4415
4457
  }
4458
+ /**
4459
+ * Derive the fan-out position (`ctx.fanout`) from a dispatch request. Returns
4460
+ * `undefined` for a non-fan-out job (no `fanoutTotal`), so `ctx.fanout` is only
4461
+ * set for `runsOnAll` host children and matrix combinations.
4462
+ */
4463
+ function deriveFanout(request) {
4464
+ if (request.fanoutTotal === void 0) return void 0;
4465
+ const index = request.fanoutIndex ?? 0;
4466
+ return {
4467
+ index,
4468
+ total: request.fanoutTotal,
4469
+ first: index === 0,
4470
+ last: index === request.fanoutTotal - 1
4471
+ };
4472
+ }
4416
4473
  /** Raw provider webhook body for ctx.rawPayload — nested in the envelope. */
4417
4474
  function rawPayloadFromEvent(event) {
4418
4475
  if (!event) return void 0;
@@ -5026,7 +5083,7 @@ function buildOutputInfrastructure(request, refMap) {
5026
5083
  */
5027
5084
  async function maybeSkipJobOnRules(job, request, normalizedSteps) {
5028
5085
  if (!job?.rules || job.rules.length === 0) return false;
5029
- const ruleCtx = createRuleContext(request.event ?? {}, [], process.env);
5086
+ const ruleCtx = createRuleContext(request.event ?? {}, [], process.env, request.dispatchInputs ?? {}, deriveFanout(request));
5030
5087
  if ((await evaluateRules(job.rules, ruleCtx, request.jobName)).allPassed) return false;
5031
5088
  const skippedResults = normalizedSteps.map((s, i) => ({
5032
5089
  name: s.name,
@@ -5281,6 +5338,8 @@ async function main() {
5281
5338
  outputsMap,
5282
5339
  event: request.event ?? {},
5283
5340
  env: process.env,
5341
+ dispatchInputs: request.dispatchInputs ?? {},
5342
+ fanout: deriveFanout(request),
5284
5343
  jobHooks,
5285
5344
  cachePhaseDeps,
5286
5345
  isAborted: () => aborted,
@@ -5389,6 +5448,6 @@ main().catch((error) => {
5389
5448
  setTimeout(() => process.exit(1), 100);
5390
5449
  });
5391
5450
  //#endregion
5392
- export { buildStepNeedsContext, createSandboxStepContext, rawPayloadFromEvent };
5451
+ export { buildStepNeedsContext, createSandboxStepContext, deriveFanout, rawPayloadFromEvent };
5393
5452
 
5394
5453
  //# sourceMappingURL=workflow-runner.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kici-dev/agent",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Customer-deployable agent for the KiCI CI/CD stack. Connects to an orchestrator, clones the workflow repo, executes steps, and streams logs back.",
5
5
  "keywords": [
6
6
  "ci",
@@ -64,10 +64,10 @@
64
64
  "yaml": "^2.9.0",
65
65
  "zod": "^4.4.3",
66
66
  "zx": "^8.8.5",
67
- "@kici-dev/core": "0.1.22",
68
- "@kici-dev/engine": "0.1.22",
69
- "@kici-dev/sdk": "0.1.22",
70
- "@kici-dev/shared": "0.1.22"
67
+ "@kici-dev/core": "0.1.23",
68
+ "@kici-dev/engine": "0.1.23",
69
+ "@kici-dev/sdk": "0.1.23",
70
+ "@kici-dev/shared": "0.1.23"
71
71
  },
72
72
  "kici": {
73
73
  "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/agent@0.1.22",
6
- "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fagent/0.1.22/c9b5cd43-8980-4ccc-9212-759130a7d3ae",
5
+ "name": "@kici-dev/agent@0.1.23",
6
+ "documentNamespace": "https://kici.dev/sbom/%40kici-dev%2Fagent/0.1.23/55c6fdcb-9b29-45b7-b421-94309c584037",
7
7
  "creationInfo": {
8
- "created": "2026-06-24T06:03:27Z",
8
+ "created": "2026-06-25T11:13:08Z",
9
9
  "creators": [
10
10
  "Tool: kici-sbom-generator"
11
11
  ]
@@ -816,7 +816,7 @@
816
816
  {
817
817
  "SPDXID": "SPDXRef-RootPackage",
818
818
  "name": "@kici-dev/agent",
819
- "versionInfo": "0.1.22",
819
+ "versionInfo": "0.1.23",
820
820
  "downloadLocation": "NOASSERTION",
821
821
  "filesAnalyzed": false,
822
822
  "licenseConcluded": "NOASSERTION",
@@ -827,16 +827,16 @@
827
827
  {
828
828
  "referenceCategory": "PACKAGE-MANAGER",
829
829
  "referenceType": "purl",
830
- "referenceLocator": "pkg:npm/%40kici-dev/agent@0.1.22"
830
+ "referenceLocator": "pkg:npm/%40kici-dev/agent@0.1.23"
831
831
  }
832
832
  ],
833
833
  "description": "Customer-deployable agent for the KiCI CI/CD stack. Connects to an orchestrator, clones the workflow repo, executes steps, and streams logs back.",
834
834
  "homepage": "https://kici.dev"
835
835
  },
836
836
  {
837
- "SPDXID": "SPDXRef-Package--kici-dev-core-0.1.22",
837
+ "SPDXID": "SPDXRef-Package--kici-dev-core-0.1.23",
838
838
  "name": "@kici-dev/core",
839
- "versionInfo": "0.1.22",
839
+ "versionInfo": "0.1.23",
840
840
  "downloadLocation": "NOASSERTION",
841
841
  "filesAnalyzed": false,
842
842
  "licenseConcluded": "NOASSERTION",
@@ -847,16 +847,16 @@
847
847
  {
848
848
  "referenceCategory": "PACKAGE-MANAGER",
849
849
  "referenceType": "purl",
850
- "referenceLocator": "pkg:npm/%40kici-dev/core@0.1.22"
850
+ "referenceLocator": "pkg:npm/%40kici-dev/core@0.1.23"
851
851
  }
852
852
  ],
853
853
  "description": "Light shared utilities for the KiCI stack (logging, errors, formatting, crypto, zx init, the TypeScript ESM loader hook). No server-side dependencies.",
854
854
  "homepage": "https://kici.dev"
855
855
  },
856
856
  {
857
- "SPDXID": "SPDXRef-Package--kici-dev-engine-0.1.22",
857
+ "SPDXID": "SPDXRef-Package--kici-dev-engine-0.1.23",
858
858
  "name": "@kici-dev/engine",
859
- "versionInfo": "0.1.22",
859
+ "versionInfo": "0.1.23",
860
860
  "downloadLocation": "NOASSERTION",
861
861
  "filesAnalyzed": false,
862
862
  "licenseConcluded": "NOASSERTION",
@@ -867,16 +867,16 @@
867
867
  {
868
868
  "referenceCategory": "PACKAGE-MANAGER",
869
869
  "referenceType": "purl",
870
- "referenceLocator": "pkg:npm/%40kici-dev/engine@0.1.22"
870
+ "referenceLocator": "pkg:npm/%40kici-dev/engine@0.1.23"
871
871
  }
872
872
  ],
873
873
  "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.",
874
874
  "homepage": "https://kici.dev"
875
875
  },
876
876
  {
877
- "SPDXID": "SPDXRef-Package--kici-dev-sdk-0.1.22",
877
+ "SPDXID": "SPDXRef-Package--kici-dev-sdk-0.1.23",
878
878
  "name": "@kici-dev/sdk",
879
- "versionInfo": "0.1.22",
879
+ "versionInfo": "0.1.23",
880
880
  "downloadLocation": "NOASSERTION",
881
881
  "filesAnalyzed": false,
882
882
  "licenseConcluded": "NOASSERTION",
@@ -887,16 +887,16 @@
887
887
  {
888
888
  "referenceCategory": "PACKAGE-MANAGER",
889
889
  "referenceType": "purl",
890
- "referenceLocator": "pkg:npm/%40kici-dev/sdk@0.1.22"
890
+ "referenceLocator": "pkg:npm/%40kici-dev/sdk@0.1.23"
891
891
  }
892
892
  ],
893
893
  "description": "TypeScript SDK for defining KiCI workflows. Import into `.kici/workflows/*.ts` to declare workflows, jobs, steps, triggers, rules, and matrix configurations.",
894
894
  "homepage": "https://kici.dev"
895
895
  },
896
896
  {
897
- "SPDXID": "SPDXRef-Package--kici-dev-shared-0.1.22",
897
+ "SPDXID": "SPDXRef-Package--kici-dev-shared-0.1.23",
898
898
  "name": "@kici-dev/shared",
899
- "versionInfo": "0.1.22",
899
+ "versionInfo": "0.1.23",
900
900
  "downloadLocation": "NOASSERTION",
901
901
  "filesAnalyzed": false,
902
902
  "licenseConcluded": "NOASSERTION",
@@ -907,7 +907,7 @@
907
907
  {
908
908
  "referenceCategory": "PACKAGE-MANAGER",
909
909
  "referenceType": "purl",
910
- "referenceLocator": "pkg:npm/%40kici-dev/shared@0.1.22"
910
+ "referenceLocator": "pkg:npm/%40kici-dev/shared@0.1.23"
911
911
  }
912
912
  ],
913
913
  "description": "Shared utilities for the KiCI CI/CD stack — logging, zx setup, crypto, telemetry, health and metrics routes. No business logic.",
@@ -6209,22 +6209,22 @@
6209
6209
  },
6210
6210
  {
6211
6211
  "spdxElementId": "SPDXRef-RootPackage",
6212
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.1.22",
6212
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.1.23",
6213
6213
  "relationshipType": "DEPENDS_ON"
6214
6214
  },
6215
6215
  {
6216
6216
  "spdxElementId": "SPDXRef-RootPackage",
6217
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.1.22",
6217
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.1.23",
6218
6218
  "relationshipType": "DEPENDS_ON"
6219
6219
  },
6220
6220
  {
6221
6221
  "spdxElementId": "SPDXRef-RootPackage",
6222
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-sdk-0.1.22",
6222
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-sdk-0.1.23",
6223
6223
  "relationshipType": "DEPENDS_ON"
6224
6224
  },
6225
6225
  {
6226
6226
  "spdxElementId": "SPDXRef-RootPackage",
6227
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-shared-0.1.22",
6227
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-shared-0.1.23",
6228
6228
  "relationshipType": "DEPENDS_ON"
6229
6229
  },
6230
6230
  {
@@ -6278,192 +6278,192 @@
6278
6278
  "relationshipType": "DEPENDS_ON"
6279
6279
  },
6280
6280
  {
6281
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.22",
6281
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.23",
6282
6282
  "relatedSpdxElement": "SPDXRef-Package-oxc-transform-0.135.0",
6283
6283
  "relationshipType": "DEPENDS_ON"
6284
6284
  },
6285
6285
  {
6286
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.22",
6286
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.23",
6287
6287
  "relatedSpdxElement": "SPDXRef-Package-picocolors-1.1.1",
6288
6288
  "relationshipType": "DEPENDS_ON"
6289
6289
  },
6290
6290
  {
6291
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.22",
6291
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.23",
6292
6292
  "relatedSpdxElement": "SPDXRef-Package-winston-daily-rotate-file-5.0.0",
6293
6293
  "relationshipType": "DEPENDS_ON"
6294
6294
  },
6295
6295
  {
6296
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.22",
6296
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.23",
6297
6297
  "relatedSpdxElement": "SPDXRef-Package-winston-3.19.0",
6298
6298
  "relationshipType": "DEPENDS_ON"
6299
6299
  },
6300
6300
  {
6301
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.22",
6301
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.23",
6302
6302
  "relatedSpdxElement": "SPDXRef-Package-zod-4.4.3",
6303
6303
  "relationshipType": "DEPENDS_ON"
6304
6304
  },
6305
6305
  {
6306
- "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.22",
6306
+ "spdxElementId": "SPDXRef-Package--kici-dev-core-0.1.23",
6307
6307
  "relatedSpdxElement": "SPDXRef-Package-zx-8.8.5",
6308
6308
  "relationshipType": "DEPENDS_ON"
6309
6309
  },
6310
6310
  {
6311
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.22",
6311
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.23",
6312
6312
  "relatedSpdxElement": "SPDXRef-Package-jose-6.2.3",
6313
6313
  "relationshipType": "DEPENDS_ON"
6314
6314
  },
6315
6315
  {
6316
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.22",
6316
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.23",
6317
6317
  "relatedSpdxElement": "SPDXRef-Package-jsonpath-plus-10.4.0",
6318
6318
  "relationshipType": "DEPENDS_ON"
6319
6319
  },
6320
6320
  {
6321
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.22",
6321
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.23",
6322
6322
  "relatedSpdxElement": "SPDXRef-Package-picomatch-4.0.4",
6323
6323
  "relationshipType": "DEPENDS_ON"
6324
6324
  },
6325
6325
  {
6326
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.22",
6326
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.23",
6327
6327
  "relatedSpdxElement": "SPDXRef-Package-safe-regex-2.1.1",
6328
6328
  "relationshipType": "DEPENDS_ON"
6329
6329
  },
6330
6330
  {
6331
- "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.22",
6331
+ "spdxElementId": "SPDXRef-Package--kici-dev-engine-0.1.23",
6332
6332
  "relatedSpdxElement": "SPDXRef-Package-zod-4.4.3",
6333
6333
  "relationshipType": "DEPENDS_ON"
6334
6334
  },
6335
6335
  {
6336
- "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.22",
6337
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.1.22",
6336
+ "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.23",
6337
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.1.23",
6338
6338
  "relationshipType": "DEPENDS_ON"
6339
6339
  },
6340
6340
  {
6341
- "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.22",
6342
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.1.22",
6341
+ "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.23",
6342
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-engine-0.1.23",
6343
6343
  "relationshipType": "DEPENDS_ON"
6344
6344
  },
6345
6345
  {
6346
- "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.22",
6346
+ "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.23",
6347
6347
  "relatedSpdxElement": "SPDXRef-Package-micromatch-4.0.8",
6348
6348
  "relationshipType": "DEPENDS_ON"
6349
6349
  },
6350
6350
  {
6351
- "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.22",
6351
+ "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.23",
6352
6352
  "relatedSpdxElement": "SPDXRef-Package-zod-4.4.3",
6353
6353
  "relationshipType": "DEPENDS_ON"
6354
6354
  },
6355
6355
  {
6356
- "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.22",
6356
+ "spdxElementId": "SPDXRef-Package--kici-dev-sdk-0.1.23",
6357
6357
  "relatedSpdxElement": "SPDXRef-Package-zx-8.8.5",
6358
6358
  "relationshipType": "DEPENDS_ON"
6359
6359
  },
6360
6360
  {
6361
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6361
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6362
6362
  "relatedSpdxElement": "SPDXRef-Package--aws-sdk-client-s3-3.1064.0",
6363
6363
  "relationshipType": "DEPENDS_ON"
6364
6364
  },
6365
6365
  {
6366
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6367
- "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.1.22",
6366
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6367
+ "relatedSpdxElement": "SPDXRef-Package--kici-dev-core-0.1.23",
6368
6368
  "relationshipType": "DEPENDS_ON"
6369
6369
  },
6370
6370
  {
6371
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6371
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6372
6372
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-api-1.9.1",
6373
6373
  "relationshipType": "DEPENDS_ON"
6374
6374
  },
6375
6375
  {
6376
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6376
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6377
6377
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-exporter-metrics-otlp-http-0.218.0",
6378
6378
  "relationshipType": "DEPENDS_ON"
6379
6379
  },
6380
6380
  {
6381
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6381
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6382
6382
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-exporter-prometheus-0.218.0",
6383
6383
  "relationshipType": "DEPENDS_ON"
6384
6384
  },
6385
6385
  {
6386
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6386
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6387
6387
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-exporter-trace-otlp-http-0.218.0",
6388
6388
  "relationshipType": "DEPENDS_ON"
6389
6389
  },
6390
6390
  {
6391
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6391
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6392
6392
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-instrumentation-runtime-node-0.31.0",
6393
6393
  "relationshipType": "DEPENDS_ON"
6394
6394
  },
6395
6395
  {
6396
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6396
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6397
6397
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-resources-2.7.1",
6398
6398
  "relationshipType": "DEPENDS_ON"
6399
6399
  },
6400
6400
  {
6401
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6401
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6402
6402
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-sdk-node-0.218.0",
6403
6403
  "relationshipType": "DEPENDS_ON"
6404
6404
  },
6405
6405
  {
6406
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6406
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6407
6407
  "relatedSpdxElement": "SPDXRef-Package--opentelemetry-semantic-conventions-1.41.1",
6408
6408
  "relationshipType": "DEPENDS_ON"
6409
6409
  },
6410
6410
  {
6411
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6411
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6412
6412
  "relatedSpdxElement": "SPDXRef-Package-archiver-8.0.0",
6413
6413
  "relationshipType": "DEPENDS_ON"
6414
6414
  },
6415
6415
  {
6416
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6416
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6417
6417
  "relatedSpdxElement": "SPDXRef-Package-diff-9.0.0",
6418
6418
  "relationshipType": "DEPENDS_ON"
6419
6419
  },
6420
6420
  {
6421
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6421
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6422
6422
  "relatedSpdxElement": "SPDXRef-Package-hono-4.12.25",
6423
6423
  "relationshipType": "DEPENDS_ON"
6424
6424
  },
6425
6425
  {
6426
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6426
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6427
6427
  "relatedSpdxElement": "SPDXRef-Package-kysely-0.29.2",
6428
6428
  "relationshipType": "DEPENDS_ON"
6429
6429
  },
6430
6430
  {
6431
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6431
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6432
6432
  "relatedSpdxElement": "SPDXRef-Package-oxc-transform-0.135.0",
6433
6433
  "relationshipType": "DEPENDS_ON"
6434
6434
  },
6435
6435
  {
6436
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6436
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6437
6437
  "relatedSpdxElement": "SPDXRef-Package-pg-8.21.0",
6438
6438
  "relationshipType": "DEPENDS_ON"
6439
6439
  },
6440
6440
  {
6441
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6441
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6442
6442
  "relatedSpdxElement": "SPDXRef-Package-picocolors-1.1.1",
6443
6443
  "relationshipType": "DEPENDS_ON"
6444
6444
  },
6445
6445
  {
6446
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6446
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6447
6447
  "relatedSpdxElement": "SPDXRef-Package-winston-daily-rotate-file-5.0.0",
6448
6448
  "relationshipType": "DEPENDS_ON"
6449
6449
  },
6450
6450
  {
6451
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6451
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6452
6452
  "relatedSpdxElement": "SPDXRef-Package-winston-3.19.0",
6453
6453
  "relationshipType": "DEPENDS_ON"
6454
6454
  },
6455
6455
  {
6456
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6456
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6457
6457
  "relatedSpdxElement": "SPDXRef-Package-yaml-2.9.0",
6458
6458
  "relationshipType": "DEPENDS_ON"
6459
6459
  },
6460
6460
  {
6461
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6461
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6462
6462
  "relatedSpdxElement": "SPDXRef-Package-zod-4.4.3",
6463
6463
  "relationshipType": "DEPENDS_ON"
6464
6464
  },
6465
6465
  {
6466
- "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.22",
6466
+ "spdxElementId": "SPDXRef-Package--kici-dev-shared-0.1.23",
6467
6467
  "relatedSpdxElement": "SPDXRef-Package-zx-8.8.5",
6468
6468
  "relationshipType": "DEPENDS_ON"
6469
6469
  },