Files
boxmaker/docs/plans/M4b/09-gatewayd-mm-edit.md
T
kyleandClaude Opus 5.5 2d415fc1be Plan M4b: approvals over Mattermost in 15 tasks
Skeletons for new files only; existing files change by exact edits, so no task overwrites an
earlier one's work. Each task's tests were run against a reference built in order from master,
with the gate at each step and mutations checked; each skeleton compiles and fails its tests, and
every todo!() comment was read beside its signature. The reference is kept off this machine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 03:34:21 -07:00

2.7 KiB

M4b task 09: editing a post, and a username

Branch: m4b (run git switch m4b; git status --short must be empty, otherwise stop) Commit subject: gatewayd: mm, patch_post and username

Goal

When an approval ends, gatewayd edits its post to say how, naming who answered (M4b spec, section 6). Two REST calls, checked against Mattermost v11.11.0, and confirmed on the owner's server (the bot may edit its own posts):

  • PUT /api/v4/posts/{post_id}/patch with {"message": "…"}: replaces the post's text; the answer is the post.
  • GET /api/v4/users/{user_id}: the user, with username.

Every id that goes into a path must be a Mattermost id first, or nothing is sent.

Files

  • Copy: crates/gatewayd/tests/mm_edit.rs
  • Modify: crates/gatewayd/src/mm/rest.rs, docs/implementer-log.md

The changes, exactly

In crates/gatewayd/src/mm/rest.rs, after struct Channel:

#[derive(Deserialize)]
struct User {
    username: String,
}

/// A Mattermost username: 1 to 64 characters of `a-z`, `0-9`, `.`, `-` and `_`.
fn valid_username(name: &str) -> bool {
    (1..=64).contains(&name.len())
        && name.bytes().all(|b| {
            b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'-' | b'_')
        })
}

and two methods at the end of impl Client, written like posts_since and create_post:

  1. pub fn patch_post(&self, post: &str, message: &str) -> Result<(), MmError>:
    • post not valid_id: Err(MmError::Json("a post path must be a Mattermost id".to_string())), nothing sent.
    • PUT /api/v4/posts/{post}/patch with the body serde_json::json!({ "message": message }).to_string(), through self.call.
    • The answer is a Post: json::<Post>(&body)?.check().map(|_| ()).
  2. pub fn username(&self, user: &str) -> Result<String, MmError>:
    • user not valid_id: Err(MmError::Json("a user path must be a Mattermost id".to_string())).
    • GET /api/v4/users/{user} into User; a username that is not valid_username is Err(MmError::Json("the user has no valid username".to_string())). Else Ok(username).

Steps

  • 1. Copy. git switch m4b, then cp docs/plans/M4b/files/crates/gatewayd/tests/mm_edit.rs crates/gatewayd/tests/
  • 2. See it fail. cargo test -p gatewayd --test mm_edit. Expected: it does not compile.
  • 3. Make the changes. Run cargo fmt --all.
  • 4. See it pass. cargo test -p gatewayd --test mm_edit --test mm_rest. Expected: 4 and 10 passed.
  • 5. Run the gate. make gate. Expected last line: gate: ok.
  • 6. Log and commit. git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit

Done when

  • Both suites pass; make gate prints gate: ok.