Skip to main content

weavatrix_refactor_plan/
evidence.rs

1use crate::{ScopeKind, StatusCode, SubjectKind, UncertaintyCode, WarningCode};
2use blazingly_json::Value;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::collections::BTreeMap;
5
6/// A graph revision preserving missing, explicit JSON null, and string states.
7#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
8pub enum GraphRevision {
9    #[default]
10    Missing,
11    Null,
12    Value(String),
13}
14
15impl GraphRevision {
16    #[must_use]
17    pub const fn is_missing(&self) -> bool {
18        matches!(self, Self::Missing)
19    }
20
21    #[must_use]
22    pub fn as_deref(&self) -> Option<&str> {
23        match self {
24            Self::Value(value) => Some(value),
25            Self::Missing | Self::Null => None,
26        }
27    }
28}
29
30impl Serialize for GraphRevision {
31    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32    where
33        S: Serializer,
34    {
35        match self {
36            Self::Missing | Self::Null => serializer.serialize_none(),
37            Self::Value(value) => serializer.serialize_str(value),
38        }
39    }
40}
41
42impl<'de> Deserialize<'de> for GraphRevision {
43    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
44    where
45        D: Deserializer<'de>,
46    {
47        Option::<String>::deserialize(deserializer)
48            .map(|value| value.map_or(Self::Null, Self::Value))
49    }
50}
51
52/// A typed symbol, resource, declaration, or other evidence subject.
53#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct TypedSubject {
56    pub kind: SubjectKind,
57    pub value: String,
58    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
59    pub extensions: BTreeMap<String, Value>,
60}
61
62impl TypedSubject {
63    #[must_use]
64    pub fn new(kind: impl Into<SubjectKind>, value: impl Into<String>) -> Self {
65        Self {
66            kind: kind.into(),
67            value: value.into(),
68            extensions: BTreeMap::new(),
69        }
70    }
71}
72
73/// Machine-comparable scope covered by a completeness claim.
74#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct EvidenceScope {
77    pub kind: ScopeKind,
78    pub value: String,
79    #[serde(default, skip_serializing_if = "Vec::is_empty")]
80    pub roots: Vec<String>,
81    #[serde(default, skip_serializing_if = "Vec::is_empty")]
82    pub languages: Vec<String>,
83    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
84    pub extensions: BTreeMap<String, Value>,
85}
86
87impl EvidenceScope {
88    #[must_use]
89    pub fn new(kind: impl Into<ScopeKind>, value: impl Into<String>) -> Self {
90        Self {
91            kind: kind.into(),
92            value: value.into(),
93            roots: Vec::new(),
94            languages: Vec::new(),
95            extensions: BTreeMap::new(),
96        }
97    }
98}
99
100/// Planner and backend identity behind a completeness claim.
101#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct PlannerIdentity {
104    pub name: String,
105    pub version: String,
106    pub backend: String,
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub backend_version: Option<String>,
109    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
110    pub extensions: BTreeMap<String, Value>,
111}
112
113impl PlannerIdentity {
114    #[must_use]
115    pub fn new(
116        name: impl Into<String>,
117        version: impl Into<String>,
118        backend: impl Into<String>,
119    ) -> Self {
120        Self {
121            name: name.into(),
122            version: version.into(),
123            backend: backend.into(),
124            backend_version: None,
125            extensions: BTreeMap::new(),
126        }
127    }
128}
129
130/// Typed proof identifying both the measured scope and its producer.
131#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct CompletenessProof {
134    pub scope: EvidenceScope,
135    pub planner: PlannerIdentity,
136    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
137    pub extensions: BTreeMap<String, Value>,
138}
139
140impl CompletenessProof {
141    #[must_use]
142    pub fn new(scope: EvidenceScope, planner: PlannerIdentity) -> Self {
143        Self {
144            scope,
145            planner,
146            extensions: BTreeMap::new(),
147        }
148    }
149}
150
151/// One explicitly unproven or uncovered reference.
152#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct UncertainReference {
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub path: Option<String>,
157    #[serde(default, skip_serializing_if = "Option::is_none")]
158    pub file: Option<String>,
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub line: Option<u32>,
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub subject: Option<TypedSubject>,
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub kind: Option<UncertaintyCode>,
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub reason: Option<UncertaintyCode>,
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub excerpt: Option<String>,
169    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
170    pub extensions: BTreeMap<String, Value>,
171}
172
173impl UncertainReference {
174    #[must_use]
175    pub fn repo_path(&self) -> Option<&str> {
176        self.path.as_deref().or(self.file.as_deref())
177    }
178}
179
180/// One intentionally omitted change with an evidence-backed explanation.
181#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct NotModified {
184    #[serde(default, skip_serializing_if = "Option::is_none")]
185    pub path: Option<String>,
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub file: Option<String>,
188    #[serde(default, skip_serializing_if = "Option::is_none")]
189    pub subject: Option<TypedSubject>,
190    #[serde(default, skip_serializing_if = "Option::is_none")]
191    pub operation_index: Option<u32>,
192    pub reason: String,
193    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
194    pub extensions: BTreeMap<String, Value>,
195}
196
197/// Typed evidence flattened into the top-level refactor-plan object.
198#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct PlanEvidence {
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub created_at: Option<String>,
203    #[serde(default, skip_serializing_if = "GraphRevision::is_missing")]
204    pub graph_revision: GraphRevision,
205    #[serde(default, skip_serializing_if = "Option::is_none")]
206    pub completeness_proof: Option<CompletenessProof>,
207    #[serde(default, skip_serializing_if = "Option::is_none")]
208    pub uncertain_references: Option<Vec<UncertainReference>>,
209    #[serde(default, skip_serializing_if = "Option::is_none")]
210    pub not_modified: Option<Vec<NotModified>>,
211    #[serde(default, skip_serializing_if = "Option::is_none")]
212    pub warnings: Option<Vec<WarningCode>>,
213    #[serde(default, skip_serializing_if = "Option::is_none")]
214    pub follow_up: Option<String>,
215    #[serde(default, skip_serializing_if = "Option::is_none")]
216    pub syntax_check: Option<StatusCode>,
217    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
218    pub extensions: BTreeMap<String, Value>,
219}
220
221/// Compatibility name for the evidence type used by the former annotation API.
222pub type PlanAnnotations = PlanEvidence;