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>
6.8 KiB
M4b task 15: commands answer approvals; typing pauses; the notice
Branch: m4b (run git switch m4b; git status --short must be empty, otherwise stop)
Commit subject: gatewayd: approvals answered from Mattermost; typing paused; the notice
Goal
The last three pieces (M4b spec, section 7):
- Commands.
Route::Approval(task 08) goes to the registry'sjudge(task 11): an accepted answer goes tobrokerd; anything else is answered in the thread with its reason. M4a's noticeM4B_COMMANDgoes away. - Typing. A turn waiting for its approval is not working: no
user_typingfor its thread while the registry holds an approval of its session. - The notice. On
approval_pending, the turn waits up to 10 s forbrokerdto bring that approval here (Registry::was_posted); only if it never does, it postswaiting for approval <id>: brokerd has not sent it here; answer it with bxctl approvals.
Files
- Copy:
crates/gatewayd/tests/serve_answers.rs,crates/gatewayd/tests/deliver_notice.rs, and new versions of three given files:crates/gatewayd/tests/deliver.rs(passes aNotice; the new notice text),crates/gatewayd/tests/serve.rs(!approve 3is now "approval 3 is not pending here"),crates/gatewayd/tests/support/gateway.rs(Tuninghasnotice) - Modify:
crates/gatewayd/src/approvals/mod.rs,crates/gatewayd/src/deliver.rs,crates/gatewayd/src/serve/mod.rs,crates/gatewayd/src/serve/handle.rs,crates/gatewayd/src/sessions.rs,docs/implementer-log.md
The changes, exactly
approvals/mod.rs: every approval ever posted, for the notice. Registry gains a field
ever: Mutex<HashSet<u64>> (doc comment: "Every approval ever posted here, answered or not: for
the notice."; import HashSet beside HashMap); add also inserts posted.approval into it
(self.ever.lock().unwrap_or_else(|p| p.into_inner()).insert(…), before inserting into posted);
and a new method:
/// Was approval `id` ever posted here?
pub fn was_posted(&self, id: u64) -> bool {
self.ever
.lock()
.unwrap_or_else(|p| p.into_inner())
.contains(&id)
}
deliver.rs:
approval_textreturnsformat!("waiting for approval {approval}: brokerd has not sent it here; answer it with bxctl approvals").- After it, and
use std::time::{Duration, Instant};at the top:
/// How long a turn waits for `brokerd` to bring its approval here before posting the notice.
pub const NOTICE_WAIT: Duration = Duration::from_secs(10);
/// Whether an approval was brought here (and so is posted already), and how long to wait for it.
pub struct Notice<'a> {
pub posted: &'a dyn Fn(u64) -> bool,
pub wait: Duration,
}
and write impl Notice<'_> { fn arrives(&self, id: u64) -> bool }: loop: (self.posted)(id)
→ true; past Instant::now() + self.wait (taken once, before the loop) → false; otherwise sleep
Duration::from_millis(50).min(<the time left>) (saturating_duration_since).
delivergains a last parameternotice: &Notice, and itsApprovalPendingbranch posts the notice only when it does not arrive:
// The approval post itself comes through `gateway.sock`; the notice only when it does not.
if let TurnEvent::ApprovalPending { approval, .. } = event
&& !notice.arrives(*approval)
{
post(poster, &batch.thread, approval_text(*approval), log);
}
serve/mod.rs: Tuning gains pub notice: Duration, (doc: "How long a turn waits for its
approval to come through gateway.sock before the notice."), crate::deliver::NOTICE_WAIT in its
Default; Gateway gains a field notice: Duration (doc "Tuning::notice."), set by a new last
parameter of Gateway::new, which run passes as tuning.notice.
serve/handle.rs:
- The
Route::Approvalarm becomes:
Route::Approval {
thread,
command,
user,
post: command_post,
at,
} => {
let approvers = self.config.approvers();
let judged = self.registry.judge(
&command,
&user,
&thread.root,
&command_post,
at,
approvers,
);
if let Judged::Reply(text) = judged {
self.post(&thread.channel, &thread.root, &text);
}
}
- In
start, the thread gets the registry and the wait, and passes aNotice:
let (registry, wait) = (Arc::clone(&self.registry), self.notice);
let spawned = std::thread::Builder::new().spawn(move || {
let posted = |id: u64| registry.was_posted(id);
let notice = Notice {
posted: &posted,
wait,
};
deliver(client.as_ref(), &socket, &batch, &|line| log(line), ¬ice);
let _ = done.send(batch.session);
});
- In
typing, before the loop:let waiting = self.registry.waiting_sessions();, and at the top of the loop body:if waiting.contains(&format!("mm-{}", thread.root)) { continue; }(with the comment "A turn waiting for its approval is not working: no typing in its thread."). - Imports:
use crate::approvals::Judged;;Noticebesidedeliver;M4B_COMMANDremoved.
sessions.rs: delete pub const M4B_COMMAND.
Steps
- 1. Copy.
git switch m4b, thencp docs/plans/M4b/files/crates/gatewayd/tests/serve_answers.rs docs/plans/M4b/files/crates/gatewayd/tests/deliver_notice.rs docs/plans/M4b/files/crates/gatewayd/tests/deliver.rs docs/plans/M4b/files/crates/gatewayd/tests/serve.rs crates/gatewayd/tests/ && cp docs/plans/M4b/files/crates/gatewayd/tests/support/gateway.rs crates/gatewayd/tests/support/ - 2. See it fail.
cargo test -p gatewayd --test serve_answers --test deliver_notice. Expected: they do not compile (deliver::NoticeandTuning::noticedo not exist yet). - 3. Make the changes, one file at a time,
cargo check -p gatewaydafter each. Runcargo fmt --all. - 4. See it pass.
cargo test -p gatewayd --test serve_answers --test deliver_notice --test deliver --test serve, three times: 4, 4, 9 and 6 passed. Thencargo test -p gatewayd: every suite passes. - 5. Run the gate.
make gate. Expected last line:gate: ok, with about 845 tests. - 6. Log and commit.
git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit
This is the last task of M4b. Stop after the commit.
Done when
cargo test -p gatewaydpasses;make gateprintsgate: ok.
Stop and report if
- A test passes only sometimes, or waits much longer than stated.