M4b review, and follow-up tasks 16 to 18

The review of tasks 01 to 15 goes in the implementer log, with the
device check against the owner's Mattermost. Three follow-ups:

- 16: the refusal without a runner says why.
- 17: the texts that say who asked.
- 18: approvals from non-approvers in direct or group messages go to
  an approver.

Task 14's given serve_approvals.rs becomes serve_approvals.rs-task14,
since task 18 hands over a new version. Tip T31 is added.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 11:35:24 -07:00
co-authored by Claude Opus 5.5
parent d2e814161f
commit f6dd7f1741
12 changed files with 1202 additions and 19 deletions
+20 -6
View File
@@ -1,10 +1,11 @@
# M4b implementation plan: approvals over Mattermost
> **For the implementing model:** do not work from this file. The owner gives you one task file at
> a time (`01-…` to `15-…`). This file is the index for the owner and the reviewer.
> a time (`01-…` to `18-…`). This file is the index for the owner and the reviewer.
**Goal:** an `ask` call is posted in Mattermost, in the thread that asked for it (or, for a session
not in Mattermost, in the first approver's direct messages), and an approver answers it there with
**Goal:** an `ask` call is posted in Mattermost, in the thread that asked for it (or, when no
approver could answer it there or the session is not followed, in the first approver's direct
messages, with who asked and what), and an approver answers it there with
`!approve <id>` or `!deny <id> [reason]`. The answer is recorded exactly as `bxctl`'s is, naming
the approver and the post; the post then shows what came of it. Nobody else can answer, and no
post is left looking answerable when it is not.
@@ -16,7 +17,9 @@ gets the **courier**, which carries each pending approval to `gatewayd` and its
commands (08), can edit a post and look up a name (09), renders an approval's posts (10), keeps
the approvals it posted and judges commands (11), records their posts in the state file (12),
listens on `gateway.sock` (13), posts and marks approvals from its serve loop (14), and wires the
commands, the typing pause and the fallback notice (15).
commands, the typing pause and the fallback notice (15). After the review: `brokerd`'s refusal
without a runner says why (16); the texts that say who asked (17); and approvals asked by
non-approvers in direct or group messages go to an approver (18).
**Spec:** `docs/specs/2026-09-24-m4b-approvals.md` (approved; P16 applied to the brief). Runbook:
task 06 adds `gateway-unreachable`, task 07 widens `gatewayd-start-failed`.
@@ -29,7 +32,7 @@ for `brokerd`, and for `gatewayd` a fake Mattermost, a fake `loopd` and a fake `
- Everything in `AGENTS.md`, including "Lessons from earlier reviews".
- No new dependency.
- Branch `m4b`. One task, one fresh OpenCode session, one commit. Run `cargo fmt --all` before the
gate. Review happens once, after task 15.
gate. Review happens once, after task 15; tasks 16 to 18 are its follow-ups.
- **New files** come as skeletons: types, constants and the glue written, `todo!()` bodies with the
code to write in the comment above. **Existing files** are changed by the exact edits in the
task, never replaced whole: a whole-file copy would overwrite an earlier task's work.
@@ -62,8 +65,11 @@ signature (tip T29).
| 13 | `13-gatewayd-gateway-sock.md` | the listener and connection threads | `gateway_sock.rs` | reference; 6 mutations, 5 caught, 1 that cannot change behaviour |
| 14 | `14-gatewayd-serve-approvals.md` | posting, marking, the restart, the listener in `run` | `serve_approvals.rs`, `support/brokerd_client.rs`; `fake_mm.rs` updated | reference; 5 mutations caught |
| 15 | `15-gatewayd-answers.md` | commands judged, typing paused, the notice | `serve_answers.rs`, `deliver_notice.rs`; `deliver.rs`, `serve.rs`, `support/gateway.rs` updated | reference; 4 mutations caught |
| 16 | `16-brokerd-refusing-message.md` | the refusal without a runner says why (review finding 2) | `runner.rs` updated | reference |
| 17 | `17-gatewayd-asked-texts.md` | the texts that say who asked, and the note | `approvals_asked.rs` | reference; 5 mutations caught |
| 18 | `18-gatewayd-redirect.md` | a non-approver's approval goes to an approver (review finding 1) | `serve_redirect.rs`; `serve_approvals.rs` updated (task 14's copy is `serve_approvals.rs-task14`) | reference; 9 mutations caught; 5 runs |
At the end of task 15: about 845 tests (768 before task 01).
At the end of task 15: about 845 tests (768 before task 01); at the end of task 18, about 854.
## Changes during the run
@@ -78,12 +84,20 @@ At the end of task 15: about 845 tests (768 before task 01).
Task 15 now lists `serve/approvals.rs` with the exact fix. Checked in a scratch copy: the saved
attempt (`.state/runs/M4b/15-first-attempt*`) plus that fix passes every suite three times and
the gate, 845 tests. Resume from task 15.
- 2026-09-24, review: accepted (`docs/implementer-log.md`, "M4b, tasks 01 to 15"). The device
check found that an approval asked by a non-approver in a direct message could be answered only
with `bxctl`; the spec was changed (`d2e8141`), and tasks 16 to 18 were added, each with a
reference built from `m4b`, gated, and moved off this machine. Task 14's given
`serve_approvals.rs` is now `serve_approvals.rs-task14`, since task 18 hands over a new version.
Resume from task 16.
## Running it
```sh
git switch m4b
BOXMAKER_MODEL=straylight/ornith-1.5-35b-a3b tools/run-plan.sh docs/plans/M4b
# the review's follow-ups:
BOXMAKER_MODEL=straylight/ornith-1.5-35b-a3b tools/run-plan.sh docs/plans/M4b 16
```
Keep the OpenCode TUI closed while it runs.