M4b spec: approvals from non-approvers in direct messages go to an approver, with who asked and what

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 11:09:59 -07:00
co-authored by Claude Opus 5.5
parent edf9ba3515
commit d2e814161f
+29 -4
View File
@@ -1,6 +1,8 @@
# M4b design: approvals over Mattermost
Status: approved by the owner, 2026-09-24, with P16 applied to the brief. It builds on M4a
Status: approved by the owner, 2026-09-24, with P16 applied to the brief. Changed after the review
of the same day (owner's decision): approvals asked by non-approvers in direct or group messages
are redirected to an approver with who asked and their message (section 6). It builds on M4a
(`docs/specs/2026-09-23-m4a-gateway.md`, section 12 there is the agreed outline) and M3a's approval
path (`docs/specs/2026-09-18-m3a-decision-path.md`, section 6). Owner's decisions of 2026-09-24,
changing the outline: **no reactions**, answers are commands only; an `[approvals] users` list
@@ -161,10 +163,33 @@ One thread per accepted `gateway.sock` connection. It reads frame 1 (bounded by
checks the ids in it, and decides **where**:
- If the session is `mm-<root>` and `state.in_flight` holds a turn of that session, the approval
goes into that turn's thread (its channel and root). This covers direct messages and channels.
goes into that turn's thread (its channel and root), **unless** the turn was asked by someone
who is not an approver in a direct or group message: no approver could answer it there. (In a
channel an approver can reply in the thread, so it stays.)
- A redirected approval (asked by a non-approver in a direct or group message) goes to the first
approver's direct channel as a new thread whose **first post** says who asked, where, and what
they asked (review of 2026-09-24, owner's requirement: the approver must know who is asking,
their prompt, and what Boxmaker is trying to do):
````text
**Approval 3** was asked by @alex in a direct message with the bot. Their message:
```
Read the file /srv/notes/q3.txt and summarise it.
```
````
The message is the text of the turn that asked (a burst, joined), escaped and fenced like the
arguments; it is context, not what is approved, so it is cut at 2,000 characters with
`(… <n> more characters)` after the fence. The approval's own posts follow in that thread, and
the asking thread gets one line: `waiting for approval <id>: it was sent to an approver`.
- Otherwise (`bxctl chat`, a session no longer in flight, later scheduled jobs), it goes to the
first approver's direct channel (`POST /channels/direct`), as a new top-level post whose own
thread is where it is answered.
first approver's direct channel as a new thread whose first post says
`**Approval <id>** was asked in session `<session>`, which gatewayd does not follow now (a
restart, or a session from outside Mattermost).`, followed by the approval's posts.
`gatewayd` knows who asked from the posts it queued: for each session it keeps the author of the
last message queued, the channel's type, and, when a turn starts, that turn's text; it forgets them
when the turn ends. They are not in the state file: after a restart the turn is interrupted anyway.
### The text