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>
3.9 KiB
M4b task 04: an answer records its post; the table says what is pending
Branch: m4b (run git switch m4b; git status --short must be empty, otherwise stop)
Commit subject: brokerd: answers carry the Mattermost post; Table::contains
Goal
When an approval is answered from Mattermost, the audit record must name the approver and the
post that answered (the Approval record has had a post field since M3a, always None so
far). And the courier (task 06) needs to ask the approval table whether an approval is still
pending.
ledger.rs is at 499 lines, so first Answer and Answered move to approvals.rs, beside
Verdict and Entry; ledger re-exports them, so every crate::ledger::Answer still works.
Files
- Copy:
crates/brokerd/tests/ledger_post.rs, and new versions of three given tests,crates/brokerd/tests/broker_pending.rs,broker_sequence.rsandledger_answer.rs(the only change in each:post: None,in everyAnswer::ApprovedandAnswer::Refused) - Modify:
crates/brokerd/src/approvals.rs,crates/brokerd/src/ledger.rs,crates/brokerd/src/admin.rs,docs/implementer-log.md
The changes, exactly
1. Move. Cut enum Answer and struct Answered from ledger.rs, with their doc comments,
and paste them into approvals.rs, before pub struct Entry. In ledger.rs, add
pub use crate::approvals::{Answer, Answered}; next to use crate::approvals::Verdict;. In
approvals.rs, use proto::{DenyReason, PendingApproval, Timestamp}; becomes
use proto::{DecisionRecord, DenyReason, PendingApproval, Timestamp};.
2. post. In approvals.rs, Answer becomes:
/// An owner's answer to a pending call.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Answer {
/// `by` is "bxctl" or a Mattermost user id; `post` the Mattermost post that answered.
Approved {
by: Option<String>,
post: Option<String>,
},
Refused {
by: Option<String>,
post: Option<String>,
reason: Option<String>,
},
Expired,
}
In ledger.rs, answer, step 3 becomes:
let (answer, by, post, reason) = match &answer {
Answer::Approved { by, post } => {
(ApprovalAnswer::Approved, by.clone(), post.clone(), None)
}
Answer::Refused { by, post, reason } => (
ApprovalAnswer::Refused,
by.clone(),
post.clone(),
reason.clone(),
),
Answer::Expired => (ApprovalAnswer::Expired, None, None, None),
};
and in step 6, the AuditEvent::Approval field post: None, becomes post,.
In admin.rs, the two answers bxctl gives get post: None, after their by: Some(BY.to_string()),.
3. contains. In approvals.rs, impl Table, before take:
/// Is approval `id` still pending? Only a hint: it may be taken the moment after.
pub fn contains(&self, id: u64) -> bool {
self.lock().contains_key(&id)
}
Steps
- 1. Copy.
git switch m4b, thencp docs/plans/M4b/files/crates/brokerd/tests/ledger_post.rs docs/plans/M4b/files/crates/brokerd/tests/broker_pending.rs docs/plans/M4b/files/crates/brokerd/tests/broker_sequence.rs docs/plans/M4b/files/crates/brokerd/tests/ledger_answer.rs crates/brokerd/tests/ - 2. See it fail.
cargo test -p brokerd --test ledger_post. Expected: it does not compile (postis not a field ofAnswer;containsdoes not exist). - 3. Make the three changes. Run
cargo fmt --all. - 4. See it pass.
cargo test -p brokerd. Expected: every suite passes;ledger_post3 passed.wc -l crates/brokerd/src/ledger.rsis under 490. - 5. Run the gate.
make gate. Expected last line:gate: ok. - 6. Log and commit.
git add crates/brokerd docs/implementer-log.md Cargo.lock && git commit
Done when
cargo test -p brokerdpasses;make gateprintsgate: ok.