@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
@@ -8,7 +8,7 @@ import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:pa
8
8
  import { $ } from "zx";
9
9
  import { createLogger, deriveSharedSecret, initZx, normalizeLineEndings, sha256, sha256File, toErrorMessage } from "@kici-dev/shared";
10
10
  import { createTempScope, makeTempDir } from "@kici-dev/core/tmp";
11
- import { CacheOutcome, CacheStepType, CheckMode, CheckStepOutcome, ExecutionJobStatus, ExecutionStepStatus, LogStream, StepConcurrencyKind, TimeoutReason, artifactInvalidNameError, checkArtifactName } from "@kici-dev/engine";
11
+ import { CacheOutcome, CacheStepType, CheckMode, CheckStepOutcome, ExecutionJobStatus, ExecutionStepStatus, LogStream, StepConcurrencyKind, TimeoutReason, artifactInvalidNameError, checkArtifactName, reservedEventNamePrefix } from "@kici-dev/engine";
12
12
  import { execFile, execFileSync } from "node:child_process";
13
13
  import { buildKiciApi, buildNeedsContext, createRuleContext, createStepSecrets, evaluateRules, isDynamicJobFn, isEventDefinition, isParallelGroup, normalizeApproval, normalizeCacheSpecs, provenanceSubjectIsPath, resolveJobOutputs, resolveStepOutputs, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
14
14
  import { OIDC_TOKEN_REQUEST_METHOD } from "@kici-dev/engine/protocol/messages/oidc-token-relay";
@@ -123,9 +123,11 @@ async function setupSshAuth(opts) {
123
123
  if (opts.hostKeyPolicy === "pinned" && !opts.knownHosts) throw new Error("pinned hostKeyPolicy requires knownHosts content");
124
124
  const { path: tempDir, cleanup } = await makeTempDir("ssh");
125
125
  const keyPath = join(tempDir, "id");
126
- await writeFile(keyPath, opts.privateKey.endsWith("\n") ? opts.privateKey : `${opts.privateKey}\n`, { mode: 384 });
126
+ const pem = opts.privateKey.endsWith("\n") ? opts.privateKey : `${opts.privateKey}\n`;
127
+ await writeFile(keyPath, pem, { mode: 384 });
127
128
  const knownHostsPath = join(tempDir, "known_hosts");
128
- await writeFile(knownHostsPath, opts.hostKeyPolicy === "pinned" ? opts.knownHosts : "", { mode: 384 });
129
+ const knownHostsBody = opts.hostKeyPolicy === "pinned" ? opts.knownHosts : "";
130
+ await writeFile(knownHostsPath, knownHostsBody, { mode: 384 });
129
131
  const parts = [
130
132
  "ssh",
131
133
  "-i",
@@ -462,13 +464,14 @@ async function signStatementDsse(payloadType, statementBytes) {
462
464
  const kid = await calculateJwkThumbprint(publicJwk, "sha256");
463
465
  publicJwk.kid = kid;
464
466
  const pae = dssePae(payloadType, statementBytes);
467
+ const sig = new Uint8Array(await crypto.subtle.sign({
468
+ name: "ECDSA",
469
+ hash: "SHA-256"
470
+ }, privateKey, pae));
465
471
  return {
466
472
  envelope: buildDsseEnvelope(payloadType, statementBytes, [{
467
473
  keyid: kid,
468
- sig: new Uint8Array(await crypto.subtle.sign({
469
- name: "ECDSA",
470
- hash: "SHA-256"
471
- }, privateKey, pae))
474
+ sig
472
475
  }]),
473
476
  publicJwk
474
477
  };
@@ -495,7 +498,8 @@ async function attestProvenance(deps, input) {
495
498
  startedOn: now,
496
499
  finishedOn: now
497
500
  });
498
- const { envelope, publicJwk } = await signStatementDsse(IN_TOTO_PAYLOAD_TYPE, new TextEncoder().encode(JSON.stringify(statement)));
501
+ const statementBytes = new TextEncoder().encode(JSON.stringify(statement));
502
+ const { envelope, publicJwk } = await signStatementDsse(IN_TOTO_PAYLOAD_TYPE, statementBytes);
499
503
  const bundle = {
500
504
  mediaType: KICI_PROVENANCE_BUNDLE_MEDIA_TYPE,
501
505
  dsseEnvelope: envelope,
@@ -780,7 +784,7 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
780
784
  await extractTarball(data, scratchDir);
781
785
  await moveScratchIntoRepo(scratchDir, workDir);
782
786
  await cleanupScratch(scratchDir);
783
- const sizeMB = (data.length / (1024 * 1024)).toFixed(2);
787
+ const sizeMB = (data.length / 1048576).toFixed(2);
784
788
  logger$6.info("Dependencies restored from cache (file)", {
785
789
  sizeMB,
786
790
  targetDir: workDir
@@ -814,7 +818,7 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
814
818
  var logger$6, DOWNLOAD_TIMEOUT_MS$2, SCRATCH_DIR_BASENAME_PREFIX, SCRATCH_DIR_GIT_EXCLUDE_GLOB;
815
819
  var init_dep_restore = __esmMin((() => {
816
820
  logger$6 = createLogger({ prefix: "dep-restore" });
817
- DOWNLOAD_TIMEOUT_MS$2 = 300 * 1e3;
821
+ DOWNLOAD_TIMEOUT_MS$2 = 3e5;
818
822
  SCRATCH_DIR_BASENAME_PREFIX = ".dep-restore-scratch-";
819
823
  SCRATCH_DIR_GIT_EXCLUDE_GLOB = `.kici/${SCRATCH_DIR_BASENAME_PREFIX}*`;
820
824
  }));
@@ -952,8 +956,8 @@ var logger$5, DOWNLOAD_TIMEOUT_MS$1, UPLOAD_TIMEOUT_MS, UPLOAD_RETRY_BASE_DELAY_
952
956
  var init_download = __esmMin((() => {
953
957
  init_dep_restore();
954
958
  logger$5 = createLogger({ prefix: "agent:download" });
955
- DOWNLOAD_TIMEOUT_MS$1 = 300 * 1e3;
956
- UPLOAD_TIMEOUT_MS = 300 * 1e3;
959
+ DOWNLOAD_TIMEOUT_MS$1 = 3e5;
960
+ UPLOAD_TIMEOUT_MS = 3e5;
957
961
  UPLOAD_RETRY_BASE_DELAY_MS = 500;
958
962
  PresignedUploadHttpError = class extends Error {
959
963
  statusCode;
@@ -986,11 +990,13 @@ init_download();
986
990
  * right destination (repo entries under `workDir`, home entries under the
987
991
  * homedir). Extraction lands in a scratch dir first, then moves each group
988
992
  * into place so a partial restore never leaves half-written paths in the live
989
- * tree (mirrors dep-restore).
993
+ * tree (mirrors dep-restore). The scratch dir honors `KICI_TMPDIR`, which may
994
+ * sit on a different filesystem from the workspace, so the move falls back to
995
+ * copy-then-remove on `EXDEV` rather than assuming a same-filesystem rename.
990
996
  */
991
997
  const logger$4 = createLogger({ prefix: "cache-engine" });
992
998
  /** Download timeout for a presigned cache GET: 5 minutes. */
993
- const DOWNLOAD_TIMEOUT_MS = 300 * 1e3;
999
+ const DOWNLOAD_TIMEOUT_MS = 3e5;
994
1000
  /**
995
1001
  * Resolve a cache path. `~`-prefixed -> home root; otherwise repo-root-relative.
996
1002
  * Rejects absolute paths and `..` escapes so a workflow cannot read or clobber
@@ -1065,6 +1071,32 @@ async function packCachePaths(workDir, paths, roots) {
1065
1071
  await cleanup();
1066
1072
  }
1067
1073
  }
1074
+ /**
1075
+ * Move a tree from `src` to `dest`. Attempts a rename (same-filesystem, cheap)
1076
+ * and falls back to copy-then-remove only on `EXDEV` — the errno `rename(2)`
1077
+ * reports when the scratch dir (which honors `KICI_TMPDIR`) is on a different
1078
+ * filesystem from the destination workspace. `verbatimSymlinks` keeps a cached
1079
+ * symlink graph intact (mirroring packCachePaths), and `preserveTimestamps`
1080
+ * keeps mtimes stable so the fallback is byte-for-byte equivalent to the
1081
+ * rename. Any non-`EXDEV` error propagates so a real permission or corruption
1082
+ * failure is never silently turned into a copy.
1083
+ */
1084
+ async function moveOrCopy(src, dest) {
1085
+ try {
1086
+ await rename(src, dest);
1087
+ } catch (err) {
1088
+ if (err.code !== "EXDEV") throw err;
1089
+ await cp(src, dest, {
1090
+ recursive: true,
1091
+ verbatimSymlinks: true,
1092
+ preserveTimestamps: true
1093
+ });
1094
+ await rm(src, {
1095
+ recursive: true,
1096
+ force: true
1097
+ });
1098
+ }
1099
+ }
1068
1100
  /** Move the extracted `__repo__` / `__home__` groups from a scratch dir into place. */
1069
1101
  async function moveAnchoredGroups(scratchDir, workDir, home) {
1070
1102
  for (const anchor of await readdir(scratchDir)) {
@@ -1078,7 +1110,7 @@ async function moveAnchoredGroups(scratchDir, workDir, home) {
1078
1110
  recursive: true,
1079
1111
  force: true
1080
1112
  });
1081
- await rename(join(anchorDir, child), dest);
1113
+ await moveOrCopy(join(anchorDir, child), dest);
1082
1114
  }
1083
1115
  }
1084
1116
  }
@@ -1557,7 +1589,7 @@ function buildMergedFlatSecrets(orchestratorSecrets, namespacedSecrets) {
1557
1589
  //#endregion
1558
1590
  //#region src/execution/hook-executor.ts
1559
1591
  /** Default hook timeout: 5 minutes */
1560
- const DEFAULT_HOOK_TIMEOUT_MS = 300 * 1e3;
1592
+ const DEFAULT_HOOK_TIMEOUT_MS = 3e5;
1561
1593
  /**
1562
1594
  * Build outcome metadata from execution state.
1563
1595
  *
@@ -2435,6 +2467,7 @@ async function runJobCompletionHooks(opts, initial, outputsMap, startTime) {
2435
2467
  opts
2436
2468
  });
2437
2469
  }
2470
+ opts.sendIpc({ type: "completion-hooks-done" });
2438
2471
  return state;
2439
2472
  }
2440
2473
  /**
@@ -2451,7 +2484,7 @@ async function runJobCompletionHooks(opts, initial, outputsMap, startTime) {
2451
2484
  async function executeStepLoop(opts) {
2452
2485
  const startTime = opts.startTime ?? Date.now();
2453
2486
  const stepResults = [];
2454
- const state = { failed: false };
2487
+ const state = { failed: opts.forceInitialFailure === true };
2455
2488
  const nodes = opts.stepNodes ?? opts.steps.map((step, i) => ({
2456
2489
  kind: "sequential",
2457
2490
  step,
@@ -2555,7 +2588,7 @@ var StepTaskRegistry = class {
2555
2588
  //#endregion
2556
2589
  //#region src/execution/env-init/init-phase.ts
2557
2590
  /** Default init timeout when a spec sets none: 10 minutes. */
2558
- const DEFAULT_INIT_TIMEOUT_MS = 600 * 1e3;
2591
+ const DEFAULT_INIT_TIMEOUT_MS = 6e5;
2559
2592
  /**
2560
2593
  * Marker thrown when an init command exceeds its wall-clock budget. Carries the
2561
2594
  * distinct P3 timeout reason so the phase result + job.complete report a timeout
@@ -2921,6 +2954,37 @@ function armJobDeadline(timeoutMs, onTimeout) {
2921
2954
  //#endregion
2922
2955
  //#region src/checkout/git-clone.ts
2923
2956
  /**
2957
+ * Point a clone at the agent's credential helper.
2958
+ *
2959
+ * Only the helper PATH is written — never a secret — which is what makes it
2960
+ * safe to persist in `.git/config`. `useHttpPath` makes git include
2961
+ * `path=owner/repo.git` in every credential query, without which the helper
2962
+ * could not tell one repository from another and a write grant could not be
2963
+ * confined to its own repo.
2964
+ */
2965
+ function configureCredentialHelper(workDir, helperPath) {
2966
+ execFileSync("git", [
2967
+ "-C",
2968
+ workDir,
2969
+ "config",
2970
+ "credential.helper",
2971
+ helperPath
2972
+ ], {
2973
+ stdio: "pipe",
2974
+ timeout: 1e4
2975
+ });
2976
+ execFileSync("git", [
2977
+ "-C",
2978
+ workDir,
2979
+ "config",
2980
+ "credential.useHttpPath",
2981
+ "true"
2982
+ ], {
2983
+ stdio: "pipe",
2984
+ timeout: 1e4
2985
+ });
2986
+ }
2987
+ /**
2924
2988
  * Strip auth credentials from git error messages to prevent token leakage.
2925
2989
  * Node's execFileSync includes the full command line (including -c http.extraHeader
2926
2990
  * and GIT_SSH_COMMAND flags) in error messages, which would expose Base64-encoded
@@ -2947,7 +3011,7 @@ function redactSensitive(input) {
2947
3011
  * @throws Error if clone fails or SHA does not match
2948
3012
  */
2949
3013
  async function gitClone(options) {
2950
- const { repoUrl, ref, sha, workDir, token, gitAuth, depth = 1 } = options;
3014
+ const { repoUrl, ref, sha, workDir, token, gitAuth, depth = 1, credentialHelperPath, sshCleanupRegistry } = options;
2951
3015
  const auth = gitAuth ? gitAuth : token ? {
2952
3016
  kind: "basic",
2953
3017
  user: "x-access-token",
@@ -3001,6 +3065,7 @@ async function gitClone(options) {
3001
3065
  } catch (err) {
3002
3066
  throw sanitizeGitError(err);
3003
3067
  }
3068
+ if (credentialHelperPath) configureCredentialHelper(workDir, credentialHelperPath);
3004
3069
  if (!sha || sha === "HEAD") return;
3005
3070
  const envOpts = env ? { env } : {};
3006
3071
  if (!execFileSync("git", [
@@ -3056,11 +3121,90 @@ async function gitClone(options) {
3056
3121
  if (!recheckedSha.startsWith(sha)) throw new Error(`SHA mismatch: expected ${sha}, got ${recheckedSha}`);
3057
3122
  }
3058
3123
  } finally {
3059
- if (sshSetup) await sshSetup.cleanup().catch(() => {});
3124
+ if (sshSetup) {
3125
+ if (sshCleanupRegistry) {
3126
+ const setup = sshSetup;
3127
+ sshCleanupRegistry.defer(() => setup.cleanup().catch(() => {}));
3128
+ } else await sshSetup.cleanup().catch(() => {});
3129
+ }
3060
3130
  if (safeDirCleanup) await safeDirCleanup();
3061
3131
  }
3062
3132
  }
3063
3133
  //#endregion
3134
+ //#region src/checkout/clone-job-repos.ts
3135
+ /**
3136
+ * Clone a job's repositories — from the host or from inside the sandbox.
3137
+ *
3138
+ * This used to live only inside the workflow runner, which meant the clone
3139
+ * always happened wherever the runner ran: for a container job, inside the
3140
+ * customer's image, which therefore had to ship git. Extracting it lets the
3141
+ * AGENT clone on the host and copy the tree in, so the image needs no git —
3142
+ * and it puts clone-time credentials on the host, where the credential helper
3143
+ * already works, rather than needing a route into a container hardened with
3144
+ * `CapDrop: ALL`.
3145
+ *
3146
+ * Both callers run the SAME code: the runner keeps calling it for bare-metal
3147
+ * and for the legacy container path, and the agent calls it for a host-side
3148
+ * checkout. A second implementation would be two subtly different clones.
3149
+ */
3150
+ /**
3151
+ * Clone whatever this job needs, or nothing.
3152
+ *
3153
+ * Three modes, unchanged from where this logic used to live: full-repo overlay
3154
+ * (no clone), global dual-clone (workflow repo + source repo), and the ordinary
3155
+ * single-repo clone.
3156
+ */
3157
+ async function cloneJobRepos(request, dirs, deps) {
3158
+ if (request.checkout === false) return;
3159
+ const helper = request.credentialHelperPath ? { credentialHelperPath: request.credentialHelperPath } : {};
3160
+ if (request.fullRepo) {
3161
+ await mkdir(dirs.workDir, { recursive: true });
3162
+ deps.log("Full-repo mode: skipping git clone (workspace from overlay tarball)");
3163
+ return;
3164
+ }
3165
+ if (deps.isGlobal) {
3166
+ await mkdir(dirs.workflowDir, { recursive: true });
3167
+ await mkdir(dirs.sourceDir, { recursive: true });
3168
+ const workflowAuth = request.workflowAuth ?? request.sourceAuth;
3169
+ const sourceAuth = request.sourceAuth ?? request.workflowAuth;
3170
+ deps.log(`Global workflow: cloning workflow repo ${request.workflowRepoUrl} ref=${request.workflowRef} into ${dirs.workflowDir}`);
3171
+ await gitClone({
3172
+ repoUrl: request.workflowRepoUrl,
3173
+ ref: request.workflowRef ?? "",
3174
+ sha: request.workflowSha ?? "",
3175
+ workDir: dirs.workflowDir,
3176
+ gitAuth: workflowAuth,
3177
+ token: workflowAuth ? void 0 : request.token,
3178
+ ...helper
3179
+ });
3180
+ await deps.excludeScratchFromGit(dirs.workflowDir);
3181
+ deps.log(`Global workflow: cloning source repo ${request.repoUrl} ref=${request.ref} into ${dirs.sourceDir}`);
3182
+ await gitClone({
3183
+ repoUrl: request.repoUrl,
3184
+ ref: request.ref,
3185
+ sha: request.sha,
3186
+ workDir: dirs.sourceDir,
3187
+ gitAuth: sourceAuth,
3188
+ token: sourceAuth ? void 0 : request.token,
3189
+ ...helper
3190
+ });
3191
+ deps.log("Dual-clone complete");
3192
+ return;
3193
+ }
3194
+ deps.log(`Cloning ${request.repoUrl} ref=${request.ref} into ${dirs.workDir}`);
3195
+ await gitClone({
3196
+ repoUrl: request.repoUrl,
3197
+ ref: request.ref,
3198
+ sha: request.sha,
3199
+ workDir: dirs.workDir,
3200
+ gitAuth: request.sourceAuth,
3201
+ token: request.sourceAuth ? void 0 : request.token,
3202
+ ...helper
3203
+ });
3204
+ await deps.excludeScratchFromGit(dirs.workDir);
3205
+ deps.log("Clone complete");
3206
+ }
3207
+ //#endregion
3064
3208
  //#region src/execution/npm-resolver.ts
3065
3209
  /**
3066
3210
  * Resolve the npm CLI path relative to the running Node.js binary.
@@ -3589,7 +3733,7 @@ const logger$2 = createLogger({ prefix: "dep-installer" });
3589
3733
  const execFileAsync = promisify(execFile);
3590
3734
  /** Install subprocess timeout (10 min) and stdout/stderr buffer (128 MiB). */
3591
3735
  const INSTALL_TIMEOUT_MS = 6e5;
3592
- const INSTALL_MAX_BUFFER = 128 * 1024 * 1024;
3736
+ const INSTALL_MAX_BUFFER = 134217728;
3593
3737
  /**
3594
3738
  * Detect the package manager for the cloned repo from its committed manifests.
3595
3739
  * A pnpm workspace's `packageManager` field + `pnpm-lock.yaml` live at the repo
@@ -4002,7 +4146,8 @@ function buildGeneratorContext(input) {
4002
4146
  */
4003
4147
  async function resolveWorkflowSdkSetters(workflowFilePath) {
4004
4148
  try {
4005
- const sdk = await import(pathToFileURL(createRequire(workflowFilePath).resolve("@kici-dev/sdk")).href);
4149
+ const sdkEntry = createRequire(workflowFilePath).resolve("@kici-dev/sdk");
4150
+ const sdk = await import(pathToFileURL(sdkEntry).href);
4006
4151
  if (typeof sdk.setStepOutputsMap === "function" && typeof sdk.setStepRefMap === "function" && typeof sdk.setJobOutputsMap === "function") return {
4007
4152
  setStepOutputsMap: sdk.setStepOutputsMap,
4008
4153
  setStepRefMap: sdk.setStepRefMap,
@@ -4015,8 +4160,8 @@ async function resolveWorkflowSdkSetters(workflowFilePath) {
4015
4160
  setJobOutputsMap
4016
4161
  };
4017
4162
  }
4018
- const AGENT_SDK_VERSION = "0.5.0";
4019
- const AGENT_SDK_BUNDLE_HASH = "5b85e7cffa4a08e39329ff80448a2744af8e5b5f9840ec3801fad45ed11a3de9";
4163
+ const AGENT_SDK_VERSION = "0.6.0";
4164
+ const AGENT_SDK_BUNDLE_HASH = "22faf0da45de7c243ce87f80fd33ee51b1df52809fde457b16bb821678f65eb3";
4020
4165
  /**
4021
4166
  * Register the ESM loader hook that transforms `.ts` / `.tsx` files on the fly
4022
4167
  * for subsequent dynamic `import()` calls. Idempotent at our level via the
@@ -4362,7 +4507,8 @@ async function applyOverlay(config) {
4362
4507
  throw new Error(`Overlay download failed from ${tarballUrl.replace(/\?.*$/, "?[redacted]")}: ${toErrorMessage(err)}`);
4363
4508
  }
4364
4509
  const cliPubKeyBuf = Buffer.from(cliPublicKey, "base64");
4365
- const aesKey = deriveSharedSecret(Buffer.from(orchestratorPrivateKey, "base64"), cliPubKeyBuf);
4510
+ const orchPrivKeyBuf = Buffer.from(orchestratorPrivateKey, "base64");
4511
+ const aesKey = deriveSharedSecret(orchPrivKeyBuf, cliPubKeyBuf);
4366
4512
  const decryptedData = decryptBuffer(encryptedData, aesKey);
4367
4513
  logger.info("Extracting overlay tarball", { size: decryptedData.length });
4368
4514
  const extractDir = path.join(tmpDir, "extracted");
@@ -4448,7 +4594,7 @@ async function applyOverlay(config) {
4448
4594
  */
4449
4595
  init_download();
4450
4596
  init_dep_restore();
4451
- const AGENT_VERSION = "0.5.0";
4597
+ const AGENT_VERSION = "0.6.0";
4452
4598
  process.on("uncaughtException", (err) => {
4453
4599
  process.stderr.write(`[workflow-runner] UNCAUGHT EXCEPTION: ${err.message}\n`);
4454
4600
  if (err.stack) process.stderr.write(`[workflow-runner] Stack: ${err.stack}\n`);
@@ -4665,6 +4811,56 @@ const EMIT_RESPONSE_TIMEOUT_MS = 5e3;
4665
4811
  * (per research doc pitfall 5: event was already persisted by orchestrator,
4666
4812
  * so it will still be routed even if the ack is lost).
4667
4813
  */
4814
+ /** In-flight git write-grant requests, keyed by requestId. */
4815
+ const pendingGitGrants = /* @__PURE__ */ new Map();
4816
+ /** How long to wait for the agent to answer a grant request. */
4817
+ const GIT_GRANT_TIMEOUT_MS = 6e4;
4818
+ function waitForGitGrant(requestId) {
4819
+ return new Promise((resolve) => {
4820
+ const timer = setTimeout(() => {
4821
+ pendingGitGrants.delete(requestId);
4822
+ resolve({
4823
+ type: "git.grant.response",
4824
+ requestId,
4825
+ error: "timed out waiting for the agent to answer a git write-grant request"
4826
+ });
4827
+ }, GIT_GRANT_TIMEOUT_MS);
4828
+ pendingGitGrants.set(requestId, (r) => {
4829
+ clearTimeout(timer);
4830
+ pendingGitGrants.delete(requestId);
4831
+ resolve(r);
4832
+ });
4833
+ });
4834
+ }
4835
+ /**
4836
+ * Open a write window for one repository, run `fn`, and close it.
4837
+ *
4838
+ * The revoke rides a `finally`, so a throwing callback still closes the
4839
+ * window — and the agent's TTL is the backstop if this process dies outright.
4840
+ */
4841
+ async function withRepoWrite(repository, opts, fn, send, wait) {
4842
+ const requestId = randomUUID();
4843
+ send({
4844
+ type: "git.grant.request",
4845
+ requestId,
4846
+ op: "elevate",
4847
+ repository,
4848
+ permissions: opts.permissions ?? { contents: "write" },
4849
+ ...opts.credential ? { credentialName: opts.credential } : {}
4850
+ });
4851
+ const granted = await wait(requestId);
4852
+ if (granted.error) throw new Error(`Cannot open a git write window for '${repository}': ${granted.error}`);
4853
+ try {
4854
+ await fn();
4855
+ } finally {
4856
+ send({
4857
+ type: "git.grant.request",
4858
+ requestId: randomUUID(),
4859
+ op: "revoke",
4860
+ grantId: granted.grantId
4861
+ });
4862
+ }
4863
+ }
4668
4864
  function waitForEmitResponse(requestId) {
4669
4865
  return new Promise((resolve) => {
4670
4866
  const timer = setTimeout(() => {
@@ -4849,7 +5045,7 @@ const pendingApprovalResolutions = /* @__PURE__ */ new Map();
4849
5045
  * and sends `expired` when it lapses; this is a safety net well above any sane
4850
5046
  * approval window so the runner cannot hang forever if the resolution is lost.
4851
5047
  */
4852
- const APPROVAL_RESOLUTION_TIMEOUT_MS = 10080 * 60 * 1e3;
5048
+ const APPROVAL_RESOLUTION_TIMEOUT_MS = 6048e5;
4853
5049
  /** Wait for an approval.resolved from the agent with the given requestId. */
4854
5050
  function waitForApprovalResolution(requestId) {
4855
5051
  return new Promise((resolve) => {
@@ -5175,12 +5371,15 @@ function dispatchAgentMessage(msg) {
5175
5371
  } else if (msg.type === "event.emit.response") {
5176
5372
  const pending = pendingEmitResponses.get(msg.requestId);
5177
5373
  if (pending) pending.resolve(msg);
5178
- } else if (msg.type === "concurrency.ack") {
5374
+ } else if (msg.type === "git.grant.response") pendingGitGrants.get(msg.requestId)?.(msg);
5375
+ else if (msg.type === "concurrency.ack") {
5179
5376
  if (pendingConcurrencyAck) pendingConcurrencyAck.resolve(msg);
5180
5377
  } else if (msg.type === "agent.api.response") {
5181
5378
  const pending = pendingApiResponses.get(msg.requestId);
5182
- if (pending) if (msg.error) pending.reject(new Error(msg.error));
5183
- else pending.resolve(msg.result);
5379
+ if (pending) {
5380
+ if (msg.error) pending.reject(new Error(msg.error));
5381
+ else pending.resolve(msg.result);
5382
+ }
5184
5383
  } else if (msg.type === "cache.response") {
5185
5384
  const pending = pendingCacheResponses.get(msg.requestId);
5186
5385
  if (pending) pending.resolve(msg);
@@ -5276,16 +5475,22 @@ function needBaseName(need) {
5276
5475
  * `{ result, status }` / ordered-array shape via the shared SDK builder. Returns
5277
5476
  * undefined when the job declares no needs.
5278
5477
  */
5279
- function buildStepNeedsContext(declaredNeeds, upstreamJobOutputs, upstreamJobStatuses) {
5478
+ function buildStepNeedsContext(declaredNeeds, upstreamJobOutputs, upstreamJobStatuses, upstreamInvokeResults) {
5280
5479
  if (!declaredNeeds || declaredNeeds.length === 0) return void 0;
5281
5480
  const statuses = upstreamJobStatuses ?? {};
5282
5481
  const jobs = {};
5283
5482
  const groups = {};
5284
5483
  const snapStatuses = {};
5484
+ const invokeResults = {};
5285
5485
  const resolvedNeeds = [];
5286
5486
  for (const need of declaredNeeds) {
5287
5487
  const base = needBaseName(need);
5288
5488
  if (!base) continue;
5489
+ if (base.kind === "job" && upstreamInvokeResults?.[base.key]) {
5490
+ invokeResults[base.key] = upstreamInvokeResults[base.key];
5491
+ resolvedNeeds.push(base.key);
5492
+ continue;
5493
+ }
5289
5494
  const childNames = Object.keys(statuses).filter((n) => n.startsWith(`${base.key} (`));
5290
5495
  if (base.kind === "group" || childNames.length > 0) {
5291
5496
  groups[base.key] = [...childNames].sort();
@@ -5302,11 +5507,13 @@ function buildStepNeedsContext(declaredNeeds, upstreamJobOutputs, upstreamJobSta
5302
5507
  resolvedNeeds.push(base.key);
5303
5508
  }
5304
5509
  }
5305
- return buildNeedsContext({
5510
+ const snapshot = {
5306
5511
  jobs,
5307
5512
  groups,
5308
5513
  statuses: snapStatuses
5309
- }, resolvedNeeds);
5514
+ };
5515
+ if (Object.keys(invokeResults).length > 0) snapshot.invokeResults = invokeResults;
5516
+ return buildNeedsContext(snapshot, resolvedNeeds);
5310
5517
  }
5311
5518
  /**
5312
5519
  * Extract per-child output records from a fan-out outputs envelope, keyed by the
@@ -5347,7 +5554,8 @@ function buildStepSecrets(request, masker, onMaskerSecretsAdded) {
5347
5554
  async writeMountedFile(args) {
5348
5555
  const dir = await ensureTmpdir();
5349
5556
  mountCounter += 1;
5350
- const filePath = join(dir, args.name ?? `secret-${mountCounter}`);
5557
+ const filename = args.name ?? `secret-${mountCounter}`;
5558
+ const filePath = join(dir, filename);
5351
5559
  await fsPromises.writeFile(filePath, args.content);
5352
5560
  await fsPromises.chmod(filePath, args.mode);
5353
5561
  masker.registerSecrets({ [`__mount_${mountCounter}__`]: args.content });
@@ -5443,6 +5651,19 @@ function resolveEmitEventName(nameOrDefinition) {
5443
5651
  return isEventDefinition(nameOrDefinition) ? nameOrDefinition.name : nameOrDefinition;
5444
5652
  }
5445
5653
  /**
5654
+ * Reject a user `ctx.emit` whose event name uses a reserved prefix: `kici.`
5655
+ * (KiCI-internal system events — the event scaler's scale-up / scale-down) or
5656
+ * `__` (the events the orchestrator mints for itself). A workflow step must
5657
+ * forge neither — a `__` name is dispatched as a TRUSTED ref and skips the
5658
+ * event-storm rate limiter, so emitting one would be a privilege escalation,
5659
+ * not merely a naming collision. Throws a clear error at the emit call; the
5660
+ * orchestrator enforces the same reservation authoritatively.
5661
+ */
5662
+ function assertUserEmittableEventName(eventName) {
5663
+ const reservedPrefix = reservedEventNamePrefix(eventName);
5664
+ if (reservedPrefix) throw new Error(`event name prefix "${reservedPrefix}" is reserved for KiCI internal events and cannot be emitted from a workflow step (got "${eventName}")`);
5665
+ }
5666
+ /**
5446
5667
  * Sanitize a raw identifier into a valid temp label: lowercase, every
5447
5668
  * non-`[a-z0-9-]` char to `-`, falling back to `'step'` when the result is
5448
5669
  * empty. Applied to both the caller-supplied `ctx.mktemp(label)` and the
@@ -5545,6 +5766,7 @@ function createSandboxStepContext(workDir, stepIndex, stepName, request, maskedS
5545
5766
  artifacts: createArtifactsApi(workDir, buildArtifactTransport()),
5546
5767
  emit: async (nameOrDefinition, payload, options) => {
5547
5768
  const eventName = resolveEmitEventName(nameOrDefinition);
5769
+ assertUserEmittableEventName(eventName);
5548
5770
  const reqId = randomUUID();
5549
5771
  sendMessage({
5550
5772
  type: "event.emit",
@@ -5569,6 +5791,13 @@ function createSandboxStepContext(workDir, stepIndex, stepName, request, maskedS
5569
5791
  mktemp: (label) => jobTempScope.mktemp(sanitizeTempLabel(label ?? stepName)),
5570
5792
  mktempFile: (label, opts) => jobTempScope.mktempFile(sanitizeTempLabel(label ?? stepName), opts),
5571
5793
  kici,
5794
+ repo: {
5795
+ identifier: repoIdentifierFromUrl(request.repoUrl),
5796
+ path: workDir,
5797
+ ref: request.ref,
5798
+ sha: request.sha,
5799
+ withWrite: (opts, fn) => withRepoWrite(repoIdentifierFromUrl(request.repoUrl), opts, fn, sendMessage, waitForGitGrant)
5800
+ },
5572
5801
  attestProvenance: buildAttestProvenanceFn(request, workDir, (o) => kici.oidc.token(o)),
5573
5802
  ...rawPayload && { rawPayload },
5574
5803
  ...request.provider && { provider: request.provider },
@@ -5581,7 +5810,7 @@ function createSandboxStepContext(workDir, stepIndex, stepName, request, maskedS
5581
5810
  })(),
5582
5811
  dispatchInputs: request.dispatchInputs ?? {},
5583
5812
  ...(() => {
5584
- const needs = buildStepNeedsContext(request.jobNeeds, request.upstreamJobOutputs, request.upstreamJobStatuses);
5813
+ const needs = buildStepNeedsContext(request.jobNeeds, request.upstreamJobOutputs, request.upstreamJobStatuses, request.upstreamInvokeResults);
5585
5814
  return needs ? { needs } : {};
5586
5815
  })()
5587
5816
  };
@@ -5639,80 +5868,22 @@ function abortAndExit(reason) {
5639
5868
  * progress IPC log lines as before.
5640
5869
  */
5641
5870
  async function cloneRepoIfRequested(request, workDir, workflowDir, sourceDir, isGlobal) {
5642
- if (request.checkout === false) return;
5643
- if (request.fullRepo) {
5644
- trace("fullRepo mode -- skipping git clone, workspace from overlay");
5645
- await fsPromises.mkdir(workDir, { recursive: true });
5646
- sendMessage({
5647
- type: "log.line",
5648
- stepIndex: -1,
5649
- line: "[workflow-runner] Full-repo mode: skipping git clone (workspace from overlay tarball)"
5650
- });
5651
- return;
5652
- }
5653
- if (isGlobal) {
5654
- trace(`starting dual-clone (global workflow)`);
5655
- await fsPromises.mkdir(workflowDir, { recursive: true });
5656
- await fsPromises.mkdir(sourceDir, { recursive: true });
5657
- const workflowAuth = request.workflowAuth ?? request.sourceAuth;
5658
- const sourceAuth = request.sourceAuth ?? request.workflowAuth;
5659
- sendMessage({
5660
- type: "log.line",
5661
- stepIndex: -1,
5662
- line: `[workflow-runner] Global workflow: cloning workflow repo ${request.workflowRepoUrl} ref=${request.workflowRef} into ${workflowDir}`
5663
- });
5664
- await gitClone({
5665
- repoUrl: request.workflowRepoUrl,
5666
- ref: request.workflowRef ?? "",
5667
- sha: request.workflowSha ?? "",
5668
- workDir: workflowDir,
5669
- gitAuth: workflowAuth,
5670
- token: workflowAuth ? void 0 : request.token
5671
- });
5672
- trace("workflow repo clone complete");
5673
- await excludeScratchFromGit(workflowDir);
5674
- sendMessage({
5675
- type: "log.line",
5676
- stepIndex: -1,
5677
- line: `[workflow-runner] Global workflow: cloning source repo ${request.repoUrl} ref=${request.ref} into ${sourceDir}`
5678
- });
5679
- await gitClone({
5680
- repoUrl: request.repoUrl,
5681
- ref: request.ref,
5682
- sha: request.sha,
5683
- workDir: sourceDir,
5684
- gitAuth: sourceAuth,
5685
- token: sourceAuth ? void 0 : request.token
5686
- });
5687
- trace("source repo clone complete");
5688
- sendMessage({
5689
- type: "log.line",
5690
- stepIndex: -1,
5691
- line: "[workflow-runner] Dual-clone complete"
5692
- });
5693
- return;
5694
- }
5695
- trace("starting git clone");
5696
- sendMessage({
5697
- type: "log.line",
5698
- stepIndex: -1,
5699
- line: `[workflow-runner] Cloning ${request.repoUrl} ref=${request.ref} into ${workDir}`
5700
- });
5701
- await gitClone({
5702
- repoUrl: request.repoUrl,
5703
- ref: request.ref,
5704
- sha: request.sha,
5871
+ await cloneJobRepos(request, {
5705
5872
  workDir,
5706
- gitAuth: request.sourceAuth,
5707
- token: request.sourceAuth ? void 0 : request.token
5708
- });
5709
- await excludeScratchFromGit(workDir);
5710
- sendMessage({
5711
- type: "log.line",
5712
- stepIndex: -1,
5713
- line: "[workflow-runner] Clone complete"
5873
+ workflowDir,
5874
+ sourceDir
5875
+ }, {
5876
+ isGlobal,
5877
+ log: (line) => {
5878
+ trace(line);
5879
+ sendMessage({
5880
+ type: "log.line",
5881
+ stepIndex: -1,
5882
+ line: `[workflow-runner] ${line}`
5883
+ });
5884
+ },
5885
+ excludeScratchFromGit
5714
5886
  });
5715
- trace("git clone complete");
5716
5887
  }
5717
5888
  /**
5718
5889
  * Phase 1b — Apply the encrypted overlay tarball when present (test runs
@@ -6155,6 +6326,7 @@ async function runCancelPathHooks(args) {
6155
6326
  stepIndex: -1,
6156
6327
  line: `[kici] Cancel complete, job status: ${cancelFailureReason ? "failed" : "cancelled"}`
6157
6328
  });
6329
+ maskedSend({ type: "completion-hooks-done" });
6158
6330
  return {
6159
6331
  finalStatus: ExecutionJobStatus.enum.failed,
6160
6332
  cancelFailureReason
@@ -6565,6 +6737,30 @@ async function runInitPhaseOrFailJob(args) {
6565
6737
  * 7. Execute steps sequentially with IPC reporting
6566
6738
  * 8. Send job.complete and exit
6567
6739
  */
6740
+ /**
6741
+ * Extract + normalize the job's steps, or return an empty set for a cleanup-only
6742
+ * re-run (which runs no steps and skips the generator re-evaluation this would
6743
+ * otherwise trigger). Owns the `apiTransport` closure the generator uses.
6744
+ */
6745
+ async function extractStepsForRun(workflow, request, globalRepoInfo) {
6746
+ if (request.cleanupOnly) return {
6747
+ normalizedSteps: [],
6748
+ nodes: [],
6749
+ refMap: /* @__PURE__ */ new WeakMap(),
6750
+ driftDroppedJobs: []
6751
+ };
6752
+ const apiTransport = async (method, params) => {
6753
+ const reqId = randomUUID();
6754
+ sendMessage({
6755
+ type: "agent.api.request",
6756
+ requestId: reqId,
6757
+ method,
6758
+ params: params ?? {}
6759
+ });
6760
+ return waitForApiResponse(reqId);
6761
+ };
6762
+ return extractAndNormalizeSteps(workflow, request, apiTransport, globalRepoInfo);
6763
+ }
6568
6764
  async function main() {
6569
6765
  trace(`main() started, isForkMode=${isForkMode}, pid=${process.pid}`);
6570
6766
  sendMessage({ type: "ready" });
@@ -6572,7 +6768,7 @@ async function main() {
6572
6768
  const request = (await receiveRequest()).request;
6573
6769
  trace(`execute request received: workDir=${request.workDir}, workflow=${request.workflowName}, job=${request.jobName}`);
6574
6770
  const workDir = request.workDir;
6575
- const defaultTimeoutMs = request.defaultStepTimeoutMs ?? 1800 * 1e3;
6771
+ const defaultTimeoutMs = request.defaultStepTimeoutMs ?? 18e5;
6576
6772
  const isGlobal = request.isGlobalWorkflow === true;
6577
6773
  const workflowDir = isGlobal ? join(workDir, "workflow") : workDir;
6578
6774
  const sourceDir = isGlobal ? join(workDir, "source") : workDir;
@@ -6596,48 +6792,47 @@ async function main() {
6596
6792
  });
6597
6793
  jobDeadlineAbort.abort();
6598
6794
  });
6599
- await cloneRepoIfRequested(request, workDir, workflowDir, sourceDir, isGlobal);
6600
- await applyOverlayIfRequested(request, workflowDir);
6601
- await makeOverlayGitUsable(request, workflowDir);
6602
- if (aborted) abortAndExit("aborted after clone");
6603
- await installDependenciesIfNeeded(workflowDir, request);
6604
- if (aborted) abortAndExit("aborted after deps");
6605
- await restoreSourceTarballIfRequested(workflowDir, request);
6795
+ if (!request.cleanupOnly) {
6796
+ await cloneRepoIfRequested(request, workDir, workflowDir, sourceDir, isGlobal);
6797
+ await applyOverlayIfRequested(request, workflowDir);
6798
+ await makeOverlayGitUsable(request, workflowDir);
6799
+ if (aborted) abortAndExit("aborted after clone");
6800
+ await installDependenciesIfNeeded(workflowDir, request);
6801
+ if (aborted) abortAndExit("aborted after deps");
6802
+ await restoreSourceTarballIfRequested(workflowDir, request);
6803
+ }
6606
6804
  const loaded = await loadWorkflowModuleWithCapture(workflowDir, request, isGlobal, maskedSend);
6607
6805
  const module = loaded.module;
6608
6806
  const workflow = extractWorkflow(module, request.workflowName);
6609
- await evaluateConcurrencyGroupIfPresent(workflow, request);
6807
+ if (!request.cleanupOnly) await evaluateConcurrencyGroupIfPresent(workflow, request);
6610
6808
  const globalRepoInfo = setupGlobalWorkflowEnv(request, isGlobal, workflowDir, sourceDir);
6611
- const apiTransport = async (method, params) => {
6612
- const reqId = randomUUID();
6613
- sendMessage({
6614
- type: "agent.api.request",
6615
- requestId: reqId,
6616
- method,
6617
- params: params ?? {}
6618
- });
6619
- return waitForApiResponse(reqId);
6620
- };
6621
- const { normalizedSteps, nodes, refMap, driftDroppedJobs } = await extractAndNormalizeSteps(workflow, request, apiTransport, globalRepoInfo);
6809
+ const { normalizedSteps, nodes, refMap, driftDroppedJobs } = await extractStepsForRun(workflow, request, globalRepoInfo);
6622
6810
  const { operatorSecretKeys, outputsMap, secretOutputs, jobOutputsMap } = buildOutputInfrastructure(request, refMap, loaded.sdkSetters);
6623
6811
  const job = findJob(workflow, request.jobName);
6624
- const jobHasRules = (job?.rules?.length ?? 0) > 0;
6625
- const anyStepHasRules = normalizedSteps.some((s) => (s.rules?.length ?? 0) > 0);
6626
- await resolveChangedFilesForRules(request, sourceDir, jobHasRules || anyStepHasRules);
6627
- await maybeSkipJobOnRules(job, request, normalizedSteps, globalRepoInfo);
6628
- if (aborted) abortAndExit("aborted after rules");
6812
+ if (!request.cleanupOnly) {
6813
+ const jobHasRules = (job?.rules?.length ?? 0) > 0;
6814
+ const anyStepHasRules = normalizedSteps.some((s) => (s.rules?.length ?? 0) > 0);
6815
+ await resolveChangedFilesForRules(request, sourceDir, jobHasRules || anyStepHasRules);
6816
+ await maybeSkipJobOnRules(job, request, normalizedSteps, globalRepoInfo);
6817
+ if (aborted) abortAndExit("aborted after rules");
6818
+ }
6629
6819
  const jobHooks = collectJobHooks(job);
6820
+ maskedSend({
6821
+ type: "hooks-declared",
6822
+ declaresCleanup: Boolean(jobHooks.onFailure || jobHooks.cleanup)
6823
+ });
6630
6824
  flushOutputCapture();
6631
6825
  capturePrepareActive = false;
6632
6826
  const stepCwd = sourceDir;
6633
- await runInitPhaseOrFailJob({
6827
+ const envFiles = await createEnvFiles(tmpdir());
6828
+ if (!request.cleanupOnly) await runInitPhaseOrFailJob({
6634
6829
  job,
6635
6830
  stepCwd,
6636
- envFiles: await createEnvFiles(tmpdir()),
6831
+ envFiles,
6637
6832
  operatorSecretKeys,
6638
6833
  maskedSend
6639
6834
  });
6640
- const { cachePhaseDeps, jobCacheSpecs, jobCacheRestore } = await setupJobCache(stepCwd, normalizedSteps.length, job?.cache, maskedSend);
6835
+ const { cachePhaseDeps, jobCacheSpecs, jobCacheRestore } = await setupJobCache(stepCwd, normalizedSteps.length, request.cleanupOnly ? void 0 : job?.cache, maskedSend);
6641
6836
  const stepTasks = new StepTaskRegistry();
6642
6837
  const stepAbortControllers = /* @__PURE__ */ new Map();
6643
6838
  const jobTempScope = createTempScope();
@@ -6669,6 +6864,7 @@ async function main() {
6669
6864
  abortStep: (stepIndex) => stepAbortControllers.get(stepIndex)?.abort(),
6670
6865
  getStepAbortSignal: (stepIndex) => stepAbortControllers.get(stepIndex)?.signal,
6671
6866
  checkMode: request.checkMode,
6867
+ ...request.cleanupOnly ? { forceInitialFailure: true } : {},
6672
6868
  createStepContext: createStepCtxWithCapture,
6673
6869
  sendIpc: maskedSend,
6674
6870
  defaultTimeoutMs,
@@ -6777,6 +6973,6 @@ main().catch((error) => {
6777
6973
  setTimeout(() => process.exit(1), 100);
6778
6974
  });
6779
6975
  //#endregion
6780
- export { buildConcurrencyGroupContext, buildJobRuleCompletion, buildSandboxShell, buildStepEnvFileHooks, buildStepLoopRuleInputs, buildStepNeedsContext, coerceStep, createSandboxStepContext, deriveFanout, drainJobTempScope, rawPayloadFromEvent, resolveChangedFilesForRules, resolveEmitEventName, sanitizeTempLabel, setupGlobalWorkflowEnv };
6976
+ export { assertUserEmittableEventName, buildConcurrencyGroupContext, buildJobRuleCompletion, buildSandboxShell, buildStepEnvFileHooks, buildStepLoopRuleInputs, buildStepNeedsContext, coerceStep, createSandboxStepContext, deriveFanout, drainJobTempScope, rawPayloadFromEvent, resolveChangedFilesForRules, resolveEmitEventName, sanitizeTempLabel, setupGlobalWorkflowEnv, withRepoWrite };
6781
6977
 
6782
6978
  //# sourceMappingURL=workflow-runner.js.map