plans: row 156 brief wakes the landing run with a landing turn (Filbert, comment 28913)

A relaunched merge-gate run has land_work but no message to turn on: the
raising run's poll consumes the approval DECISION. The host writes the
instance's landHolders entries into session.json as landing, and the
runner takes one fixed-template turn per entry before its receive loop.
Row 149's chief-of-staff message stays as the fallback; a second land
gets decision-consumed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-18 10:03:28 -05:00
co-authored by Claude Opus 5.5
parent a132b46058
commit 957fbfd410
+37 -2
View File
@@ -37,8 +37,12 @@ This row makes the host start the landing run itself.
- `packages/bus/src/runtime.mjs`, `packages/bus/src/process.mjs`: the
launch op `landHolders`, beside `threadsState` (`runtime.mjs:163`,
`process.mjs:18`, `:36`)
- `packages/cli/src/launcher.mjs`: the tick and its header comment
- `packages/bus/tests/land.test.mjs`, `packages/cli/tests/launcher.test.mjs`
- `packages/cli/src/launcher.mjs`: the tick, the session file's
`landing` list, and its header comment
- `packages/harness/src/runner.mjs`: the landing turns before the receive
loop
- `packages/bus/tests/land.test.mjs`, `packages/cli/tests/launcher.test.mjs`,
`packages/harness/tests/runner.test.mjs`
- `packages/cli/README.md`, `docs/TOOLS.md`
- `roles/merge-gate.md`, `roles/chief-of-staff.md`, `roles/cto.md`: only
to say the host now starts the run. Row 149's steps stay as the
@@ -74,6 +78,29 @@ This row makes the host start the landing run itself.
5. After a host restart, the first tick starts the holder, so step 7's
landing comes from a new run of the same instance with the approval
from before the restart.
6. The new run takes a turn without a message. A runner turns only on a
received message (`packages/harness/src/runner.mjs:346-400`), and
`message.receive` skips anything already delivered to the role
(`broker.mjs:1700-1721`). The approval DECISION went to the raising
run, which is still polling, so the new run's inbox is empty
(Filbert's probe on row 149 round 3, comment 28913). So at every
launch of a non-thread instance, by the tick, `role.launch` or pm, the
host reads that instance's entries from `landHolders` beside the
`landHeld` call (`launcher.mjs:262`) and writes them into the session
file as `landing: [{ decision, task_ref }]`. `task_ref` is the raised
context's target (`broker.mjs:964`). Before its receive loop, the
runner takes one turn per entry. The request is a fixed template that
names the decision id and the task ref and says to land it with
`land_work`. The run sends no reply message for these turns, and the
runner logs each turn's outcome. Nothing in the template comes from a
message body. The broker checks the decision again on the land, as
today.
7. Row 149's fallback, where the chief of staff launches merge-gate and
then sends it a message naming the task ref and the decision, still
works. If the landing turn already spent the decision, the second
land is refused with `decision-consumed` (`broker.mjs:853`), and
merge-gate's contract says that refusal means the landing already
happened.
Tests:
@@ -87,6 +114,14 @@ Tests:
- After the landing spends the decision, the tick starts nothing.
- A host stopped and started with a held decision starts the holder on
its first tick.
- A run launched with a held decision has the entry in its session file's
`landing`, takes one turn on it before any message arrives, and lands.
This holds with the raising run's poll active, which is the shape of
Filbert's probe. A run with no held decision has no `landing` and
takes no turn until a message arrives. A thread run has no `landing`.
- A malformed `landing` entry, one whose decision isn't an id or whose
task ref isn't a task ref, makes the runner refuse to start rather than
take the turn.
Mutants: one per behavior above, with the killing test in the packet.