The workspace layout
What lives where in a workspace, for one repository or several, and the operator runbook for putting a workspace into that layout.
The layout
A workspace is one directory. The layout below is the one the fleet is built around; more than one product repository is normal.
<workspace> is the directory you pass as --workspace.
One repository or several
The layout does not change with the number of repositories. Each repository gets its own directory under sources/ and its own directory under worktrees/, and a stream's worktree sits under the repository it belongs to.
The names api and web are examples; a repository's name is whatever you give it with saphan workspace repo add.
The worktrees directory is worktrees under the workspace, unless SAPHAN_WORKTREES_DIR names another.
Runbook: put a workspace into the layout
These steps are for the operator's hand. The commands are described on the saphan workspace page, and the permissions step is saphan init.
A. Register a repository
-
List what the workspace knows now and read each path's state:
The states it can print are
git,not-git,missing,alias-of=<name>,outside-workspace=<dir>,linked-worktree,not-a-directoryandunresolvable. -
Add a repository that is already on disk. It is added to
workspace.jsonwithout moving or cloning it. Without--paththe repository is expected atsources/<name>;./xandx/are stored asx: -
List again and confirm the new row reads
git. Any other state is the reason the path cannot be used as it stands; correct the path or the directory, then list again.
B. Move an existing workspace into the layout
saphan workspace migrate moves an existing workspace into the per-repo layout, on this host only:
Before
-
Pause the fleet. The command refuses while any run of this workspace is live on this host, so let every run in flight finish and start no new one.
-
Rehearse. Without
--applythe command prints every move and changes nothing: -
Read the printed moves line by line. If a move is not the one you want, stop here: nothing has changed. The command also refuses when any destination already exists, and it lists, without moving, a stream whose repo cannot be established or whose readings disagree. A stream's repo is its record's repo name, else its
_EXEC_ORDER.md- repo:, else the repo its git worktree belongs to. A direct child that is a symbolic link to a git repository is listed with its target and never moved.
The act
-
Make the moves with the same command and
--apply:What
--applydoes besides moving: every linked worktree is re-joined to its repo withgit worktree repair; the absolute repository paths the record holds (events, run records, the cost ledger) are rewritten in one transaction; signed rows (gate decisions, identity records) are counted and left as signed; and the trunk certification directory keyed by each old path is re-keyed.
After
-
Run
saphan workspace repo list --workspace <workspace>and confirm every repository readsgit. -
Check that each stream's worktree is under
<worktrees>/<repo>/and that the repositories are undersources/. -
Resume the fleet.
-
Far hosts. Every admitted
transport=sshmachine with aworktrees_rootis probed by the same--apply, and its legacy flat tree is moved, on that host, into the layout its row'sfar_layoutmarker composes:Then
git worktree repairruns there, a re-probe verifies the tree, and only then is the machine row signed withfar_layoutrepo-scoped-v1and itsrepo_dirsre-keyed. That signing is an owner act: when a far machine has work to move,--applyasks for the one-time code on your terminal once, before anything on this host or a far one moves. A machine with a live run is refused and left untouched. A machine that cannot be dialled, and any far worktree whose repo the record does not name, is listed and keeps the machine legacy.
When it refuses
- A run of this workspace is live on this host. Wait for the run to finish, or end it, then rehearse again. Do not apply while it runs.
- A destination already exists. The command refuses when any destination already exists; resolve that path, then rehearse again.
- A stream is listed and not moved. Its repo could not be established, or its readings disagree. Fix the record for that stream, then rehearse again.
A run that stopped after a move is finished by the next --apply: a worktree already under <worktrees>/<repo>/ whose git link is stale is repaired there. The same holds for a run that stopped part-way on a far host.
A dirty or non-detached landing worktree is not a saphan workspace migrate rule.
⚠ This page does not say how to reverse a migration afterwards; the command's description does not cover it. Rehearse first, and keep a copy of anything you cannot recreate.