Problem
Two things the project recommends conflict on disk:
- Hardening guidance says flow code should be read-only to the flow, so that a compromised station cannot rewrite the flow it runs in.
- Run workspaces are created inside the flow's project root. With
defaults.workspace: per_run, src/cli/main.ts:944-946 roots each run at <project_root>/.conduit/runs/<run-id>/, and project_root is normally the flow directory. That is inside the tree that was just made read-only.
A hardened deployment therefore cannot mount the checkout :ro and add one state volume. It has to add writable volumes back into the read-only tree, one per served flow, plus one per flow-specific writable path (#54). A single-flow deployment in the private predecessor needed four volumes over one :ro bind, and each new flow in the manifest needs another.
Why --runs-root on listen is not a small change
listen accepts no project-root or runs-root flag (src/cli/main.ts:1889-1890), and ingress-spawned runs never pass --project-root on purpose (src/cli/main.ts:942-943). Adding one would break existing flows, because station command and args resolve against the run's cwd, and the workspace sits exactly three directories below the flow dir. Flows are written against that depth:
command: ../../../.venv/bin/python
args: ["../../../scripts/ingest.py", "work/request.json"]
The three .. segments are .conduit/runs/<run-id>/. If the runs root moves, those paths resolve to nothing. The same coupling makes deploy layouts easy to get wrong: a .venv symlinked at the checkout root instead of the flow dir looks correct and fails at exec.
Proposed fix
Resolve station command and args against the flow directory, and keep the workspace as the cwd only for work/-relative I/O. The workspace could then live anywhere, a --runs-root flag on listen would be safe, and a hardened deployment would need one :ro bind plus one writable mount.
This breaks the flow contract, so it needs a migration path: a flow_version bump, or a per-flow opt-in that accepts both resolutions during a transition. Filed as a design discussion.
Related: #54 (flow-declared writable paths), #45 (station processes can write the state DB).
Problem
Two things the project recommends conflict on disk:
defaults.workspace: per_run,src/cli/main.ts:944-946roots each run at<project_root>/.conduit/runs/<run-id>/, andproject_rootis normally the flow directory. That is inside the tree that was just made read-only.A hardened deployment therefore cannot mount the checkout
:roand add one state volume. It has to add writable volumes back into the read-only tree, one per served flow, plus one per flow-specific writable path (#54). A single-flow deployment in the private predecessor needed four volumes over one:robind, and each new flow in the manifest needs another.Why
--runs-rootonlistenis not a small changelistenaccepts no project-root or runs-root flag (src/cli/main.ts:1889-1890), and ingress-spawned runs never pass--project-rooton purpose (src/cli/main.ts:942-943). Adding one would break existing flows, because stationcommandandargsresolve against the run's cwd, and the workspace sits exactly three directories below the flow dir. Flows are written against that depth:The three
..segments are.conduit/runs/<run-id>/. If the runs root moves, those paths resolve to nothing. The same coupling makes deploy layouts easy to get wrong: a.venvsymlinked at the checkout root instead of the flow dir looks correct and fails at exec.Proposed fix
Resolve station
commandandargsagainst the flow directory, and keep the workspace as the cwd only forwork/-relative I/O. The workspace could then live anywhere, a--runs-rootflag onlistenwould be safe, and a hardened deployment would need one:robind plus one writable mount.This breaks the flow contract, so it needs a migration path: a
flow_versionbump, or a per-flow opt-in that accepts both resolutions during a transition. Filed as a design discussion.Related: #54 (flow-declared writable paths), #45 (station processes can write the state DB).