M4b spec: names and details settled while building the reference
approval_reply and GatewayOutcome (proto already has an ApprovalAnswer); the bot's edit permission confirmed on the owner's server; a restart replaces a dead post's text; the reason escaped as inline code; the part size bound; the reply for an approval too long to show. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,8 @@ server's version, v11.11.0:
|
||||
|
||||
- `PUT /api/v4/posts/{post_id}/patch` with `{"message": …}` replaces a post's text; the other
|
||||
fields are left alone. It needs the `edit_post` permission, which a user and a bot have for
|
||||
their own posts by default (to be confirmed on the owner's server at the device check).
|
||||
their own posts by default: on the owner's server the bot edited its own post (200, checked
|
||||
2026-09-24 in its direct channel with itself, then deleted).
|
||||
- `GET /api/v4/users/{user_id}` returns the user, with `username`.
|
||||
- M4a's facts (posts, `posted` events, threads, the 16,383-character limit) still hold.
|
||||
|
||||
@@ -104,18 +105,22 @@ On one `gateway.sock` connection, every frame is a `proto` `Envelope` (`read_fra
|
||||
|---|---|---|---|---|
|
||||
| 1 | `brokerd` | `approval_request` | true | `{ approval: PendingApproval }` |
|
||||
| 2 | `gatewayd` | `approval_posted` | false | `{ posts: [String] }`, the post ids, first part first |
|
||||
| 3 | `gatewayd` | `approval_answer` | true | `{ approval: u64, approved: bool, by: String, post: String, reason: Option<String> }` |
|
||||
| 4 | `brokerd` | `approval_outcome` | true | `{ outcome: ApprovalOutcome }` |
|
||||
| 3 | `gatewayd` | `approval_reply` | true | `{ approval: u64, approved: bool, by: String, post: String, reason: Option<String> }` |
|
||||
| 4 | `brokerd` | `approval_outcome` | true | `{ outcome: GatewayOutcome }` |
|
||||
|
||||
```rust
|
||||
pub enum ApprovalOutcome { // tag "outcome", snake_case
|
||||
Runs, // approved, and the grants as they are now still allow it
|
||||
Denied { reason: DenyReason }, // approved, but denied on the re-check; or the record failed
|
||||
Refused, // the refusal is recorded
|
||||
Gone, // no longer pending when the answer came
|
||||
pub enum GatewayOutcome { // tag "outcome", snake_case, beside the fields
|
||||
Runs {}, // approved, and the grants as they are now still allow it
|
||||
Denied { reason: DenyReason }, // approved, but denied on the re-check; or the record failed
|
||||
Refused {}, // the refusal is recorded
|
||||
Gone {}, // no longer pending when the answer came
|
||||
}
|
||||
```
|
||||
|
||||
(`approval_reply`, not `approval_answer`: `proto` already has an `ApprovalAnswer`, the audit
|
||||
record's enum. The types live in `proto::gateway`; `ApprovalReply::check` holds the id and reason
|
||||
rules, so `brokerd` and `gatewayd` share one definition.)
|
||||
|
||||
Frames 1 and 4 carry `id` 1 and 2; frames 2 and 3 answer id 1. Instead of 2, `gatewayd` may send
|
||||
`error { code: internal, detail }` (final) when it cannot post; `brokerd` then tries again as in
|
||||
section 5. `by` and `post` are Mattermost ids (26 characters of `a-z0-9`), `reason` at most 500
|
||||
@@ -206,10 +211,13 @@ plus one line:
|
||||
| `Denied` | `Approved by @<username>, but denied on the re-check: <deny reason>.` |
|
||||
| `Refused` | `❌ Refused by @<username>: <reason, or "no reason given">.` |
|
||||
| `Gone`, or `brokerd` closed first | `No longer pending (answered elsewhere, expired, or its turn ended).` |
|
||||
| `gatewayd` restarted | `No longer answerable here: gatewayd restarted. If it is still pending it is posted again.` |
|
||||
| `gatewayd` restarted | `No longer answerable here: gatewayd restarted. If it is still pending it is posted again.` The state file keeps post ids, not their text, so here the last post's text is **replaced** by `**Approval <id>**`, a blank line and this line. |
|
||||
|
||||
`<username>` comes from `GET /users/{id}`, falling back to the id. An edit that fails is logged and
|
||||
not retried.
|
||||
`<username>` comes from `GET /users/{id}`, falling back to the id. A refusal's reason is escaped
|
||||
and shown as inline code, so it cannot mention anyone. An edit that fails is logged and not
|
||||
retried. A part's post is at most 15,500 characters, so the added line (up to about 600) keeps it
|
||||
within Mattermost's limit; arguments whose fences would push a part over that are treated as too
|
||||
long to show.
|
||||
|
||||
## 7. `gatewayd`: answers, typing, and the old notice
|
||||
|
||||
@@ -234,9 +242,10 @@ most 500 characters) now goes to `approvals`, which accepts it only if **all** h
|
||||
|
||||
Then the answer goes to the courier's connection as frame 3, and the thread says nothing more
|
||||
until the edit of section 6. Otherwise `gatewayd` replies in the thread with the first reason that
|
||||
applies: `only an approver can answer approvals`, `approval <id> is not pending here`, `approval
|
||||
<id> is answered in its own thread`, `approval <id> was answered before it was shown in full`, or
|
||||
`use !approve <id> or !deny <id> <reason>`. A command never reaches `loopd`. `!!` escapes as in
|
||||
applies: `only an approver can answer approvals`, `use !approve <id> or !deny <id> <reason>` (a
|
||||
malformed command), `approval <id> is not pending here`, `approval <id> can only be answered with
|
||||
bxctl approvals` (too long to show), `approval <id> is answered in its own thread`, or `approval
|
||||
<id> was answered before it was shown in full`. A command never reaches `loopd`. `!!` escapes as in
|
||||
M4a.
|
||||
|
||||
**Typing.** While a session has a pending approval in `approvals`, `user_typing` is not sent for
|
||||
|
||||
Reference in New Issue
Block a user