Add in-process execution mode for embedders
What
Adds an in-process execution mode for embedders that host step-runner inside a long-lived process with no job workspace.
di.WithInProcessOnly() yields a container that:
- registers none of the shipped builtins (
script,docker/auth, the OCI build/publish functions and their deprecated aliases) — embedders register their own with the existingdi.WithBuiltinFunc - accepts
builtin://function references and nothing else - rejects
execfunctions
Pair it with runner.WithGlobalCtxInProcessOnly() on the job's GlobalContext so an output_file/export_file expression fails rather than materializing a file. Two knobs rather than one because the container does not own the GlobalContext — the embedder constructs it.
Rejections happen at parse time, before any step runs, and each names the mode:
git function references are disabled: the runner is configured for in-process execution
exec functions are disabled: the runner is configured for in-process execution
step files are disabled: the runner is configured for in-process executionWhy
pkg/di has a real downstream consumer: argo-rollout registers seven custom builtins and drives StepParser directly. That module is loaded into kas, which has no job workspace, so "does not touch disk" is a security boundary there rather than a preference. Its own design contract already says so (docs/specs/functions-autoflow-module.md), but step-runner gave it no way to comply — it works around the shipped builtins with a builtin:// scheme gate at its own call site.
Most of the groundwork already landed: BuiltinContext exposes no file paths, builtins return a BuiltinResult, and StepFile materializes lazily. What remained was the shipped builtins, the eagerly created git cache dir, off-process references, exec, and the output_file/export_file hole.
Notable decisions
Protocol gating is an allowlist of one. Only function is permitted. local, git, oci, dynamic, and spec_def are all rejected, as is any protocol added to the enum later, until someone declares it safe. spec_def and dynamic do resolve in process, but a step in this mode should name a function the embedder registered and nothing else — and rejecting dynamic on sight avoids having to re-check after expansion.
Composition is unaffected: a registered builtin whose definition nests further steps runs them, provided each names a registered builtin in turn.
The mode is called "in-process", not "in-memory". "In-memory" would claim a filesystem guarantee the mode does not make — a builtin the embedder registers can write wherever it likes. "In-process" is a claim about what runs, which is what the mode actually enforces. (The branch name predates the rename.)
Default builtins now register after the options loop (first commit, a pure refactor). They previously registered first and relied on a caller's WithBuiltinFunc overwriting the map entry. Filling only unclaimed names afterwards preserves that precedence exactly while making the new option order-independent. Both behaviours are now pinned by tests; neither was before.
Out of scope
The gRPC service path is unchanged and still uses disk: jobs.New creates a job work dir, a scratch dir, and a log file, and file-type CI variables are written out. None of that is reachable from a container option, and the library-embedding case drives StepParser directly.
The work and project directories remain strings the embedder supplies. The runner neither creates nor validates them here; a builtin that treats them as real paths and writes there is doing so on its own behalf.
Reference
Closes https://gitlab.com/gitlab-org/ci-cd/runner-tools/argo-rollout/-/work_items/22+.