Dispatch run: through concrete via a nested step-runner job
What does this MR do?
When FF_USE_CONCRETE is enabled, this MR makes concrete own the entire job lifecycle — including when the user has a run: keyword. The user's run: steps are dispatched as a nested job by dialing back into the hosting step-runner under a derived tenant ID (<job-id>-run), so a failure in the user's steps surfaces as an error in concrete while the surrounding cache, artifact, and cleanup stages continue to run.
Previously, concrete only took over when len(b.Job.Run) == 0. This MR removes that guard, allowing concrete to handle all jobs behind the feature flag.
Problem
With the FF_USE_CONCRETE feature flag on, jobs that used the run: keyword fell through to the legacy execution path because executeScript short-circuited when Run was non-empty. This meant concrete's lifecycle management (cache extract → script → cache archive → artifact upload → cleanup) was bypassed for run: jobs, losing the resilience guarantees concrete provides (e.g. cleanup stages running even when user steps fail).
Additionally, the nested job's log stream needed careful handling: step-runner emits pre-stamped log lines with its own timestamp and stream markers. Without parsing and re-routing these, the outer masker's byte-by-byte phrase matcher would break on artificial newlines injected at the inner 8 KiB buffer boundary, causing masked secrets to leak in the trace.
Solution
Nested job dispatch (functions/concrete/run/run_steps.go — new)
runUserStepsmarshals the user'srun:steps into YAML and dispatches them via gRPC to the hosting step-runner's socket as a separate tenant (<job-id>-run).- The socket path is discovered via the
STEP_RUNNER_SOCKETenvironment variable, advertised byServe()at startup. - Script timeout and cancellation semantics mirror
runScriptStepsso timeout/cancel classification is consistent.
Stream splitter (streamSplitter in run_steps.go)
- Parses the inner step-runner's 32-byte wire header (timestamp + stream ID + stream type + line type) and routes stdout/stderr to the correct outer writers.
- Honors the
PartialLineTypecontinuation marker (+): when the inner timestamper splits a logical line across two physical lines at a buffer boundary, the splitter strips the artificial\nso the logical line lands contiguous in the outer trace. This preserves the outer masker's ability to match phrases that straddle the split. - Malformed lines (shorter than the header) are silently dropped.
Build-level changes (common/build.go)
- Removes the
len(b.Job.Run) == 0guard so concrete handles all jobs whenFF_USE_CONCRETEis on. - Switches the step-runner log stream from
b.logger.Stream()tob.logger.StepRunnerStream()(introduced in !6710 (merged)) to prevent double-timestamping.
Concrete builder (functions/concrete/builder/builder.go)
- Passes
job.Runthrough to the concrete config asRunSteps, so the nested dispatch has access to the user's step definitions.
Socket advertisement (commands/steps/steps.go)
Serve()now setsSTEP_RUNNER_SOCKETin the environment so in-process builtins (like concrete) can discover the socket path without re-deriving it.- Exports
EnvSocketPathconstant for the variable name.
How to test
Run the new unit tests for the stream splitter:
go test -race -run TestStreamSplitter ./functions/concrete/run/ -v -count=1The tests cover:
- Routing by stream type (stdout vs stderr)
- Continuation merging across buffer-overflow splits
- Cross-stream partial markers (continuation only affects its own stream)
- Byte-at-a-time partial reads (inBuf accumulation)
- Masker-safe contiguous output for phrases straddling a split
- Empty-body lines (header + newline only)
- Malformed short lines (dropped silently)
Dependencies
This MR is blocked by !6710 (merged), which introduces the StepRunnerStream writer on the build logger that this MR uses to prevent double-timestamping.