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]>
153 lines
9.9 KiB
Markdown
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.
|