brokk-mj-controller 2.24.0

Daemon-side controller, session manager, and web server for Mjolnir
Documentation
use super::*;

impl ReviewSlot {
    /// The forms each role's harness is waiting on, read from that role's
    /// journal. A reviewer's own plan decision is not a question for the
    /// person: the review pane declines it for them.
    pub(super) fn questions(&self) -> impl Iterator<Item = ReviewerQuestion> + '_ {
        self.roles
            .iter()
            .filter_map(|(role, transcript)| Some((role, transcript.session.as_ref()?)))
            .flat_map(|(role, session)| {
                session
                    .pending_elicitations
                    .iter()
                    .filter(|request| !mj_core::acp::is_plan_review_id(&request.id))
                    .map(|request| ReviewerQuestion {
                        role: role.clone(),
                        request: request.clone(),
                    })
            })
    }

    pub(super) fn view(&self, session_id: &str) -> RuntimeReviewView {
        let verdict = match self.driver.phase() {
            TurnReviewPhase::Forwarding { synthesis, .. } => Some(VerdictView {
                kind: VerdictKind::Findings,
                text: synthesis.clone(),
                allowed: if matches!(
                    self.driver.phase(),
                    TurnReviewPhase::Forwarding { error: Some(_), .. }
                ) {
                    vec![Resolution::Forwarded, Resolution::Cancelled]
                } else {
                    Vec::new()
                },
            }),
            _ => self.driver.verdict().map(|verdict| match verdict {
                ReviewVerdict::Clean => VerdictView {
                    kind: VerdictKind::Clean,
                    text: String::new(),
                    allowed: Vec::new(),
                },
                ReviewVerdict::Findings { synthesis, .. } => VerdictView {
                    kind: VerdictKind::Findings,
                    text: synthesis.clone(),
                    allowed: vec![
                        Resolution::Forwarded,
                        Resolution::Dismissed,
                        Resolution::Cancelled,
                    ],
                },
                ReviewVerdict::Failed { reason } => VerdictView {
                    kind: VerdictKind::Failed,
                    text: reason.clone(),
                    // A failed review has nothing to forward, and dismissing
                    // it does not advance the baseline: the change stays
                    // unreviewed either way.
                    allowed: vec![Resolution::Dismissed, Resolution::Cancelled],
                },
            }),
        };
        RuntimeReviewView {
            session_id: session_id.to_owned(),
            questions: self.questions().collect(),
            tier: self.driver.tier(),
            phase: self.driver.phase().clone(),
            roles: self.driver.roles(),
            // A review that needed no reviewer (nothing changed, or only a
            // handoff to finish) has no reviewer to describe.
            status: if self.reviewer.profile.is_empty() {
                self.driver.status().to_owned()
            } else {
                format!("{} ยท {}", self.reviewer.description(), self.driver.status())
            },
            verdict,
        }
    }
}

pub(super) fn answer(
    reply: Option<oneshot::Sender<Result<(), StartRefusal>>>,
    result: Result<(), StartRefusal>,
) {
    if let Some(reply) = reply {
        let _ = reply.send(result);
    }
}

pub(super) fn unexpected(outcome: Result<ReviewerOutcome, String>) -> String {
    match outcome {
        Ok(other) => format!("unexpected reviewer response {other:?}"),
        Err(error) => error,
    }
}