Skip to content

feat(managed-runtime): soul home mounts — resolve in-home {file:} config references - #844

Merged
danshapiro merged 19 commits into
mainfrom
the-usual/soul-home-mounts
Oct 13, 2026
Merged

danshapiro merged 19 commits into
mainfrom
the-usual/soul-home-mounts

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

What

Fresh-agent souls died at launch whenever a projected provider config referenced an absolute path under the operator's home — e.g. opencode "apiKey": "{file:/home/dan/.amplifier/ms-runpod.key}" — because the 65534:0 provider child could not read the file ("Configuration is invalid ... bad file reference"). Souls now mount the operator's home read-only at its own canonical path, and provider children keep their identity: under the rootless production daemon the child's gid 0 is the operator's group, so group-readable reference targets resolve with no identity change, no migration, and no digest churn.

Changes

  • Supervisor (freshell-supervisor): OperatorHomeMount derivation — owner-guarded --operator-home flag; HOME env fallback that declines for root/containerized supervisors; masks fixed to .freshell/.amplifier/onecli/.config/gcloud, filtered to destinations existing at creation. Wiring: RO home bind + tmpfs masks in container creation, additive marker env (FRESHELL_OPERATOR_HOME_MOUNT=1 + FRESHELL_OPERATOR_HOME), the resolved mount persisted as a Docker container label, and marker-gated time-invariant verify_inspect_config (compares live topology against the persisted creation-time mask list; pre-upgrade souls keep passing). A mount that cannot be serialized is dropped, never a launch refusal.
  • Session host (freshell-session-host): POSIX first-match child_can_traverse + one operator_home_readiness JSONL event (home-root scope, home_root_traversable_by_child), stat-failure tolerant, never affects launch.
  • E2E regression (LOCAL_ONLY): a freshopencode pane with a {file:}-referencing config boots on the real rootless daemon — RED reproduced the production incident error verbatim; GREEN proves a positive control (child reads the unmasked 0640 key) plus three mask canaries, launching in the nested production layout (<home>/code/... inside the RO home). Rig operatorHome option + broker allow-list arms with unit tests.
  • Host prep + deployment: scripts/prepare-operator-home.sh (idempotent; creates mask destinations owner-only; group traversal for home root + config roots; group read for the ~/.amplifier tree except onecli, which is pruned and actively kept owner-only so a re-run can never expose a running soul) with a behavioral vitest harness; compose wiring for both controller services (supervisor flag + bind; web stages config via HOME while FRESHELL_HOME holds writable state on a named volume); posture docs (managed-runtime.md contract + residuals + invariant pair, launch runbook prep step, runtime README).

Testing

  • New regression spec green on the real rootless daemon (RED = the verbatim incident error; GREEN in the nested production layout).
  • Full coordinated suite passed on this branch at 4bbbb93e2 (2026-10-12 15:10–17:20 PDT: workspace cargo test --workspace --locked, client, Rust, and Electron phases all green; Electron 395/395).
  • Pre-push note: the first pre-push run hit a flake in net_bind::tests::rebind_serves_a_connection_queued_on_the_retiring_listener (unrelated to this branch — it passes in isolation and passed in the full-suite gate at this commit); pushed with the documented --no-verify bypass. rust-gate is the backstop.

Review trail

Executed subagent-driven (fresh implementer + fresh reviewer per task, whole-branch review with one fix wave), then an independent delta review (the-usual stage 5): rounds 1–2 found and fixed a real exposure sequence (prep re-run vs. filtered masks), incomplete compose web staging/state, and unsound mask canaries; round 3 PASSED. Plan + run record: docs/plans/2026-10-11-soul-home-mounts.md and its superseded provenance plan.

Deployment notes

  • Host prep is already applied on garageserver; re-run scripts/prepare-operator-home.sh after any Amplifier key rotation.
  • Server deploy (restart) still requires the operator's explicit approval; running souls pick the mount up at natural incarnation replacement.

…path, rig lifecycle, allow-list sites, DAC test views, cargo filters, ratified mechanism block
…rkspace==home drop, real UI signals, POSIX DAC classes
…t-guard for env path, owned prep script, send-then-assert, placeholder locator, mask canaries
…ript behavioral test, rotation re-run rule, canary rejection semantics, flag-based red fallback, precise residuals
…ersal in prep, compose canonical-path bind, clone fix, sync-throw canary assertion, ratified boundary stated precisely
…+ positive control, prep mkdirs mask destinations with invariant-pair guarantee, time-invariant persisted-mask verification contract
…ferences

Adds the local-only regression that proves the operator-home soul mounts fix the {file:} API-key-reference incident class through the real fresh-agent launch path on the real rootless Docker daemon, plus the rig/broker plumbing that admits the new mounts: the operatorHome rig option (passed to the supervisor as --operator-home and used as the web home), the broker's allowedOperatorHomeRoots policy with the same-path read-only bind arm and the three secret-mask tmpfs arms (with unit tests), and the LOCAL_ONLY_SPECS registration.

Scratch-red evidence: with the rig's operatorHome flag forwarding removed (startSupervisor operatorHome: undefined), the spec fails with the production incident's 'Configuration is invalid ... bad file reference ... does not exist' in the soul's terminal spool and no native session ever materializes; with the flag wired the spec passes.
… parse

- backend: the three fail-closed arms of inspected_operator_home now have
  unit tests through verify_inspect_config — a marker env without its
  persisted label, a malformed label record, and an env home that diverges
  from the persisted record each fail with OwnershipMismatch (each test
  pins its arm's message and passes against the pre-change code, proving
  the arms were correct but untested).
- session-host: factor provider_run_as_identity's pure parse into
  provider_run_as_identity_from(lookup) and cover the set-and-parseable
  path without a process-wide env race; the emission's observable
  behavior is unchanged and the default path keeps its existing test.
…ompose image contract

- create_stopped no longer refuses a launch when the operator-home
  record cannot serialize (a non-UTF8 home path): the new
  persisted_operator_home_record drops the mount whole — no home bind,
  no marker env, no label, exactly the pre-feature launch shape — and
  logs supervisor.operator_home_mount_dropped. A unit test builds a real
  non-UTF8 path via OsStringExt to pin the drop and the surviving-record
  path.
- compose: the supervisor command block's comment now states the
  deployment-provided image contract (no supervisor ENTRYPOINT, binary
  on PATH) and both contract-violation signatures — an
  executable-not-found restart loop vs the CLI's usage-line
  supervisor.fatal loop — instead of claiming an unverifiable mirror of
  the systemd launch. No pinned supervisor image exists in the repo,
  docs, or local daemon to inspect, so the contract is documented rather
  than verified.
…uns; compose web stages operator config; sound mask canaries

Delta-review round 1 remediation: the prep's group-read pass is pruned away
from ~/.amplifier/onecli and actively revokes group bits there (a soul created
before onecli existed keeps that mask filtered for its lifetime, so a re-run
must never make onecli content readable to it); mask destinations the script
creates are owner-only (0700) regardless of umask; the compose web service
binds the operator home and points HOME at it so managed_provider_bootstrap
stages the operator's global config (the file containing the {file:}
references); the e2e canaries get explicit chmods (Node applies the process
umask to writeFileSync mode, leaving requested 0640 at 0600 where a canary cat
would throw for the wrong reason); rootful-degradation doc wording now states
the observable is the mount's absence; README fixes the uid/passwd claim.
…HELL_HOME; e2e launches in the nested production layout

Delta-review round 2 remediation: the web service keeps HOME at the operator
home (managed_provider_bootstrap stages the global provider config from it)
while FRESHELL_HOME — which the server's resolve_home prefers for ALL state
(settings, projections, instance id) — points at a new writable web-state
named volume, so state no longer lands under the read-only home bind. The e2e
regression now launches its freshopencode pane with a cwd nested INSIDE the
fixture operator home (group-writable, like production repos), exercising the
RO-home + deeper RW-workspace bind shadowing the production layout depends on.
@danshapiro
danshapiro merged commit d70a563 into main Oct 13, 2026
7 checks passed
@danshapiro
danshapiro deleted the the-usual/soul-home-mounts branch October 13, 2026 02:10
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.

1 participant