Skip to main content

plan_issue/tracking/
checkpoint.rs

1//! Tracking checkpoint behavior (dry-run and live).
2//!
3//! Task 1.1 declares the result shapes; Tasks 5.2, 5.3, and 6.1 implement
4//! the dry-run renderer, refusal cases, and live adapter over the existing
5//! lifecycle primitives.
6
7use serde::{Deserialize, Serialize};
8
9use crate::lifecycle_record::PayloadRole;
10
11/// Selected role with a fully rendered Markdown body ready to post.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CheckpointSlice {
14    pub role: PayloadRole,
15    pub body: String,
16    #[serde(default)]
17    pub planned_comment_url: Option<String>,
18}
19
20/// Outcome of one checkpoint pass (dry-run or live).
21#[derive(Debug, Clone, Default, Serialize, Deserialize)]
22pub struct CheckpointPlan {
23    pub roles_planned: Vec<PayloadRole>,
24    pub roles_skipped: Vec<SkippedRole>,
25    pub rendered: Vec<CheckpointSlice>,
26    pub repair_dashboard: bool,
27    pub blocked: Vec<BlockedCheckpoint>,
28}
29
30impl CheckpointPlan {
31    pub fn is_blocked(&self) -> bool {
32        !self.blocked.is_empty()
33    }
34}
35
36/// Skipped role with the reason recorded for the JSON envelope.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct SkippedRole {
39    pub role: PayloadRole,
40    pub reason: String,
41}
42
43/// Stable blocked code returned when a checkpoint is refused. Codes follow
44/// `<role>-<rule>` (`state-stale-run-state`, `validation-missing-overall`,
45/// `closeout-not-allowed-from-checkpoint`, …) — the full catalog is finalized
46/// in Task 5.3.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct BlockedCheckpoint {
49    pub code: String,
50    pub role: Option<PayloadRole>,
51    pub message: String,
52    pub suggested_unblock: String,
53}