Row 117. At a launch, update() kept a clone that was on another branch
or dirty and ran no git in it, so its refs/remotes/origin/* stayed as
old as the last clean launch. Now those two reasons get the clean
path's fetch, with the same arguments and environment, and no merge.
The result is a new outcome, fetched, with the reason, the clone's
branch and HEAD, and origin { branch, commit } after the fetch. Every
earlier check still keeps the clone with no fetch. The launcher writes
a checkout-fetched launch-log line, and the bundle prompt names a
fetched clone's branch, commit and origin. Nothing in unsafe(),
allowed(), gitEnv() or SAFE changed.
Candidate manifest 9fa839edfb2bd04771bbac1913b142cb16aef227a1265120e0d318fb64d6a502
from Darkwing's round 1 packet (17d801f48), on BASE 559ef2287. Dewey
approved in 28443. Sage reran the full gate at the landing target with
the candidate applied.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
413 lines
22 KiB
Markdown
413 lines
22 KiB
Markdown
# seat
|
|
|
|
`mosaic launch <seat>` starts a seat through its existing launch script,
|
|
unchanged, and leaves one registration record that the control board reads
|
|
instead of guessing. `mosaic seat task <seat> <text>` changes the task on
|
|
that record and notes who set it. Nothing else. No provider or auth registry, no roster schema
|
|
change, no stopping or killing of seats, one record per seat.
|
|
|
|
Issue #1504. Plain ESM, no dependencies, Node 24 or newer.
|
|
|
|
## Commands
|
|
|
|
The repository wrapper is `scripts/mosaic`. It is not on PATH and is not
|
|
the npm-global `mosaic` CLI from the estate tooling, which has no `launch`
|
|
or `seat` command; run it by path.
|
|
|
|
```
|
|
scripts/mosaic launch <seat|seat-dir> [--task TEXT] [--project NAME] [--workspace PATH]
|
|
[--harness NAME] [--repo PATH] [--config PATH] [-- args...]
|
|
scripts/mosaic seat task <seat> <text> [--by NAME] [--layout repo|fleet|unknown] [--config PATH]
|
|
```
|
|
|
|
- `<seat>` is a name under `<repo>/agents/` (`--repo` defaults to the
|
|
current directory) or a path to a seat directory such as
|
|
`~/.mosaic/fleet/agents/orch-01`. Either way the directory must hold an
|
|
executable `launch.sh`.
|
|
- Everything after `--` goes to the launch script untouched.
|
|
- `launch` writes the registration, then replaces itself with the launch
|
|
script (`process.execve`). The seat keeps the same pid, terminal and
|
|
process group as if you had run `launch.sh` yourself, the launch script's
|
|
exit code is yours, and the tmux pane's foreground command stays the
|
|
harness, which the control board's liveness check depends on. Node marks
|
|
`process.execve` experimental (present since 24); it is the only way to
|
|
keep the seat's pid, so the package accepts that and pins Node 24 or
|
|
newer. The seat tests exercise it directly (exit passthrough).
|
|
- The launch script receives `MOSAIC_LAUNCH_REGISTERED=<record path>`. A
|
|
launch script that sees this variable is already registered and must not
|
|
call `mosaic launch` again; `mosaic launch` refuses to run when it is set.
|
|
- `seat task` records who set the task in `taskSetBy` (#1511): `--by NAME`
|
|
if given, else `$MOSAIC_AGENT_NAME` if set and non-empty, else the word
|
|
`unknown`. An explicit value must be one token of 1 to 64 characters
|
|
(letters, digits, `.` `_` `@` `:` `-`, starting with a letter or digit);
|
|
anything else refuses with exit 4 before the record is touched. A set but
|
|
malformed `MOSAIC_AGENT_NAME` also refuses rather than being reported as
|
|
`unknown`; `--by` is the explicit way past it. The value is never echoed
|
|
back in a refusal. **This is what the caller said, not a verified
|
|
identity.** Nothing checks it, nothing grants on it; the board displays
|
|
it next to the task and does no more with it. `launch --task` does not
|
|
set the field.
|
|
|
|
The four repository seats (`agents/darkwing`, `agents/dewey`,
|
|
`agents/filbert`, `agents/rocko`) register themselves: their `launch.sh`
|
|
re-enters through `scripts/mosaic launch` unless already registered or
|
|
called with `--check`. So `agents/darkwing/launch.sh` and
|
|
`scripts/mosaic launch darkwing --task "..."` are the same path; the second
|
|
form is how you attach a task. A `--check` run never writes a record.
|
|
|
|
## The record
|
|
|
|
`<dataRoot>/seats/<layout>/<seat>/registration.json`, directory 0700, file 0600,
|
|
written atomically. `layout` is `repo`, `fleet` or `unknown` (see below);
|
|
it is part of the path because a seat name alone is not unique, and
|
|
`seat task` refuses a bare name that is registered in more than one layout
|
|
until `--layout` says which. `dataRoot` comes from `~/.config/mosaic-dev/config.json`
|
|
(or `$MOSAIC_CONFIG`); a missing or unreadable config refuses the launch.
|
|
|
|
The record is rewritten on every launch. It is written before the launch
|
|
script runs, so a launch the script itself refuses (a failed `--check`-style
|
|
precondition, a missing context file) still leaves a record with a pid that
|
|
is no longer running; the next launch replaces it, and the board treats a
|
|
record whose pid is gone as stale (shown, but the derived values win). It is a launch record,
|
|
not a run record: it is not evidence, it holds one seat's latest launch only, and it
|
|
lives outside `<dataRoot>/board/` because the board never writes here and a
|
|
scan never changes it.
|
|
|
|
```json
|
|
{
|
|
"version": 1,
|
|
"seat": "darkwing",
|
|
"project": "mosaic-stack",
|
|
"task": "Control board: seat registration (#1504)",
|
|
"workspace": "/mnt/storage/src/mosaic-stack",
|
|
"tmux": { "socket": null, "session": "darkwing" },
|
|
"harness": "pi",
|
|
"startedAt": "2026-09-12T16:20:11.000Z",
|
|
"pid": 431734,
|
|
"sessionsDir": "/mnt/storage/src/mosaic-stack/.pi/state/darkwing/sessions",
|
|
"seatDir": "/mnt/storage/src/mosaic-stack/agents/darkwing",
|
|
"launchScript": "/mnt/storage/src/mosaic-stack/agents/darkwing/launch.sh",
|
|
"layout": "repo",
|
|
"updatedAt": null
|
|
}
|
|
```
|
|
|
|
After `scripts/mosaic seat task darkwing "Row 6 (#1511)" --by jason` the
|
|
same record also carries `"taskSetBy": "jason"` and a non-null
|
|
`updatedAt`; `startedAt` and every other field are unchanged.
|
|
|
|
Fields asked for in the brief: `seat`, `project`, `task` (empty unless
|
|
`--task` or a later `seat task`), `workspace`, `tmux` (session name and
|
|
socket, from the `TMUX` variable of the pane the launch ran in; null outside
|
|
tmux), `harness` (only what `--harness` says; the repository launch scripts
|
|
pass `pi` or `claude-code`), `startedAt`, `pid`.
|
|
|
|
Fields added, and why:
|
|
|
|
- `sessionsDir`: how the board matches a record to a row. Seat names are not
|
|
unique across layouts (there is a `darkwing` in this repository and a
|
|
`darkwing` in the fleet), so the record names the sessions directory the
|
|
board already scans, and only an exact match counts.
|
|
- `seatDir`, `launchScript`, `layout`: what was launched and how the paths
|
|
were derived, so a wrong record can be traced without re-running anything.
|
|
`layout` is `repo` (`<repo>/agents/<seat>` with `<repo>/.git`), `fleet`
|
|
(`<seatDir>/.pi` exists) or `unknown` (nothing derived, `sessionsDir` null,
|
|
the record is still written but the board cannot match it).
|
|
- `updatedAt`: set only by `seat task`, so a task change is distinguishable
|
|
from a relaunch.
|
|
- `taskSetBy`: set only by `seat task` (see above). Optional in the shape:
|
|
a record written before the field existed loads unchanged, the board shows
|
|
`unknown` for it, and nothing rewrites it until the next `seat task` or
|
|
launch. The record version stays 1; a value that is not one bounded token
|
|
makes the record unreadable (refused, not misread).
|
|
- `version`: so a later shape change can be refused rather than misread.
|
|
|
|
`project` and `workspace` are derived only for the repo layout (the
|
|
repository's basename and root). For the fleet layout they are null unless
|
|
`--project` and `--workspace` are given, because the roster has no project
|
|
field and all fleet seats run in `~/.mosaic`; the board then keeps its own
|
|
derived values for those rows.
|
|
|
|
## How the board uses it
|
|
|
|
`packages/control-board` reads every `<dataRoot>/seats/<layout>/<seat>/registration.json`
|
|
on each scan. A registered task, project or workspace wins over the derived
|
|
value, and the row's `taskSource`, `activeProjectSource` or
|
|
`workspaceSource` says `registration`. An empty task or a null project or
|
|
workspace in the record leaves the derived value in place. Rows without a
|
|
registration are unchanged. A malformed record is reported in
|
|
`registrationErrors` on the index and skipped; it never takes the board down
|
|
and it is never treated as absent silently. A record whose pid is no
|
|
longer running is stale: still shown on the Registered line, but it does
|
|
not override anything. The row's `taskSetBy` is the record's setter only
|
|
while the task shown is the registered one (`taskSource` `registration`);
|
|
otherwise it is null, so a stale or empty registration never attributes a
|
|
transcript-derived task, and a Discord connector's fixed task never borrows
|
|
a native record's setter.
|
|
|
|
The repository launch scripts are the only launchers that register.
|
|
Fleet seats under `~/.mosaic` stay on their own launchers (Jason's ruling,
|
|
2026-09-12); a fleet seat can still be registered by hand with
|
|
`scripts/mosaic launch <seat-dir>`.
|
|
|
|
## Managed role sessions (slice 1 S6, #1523)
|
|
|
|
Separate from seats: `src/session.mjs` holds what the bus host's launcher
|
|
(`packages/cli/src/launcher.mjs`) uses to start and stop a managed role
|
|
session, and `src/proc.mjs` the `/proc` reads (start time, command line,
|
|
children) that the host, the launcher and `mosaic stop` share. Nothing here
|
|
decides authority or capacity; `packages/cli/README.md` ("Sessions") has the
|
|
launch flow and the on-disk layout.
|
|
|
|
- `newRun` picks a run id (`r` and 12 hex digits) and makes its 0700
|
|
directory, refusing a data root too deep for the run's socket path.
|
|
- `sessionEnv` builds the session's environment from an allowlist
|
|
(`ENV_ALLOW`), the repository's `node_modules/.bin` on `PATH`, and
|
|
`MOSAIC_RUN_ID`. Founder credential variables never pass.
|
|
`MOSAIC_CLAUDE_BIN`, the host's pinned Claude Code binary, does.
|
|
- `spawnSession` starts `packages/harness/src/runner.mjs` in its sandbox
|
|
(`src/sandbox.mjs`, below), detached, with stdin open for the capability
|
|
line and output to `runner.log` (0600). `runnerOf` finds the runner, the
|
|
outer bwrap process's only child.
|
|
- `Registry` is the host's list of running sessions, mirrored to
|
|
`bus-host/sessions.json` (0600); `readSessions` reads it and marks each
|
|
entry live or stale by the runner's start time, and refuses a file it
|
|
can't read.
|
|
- `stopSession` is `mosaic stop`: SIGTERM to a live runner, resent every
|
|
second until it exits.
|
|
|
|
### The sandbox (#1586)
|
|
|
|
`src/sandbox.mjs` builds the bubblewrap command line a session runs under.
|
|
The mount namespace starts from an empty root and gets only these binds,
|
|
each at its own host path; `binds()` returns them with the reason for each,
|
|
and the file's header comment has the table.
|
|
|
|
- Read-only: `/usr`, `/etc` and the host's `/bin`, `/lib`, `/lib64`,
|
|
`/sbin` links into `/usr`; the twelve repository files the runner loads
|
|
(`RUNNER_FILES`); the adapter; for pi the repository's `node_modules`,
|
|
for Claude Code the `MOSAIC_CLAUDE_BIN` binary when it lives outside
|
|
`/usr`; node when it lives outside `/usr`; the broker socket, the
|
|
launch socket for the role whose launch tool calls it, and the host's
|
|
git socket (`bus-host/git.sock`) for a role that holds
|
|
git.push.working or review.verdict.
|
|
- Read-write: the instance's workspace and the run directory. The run's
|
|
`bundle/` and `session.json` are bound read-only again on top.
|
|
- Private: `/tmp` is the run's `tmp/`, `HOME` the run's empty `home/`, and
|
|
`/dev` and `/proc` are the namespace's own.
|
|
|
|
The environment is `sessionEnv`'s allowlist plus `HOME`, `TMPDIR` and the
|
|
session's git identity (#1598). `gitIdentity()` sets `GIT_AUTHOR_NAME` and
|
|
`GIT_COMMITTER_NAME` to the role instance's id, and `GIT_AUTHOR_EMAIL` and
|
|
`GIT_COMMITTER_EMAIL` to `<instance>@<business>.mosaic.invalid`, a domain
|
|
that can't receive mail. The launcher passes the instance and the business
|
|
id, and `sandboxArgv` throws when either isn't an id. The brief names the
|
|
role; the instance id is used instead, so two instances of one role commit
|
|
as different authors (Sage's decision on row 104). The identity lives in the
|
|
environment and not in the clone's config, because a `user.name` or
|
|
`user.email` key there makes every later launch keep the clone
|
|
(`config-changed`, below).
|
|
|
|
The root is remounted read-only, so a write anywhere else fails. Nothing
|
|
else of the host's filesystem exists inside: no data root, no other
|
|
workspace or run, no `~/.config`, `~/.mosaic`, `~/.ssh` or `~/.claude`, no
|
|
checkout beyond the runner's files. Claude Code logs in with a
|
|
credential variable the environment passes (`SESSION_CREDENTIALS`;
|
|
`packages/cli/README.md`, "Sessions"), never with a file from the host's
|
|
`~/.claude`. Pi likewise logs in only through `ENV_ALLOW`'s variables,
|
|
never with the host's `~/.pi/agent`.
|
|
|
|
There is no unconfined fallback. `sandboxProblem` runs bwrap once with the
|
|
system binds; when bwrap is missing or fails, the launcher refuses the
|
|
launch with `sandbox-unavailable` and the reason.
|
|
|
|
The namespaces and how a session dies:
|
|
|
|
- bwrap unshares the user, PID and IPC namespaces. The runner is pid 1 of
|
|
its PID namespace (`--as-pid-1`). A detached child is reparented to it,
|
|
so it stays under the launch's pid. Pid 1 ignores a signal it has no
|
|
handler for; the runner installs its handlers first, and the senders
|
|
resend SIGTERM to cover Node's startup.
|
|
- The runner dies when the outer bwrap process does: bwrap starts it
|
|
through `setpriv --pdeathsig KILL`. bwrap's own `--die-with-parent`
|
|
isn't used, because it also ties bwrap to the host, and a session has to
|
|
outlive a host that died hard so the next host can recover it. The
|
|
parent-death signal is set inside the namespace just before the runner's
|
|
exec; if bwrap died in that instant, the runner would keep going until
|
|
its broker polls fail.
|
|
- With its own `/proc`, a session sees no process outside its namespace,
|
|
so it can't signal or `ptrace` another session or the host by pid.
|
|
- The outer bwrap exits with 128 plus the signal number when the runner is
|
|
killed; `sessionExit` turns that back into the signal for the launch
|
|
log.
|
|
- The runner doesn't reap zombies other than its own turns; they go when
|
|
the namespace does.
|
|
|
|
The sandbox doesn't confine the network, abstract Unix sockets, or the
|
|
user: a session runs as the host's UID. `packages/harness/README.md`
|
|
("Limits") says what that leaves open.
|
|
|
|
### The project clones (#1585)
|
|
|
|
An instance's workspace holds one git clone per project in the business
|
|
file, at `<workspace>/<project id>/`. The launcher calls `ensureCheckouts`
|
|
(`src/checkout.mjs`) at each launch, before it builds the bundle, so a
|
|
session starts in the project and not beside it. The host runs git here,
|
|
outside the sandbox:
|
|
|
|
| The clone | What the host does | Outcome |
|
|
|---|---|---|
|
|
| doesn't exist | `git clone --no-hardlinks --template=` of the root, at the branch the root has checked out, into `.<project>.clone-<hex>` beside it, then renamed into place | `cloned` |
|
|
| is clean, on the root's branch | fetches its origin and fast-forwards | `updated`, or `current` when nothing moved |
|
|
| passes the checks below, but is on another branch or none, or dirty | fetches its origin and doesn't merge, so only `refs/remotes` moves (#1611) | `fetched`, with the reason |
|
|
| anything else | nothing | `kept`, with a reason |
|
|
|
|
The host never resets, cleans, deletes or force-updates a clone, and never
|
|
writes the root. A clone's origin is the root's path; no credential or
|
|
forge remote goes into it, and the root's own remotes and config don't
|
|
reach it.
|
|
|
|
Each result also has the clone's branch and, for `cloned`, `updated` and
|
|
`current`, the commit it is at after the launch (`null` when git can't read
|
|
it). A fetched clone has its own branch (`null` on a detached HEAD), its
|
|
commit, its reason, and `origin`: the root's branch and the commit
|
|
`refs/remotes/origin/<branch>` is at after the fetch (`null` when git can't
|
|
read it). A kept clone has only its reason, and the host runs no more git
|
|
in it to find more. The bundle's prompt names the branch and commit, says
|
|
why a fetched clone wasn't fast-forwarded and where `origin/<branch>` is,
|
|
says the host fetches every clone it can safely run git in at a launch and
|
|
fast-forwards only a clean one on the root's branch, and tells the session
|
|
not to set git config in its clone (#1598).
|
|
|
|
A kept or fetched clone's reason is one of the following. Only
|
|
`other-branch` and `dirty` fetch, and only once every check above them has
|
|
passed; every other reason runs no fetch.
|
|
|
|
| Reason | The clone |
|
|
|---|---|
|
|
| `not-a-clone` | isn't a directory with a `.git` directory; or under `.git` there is a symlink, a special file, a file with a second hard link, a `commondir` or an `objects/info/alternates` |
|
|
| `hooks-present` | has a file in `.git/hooks` |
|
|
| `submodule` | has a `.git/modules`, or a gitlink (a submodule entry) in its index |
|
|
| `config-changed` | has a `.git/config` that isn't a regular file of at most 64 KiB, or that holds a key a clone doesn't write: anything beyond `core.repositoryformatversion`, `core.filemode`, `core.bare`, `core.logallrefupdates`, the origin's one URL (the root) and fetch line, and `branch.<name>.remote` (`origin`) and `.merge` |
|
|
| `root-unreadable`, `root-detached` | is fine, but the root can't be read or has no branch checked out |
|
|
| `other-branch` | is on another branch, or on none; `fetched` |
|
|
| `dirty` | has a change, staged or not, or an untracked file; `fetched` |
|
|
| `status-failed`, `fetch-failed` | made `git ls-files`, `git status` or the fetch fail; a clone on another branch or dirty whose fetch fails is `kept`, `fetch-failed` |
|
|
| `not-fast-forward` | has commits the root's branch doesn't |
|
|
|
|
The session can write its clone, so the host treats every clone as
|
|
hostile. The checks above run before any git command in an existing clone,
|
|
and git doesn't run in a clone that fails one. Every git call also runs
|
|
with only the host's `PATH`, `HOME` at a directory that doesn't exist, no
|
|
system or global config, hooks at `/dev/null`, `core.fsmonitor` off and
|
|
only the `file` transport. A submodule has its own config, which none of
|
|
those checks reads and which can name a filter that `git status` would run,
|
|
so the host never recurses into one: a clone with a submodule is kept, and
|
|
`status` runs with `--ignore-submodules=all` and every call with
|
|
`submodule.recurse=false`. The root is read with `GIT_CEILING_DIRECTORIES`
|
|
at its parent, so a root that isn't a repository is never taken for the
|
|
one above it. Each git call gets SIGTERM after 100 s and SIGKILL 5 s
|
|
later, as below.
|
|
|
|
The checks hold only while nothing else writes the clone. Each workspace
|
|
is one instance's, and the launcher refuses `instance-running` before it
|
|
gets here, so no session of that instance runs while the host checks and
|
|
updates its clone.
|
|
|
|
A missing clone that can't be made (the root can't be read, has no branch,
|
|
or the clone fails) rejects with a `CheckoutError` naming the project, and
|
|
the temp directory is removed. The launcher refuses the launch with
|
|
`checkout-failed` (`packages/cli/README.md`, "Sessions").
|
|
|
|
### The working repositories (#1587)
|
|
|
|
A coder's pushed work goes to one bare repository per business and
|
|
project, `<dataRoot>/working/<business>/<project>.git`, which only the bus
|
|
host writes (`src/working.mjs`). It isn't the project's root and it isn't
|
|
a forge: no credential, no remote, and no sandbox binds it. A task's work
|
|
lands on `refs/heads/work/vikunja-<project>-<task>`, a name the host makes
|
|
from the task ref; the session only picks which branch of its bundle to
|
|
take.
|
|
|
|
Commits arrive as a git bundle over the host's git socket
|
|
(`packages/cli/src/git-socket.mjs`), never by the host reading the
|
|
session's clone. A live session can change its clone between any check and
|
|
any git call, so the host doesn't run git there at all. It reads the
|
|
bundle as it would a fetch from an untrusted remote.
|
|
|
|
A push runs under the repository's lock, in this order:
|
|
|
|
1. The bundle starts with the v2 header; v3 can carry a filter. Otherwise
|
|
`invalid-bundle`.
|
|
2. `main` is fetched again from the project's root, which the host trusts.
|
|
A root it can't read refuses `root-unreadable`.
|
|
3. `git bundle verify`. A prerequisite the repository lacks refuses
|
|
`missing-prerequisite`; anything else wrong is `invalid-bundle`.
|
|
4. The bundle has the named branch, or `branch-not-in-bundle`.
|
|
5. That one ref is fetched with `transfer.fsckObjects` into a temp ref
|
|
under `refs/mosaic/incoming/`. A broken object refuses `invalid-bundle`.
|
|
6. The commit shares history with `main`, or `unrelated-history`.
|
|
7. The temp ref moves to the work ref with no `+`, so git takes only a
|
|
fast-forward (`non-fast-forward`).
|
|
8. The broker records the push, an `action.allowed` for
|
|
`git.push.working` naming the commit. When it
|
|
refuses, the work ref goes back to where it was. A work ref that can't
|
|
go back refuses `rollback-failed` in place of the broker's code, with a
|
|
message naming the ref and the commit it still holds. The host logs it,
|
|
since the operator has to move that ref by hand.
|
|
|
|
The temp ref is deleted on every path, and so is the bundle file in
|
|
`bus-host/`. No other ref moves, and the root is only read.
|
|
|
|
The repository's lock is a promise chain inside the host's process, so it
|
|
assumes one launcher per dataRoot. `startHost` takes `bus-host/host.lock`
|
|
with a hard link, which fails if the file exists, and refuses while a live
|
|
host holds it, so two launchers started at the same moment can't both run
|
|
(`packages/cli/README.md`, `bus start`).
|
|
|
|
A host git call that runs past 100 s (`GIT_TIMEOUT_MS`) gets SIGTERM. git
|
|
removes its lockfiles on SIGTERM. Only a git still running 5 s later
|
|
(`GIT_GRACE_MS`) gets SIGKILL, which can leave a ref's `.lock` behind; that
|
|
lock refuses every later push to the ref until someone removes it. The
|
|
checkouts (`checkout.mjs`) and the working repositories (`working.mjs`) run
|
|
git the same way, through `runGit`. Each git is the leader of its own
|
|
process group, and both signals go to the group, so a hook or helper git
|
|
started can't outlive it. Once a signalled git has ended, the group gets
|
|
SIGKILL once more, for a process that ignored SIGTERM. A git that ends on
|
|
its own isn't signalled. Since git has its own group, a Ctrl-C at the
|
|
terminal of a host started by hand doesn't reach it; a systemd stop still
|
|
does, since it kills the unit's cgroup.
|
|
|
|
For a review, under the same lock, the host fetches `main` again and
|
|
writes a bundle of the requested commit with one ref,
|
|
`refs/mosaic/review`. It leaves out the commits the reviewer says its
|
|
clones already have (at most 32, `HAVE_MAX`), but only those the working
|
|
repository knows. A commit the repository doesn't have refuses
|
|
`unknown-commit`.
|
|
|
|
A bundle is at most 32 MiB (`BUNDLE_MAX`) both ways, or `bundle-too-large`.
|
|
For a push the git socket checks the size the request names before it
|
|
reads a byte; a review bundle over the cap isn't sent. Host git here runs as
|
|
it does for the clones: no system or global config, hooks at `/dev/null`,
|
|
`core.fsmonitor` off, only the `file` transport, no submodule recursion
|
|
and no auto-maintenance.
|
|
|
|
## Exit codes
|
|
|
|
`launch` exits with the launch script's own code once the script runs.
|
|
Before that: 1 the launch script could not be started (the record is
|
|
removed again), 2 invalid data or configuration (missing config, no such
|
|
seat, no executable `launch.sh`, bad record on disk), 4 usage.
|
|
`seat task`: 0 ok, 1 no registration for that seat, 2 as above, 4 usage.
|
|
|
|
## Tests
|
|
|
|
```
|
|
node --test packages/seat/tests/
|
|
```
|
|
|
|
The repository launch scripts are covered end to end by
|
|
`scripts/test-darkwing-launch.mjs` (darkwing, dewey, filbert) and
|
|
`scripts/test-rocko-launch.mjs`, which run each `launch.sh` in a fixture
|
|
root that is also its own data root, so no registration reaches a real one.
|