@kici-dev/agent 0.4.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 (43) 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/download.d.ts +27 -2
  15. package/dist/execution/dynamic-job-serializer.d.ts +6 -2
  16. package/dist/execution/generator-context.d.ts +54 -0
  17. package/dist/execution/global-eval-runner.d.ts +92 -0
  18. package/dist/execution/global-workflow-env.d.ts +57 -0
  19. package/dist/execution/image-build/build-engine.d.ts +75 -0
  20. package/dist/execution/image-build/build-step.d.ts +57 -0
  21. package/dist/execution/image-build/resolve-build-spec.d.ts +41 -0
  22. package/dist/execution/image-build/runtime-facts.d.ts +31 -0
  23. package/dist/execution/init-runner.d.ts +60 -3
  24. package/dist/execution/job-runner.d.ts +146 -0
  25. package/dist/execution/sandbox/bare-metal-sandbox.d.ts +25 -1
  26. package/dist/execution/sandbox/container-sandbox.d.ts +97 -2
  27. package/dist/execution/sandbox/fork-runner.d.ts +55 -1
  28. package/dist/execution/sandbox/image-preflight.d.ts +38 -0
  29. package/dist/execution/sandbox/ipc-protocol.d.ts +96 -2
  30. package/dist/execution/sandbox/kici-runtime.d.ts +48 -0
  31. package/dist/execution/sandbox/step-loop.d.ts +17 -1
  32. package/dist/execution/sandbox/types.d.ts +55 -1
  33. package/dist/execution/sandbox/workflow-runner.d.ts +75 -4
  34. package/dist/execution/workflow-loader.d.ts +8 -1
  35. package/dist/idle-shutdown.d.ts +24 -0
  36. package/dist/index.js +221 -80
  37. package/dist/metrics/prometheus.d.ts +36 -10
  38. package/dist/server.js +3197 -462
  39. package/dist/workflow-runner-bundle.js +42207 -41104
  40. package/dist/workflow-runner.js +594 -196
  41. package/dist/ws/orchestrator-client.d.ts +95 -3
  42. package/package.json +10 -10
  43. package/sbom.spdx.json +464 -454
@@ -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
@@ -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, FanoutPosition } from '@kici-dev/sdk';
8
+ import type { Step, StepContext, HookInput, OutputsMap, StepSecretMountRecord, FanoutPosition, RepoInfo } 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';
@@ -101,8 +101,24 @@ export interface StepLoopOptions {
101
101
  dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>;
102
102
  /** Fan-out position for the rule context (`ctx.fanout`); undefined on a non-fan-out job. */
103
103
  fanout?: FanoutPosition;
104
+ /**
105
+ * The repo whose event triggered this run, for the step rule context
106
+ * (`ctx.sourceRepo`). Present for a global workflow, absent otherwise — a
107
+ * step rule reads the source tree through `.path` the same way a job rule
108
+ * and a generator do.
109
+ */
110
+ sourceRepo?: RepoInfo;
111
+ /** The repo that registered the workflow, for the step rule context (`ctx.workflowRepo`). */
112
+ workflowRepo?: RepoInfo;
104
113
  /** Job-level hooks. */
105
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;
106
122
  /**
107
123
  * Declarative cache phase dependencies (cache API + IPC + pseudo-step index
108
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
@@ -16,12 +16,24 @@
16
16
  import { $ } from 'zx';
17
17
  import { type TempScope } from '@kici-dev/core/tmp';
18
18
  import { ExecutionJobStatus } from '@kici-dev/engine';
19
- import type { Step, StepInput, StepContext } from '@kici-dev/sdk';
20
- import type { NeedsContext, FanoutPosition, EventDefinition } from '@kici-dev/sdk';
19
+ import type { Step, StepInput, StepContext, RepoInfo, EventPayload } 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
+ import type { StepLoopOptions } from './step-loop.js';
25
+ import type { GeneratorRepoPair } from '../generator-context.js';
24
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>;
25
37
  /**
26
38
  * Build `ctx.needs` for a job's steps from the dispatch envelope. Reconstructs
27
39
  * an {@link UpstreamSnapshot} from `upstreamJobOutputs` (flat per single job;
@@ -30,7 +42,7 @@ import { type RuleEvaluationResult } from '../rule-evaluator.js';
30
42
  * `{ result, status }` / ordered-array shape via the shared SDK builder. Returns
31
43
  * undefined when the job declares no needs.
32
44
  */
33
- 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;
34
46
  /**
35
47
  * Build a fresh zx `$` shell bound to the sandbox working directory and the
36
48
  * sanitized environment (process.env was set by the parent via env-sanitizer
@@ -69,6 +81,16 @@ export declare function buildSandboxShell(cwd: string, stepIndex: number, masked
69
81
  * emit overload); a definition resolves to its `.name`, a string passes through.
70
82
  */
71
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;
72
94
  /**
73
95
  * Sanitize a raw identifier into a valid temp label: lowercase, every
74
96
  * non-`[a-z0-9-]` char to `-`, falling back to `'step'` when the result is
@@ -103,6 +125,42 @@ export declare function createSandboxStepContext(workDir: string, stepIndex: num
103
125
  export declare function deriveFanout(request: JobExecutionRequest): FanoutPosition | undefined;
104
126
  /** Raw provider webhook body for ctx.rawPayload — nested in the envelope. */
105
127
  export declare function rawPayloadFromEvent(event: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
128
+ /**
129
+ * Build the argument the user's `concurrency.group(...)` function receives.
130
+ *
131
+ * The only inputs an author can scope a group by. `branch` alone does not
132
+ * separate one repository from another — an organization-wide workflow runs on
133
+ * events from many repositories, and their default branches share a name — so
134
+ * `event.sourceRepo` is what makes a per-source-repository group expressible.
135
+ * That is why the orchestrator writes the whole normalized envelope into every
136
+ * global job config: an empty `event` here silently collapses every repository
137
+ * into one group, and with `cancelInProgress` (the default) one repository's
138
+ * push then cancels another's in-flight run.
139
+ *
140
+ * Boundary cast: the wire `request.event` is untyped JSON that, per the unified
141
+ * event protocol, always carries the normalized event envelope.
142
+ */
143
+ export declare function buildConcurrencyGroupContext(request: JobExecutionRequest): {
144
+ branch: string;
145
+ event: EventPayload;
146
+ };
147
+ /**
148
+ * Phase 5 — Inject env vars and build the `RepoInfo` pair that the generator,
149
+ * the job rules, and step contexts receive when the job is a global workflow.
150
+ * No-op for normal jobs.
151
+ *
152
+ * Runs at the head of phase 5, before anything that may read the source tree:
153
+ * the generator's re-evaluation (phase 5) and the job rules (phase 7) both take
154
+ * the returned pair, and both must see what the pre-dispatch evaluation saw.
155
+ *
156
+ * `sourceRepo.path` is this sandbox's own absolute path — the same repo lives at
157
+ * a different path in the evaluation that produced the job list. Read through
158
+ * it; never compare it or embed it in a job name.
159
+ */
160
+ export declare function setupGlobalWorkflowEnv(request: JobExecutionRequest, isGlobal: boolean, workflowDir: string, sourceDir: string): {
161
+ workflowRepo: RepoInfo;
162
+ sourceRepo: RepoInfo;
163
+ } | undefined;
106
164
  /**
107
165
  * Mutable state threaded through {@link coerceStep} for one job normalization
108
166
  * pass: the shared `step-N` counter, the bare-function → name ref map, and the
@@ -150,6 +208,19 @@ export declare function resolveChangedFilesForRules(request: JobExecutionRequest
150
208
  export declare function buildJobRuleCompletion(ruleResult: RuleEvaluationResult, normalizedSteps: Step[]): (RunnerToAgentMessage & {
151
209
  type: 'job.complete';
152
210
  }) | null;
211
+ /**
212
+ * Build the inputs the step loop turns into every step rule's `RuleContext`.
213
+ *
214
+ * Shares its source with `maybeSkipJobOnRules` so a step rule and a job rule
215
+ * see the same world: the same event, env, dispatch inputs, fan-out position,
216
+ * and — for a global workflow — the same source / workflow repo pair. A step
217
+ * rule that received the pair as `undefined` while the job rule beside it
218
+ * received the real thing would read the same `RuleContext` type two ways.
219
+ *
220
+ * The pair is spread conditionally: a present-but-undefined `sourceRepo` reads
221
+ * as "declared" to a rule that guards on the key rather than the value.
222
+ */
223
+ export declare function buildStepLoopRuleInputs(request: JobExecutionRequest, repos: GeneratorRepoPair | undefined): Pick<StepLoopOptions, 'event' | 'env' | 'dispatchInputs' | 'fanout' | 'sourceRepo' | 'workflowRepo'>;
153
224
  /**
154
225
  * Build the step loop's KICI_ENV/KICI_PATH callbacks with a per-step delta-file
155
226
  * pair (keyed by step index). `beforeStepEnvFiles(stepIndex)` lazily creates the
@@ -7,6 +7,7 @@
7
7
  * Node's normal ESM lookup against `.kici/node_modules/`.
8
8
  */
9
9
  import type { Workflow, StepInput, DynamicJobFn, OutputsMap, StepRefMap } from '@kici-dev/sdk';
10
+ import { type GeneratorRepoPair } from './generator-context.js';
10
11
  /**
11
12
  * SDK output-map setters resolved from a specific `@kici-dev/sdk` instance.
12
13
  * The agent uses these to wire the workflow module's OWN SDK copy (a different
@@ -99,7 +100,13 @@ export declare function extractStepsFromDynamicJob(workflow: Workflow, dynamicIn
99
100
  /** Frozen upstream snapshot for a result-aware generator (rebuilds ctx.needs). */
100
101
  upstreamSnapshot?: import('@kici-dev/engine').UpstreamSnapshot,
101
102
  /** Declared upstream needs that shape ctx.needs. */
102
- declaredNeeds?: readonly unknown[]): Promise<{
103
+ declaredNeeds?: readonly unknown[],
104
+ /**
105
+ * The source / workflow repo pair for a global workflow. Must match what the
106
+ * first evaluation saw, or a generator that reads the source tree produces a
107
+ * different job list here and the determinism check below fails the job.
108
+ */
109
+ repos?: GeneratorRepoPair): Promise<{
103
110
  steps: readonly StepInput[];
104
111
  droppedJobs: string[];
105
112
  }>;
@@ -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