@kici-dev/agent 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/checkout/clone-job-repos.d.ts +53 -0
  2. package/dist/checkout/credential-helper-bin.d.ts +28 -0
  3. package/dist/checkout/credential-helper-host.d.ts +48 -0
  4. package/dist/checkout/credential-helper.d.ts +44 -0
  5. package/dist/checkout/git-clone.d.ts +26 -0
  6. package/dist/checkout/grant-table.d.ts +30 -0
  7. package/dist/checkout/job-git-credentials.d.ts +49 -0
  8. package/dist/checkout/write-elevation.d.ts +40 -0
  9. package/dist/config.d.ts +38 -0
  10. package/dist/container-ts-loader-hook.js +2047 -1814
  11. package/dist/execution/between-jobs-controller.d.ts +50 -0
  12. package/dist/execution/between-jobs-reset.d.ts +25 -0
  13. package/dist/execution/cleanup-rerun.d.ts +21 -0
  14. package/dist/execution/dynamic-job-serializer.d.ts +6 -2
  15. package/dist/execution/image-build/build-engine.d.ts +75 -0
  16. package/dist/execution/image-build/build-step.d.ts +57 -0
  17. package/dist/execution/image-build/resolve-build-spec.d.ts +41 -0
  18. package/dist/execution/image-build/runtime-facts.d.ts +31 -0
  19. package/dist/execution/job-runner.d.ts +48 -0
  20. package/dist/execution/sandbox/bare-metal-sandbox.d.ts +25 -1
  21. package/dist/execution/sandbox/container-sandbox.d.ts +97 -2
  22. package/dist/execution/sandbox/fork-runner.d.ts +55 -1
  23. package/dist/execution/sandbox/image-preflight.d.ts +38 -0
  24. package/dist/execution/sandbox/ipc-protocol.d.ts +96 -2
  25. package/dist/execution/sandbox/kici-runtime.d.ts +48 -0
  26. package/dist/execution/sandbox/step-loop.d.ts +7 -0
  27. package/dist/execution/sandbox/types.d.ts +55 -1
  28. package/dist/execution/sandbox/workflow-runner.d.ts +23 -3
  29. package/dist/idle-shutdown.d.ts +24 -0
  30. package/dist/index.js +133 -62
  31. package/dist/metrics/prometheus.d.ts +26 -0
  32. package/dist/server.js +2050 -312
  33. package/dist/workflow-runner-bundle.js +41961 -41319
  34. package/dist/workflow-runner.js +332 -136
  35. package/dist/ws/orchestrator-client.d.ts +95 -3
  36. package/package.json +10 -10
  37. package/sbom.spdx.json +460 -455
@@ -9,6 +9,21 @@ import { type ChildProcess } from 'node:child_process';
9
9
  import type { JobDispatch } from '@kici-dev/engine';
10
10
  import type { JobExecutionOptions, JobExecutionResult } from './types.js';
11
11
  import type { JobExecutionRequest } from './ipc-protocol.js';
12
+ /**
13
+ * Best-effort SIGKILL/SIGTERM of an entire process group led by `pid`.
14
+ * `process.kill(-pid, signal)` targets the group whose leader is `pid` (the
15
+ * child was spawned `detached`, so its pid is its group id). Returns 1 when the
16
+ * group was signalled, 0 when it was already gone (ESRCH) or the pid is invalid.
17
+ * We cannot cheaply count members, so the caller treats a non-zero return as
18
+ * "reap attempted".
19
+ */
20
+ export declare function killProcessGroup(pid: number | undefined, signal: NodeJS.Signals): number;
21
+ /**
22
+ * SIGTERM the job's process group, wait a short grace, then SIGKILL. No-op
23
+ * (returns 0) when the child was not spawned detached or has no pid. Returns the
24
+ * count of reap attempts that signalled a live group (0, 1, or 2).
25
+ */
26
+ export declare function reapGroup(pid: number | undefined, detached: boolean, killFn?: (p: number | undefined, s: NodeJS.Signals) => number, sleepMs?: number): Promise<number>;
12
27
  /** Options for creating a fork-based runner. */
13
28
  interface ForkRunnerOptions {
14
29
  /** Absolute path to the compiled workflow-runner.js. */
@@ -39,6 +54,18 @@ interface ForkRunnerOptions {
39
54
  * Defaults to 30_000 (30 seconds).
40
55
  */
41
56
  maxGracePeriodMs?: number;
57
+ /**
58
+ * Spawn the runner child `detached` so it leads its own process group. Set by
59
+ * the bare-metal backend when orphan cleanup is on and bwrap is off, so the
60
+ * between-jobs phase can reap a backgrounded daemon that reparented to init.
61
+ * Ignored under bwrap (its PID namespace already contains the tree).
62
+ */
63
+ detachProcessGroup?: boolean;
64
+ /**
65
+ * Between-jobs out-of-band cleanup re-run: the runner reuses the preserved
66
+ * workdir and runs only the job's declared cleanup / onFailure hooks.
67
+ */
68
+ cleanupOnly?: boolean;
42
69
  }
43
70
  /** State of a running fork-based child process. */
44
71
  export interface ForkRunnerHandle {
@@ -59,13 +86,40 @@ export interface ForkRunnerHandle {
59
86
  * Capped by the agent's maxGracePeriodMs.
60
87
  */
61
88
  cancel: (force: boolean, gracePeriodMs?: number) => void;
89
+ /**
90
+ * Whether this runner leads its own detached process group (bare-metal,
91
+ * non-bwrap, orphan cleanup on). Drives whether `reap()` does anything.
92
+ */
93
+ detached: boolean;
94
+ /**
95
+ * True once the runner emitted `completion-hooks-done` (its onSuccess /
96
+ * onFailure / cleanup hooks ran). Stays false when the child exits before
97
+ * signalling, which is the between-jobs phase's cue to re-run declared
98
+ * cleanup out-of-band.
99
+ */
100
+ completionHooksRan: boolean;
101
+ /**
102
+ * True once the runner reported (via `hooks-declared`) that the job declares
103
+ * an `onFailure` / `cleanup` hook. Recorded early so it survives a later hard
104
+ * kill; gates whether the out-of-band cleanup re-run is attempted.
105
+ */
106
+ declaresCleanup: boolean;
107
+ /**
108
+ * SIGTERM → grace → SIGKILL the runner's process group, reaping any daemon it
109
+ * backgrounded. No-op (resolves 0) when the runner is not detached; returns
110
+ * the number of reap attempts that signalled a live group.
111
+ */
112
+ reap: () => Promise<number>;
62
113
  }
63
114
  /**
64
115
  * Build a JobExecutionRequest from a JobDispatch.
65
116
  *
66
117
  * Maps orchestrator dispatch fields to the subset needed by the workflow runner.
67
118
  */
68
- export declare function buildRequest(dispatch: JobDispatch, workDir: string): JobExecutionRequest;
119
+ export declare function buildRequest(dispatch: JobDispatch, workDir: string, extra?: {
120
+ cleanupOnly?: boolean;
121
+ credentialHelperPath?: string;
122
+ }): JobExecutionRequest;
69
123
  /**
70
124
  * Build bubblewrap (bwrap) arguments for namespace isolation.
71
125
  *
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Fail a container job on an unusable image BEFORE the job starts.
3
+ *
4
+ * KiCI injects its own runtime (a pinned official glibc-2.17 Node) into the
5
+ * customer's image, so the image needs only a glibc and a shell. When it has
6
+ * neither, the container runtime's own error is close to useless — a musl image
7
+ * reports
8
+ *
9
+ * exec container process (missing dynamic library?) `/opt/kici/node/bin/node`:
10
+ * No such file or directory
11
+ *
12
+ * which names a file that plainly exists and says nothing about musl. The
13
+ * preflight turns that into a sentence an author can act on, and it fires
14
+ * before any step runs rather than partway through a job.
15
+ *
16
+ * glibc-only is the deliberate scope of this version; a musl runtime variant is
17
+ * a documented follow-up.
18
+ */
19
+ import type Docker from 'dockerode';
20
+ /** What an image's rootfs says about whether we can run our runtime in it. */
21
+ export type ImageLibc = 'glibc' | 'musl' | 'static' | 'no-shell';
22
+ /** Every path the preflight stats in the image. */
23
+ export declare const PROBE_PATHS: readonly string[];
24
+ /**
25
+ * Classify an image from the subset of {@link PROBE_PATHS} that exist in it.
26
+ *
27
+ * Pure, so the decision table is testable without a container runtime.
28
+ */
29
+ export declare function classifyImageLibc(presentPaths: readonly string[]): ImageLibc;
30
+ /**
31
+ * Throw unless `image` can host the injected runtime.
32
+ *
33
+ * Stats the probe paths through a created-but-never-started container, so a
34
+ * shell-less or musl image is diagnosed without executing anything in it —
35
+ * running a probe command would fail for the very reason we are testing for.
36
+ */
37
+ export declare function assertImageRunnable(docker: Docker, image: string): Promise<void>;
38
+ //# sourceMappingURL=image-preflight.d.ts.map
@@ -119,6 +119,26 @@ interface JobCompleteMessage {
119
119
  /** Names of sibling jobs dropped by DynamicJobFn re-evaluation drift. */
120
120
  droppedJobs?: string[];
121
121
  }
122
+ /**
123
+ * Emitted once, right after the runner extracts the job's hooks, so the
124
+ * supervisor knows whether the job declares `onFailure` / `cleanup` even if the
125
+ * runner is later hard-killed before those hooks run. The between-jobs phase
126
+ * uses it to decide whether an out-of-band cleanup re-run is warranted.
127
+ */
128
+ export interface HooksDeclaredMessage {
129
+ type: 'hooks-declared';
130
+ /** True when the job declares an `onFailure` or `cleanup` completion hook. */
131
+ declaresCleanup: boolean;
132
+ }
133
+ /**
134
+ * The runner's job-completion hooks (onSuccess / onFailure / cleanup) have run.
135
+ * Emitted once, whether or not a hook failed. Its absence when the child exits
136
+ * tells the supervisor the runner was killed before its declared cleanup ran,
137
+ * so the between-jobs phase re-runs that cleanup out-of-band.
138
+ */
139
+ export interface CompletionHooksDoneMessage {
140
+ type: 'completion-hooks-done';
141
+ }
122
142
  /** Request to emit a custom event from a workflow step (runner -> agent). */
123
143
  export interface EventEmitRequest {
124
144
  type: 'event.emit';
@@ -186,6 +206,31 @@ export interface CacheRequestIpc {
186
206
  /** Tarball size in bytes (drives quota accounting). `completeSave` only. */
187
207
  sizeBytes?: number;
188
208
  }
209
+ /** Git write-grant operations (runner -> agent). */
210
+ export type GitGrantOp = 'elevate' | 'revoke';
211
+ /**
212
+ * Open or close a write window for one repository (runner -> agent).
213
+ *
214
+ * `withWrite` sends `elevate` before running its callback and `revoke` in a
215
+ * `finally`. The grant lives in the AGENT's grant table — the credential helper
216
+ * git spawns is a separate process and consults the agent, not the runner —
217
+ * which is why this is an agent-directed IPC call rather than an orchestrator
218
+ * one. Mirrors the {@link CacheRequestIpc} relay pattern.
219
+ */
220
+ export interface GitGrantRequestIpc {
221
+ type: 'git.grant.request';
222
+ /** UUID for correlating the response. */
223
+ requestId: string;
224
+ op: GitGrantOp;
225
+ /** `owner/repo`. `elevate` only. */
226
+ repository?: string;
227
+ /** Permissions to request from the forge. `elevate` only. */
228
+ permissions?: Record<string, string>;
229
+ /** Named credential from the job's `gitCredentials` map. `elevate` only. */
230
+ credentialName?: string;
231
+ /** Grant id returned by a previous `elevate`. `revoke` only. */
232
+ grantId?: string;
233
+ }
189
234
  /**
190
235
  * Request a step-level approval hold (runner -> agent). The sandbox runner
191
236
  * blocks the step loop before a `requireApproval` step; the agent relays this
@@ -275,7 +320,7 @@ export interface ArtifactRequestIpc {
275
320
  /** Storage key echoed from the grant response. `completeUpload` only. */
276
321
  storageKey?: string;
277
322
  }
278
- export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc | CacheRequestIpc | ProvenanceRequestIpc | ArtifactRequestIpc | StepApprovalRequestIpc;
323
+ export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | HooksDeclaredMessage | CompletionHooksDoneMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc | CacheRequestIpc | ProvenanceRequestIpc | ArtifactRequestIpc | GitGrantRequestIpc | StepApprovalRequestIpc;
279
324
  /** Instruct the workflow runner to execute a job. */
280
325
  interface ExecuteMessage {
281
326
  type: 'execute';
@@ -349,6 +394,30 @@ export interface CacheResponseIpc {
349
394
  /** Error description (present when the relay or orchestrator failed). */
350
395
  error?: string;
351
396
  }
397
+ /**
398
+ * Result of a git write-grant operation (agent -> runner).
399
+ *
400
+ * `granted` reports what the forge ACTUALLY allowed, never an echo of the
401
+ * request — a static credential comes back unscoped because an SSH key or PAT
402
+ * cannot be narrowed. `error` is set when the pre-flight found the grant
403
+ * narrower than requested, which fails the elevation before any git runs.
404
+ */
405
+ export interface GitGrantResponseIpc {
406
+ type: 'git.grant.response';
407
+ /** Matches the original request's requestId. */
408
+ requestId: string;
409
+ /** Grant id to pass to a later `revoke`. `elevate` only, on success. */
410
+ grantId?: string;
411
+ /** What the credential can actually do. `elevate` only, on success. */
412
+ granted?: {
413
+ scoped: false;
414
+ } | {
415
+ scoped: true;
416
+ permissions: Record<string, string>;
417
+ };
418
+ /** Error description (present when elevation or revocation failed). */
419
+ error?: string;
420
+ }
352
421
  /**
353
422
  * Resolution of a step-level approval hold (agent -> runner). Relayed from the
354
423
  * orchestrator's `step.approval-resolved` WS message. On `approved` the runner
@@ -423,7 +492,7 @@ export interface ArtifactResponseIpc {
423
492
  /** Error description (present when the relay or orchestrator failed). */
424
493
  error?: string;
425
494
  }
426
- export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc | CacheResponseIpc | ProvenanceResponseIpc | ArtifactResponseIpc | StepApprovalResolvedIpc;
495
+ export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc | CacheResponseIpc | ProvenanceResponseIpc | ArtifactResponseIpc | StepApprovalResolvedIpc | GitGrantResponseIpc;
427
496
  /**
428
497
  * All data the workflow runner needs to execute a job inside the sandbox.
429
498
  *
@@ -470,6 +539,15 @@ export interface JobExecutionRequest {
470
539
  workflowAuth?: GitAuthDispatch;
471
540
  /** URL to a pre-packed `.kici/` source tarball (skip clone if present). */
472
541
  sourceTarUrl?: string;
542
+ /**
543
+ * Absolute path to the agent's git credential helper, when one is available.
544
+ *
545
+ * The runner configures it on every clone it makes, so each later git network
546
+ * operation asks the agent for a freshly minted credential rather than
547
+ * relying on one captured at clone time. Absent for a container job, whose
548
+ * git has no route to the agent's socket — see the dual-mode container work.
549
+ */
550
+ credentialHelperPath?: string;
473
551
  /** SHA-256 hash of the source tarball bytes for integrity verification. */
474
552
  sourceTarHash?: string;
475
553
  /** URL to pre-built dependency tarball (skip install if present). */
@@ -568,6 +646,15 @@ export interface JobExecutionRequest {
568
646
  * Defaults to `apply` when unset.
569
647
  */
570
648
  checkMode?: CheckMode;
649
+ /**
650
+ * Between-jobs out-of-band cleanup re-run. When true, the runner reuses the
651
+ * preserved workdir (no clone, no deps, no concurrency / rule / step
652
+ * evaluation) and runs ONLY the job's `onFailure` + `cleanup` hooks with a
653
+ * synthesized failed outcome — the durable re-run for a job whose runner was
654
+ * hard-killed before its in-band completion hooks ran. Bare-metal / in-place
655
+ * only; the supervisor never sets it for a normally-finished job.
656
+ */
657
+ cleanupOnly?: boolean;
571
658
  /** When true, skip git clone -- use overlay tarball as complete workspace. */
572
659
  fullRepo?: boolean;
573
660
  /** URL to download the encrypted overlay tarball (test runs with uncommitted changes). */
@@ -612,6 +699,13 @@ export interface JobExecutionRequest {
612
699
  upstreamJobStatuses?: Record<string, import('@kici-dev/engine').ExecutionJobStatus>;
613
700
  /** This job's declared upstream needs (normalized lock edges) used to shape ctx.needs for steps. */
614
701
  jobNeeds?: readonly unknown[];
702
+ /**
703
+ * Per-invoke-gate results for any upstream invoke gate this job `needs`, keyed
704
+ * by gate job name. Populated for a standard `run:`/step downstream job so
705
+ * `ctx.needs['<gate>'].result` is an `InvokeResult[]` rather than the fan-out
706
+ * group shape its proxy children would otherwise imply.
707
+ */
708
+ upstreamInvokeResults?: Record<string, import('@kici-dev/engine').InvokeResult[]>;
615
709
  /** Resolved private npm registries for `npm install` auth (token bytes already filled). */
616
710
  npmRegistries?: ReadonlyArray<{
617
711
  url: string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The KiCI-provisioned runtime a container job executes with.
3
+ *
4
+ * A container job used to require the customer's image to ship Node (to run the
5
+ * workflow runner) and git (to clone). That coupled every image a customer might
6
+ * name to KiCI's own toolchain. Instead KiCI provisions its own runtime — a
7
+ * pinned, official glibc-2.17 Node plus the runner bundle, both already inside
8
+ * the published `kici-agent` image — and mounts it read-only at `/opt/kici`.
9
+ * The image then needs only a glibc and a shell.
10
+ *
11
+ * This module is a pure descriptor on purpose: the container-runtime calls that
12
+ * act on it live in the spawn helper, so the launch contract stays unit-testable
13
+ * without a daemon.
14
+ */
15
+ /** Architectures the published runtime covers. */
16
+ export type RuntimeArch = 'x64' | 'arm64';
17
+ /** Read-only mount point of the KiCI-provisioned runtime inside a job container. */
18
+ export declare const KICI_RUNTIME_MOUNT = "/opt/kici";
19
+ /** Mount point of the injected Node tree inside a job container. */
20
+ export declare const KICI_RUNTIME_NODE_DIR = "/opt/kici/node";
21
+ /** The injected node executable. Absolute — never resolved from the image's PATH. */
22
+ export declare const KICI_RUNTIME_NODE = "/opt/kici/node/bin/node";
23
+ /** How to materialize the runtime into a job container. */
24
+ export interface RuntimeSource {
25
+ arch: RuntimeArch;
26
+ /** Where the runtime lives inside the `kici-agent` image. */
27
+ sourceImagePath: string;
28
+ /** Where it is mounted inside the job container. */
29
+ mountPath: string;
30
+ /** Always read-only: a job must not be able to rewrite the runtime it runs under. */
31
+ readOnly: true;
32
+ }
33
+ /**
34
+ * Launch the runner with the injected node, never the image's own.
35
+ *
36
+ * The customer image is not required to ship Node, so a bare `node` would
37
+ * resolve to nothing (or, worse, to an unrelated build).
38
+ */
39
+ export declare function runnerLaunchArgv(runnerMountPath: string): string[];
40
+ /**
41
+ * Describe how to materialize `/opt/kici` for a job container on `arch`.
42
+ *
43
+ * Refuses an architecture the published runtime does not cover, rather than
44
+ * mounting a foreign-architecture binary that surfaces mid-job as a confusing
45
+ * `exec format error`.
46
+ */
47
+ export declare function resolveRuntimeSource(arch: RuntimeArch): RuntimeSource;
48
+ //# sourceMappingURL=kici-runtime.d.ts.map
@@ -112,6 +112,13 @@ export interface StepLoopOptions {
112
112
  workflowRepo?: RepoInfo;
113
113
  /** Job-level hooks. */
114
114
  jobHooks?: JobHooks;
115
+ /**
116
+ * Force the completion-hook sequence down the failed path (run `onFailure`
117
+ * then `cleanup`, not `onSuccess`) regardless of step outcomes. Set by the
118
+ * between-jobs out-of-band cleanup re-run, which runs no steps but must drive
119
+ * the declared cleanup as if the job had failed.
120
+ */
121
+ forceInitialFailure?: boolean;
115
122
  /**
116
123
  * Declarative cache phase dependencies (cache API + IPC + pseudo-step index
117
124
  * allocator). When set, each step's own `cache` specs are restored before the
@@ -1,5 +1,5 @@
1
1
  import type { JobDispatch, LogStream } from '@kici-dev/engine';
2
- import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage, CacheRequestIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, ArtifactRequestIpc, ArtifactResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './ipc-protocol.js';
2
+ import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage, CacheRequestIpc, GitGrantRequestIpc, GitGrantResponseIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, ArtifactRequestIpc, ArtifactResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './ipc-protocol.js';
3
3
  /**
4
4
  * Common interface for all execution sandbox backends.
5
5
  *
@@ -41,6 +41,28 @@ export interface ExecutionSandbox {
41
41
  * - Firecracker: no-op (VM lifecycle managed by scaler)
42
42
  */
43
43
  teardown(): Promise<void>;
44
+ /**
45
+ * SIGTERM → grace → SIGKILL the finished job's process group, reaping any
46
+ * daemon a step backgrounded. Returns the number of reap attempts that
47
+ * signalled a live group. Only the bare-metal backend reaps a process group;
48
+ * others (which reap their whole tree on teardown) omit this.
49
+ */
50
+ reap?(): Promise<number>;
51
+ /**
52
+ * Whether the runner signalled that its completion hooks ran before it exited.
53
+ * `false` means the runner was hard-killed before running declared cleanup —
54
+ * the between-jobs phase's cue to re-run it out-of-band. Absent ⇒ treated as
55
+ * `true` (no re-run) for backends that reap their whole tree.
56
+ */
57
+ readonly completionHooksRan?: boolean;
58
+ /** Whether the job declares an `onFailure` / `cleanup` hook (bare-metal). */
59
+ readonly declaresCleanup?: boolean;
60
+ /**
61
+ * Re-run the finished job's declared cleanup / onFailure hooks out-of-band,
62
+ * against the preserved workdir, in a fresh bounded child. Resolves on success
63
+ * and rejects on failure so the caller can time it out. Bare-metal only.
64
+ */
65
+ runCleanupOnly?(workDir: string, signal: AbortSignal): Promise<void>;
44
66
  }
45
67
  /** Options for preparing the sandbox environment. */
46
68
  export interface SandboxSetupOptions {
@@ -59,6 +81,19 @@ export interface SandboxSetupOptions {
59
81
  * inside `executeJob`, so it ignores this field.
60
82
  */
61
83
  extraReadOnlyBinds?: string[];
84
+ /**
85
+ * Populate the sandbox workspace from `workDir` instead of letting the runner
86
+ * clone inside it.
87
+ *
88
+ * Set when the AGENT already cloned on the host. The container backend packs
89
+ * `workDir` and copies it into the container's `/workspace` volume; the
90
+ * bare-metal backend already runs against `workDir` directly and ignores it.
91
+ *
92
+ * Cloning on the host is what lets a container image ship without git — and
93
+ * it puts clone-time credentials on the host, where the credential helper
94
+ * already works, instead of needing a route into a hardened container.
95
+ */
96
+ workspaceFromHost?: boolean;
62
97
  }
63
98
  /** Options for executing a job inside the sandbox. */
64
99
  export interface JobExecutionOptions {
@@ -101,6 +136,25 @@ export interface JobExecutionOptions {
101
136
  * working — the runner falls back to a "not configured" cache response.
102
137
  */
103
138
  onCacheRequest?: (request: CacheRequestIpc) => Promise<CacheResponseIpc>;
139
+ /**
140
+ * Callback for opening or closing a git write grant on behalf of the sandbox.
141
+ *
142
+ * The grant lives in the AGENT's grant table because the credential helper
143
+ * git spawns is a separate process that consults the agent, not the sandbox
144
+ * runner. Optional so harnesses that don't thread git credentials keep
145
+ * working — the runner falls back to a "not configured" error response.
146
+ */
147
+ onGitGrantRequest?: (request: GitGrantRequestIpc) => Promise<GitGrantResponseIpc>;
148
+ /**
149
+ * Absolute path to the agent's git credential helper.
150
+ *
151
+ * Threaded into the execution request so the runner configures it on every
152
+ * clone. Set for the bare-metal (fork) backend, whose runner is a host
153
+ * process that can reach the agent's socket. Deliberately NOT set by the
154
+ * container backend: git runs inside the container and has no route to it —
155
+ * see the dual-mode container work.
156
+ */
157
+ credentialHelperPath?: string;
104
158
  /**
105
159
  * Callback for relaying a provenance bundle upload request from the sandbox to
106
160
  * the orchestrator. The sandbox runner sends `provenance.request` IPC; the
@@ -17,13 +17,23 @@ import { $ } from 'zx';
17
17
  import { type TempScope } from '@kici-dev/core/tmp';
18
18
  import { ExecutionJobStatus } from '@kici-dev/engine';
19
19
  import type { Step, StepInput, StepContext, RepoInfo, EventPayload } from '@kici-dev/sdk';
20
- import type { NeedsContext, FanoutPosition, EventDefinition } from '@kici-dev/sdk';
20
+ import type { NeedsContext, FanoutPosition, EventDefinition, InvokeResult } from '@kici-dev/sdk';
21
21
  import type { OutputsMap, StepRefMap, TrackedStepSecrets } from '@kici-dev/sdk';
22
- import type { RunnerToAgentMessage, JobExecutionRequest } from './ipc-protocol.js';
22
+ import type { RunnerToAgentMessage, JobExecutionRequest, GitGrantResponseIpc } from './ipc-protocol.js';
23
23
  import { LogMasker } from './log-masker.js';
24
24
  import type { StepLoopOptions } from './step-loop.js';
25
25
  import type { GeneratorRepoPair } from '../generator-context.js';
26
26
  import { type RuleEvaluationResult } from '../rule-evaluator.js';
27
+ /**
28
+ * Open a write window for one repository, run `fn`, and close it.
29
+ *
30
+ * The revoke rides a `finally`, so a throwing callback still closes the
31
+ * window — and the agent's TTL is the backstop if this process dies outright.
32
+ */
33
+ export declare function withRepoWrite(repository: string, opts: {
34
+ permissions?: Record<string, string>;
35
+ credential?: string;
36
+ }, fn: () => Promise<void>, send: (msg: RunnerToAgentMessage) => void, wait: (requestId: string) => Promise<GitGrantResponseIpc>): Promise<void>;
27
37
  /**
28
38
  * Build `ctx.needs` for a job's steps from the dispatch envelope. Reconstructs
29
39
  * an {@link UpstreamSnapshot} from `upstreamJobOutputs` (flat per single job;
@@ -32,7 +42,7 @@ import { type RuleEvaluationResult } from '../rule-evaluator.js';
32
42
  * `{ result, status }` / ordered-array shape via the shared SDK builder. Returns
33
43
  * undefined when the job declares no needs.
34
44
  */
35
- export declare function buildStepNeedsContext(declaredNeeds: readonly unknown[] | undefined, upstreamJobOutputs: Record<string, Record<string, unknown>> | undefined, upstreamJobStatuses: Record<string, ExecutionJobStatus> | undefined): NeedsContext | undefined;
45
+ export declare function buildStepNeedsContext(declaredNeeds: readonly unknown[] | undefined, upstreamJobOutputs: Record<string, Record<string, unknown>> | undefined, upstreamJobStatuses: Record<string, ExecutionJobStatus> | undefined, upstreamInvokeResults?: Record<string, InvokeResult[]>): NeedsContext | undefined;
36
46
  /**
37
47
  * Build a fresh zx `$` shell bound to the sandbox working directory and the
38
48
  * sanitized environment (process.env was set by the parent via env-sanitizer
@@ -71,6 +81,16 @@ export declare function buildSandboxShell(cwd: string, stepIndex: number, masked
71
81
  * emit overload); a definition resolves to its `.name`, a string passes through.
72
82
  */
73
83
  export declare function resolveEmitEventName(nameOrDefinition: string | EventDefinition): string;
84
+ /**
85
+ * Reject a user `ctx.emit` whose event name uses a reserved prefix: `kici.`
86
+ * (KiCI-internal system events — the event scaler's scale-up / scale-down) or
87
+ * `__` (the events the orchestrator mints for itself). A workflow step must
88
+ * forge neither — a `__` name is dispatched as a TRUSTED ref and skips the
89
+ * event-storm rate limiter, so emitting one would be a privilege escalation,
90
+ * not merely a naming collision. Throws a clear error at the emit call; the
91
+ * orchestrator enforces the same reservation authoritatively.
92
+ */
93
+ export declare function assertUserEmittableEventName(eventName: string): void;
74
94
  /**
75
95
  * Sanitize a raw identifier into a valid temp label: lowercase, every
76
96
  * non-`[a-z0-9-]` char to `-`, falling back to `'step'` when the result is
@@ -0,0 +1,24 @@
1
+ /**
2
+ * What a scaler-managed agent does about its idle-shutdown timer when
3
+ * `register.ack` arrives.
4
+ *
5
+ * This lives apart from `server.ts` so the rule has exactly one
6
+ * implementation: the server acts on the verdict, and the tests assert on the
7
+ * verdict. Importing `server.ts` starts the agent (it ends in a top-level
8
+ * `guardStartup`), so a function exported from there could only ever be tested
9
+ * by a copy of itself.
10
+ */
11
+ /**
12
+ * - `none` — do nothing: either the agent is not scaler-managed, or it is busy.
13
+ * - `warm` — pre-spawned to wait for work; disarm any timer and stay up.
14
+ * - `pending-dispatch` — a bound job is on its way; arm the long safety timeout.
15
+ * - `idle` — arm the ordinary scaler-idle timer.
16
+ */
17
+ export type IdleShutdownDecision = 'none' | 'warm' | 'pending-dispatch' | 'idle';
18
+ export declare function decideIdleShutdown(input: {
19
+ scalerManaged: boolean;
20
+ activeJobs: number;
21
+ pendingDispatch?: boolean;
22
+ warmPool?: boolean;
23
+ }): IdleShutdownDecision;
24
+ //# sourceMappingURL=idle-shutdown.d.ts.map