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>
4.6 KiB
M4b task 17: the texts of a redirected approval
Branch: m4b (run git switch m4b; git status --short must be empty, otherwise stop)
Commit subject: gatewayd: the texts that say who asked for a redirected approval
Goal
Texts only; task 18 posts them. When someone who may talk to the bot but not approve asks for something in a direct or group message, no approver can answer the approval there, so it goes to the first approver's direct messages (M4b spec, section 6, as changed after the review of 2026-09-24). The approver must see who asked, where, and what they asked. Three texts:
-
asked_post: the first post of a redirected approval. The asker's message is escaped withproto::escape::escape_model_textand fenced (fence, already in this file). It is context, not what is approved, so only its firstPROMPT_SHOWN(2,000) characters are shown, then a line saying how many more there were. For example:**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. ``` -
unfollowed_post: the first post of an approval whose sessiongatewayddoes not follow (bxctl chat, or a turn from before a restart). -
sent_to_approver: the line left in the asking thread.
Files
- Copy:
crates/gatewayd/tests/approvals_asked.rs - Modify:
crates/gatewayd/src/approvals/text.rs(append only),docs/implementer-log.md
The change, exactly
Append this to the end of crates/gatewayd/src/approvals/text.rs, after with_ending, exactly
as it is. Nothing else in the file changes, and there are no new imports: proto is already a
dependency, and code and fence are in this file.
/// The most characters of an asker's message shown to the approver: it is context, not what is
/// approved, so it may be cut.
pub const PROMPT_SHOWN: usize = 2_000;
/// Who asked for an approval that is shown elsewhere (M4b spec, section 6).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Asked {
/// `@username`, or the user id when the name could not be found.
pub by: String,
/// The channel's type: `D` a direct message, `G` a group message.
pub channel_type: String,
/// The text of the turn that asked.
pub prompt: String,
}
/// The first post of a redirected approval: who asked, where, and their message.
pub fn asked_post(id: u64, asked: &Asked) -> String {
let place = if asked.channel_type == "G" {
"a group message"
} else {
"a direct message"
};
let escaped = proto::escape::escape_model_text(&asked.prompt);
let total = escaped.chars().count();
let shown: String = escaped.chars().take(PROMPT_SHOWN).collect();
let mut post = format!(
"**Approval {id}** was asked by {} in {place} with the bot. Their message:\n{}",
asked.by,
fence(&shown)
);
if total > PROMPT_SHOWN {
post.push_str(&format!("\n(… {} more characters)", total - PROMPT_SHOWN));
}
post
}
/// The first post of an approval whose session `gatewayd` does not follow.
pub fn unfollowed_post(id: u64, session: &str) -> String {
format!(
"**Approval {id}** was asked in session {}, which gatewayd does not follow now (a restart, or a session from outside Mattermost).",
code(session)
)
}
/// The line left in the asking thread when its approval goes to an approver.
pub fn sent_to_approver(id: u64) -> String {
format!("waiting for approval {id}: it was sent to an approver")
}
Characters are counted with chars(), never len(): len() counts bytes, and a message in any
language but English would be cut short.
Steps
- 1. Copy.
git switch m4b, thencp docs/plans/M4b/files/crates/gatewayd/tests/approvals_asked.rs crates/gatewayd/tests/ - 2. See it fail.
cargo test -p gatewayd --test approvals_asked. Expected: it does not compile (Askedand the three functions do not exist yet). - 3. Make the change. Run
cargo fmt --all. - 4. See it pass.
cargo test -p gatewayd --test approvals_asked: 4 passed. Thencargo test -p gatewayd: every suite passes. - 5. Run the gate.
make gate. Expected last line:gate: ok, with 4 more tests than before this task (about 850). - 6. Log and commit.
git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit
Done when
cargo test -p gatewaydpasses;make gateprintsgate: ok.
Stop and report if
- A test in
approvals_asked.rsstill fails with the code above appended exactly. Do not change the test or the texts to make it pass.