use crate::handoff;
use crate::session_boundary::{SessionBoundaryAction, SessionBoundaryRecommendation};
use crate::state::escalation as escalation_state;
use crate::state::runtime as runtime_state;
use crate::state::work_stream_decay;
use super::{
ApprovalStep, BehavioralDriftState, CandidateUpdates, ContextHealth, DriftAggregateStatus,
NextStepStatus, RadarEvaluation, WrapUpStatus,
};
pub(super) fn build_session_boundary(
checkout_state: Option<&handoff::CheckoutStateView>,
recovery: &runtime_state::RuntimeRecoveryView,
work_stream_decay: &work_stream_decay::WorkStreamDecayView,
context_health: &ContextHealth,
behavioral_drift: &BehavioralDriftState,
evaluation: &RadarEvaluation,
escalation_view: &escalation_state::EscalationView,
) -> SessionBoundaryRecommendation {
let recovery_note = (recovery.status == "loaded").then_some(
"Recovery artifacts are loaded as supporting context only; handoff and execution gates remain authoritative."
.to_owned(),
);
if let Some(checkout_state) = checkout_state.filter(|state| state.advisory) {
let mut evidence = vec![checkout_state.summary.clone()];
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Stop,
"Stop here and continue from a live checkout before trusting this session boundary.",
evidence,
);
}
if escalation_view.blocking_count > 0 {
let mut evidence = escalation_view
.entries
.iter()
.filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
.map(|entry| format!("{}: {}", entry.id, entry.reason))
.collect::<Vec<_>>();
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Stop,
"Stop and resolve blocking escalations before treating Radar wrap-up as authoritative.",
evidence,
);
}
if work_stream_decay.status == "critical" {
let mut evidence = vec![format!(
"The active work stream recorded {} consecutive no-progress attempts.",
work_stream_decay.consecutive_no_progress
)];
if let Some(last_outcome) = work_stream_decay.last_outcome.as_deref() {
evidence.push(format!("Latest attempt outcome: {last_outcome}."));
}
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::WrapUp,
"Wrap up now and restart from a fresh session boundary; the active work stream is in a low-quality retry loop.",
evidence,
);
}
if behavioral_drift.status == DriftAggregateStatus::NeedsRecalibration {
let mut evidence = behavioral_drift.evidence.clone();
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Stop,
"Stop and recalibrate the handoff or focus before closing this session.",
evidence,
);
}
if evaluation.next_step.status == NextStepStatus::ReviewRequired {
let mut evidence = evaluation.next_step.evidence.clone();
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Refresh,
"Refresh the workspace-local handoff before closing this session.",
evidence,
);
}
if evaluation.wrap_up.status == WrapUpStatus::NeedsReview {
let mut evidence = evaluation.wrap_up.evidence.clone();
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::Stop,
evaluation.wrap_up.summary.clone(),
evidence,
);
}
if matches!(
context_health.recommendation,
"wrap_up_soon" | "wrap_up_and_clear"
) || matches!(
evaluation.wrap_up.status,
WrapUpStatus::ReadyToCommit | WrapUpStatus::ReadyToPush
) {
let mut evidence = evaluation.wrap_up.evidence.clone();
evidence.push(format!(
"Context health recommends `{}` (band `{}`).",
context_health.recommendation, context_health.band
));
append_recovery_note(&mut evidence, recovery_note.as_deref());
return SessionBoundaryRecommendation::new(
SessionBoundaryAction::WrapUp,
"Wrap up now; CCD sees a clean session boundary for close-out work.",
evidence,
);
}
let mut evidence = evaluation.next_step.evidence.clone();
if evidence.is_empty() {
evidence.push(format!(
"Context health recommends `{}` (band `{}`).",
context_health.recommendation, context_health.band
));
}
append_recovery_note(&mut evidence, recovery_note.as_deref());
SessionBoundaryRecommendation::new(
SessionBoundaryAction::Continue,
"Continue the current session; CCD does not yet see a stronger boundary action.",
evidence,
)
}
fn append_recovery_note(evidence: &mut Vec<String>, note: Option<&str>) {
if let Some(note) = note {
evidence.push(note.to_owned());
}
}
pub(super) fn build_approval_steps(
evaluation: &RadarEvaluation,
candidates: &CandidateUpdates,
behavioral_drift: &BehavioralDriftState,
) -> Vec<ApprovalStep> {
let mut steps = vec![ApprovalStep {
id: "next_step",
question: "Did this session change what should be next, or block something?",
recommended_answer: if evaluation.next_step.status == NextStepStatus::ReviewRequired {
"yes"
} else {
"no"
},
recommendation: evaluation.next_step.summary.clone(),
evidence: evaluation.next_step.evidence.clone(),
}];
if behavioral_drift.status == DriftAggregateStatus::NeedsRecalibration {
steps.push(ApprovalStep {
id: "recalibrate",
question:
"Recent behavior drifted from CCD expectations. Do you want me to recalibrate the handoff or focus before wrap-up?",
recommended_answer: "yes",
recommendation: behavioral_drift.summary.clone(),
evidence: behavioral_drift.evidence.clone(),
});
}
if !candidates.memory.is_empty() {
steps.push(ApprovalStep {
id: "memory",
question:
"These are things worth persisting in MEMORY.md. Do you want me to persist them?",
recommended_answer: "yes",
recommendation: evaluation.memory.summary.clone(),
evidence: candidates
.memory
.iter()
.map(|candidate| candidate.summary.clone())
.collect(),
});
}
steps.push(ApprovalStep {
id: "wrap_up",
question: "Wrap up now? I can validate, show git status/diff, stage explicit paths, commit after approval, and push after approval.",
recommended_answer: match evaluation.wrap_up.status {
WrapUpStatus::ReadyToCommit | WrapUpStatus::ReadyToPush => "yes",
WrapUpStatus::Clean
| WrapUpStatus::NeedsReview
| WrapUpStatus::NoChange => "no",
},
recommendation: evaluation.wrap_up.summary.clone(),
evidence: evaluation.wrap_up.evidence.clone(),
});
steps
}