# Row 83 (#1576): Console service, the unit script takes --send, round 1 Owner: Dewey. Reviewers: Darkwing and Filbert, then Sage's gate. Brief: `docs/plans/2026-10-12_console-service-send.md` ยง "The unit script takes --send", blob `f8d14d70` (re-pinned at rev 630 for lead decision 102; first pinned as `739bea66` at rev 627, briefed at rev 628). Lead decision 101 gives the reason. Base `d13b9300`. None of the candidate's paths, nor the template, changed between `d13b9300` and `refactor` HEAD (`a1c5c8fc`) when the packet was built. `build.patch` is `git diff --cached --binary d13b9300 --` the four paths below. Apply it with `git apply --index` to a detached worktree of the base; `candidate-manifest.sha256` then checks 4/4 OK (checked on a fresh worktree). ## What ships - `render` and `install` take `--send`, a flag with no value. It is accepted only with all three preview flags. Otherwise `serve_flags` refuses before anything is written: exit 1, empty stdout, and on stderr `webui-service: --send needs the preview mode (--public-host, --auth-header and --allow-user)`, serve's own wording (cli.mjs:37). `install` renders before `mkdir`, so a refusal creates no directory and leaves an installed unit alone (tested). - With `--send`, ExecStart ends `... --allow-user ID --send`. Nothing else in the unit changes (tested line by line against the same render without `--send`). - Without `--send`, every render is the template with its four placeholders filled, byte for byte (new test), and the base and candidate scripts give the same stdout, stderr, status and installed files on every argument list the base tests use (`identity.txt`). - `uninstall` and `status` take no `--send`: it sets SERVE_SET like the other serve flags, so both exit 4 (tested with a stand-in systemctl). - `status` prints one new line when a unit is installed: `send: on (--send; human messages to roles)` or `send: off (read-only)`. The brief allowed this "if small"; it is five lines plus a comment. With no unit installed there is no send line. - `USAGE` and the header comment name `[--send]` inside the preview set. README's service paragraph and TOOLS.md's "Console service" section say the script takes `--send` only with the preview set (exit 1, nothing written), that ExecStart then ends with `--send`, and that the Console needs the system config at start (`--send needs the bus configured`, exit 2, which the unit doesn't restart: `RestartPreventExitStatus=2` in the template). README's "Message a role" `--send` paragraph gets one sentence pointing at `scripts/webui-service.sh install` with `--send`. - The start wording (lead decision 102). The script's header comment, its install message, TOOLS.md's "Console service" and README's service paragraph now say Jason starts and restarts the Console, and so may Mos under decision 102, which accepts that sends from a Console Mos started enter the trail as Jason (`human-cli`). Any other agent starting it, even through systemd, still works around the human proof (decisions 62 and 94). The script still has no start, stop or restart verb. A test checks the install message's wording. The template (`packages/webui/systemd/mosaic-webui.service.in`) is unchanged. `install` prints no new line; its `serve:` line already shows the ExecStart, `--send` included. The install message's opening sentence changed wording only (decision 102). ## Decisions and tradeoffs - **Refusal order.** The `--send` check runs before the all-three check. `--send` with one or two preview flags therefore gets the `--send` message, not "all three or none". The brief asks for a message naming the preview flags the way serve refuses, and serve checks the preview set first. Both messages name the three flags. A partial preview set without `--send` still gets "all three or none" (base test unchanged). - **Status detection is anchored on the allowed user.** My first version matched `^ExecStart=.* --send$`. The mutant `status-send-anywhere` survived it, and that showed a real gap: `--allow-user` accepts the value `--send` (its character set allows `-`, and serve's `USER` pattern accepts it), so a read-only unit's ExecStart can end `--allow-user --send`. The check is now `^ExecStart=.* --allow-user [^ ][^ ]* --send$`. render always puts `--send` right after the allowed user, so a sending unit ends with both the allowed user and `--send`. A test installs both cases, read-only `--allow-user --send` and sending `--allow-user --send --send`, and checks status for each. - **No config or bus check in the script** (out of scope per the brief). A unit installed with `--send` on a host without the system config installs fine and exits 2 at start; the docs say so. ## Files - `scripts/webui-service.sh`: `SEND=0`, the `--send` case, the refusal and the append in `serve_flags`, the status line, USAGE and header. - `packages/webui/tests/service.test.mjs`: 5 new tests, 2 widened. - `packages/webui/README.md`: the service paragraph, one sentence in "Message a role". - `docs/TOOLS.md`: the "Console service" FLAGS comment and prose. ## Tests `packages/webui/tests/service.test.mjs`, 15 tests (10 base, 5 new). The helper `filled(flags)` is the template with `@REPO@`, `@PATH@`, `@NODE@` and `@FLAGS@` replaced, computed in the test the way the base script does it. - *without --send, render is the template with its placeholders filled, byte for byte*: five flag combinations, from none to every flag. - *render --send with the preview set ends ExecStart with --send and changes nothing else*: four argument orders (`--send` first, last, in the middle of the preview set, with `--port` and `--business`). Each equals `filled(flags + ' --send')`, and against the same render without `--send` exactly one line differs, the ExecStart, by ` --send`. - *--send without the whole preview set is refused and writes nothing*: `--send` alone, with `--port`, with `--business`, with each one of the three preview flags, and with each two of the three. Each `render` gives exit 1, empty stdout and serve's message. Each `install` into a fresh `--dir` gives the same, and the directory is never created. An install of `--send` with two preview flags over an installed unit leaves it byte for byte, with no temp file and no systemctl call. - *install --send into --dir with --no-reload writes the sending unit, and status says whether it sends*: `written:`, the `serve:` line ending `--send`, the file equal to `filled(...)`, mode 0644, no temp file; status `send: on`; reinstall `unchanged:`; install without `--send` writes the read-only unit and status says `send: off (read-only)`; the odd allowed user `--send` case above; no `send:` line without a unit. The only systemctl calls are status's reads. Status's `/healthz` goes to a loopback port that was just closed, so it reaches no real Console. - *uninstall and status take no --send*: exit 4 for `uninstall --send` (with and without `--no-reload`) and `status --send`, against a stand-in systemctl that records no call; the unit stays installed. - Widened: the agent-marker test also renders `[...PREVIEW, '--send']`; the usage test adds `render --send yes ...PREVIEW` (`--send` takes no value); the install test checks the decision 102 start wording in the install message. Why the uninstall and status cases sit in their own test: in the usage loop they run with the real `/usr/bin/systemctl` on PATH. A mutant where `--send` doesn't set SERVE_SET would let `uninstall --send` reach a real `systemctl --user daemon-reload`. With the stand-in, no mutant can. ## Byte identity (`identity.mjs`, `identity.txt`) One scratch worktree of `d13b9300` holds the base script as `scripts/webui-service-base.sh` and the candidate as `scripts/webui-service.sh`, so REPO and the template are the same for both. Each script runs the same 86 argument lists from a clean scratch: every render the base tests make (the value and port refusals included), the usage list, `--help`, and an install, reinstall, refused install, install over, status, uninstall, uninstall and status sequence, all with `--no-reload` and a stand-in systemctl. Result: 69 identical, 17 expected differences, 0 unexpected. The 17 are the usage text on 12 exit-4 cases plus `--help` (the FLAGS line names `[--send]`), three installs whose message differs only in the decision 102 start wording (the base message's opening lines replaced by the candidate's, then everything else identical, installed unit included), and one status with a unit installed (the added `send: off (read-only)` line, nothing else). A self-check: with the append made unconditional, the same run reports 13 unexpected differences. ## Mutants (`mutants.py`, `mutants.txt`) 15 single-site mutants of `scripts/webui-service.sh`, each run against the base tests (`d13b9300`'s `service.test.mjs`), the candidate tests and `identity.mjs`. Baselines: all three pass on the unmutated candidate. Rerun on the decision 102 candidate, 2026-10-12T03:53Z; same results as the first run. | mutant | base tests | candidate tests | |---|---|---| | send-no-preview-check | survived | killed | | send-needs-any-preview | survived | killed | | send-message-reworded | survived | killed | | send-no-append | survived | killed | | send-append-always | killed | killed | | send-append-before-preview | survived | killed | | send-default-on | killed | killed | | send-no-serve-set | survived | killed | | send-takes-value | survived | killed | | send-flag-unknown | survived | killed | | status-send-always-off | survived | killed | | status-send-always-on | survived | killed | | status-no-send-line | survived | killed | | status-send-anywhere | survived | killed | | status-send-suffix-only | survived | killed | The candidate tests kill 15 of 15. The two the brief names, `send-no-preview-check` and `send-no-append`, survive the base tests. `send-append-always` and `send-default-on` are killed by the base tests too, as a change to every render should be. ## Gate (my run on the candidate) On the staged candidate in a worktree of `d13b9300`, run sequentially, 2026-10-12T03:54:20Z to 03:58:42Z (`gate-summary.txt`): - `packages/webui`, `node --test 'tests/*.test.mjs'`: 81 tests, 81 pass, rc=0. - `scripts/test-auth.sh`, `test-conductor.sh`, `test-config.sh`, `test-discord.sh`, `test-extension-package.sh`, `test-foundation.sh`, `test-queue.sh`, `test-release.sh`, `test-task.sh`: all rc=0. ## For reviewers - **Allowed user starting with `-`.** The base script and serve both accept an allowed user like `--send` or `-x`. That isn't new here, and changing it would change no-`--send` renders, which the brief rules out. The only effect on this row was status detection, now anchored. If it should be refused, that's a follow-up for the script and serve together. - **The template's comment still says Jason starts it.** `mosaic-webui.service.in` lines 6-9 say Jason starts it from a personal shell and that an agent starting it works around the human proof. The template isn't in this row's files and is rendered into every unit, so changing it would break the byte identity the brief asks for. Bringing it in line with decision 102 is a follow-up (a template change plus the render tests that pin it). - Nothing here starts, enables or restarts a unit, and no test reaches the real systemd: every install and uninstall uses `--no-reload` or a stand-in systemctl.