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)

  • runUserSteps marshals the user's run: 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_SOCKET environment variable, advertised by Serve() at startup.
  • Script timeout and cancellation semantics mirror runScriptSteps so 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 PartialLineType continuation marker (+): when the inner timestamper splits a logical line across two physical lines at a buffer boundary, the splitter strips the artificial \n so 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) == 0 guard so concrete handles all jobs when FF_USE_CONCRETE is on.
  • Switches the step-runner log stream from b.logger.Stream() to b.logger.StepRunnerStream() (introduced in !6710 (merged)) to prevent double-timestamping.

Concrete builder (functions/concrete/builder/builder.go)

  • Passes job.Run through to the concrete config as RunSteps, so the nested dispatch has access to the user's step definitions.

Socket advertisement (commands/steps/steps.go)

  • Serve() now sets STEP_RUNNER_SOCKET in the environment so in-process builtins (like concrete) can discover the socket path without re-deriving it.
  • Exports EnvSocketPath constant 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=1

The 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.

Edited by Arran Walker

Merge request reports

Loading