Skip to content

feat(runtime): add the host-process WorkloadRuntime backend (RIG-3512) - #1120

Merged
trunk-io[bot] merged 4 commits into
mainfrom
compass-service-owner/rig-3512-host-backend
Sep 13, 2026
Merged

feat(runtime): add the host-process WorkloadRuntime backend (RIG-3512)#1120
trunk-io[bot] merged 4 commits into
mainfrom
compass-service-owner/rig-3512-host-backend

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 10 PRs:

  1. main
  2. "feat(runtime): add the host-process WorkloadRuntime backend (RIG-3512)" (this PR)
  3. feat(runner): derive the agent uid from the Runner's euid on the host backend (RIG-3512) #1125
  4. feat(runner): deliver the agent socket and config by path on the host backend (RIG-3512) #1135
  5. feat(runtime): distinguish a configured egress policy from an unset one #1142
  6. feat(runtime): refuse an egress policy the host tier cannot enforce #1143
  7. feat(runner): carry no egress policy on a backend that cannot enforce one #1145
  8. feat(runner): report the runtime tier and egress posture per session #1148
  9. feat(cli): show the runtime tier and egress posture per session #1153
  10. feat(ui): mark each agent's runtime tier and egress posture #1154
  11. feat(runner): declare the runtime tier and egress posture at enrollment #1156

Implements the frozen nine-method WorkloadRuntime as direct host child
processes and registers it in SelectBackend as host. The default stays
podman: host is opted into explicitly, never fallen back to.

Create/Start/Exists are bookkeeping over a per-agent 0700 state dir;
MountLabel and Resize are degenerate by construction and say so at the
method, Resize returning a typed unsupported error rather than a silent
success. Exec and ExecStreaming accept only the Runner's own euid as
AsUser, erroring on any other uid rather than running it wrong.

A leaked grandchild holding the output pipe past its parent's exit makes
Go's WaitDelay fire on a command that already completed. That is not a
spawn failure: the exit status is real, so it is reported with the
captured output instead of being discarded.

The child inherits the Runner's environment with ExecSpec.Env overriding
per key. A host child has no image to supply a baseline, so without it
even PATH is unset and an unqualified command cannot resolve.

Joins the shared WorkloadRuntime contract suite as an untagged leg. The
podman and microVM legs are gated on an engine or KVM being present, so
the shared contract ran against nothing in most jobs; the host backend
needs neither. The rows the engine legs pinned to a baked uid 1000 now
take the uid from caps, because the host backend runs as the Runner's own
euid and that is 1001 on a stock hosted runner. The defaults keep every
podman and microVM assertion byte-identical.

Co-authored-by: Matt Wilkinson matt@rigel.build

@trunk-io

trunk-io Bot commented Sep 11, 2026

Copy link
Copy Markdown

😎 This pull request was merged.

@linear-code

linear-code Bot commented Sep 11, 2026

Copy link
Copy Markdown

RIG-3512

@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-service-owner-rig-35.compass-eng-docs.pages.dev

Deployed from compass-service-owner/rig-3512-host-backend at fdb1562.

rigel-mintaka and others added 4 commits September 12, 2026 15:14
Implements the frozen nine-method WorkloadRuntime as direct host child
processes and registers it in SelectBackend as `host`. The default stays
podman: host is opted into explicitly, never fallen back to.

Create/Start/Exists are bookkeeping over a per-agent 0700 state dir;
MountLabel and Resize are degenerate by construction and say so at the
method, Resize returning a typed unsupported error rather than a silent
success. Exec and ExecStreaming accept only the Runner's own euid as
AsUser, erroring on any other uid rather than running it wrong.

A leaked grandchild holding the output pipe past its parent's exit makes
Go's WaitDelay fire on a command that already completed. That is not a
spawn failure: the exit status is real, so it is reported with the
captured output instead of being discarded.

The child inherits the Runner's environment with ExecSpec.Env overriding
per key. A host child has no image to supply a baseline, so without it
even PATH is unset and an unqualified command cannot resolve.

Joins the shared WorkloadRuntime contract suite as an untagged leg. The
podman and microVM legs are gated on an engine or KVM being present, so
the shared contract ran against nothing in most jobs; the host backend
needs neither. The rows the engine legs pinned to a baked uid 1000 now
take the uid from caps, because the host backend runs as the Runner's own
euid and that is 1001 on a stock hosted runner. The defaults keep every
podman and microVM assertion byte-identical.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…pEff assertion (RIG-3512)

Remove read handle.proc after dropping the mutex while ExecStreaming
writes it under the mutex. Besides the data race, Remove could see a nil
proc for a child being spawned, skip the kill, and drop the handle --
leaving a live process under the operator's uid that nothing owns. The
capture now happens in the same critical section as the map delete.

The shared CapAdd row asserted an all-zero CapEff. A host child inherits
the Runner's capabilities, so that asserted the test runner is
unprivileged rather than that CapAdd was ignored, and would red on a CI
runner with any capability. The host leg now compares the child's set to
the Runner's own; the engine legs keep the all-zero assertion.

Also drops the write-only handle env field rather than document it as
honored, and stops the egress and state-root comments implying
protections the tier does not have.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
… mid-spawn (RIG-3512)

ExecStreaming records the process on the handle only after cmd.Start, so
a Remove landing in that gap found no process to kill, deleted the
handle, and wiped the state dir under a child that was already running.
Nothing could reach that child afterwards, because Remove addresses a
workload by id and the id was gone.

ExecStreaming now re-checks the handle is still present before recording
the process, and reaps its own child and errors when it is not.

The concurrency test's comment claimed it covered this; it does not, so
it now says it guards the data race only. A test for the window itself
is not included: the interleaving is not reachable from the public API,
because the handle lookup rejects a removed id before the spawn begins.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
The previous commit shipped this fix untested on the grounds that the
window was unreachable from the public API. That was wrong:
startedHandle releases the lock before returning, so the whole span from
there through the spawn is lock-free and a concurrent Remove can land
anywhere inside it.

An after-spawn hook, nil in production, lets the test occupy that gap
directly instead of racing for it. Reverting the re-check now fails the
test in milliseconds; the race-based attempt could not fail at all,
because the handle lookup rejects a removed id before the spawn starts
and won almost every interleaving.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-service-owner/rig-3512-host-backend branch from 7701fa8 to fdb1562 Compare September 12, 2026 19:37
@trunk-io
trunk-io Bot merged commit f9db16d into main Sep 13, 2026
14 checks passed
@trunk-io
trunk-io Bot deleted the compass-service-owner/rig-3512-host-backend branch September 13, 2026 00:29
@trunk-io

trunk-io Bot commented Sep 13, 2026

Copy link
Copy Markdown

This pull request was merged into main as part of stacked PR 1156.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants