gatewayd: state, the posts of open approvals, and the turn of a session

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-24 04:50:54 -07:00
parent c5df86d13d
commit 07e4e16270
3 changed files with 156 additions and 0 deletions
+60
View File
@@ -51,6 +51,19 @@ struct StateFile {
recent: Vec<String>,
threads: Vec<String>,
in_flight: Vec<InFlight>,
/// Optional, so an M4a state file loads unchanged.
#[serde(default)]
approvals: Vec<ApprovalPosts>,
}
/// The posts of an approval shown in Mattermost and not yet ended (M4b spec, section 8).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ApprovalPosts {
pub approval: u64,
pub channel: String,
/// First part first; the last is the one that is edited when it ends.
pub posts: Vec<String>,
}
impl StateFile {
@@ -84,6 +97,14 @@ impl StateFile {
return Some(format!("a turn in flight is not valid: {:?}", turn.session));
}
}
for shown in &self.approvals {
if !valid_id(&shown.channel)
|| shown.posts.is_empty()
|| !shown.posts.iter().all(|post| valid_id(post))
{
return Some(format!("approval {}'s posts are not valid", shown.approval));
}
}
None
}
}
@@ -250,4 +271,43 @@ impl State {
}
self.save().map(|()| taken)
}
/// The turn of a session, if one is in flight.
pub fn turn_of(&self, session: &str) -> Option<InFlight> {
// The entry of in_flight with that session, cloned.
self.file
.in_flight
.iter()
.find(|turn| turn.session == session)
.cloned()
}
/// A new posting of an approval shown in Mattermost; it replaces any earlier one.
pub fn add_approval(&mut self, shown: ApprovalPosts) -> Result<(), StateError> {
// Remove any entry with the same approval, push this one, save.
self.file.approvals.retain(|a| a.approval != shown.approval);
self.file.approvals.push(shown);
self.save()
}
/// Stop tracking an approval; nothing saved when it was not tracked.
pub fn remove_approval(&mut self, approval: u64) -> Result<(), StateError> {
// Remove the entries with that approval, save only if one was removed.
let before = self.file.approvals.len();
self.file.approvals.retain(|a| a.approval != approval);
if self.file.approvals.len() == before {
return Ok(());
}
self.save()
}
/// The approval posts left behind by the last run, removed from the state.
pub fn take_approvals(&mut self) -> Result<Vec<ApprovalPosts>, StateError> {
// Take them all out (std::mem::take), save only when there were some, return them.
let taken = std::mem::take(&mut self.file.approvals);
if taken.is_empty() {
return Ok(taken);
}
self.save().map(|()| taken)
}
}