seat, cli, harness: git kill reach, close bound and host.lock (#1597)

Row 103, the row 100 follow-up. Built by Darkwing, packet
agents/darkwing/work/queue-103/r1/, candidate manifest
6e5ac8c0782bc595d7c0d711d457738ffb42f1ba5f38253912943dd55e55888a.
Sage approved on #1597, comment 28252; Dewey approved, comment 28260.

Host git runs through runGit: a detached group, SIGTERM at the limit,
SIGKILL after the grace, and one more SIGKILL to the group after a
signalled git closes. git-socket close() waits at most 2 s. startHost
claims bus-host/host.lock by hard link before the broker fork.
packages/cli/README.md and tests/host.test.mjs carry row 85's hunks
too; row 85 lands first.

Sage's gate rerun on 3a1e11b83 with rows 85, 102 and 103 applied,
Docker up: node suites bus 128, business 67, cli 165, control-board
124, conversation 182, discord 178, harness 73, ledger 78, mosaic 69,
queue 155, runs 43, seat 113, tasks 92, webui 108; scripts auth 15,
conductor 17, config 24, discord 66, extension-package 18,
foundation 44, queue 28, release 14, task 98. All pass, 0 fail.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-13 16:34:47 -05:00
co-authored by Claude Opus 5.5
parent 2a9b77b56b
commit 465bbdfbbc
12 changed files with 665 additions and 106 deletions
+27 -11
View File
@@ -172,17 +172,31 @@ mosaic bus status [--json] host state file, socket, writer.lock
and `readers: [<business>]`. It gets `config.trackers` (below) when a
tracker is set.
2. It reads the notifier config (below).
3. It forks `packages/bus/src/process.mjs` and waits up to 30 s for the boot
3. It takes `<dataRoot>/bus-host/host.lock` (0600), which holds the same
state as `host.json` (step 6). It writes the state to a file of its own
and hard-links it in as `host.lock`. The link fails if the file exists,
so of two hosts started at once, one gets it. If a live host holds the
lock, or a live host started before `host.lock` existed holds
`host.json`, the start refuses with exit 3: `a bus host already runs for
<business> (pid <pid>)`. A lock whose host no longer runs is taken over
whatever its age: it is moved aside, checked to be the file that was
read, and removed; if another host's claim was moved aside in that race,
it goes back. Each refusal below removes `host.lock` again, so a host
that didn't start leaves no file.
4. It forks `packages/bus/src/process.mjs` and waits up to 30 s for the boot
reply. If the broker answers `{ok: false, error}`, the host exits 3 and
prints `broker refused to start: <code>`. A timeout or an early exit
gives exit 1.
4. It forks `src/notifier-process.mjs` and hands it the reader capability
5. It forks `src/notifier-process.mjs` and hands it the reader capability
over IPC in `{op: 'start'}`. A notifier refusal closes the broker, and
the host exits 3.
5. It writes `<dataRoot>/bus-host/host.json` (0600): the pid, the process
6. It writes `<dataRoot>/bus-host/host.json` (0600): the pid, the process
start time, the business, the start time as text and the notifier
binding. The file holds no capability.
6. It starts the session launcher (`src/launcher.mjs`, "Sessions" below)
binding. The file holds no capability. `bus stop`, `bus status` and the
human commands' default business read it. It appears only once the
broker and notifier are up, so a reader never finds a booting host with
no broker socket.
7. It starts the session launcher (`src/launcher.mjs`, "Sessions" below)
and, with `--pm`, launches the business's `launch.by` instance. A PM
launch that refuses closes the host with the refusal's exit code.
@@ -202,11 +216,11 @@ one, and the host stops with exit 1 for the unit to restart. A late reply
never answers a later request, and a launch waiting on one refuses.
SIGTERM or SIGINT stops the notifier after its poll in flight, then closes
the broker and removes `host.json`. If either child dies on its own, the
host goes down with exit 1. The unit then restarts both. The host watches
the children once both have started, and it checks each child's exit state
at that point too, so a broker that dies while the notifier starts still
takes the host down.
the broker and removes `host.json`, then `host.lock`. If either child
dies on its own, the host goes down with exit 1. The unit then restarts
both. The host watches the children once both have started, and it
checks each child's exit state at that point too, so a broker that dies
while the notifier starts still takes the host down.
`bus stop` signals only a pid that still runs with the recorded start time
and whose command line is `…/packages/cli/src/cli.mjs bus start`.
@@ -360,7 +374,9 @@ A bundle file lives in `bus-host/` (0700) under a random name and is
deleted on every path. On close the launcher answers `host-closing` to a
connection still sending its request line, since nothing of it ran yet,
and drops every other connection. It waits for any push in flight to finish
under its lock before the broker stops.
under its lock before the broker stops. It waits at most 2 s
(`CLOSE_WAIT_MS`) for its `host-closing` answers to leave, then drops those
connections too, so a peer that never reads can't hold the close.
### Trackers