Files
jason.woltjeandClaude Opus 5.5 ecf1cec070 agents: Dewey row 152 round 1 packet (#1646)
Candidate on 56639c99b: waiting gains launchable, and a message to a role
outside launch.instances gets a notice saying the launch is refused and a
talk line saying launch.by can't launch it. Mutants 8/8 killed; gate green
(bus, cli, business, webui and every scripts/test-*.sh).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 19:47:11 -05:00

153 lines
9.9 KiB
Markdown

# Row 152 (#1646): a message to a role launch.by can't launch says so at once
Builder: Dewey. Reviewer: Filbert. Gate owner: Sage.
Brief: docs/plans/2026-10-15_landing-followups.md, the second `##` section. Assignment: sage-0479 (row
briefed at rev 1494, claimed at rev 1495). Source: Filbert's row 146 note 2.
Base: 56639c99b (base.txt, origin/refactor after rows 151 and 153). The candidate is build.patch on that
base. candidate-manifest.sha256 lists the seven changed paths with their sha256. repro.sh applies the
patch to a fresh detached worktree and checks the digests; it printed DIGESTS_OK.
Owned files: the brief's six, plus `packages/cli/README.md`, the talk waiting-lines paragraph only.
Sage added that file in sage-0481 without re-pinning the brief. The paragraph lists every line talk
prints after `sent`, so without it the new line would be undocumented there. docs/TOOLS.md lists none of
the lines.
## The change
- **packages/bus/src/broker.mjs**, `#waiting` and `#notice` only.
- `waiting` gains `launchable`. It's `true` when the target is one of `launch.instances`, `false`
when it isn't, and `null` with no launch block, the same as `launcherRunning`. For a message to
`launch.by` itself it's `false`: business.mjs refuses an `instances` list that includes the
launcher. Nothing else changes in the result, and a reader that ignores the new key sees row 146's
shape.
- When the target isn't launchable, the message is still stored and routed as before, so a later
claim delivers it. `launch.by` is still told (see the call below), but the notice's body says
the launch would be refused:
`Message <id> from <sender> to <role> waits: <role> has no running session and isn't in the launch block's instances, so a launch of <role> is refused. Tell <sender> why not with message.send in_reply_to <id>.`
A launchable target keeps row 146's text, byte for byte.
- **packages/cli/src/cli.mjs**, `waitingLines` only. An explicit `launchable: false` prints
`<role> isn't running and isn't in the launch block, so <launch.by> can't launch it`, in place of
"has been told". No restart line follows, since starting `launch.by` wouldn't launch the role.
- This check comes after the no-launch-block and the message-to-`launch.by` cases, so their lines
are unchanged.
- It comes before the tester line, so a tester's message to an unlaunchable role gets the more
specific launch-block line.
- An older bus sends no `launchable`. `undefined` isn't `false`, so talk against that bus prints
what it prints today.
- **Docs.**
- packages/bus/README.md: the second notice body, why `launch.by` is still told, and the new key
in the `waiting` shape.
- packages/cli/README.md: the new line, and that no restart line follows it.
- docs/TOOLS.md: one sentence in the talk paragraph pointing at the cli README's list.
- **Tests.**
- launch-notice.test.mjs: the five existing `waiting` deepEquals now include `launchable`: `true`
twice, `false` for the message to chief (launch.by), `null` with no launch block, and `true` for
chief's own message to pm.
- A new bus test sets `instances: ['pm']`, so coder can't be launched. It asserts:
- the whole `waiting` object, with `launchable: false` and `told: true`;
- the message stored with `to_role` coder;
- one notice, to chief, in_reply_to the message, with the exact new body;
- pm, in the same business, still gets `launchable: true` and row 146's text;
- coder's later claim receives the stored message.
- A new cli test adds a `qa` role outside `instances: ['cto', 'coder']`. In it:
- `talk qa` prints only the launch-block line, with pm down and no restart line;
- `talk cto` in the same business still prints "pm has been told" and pm's restart line;
- pm got both notices, the first with the new text and the second with row 146's.
## The call: launch.by is still told
The brief leaves to me whether `launch.by` hears about a message to a role it can't launch. It is still
told, for three reasons.
1. **The Console's only signal is the notice.** The Console never reads `waiting`: no file under
packages/webui/src reads it. Its web-send receipt lists the replies to the human's message
(bus.js, the row 81 comment: "a sent receipt lists the replies to that message"). The notice is
`in_reply_to` that message, and the bus README says "The Console's replies under a human's sent
message include the notice". With no notice, a human who sends from the Console sees "No reply
yet" and nothing else. With the new notice text, they see at once that no launch can follow, which
is this row's problem stated for the Console.
2. **The contract already covers the answer.** roles/chief-of-staff.md: "A message from `_bus` is
the bus telling you that a message waits for a role with no running session. ... Launch that role
within the launch block's limits, or tell the sender why not with a message `in_reply_to` the
waiting message." It also says to launch "for the instances and within the limits the business
file's launch block lists. A launch outside them is refused, not queued." The new text names the
refusal up front and asks only for the second branch, so chief needs no contract change and
spends no turn on a launch the bus would refuse. The contract file isn't an owned file, and this
reading doesn't need it to change.
3. **The message still waits, and chief owns the structure.** The message is stored and reaches the
role at a later claim. The contract makes chief keep "the organization's structure: which role
instances exist, who holds them and what they may do". Telling chief keeps the role that owns the
structure aware of a message no launch can serve. The contract doesn't say which change would add
a role to `launch.instances`, so the notice doesn't name one.
The cost is one chief turn per such message, the same as row 146 already spends for a launchable one.
Mutant B4 (not told when unlaunchable) is killed, so this call is pinned.
### Other choices
- **The name `launchable`**, a boolean or `null`. It answers the question the sender has ("can this
launch at all?"), it matches `launcherRunning`'s nullability, and it's additive.
- **`false` for launch.by itself.** It's true to the data: `instances` can't list the launcher. talk
checks the launcher case first, so the line for it doesn't change.
- **No restart line after the launch-block line.** The restart starts `launch.by`, which still
couldn't launch the role. Mutant C2 pins this.
- **Not covered by a test: talk against an older bus** (`launchable` absent). waitingLines isn't
exported, and a broker in the tests always sends the key. The `=== false` check and its comment
carry it. A mutant to `!w.launchable` would survive the suite today, because no test sends a
`waiting` without the key. I note it here and don't add an export only for a test.
## Mutants
mutants.py applies one fault at a time, runs the owning test file with the spec reporter, and restores
the source after each one. Preflight: each target occurs once. Run 2026-10-16T00:41:24Z to 00:41:36Z on
56639c99b plus the candidate, in the sage-0483 slot: 8 of 8 killed, `restored True` (mutants-run.log,
logs in mutants/).
| Mutant | Fault | Runs | Result | Failing tests |
|---|---|---|---|---|
| B1-always-launchable | every target is launchable | bus launch-notice | KILLED | the new unlaunchable test; the launch.by/tester/human/thread test (chief's `launchable: false`) |
| B2-no-block-false | no launch block gives `false`, not `null` | bus launch-notice | KILLED | the no-launch-block test only |
| B3-launcher-launchable | launch.by counts as launchable | bus launch-notice | KILLED | the launch.by/tester/human/thread test only |
| B4-not-told-unlaunchable | launch.by isn't told for an unlaunchable target | bus launch-notice | KILLED | the new unlaunchable test only |
| B5-launch-text-always | the notice keeps row 146's text | bus launch-notice | KILLED | the new unlaunchable test only |
| B6-field-dropped | `launchable` dropped from `waiting` | bus launch-notice | KILLED | four tests: no launch block, the new unlaunchable test, the stopped-role test, the launch.by/tester/human/thread test |
| C1-no-launch-block-line | talk drops the launch-block line | cli verbs | KILLED | the new talk test only |
| C2-restart-after-line | talk adds launch.by's restart line after it | cli verbs | KILLED | the new talk test only |
## Receipts
Single files on the candidate, without the slot, TMPDIR in my scratch, 0 fail and 0 skipped, on
56639c99b: bus launch-notice 8/8, cli verbs 9/9.
Slot granted by Sage in sage-0483 (busy.sh CLEAR at 00:41:02Z).
- **Mutants**, 00:41:24Z to 00:41:36Z: 8 of 8 killed, source restored. See the kill table above.
- **Gate**, gate.sh from this packet on ~/dewey-scratch/r152/wt (56639c99b plus the candidate),
2026-10-16T00:42:47Z to 00:46:38Z, exit 0 (gate-run.log, logs under gate/). It covers the bus,
cli, business and webui suites (each with its package.json test command) and every
scripts/test-*.sh, as the brief's gate names them:
| Step | Result |
|---|---|
| bus | 276 pass, 0 fail, 0 skipped |
| cli | 215 pass, 0 fail, 0 skipped |
| business | 108 pass, 0 fail, 0 skipped |
| webui | 113 pass, 0 fail, 0 skipped |
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | 78 passed, 0 failed |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-land-work | 84 passed, 0 failed |
| test-queue | 28 passed, 0 failed |
| test-release | 14 passed, 0 failed |
| test-task | 98 passed, 0 failed |
No recurrence of flakes #258, #261, #262 or #263. gate.sh exports
`COMPOSE_PROJECT_NAME=dewey-r152`, and its EXIT trap printed `removed network
dewey-r152_default`. Afterwards no network named dewey* or wt* exists.
- **Candidate:** candidate-manifest.sha256, sha256
c333f4beffec1d901ca23200365698e42003c8256a609ad662429d15e63747c1, 7 files.