weavatrix-refactor-plan 0.1.1

Evidence metadata, validation profiles, and canonical fingerprints for Weavatrix refactor plans
Documentation
use crate::{ScopeKind, StatusCode, SubjectKind, UncertaintyCode, WarningCode};
use blazingly_json::Value;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::BTreeMap;

/// A graph revision preserving missing, explicit JSON null, and string states.
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub enum GraphRevision {
    #[default]
    Missing,
    Null,
    Value(String),
}

impl GraphRevision {
    #[must_use]
    pub const fn is_missing(&self) -> bool {
        matches!(self, Self::Missing)
    }

    #[must_use]
    pub fn as_deref(&self) -> Option<&str> {
        match self {
            Self::Value(value) => Some(value),
            Self::Missing | Self::Null => None,
        }
    }
}

impl Serialize for GraphRevision {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Self::Missing | Self::Null => serializer.serialize_none(),
            Self::Value(value) => serializer.serialize_str(value),
        }
    }
}

impl<'de> Deserialize<'de> for GraphRevision {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Option::<String>::deserialize(deserializer)
            .map(|value| value.map_or(Self::Null, Self::Value))
    }
}

/// A typed symbol, resource, declaration, or other evidence subject.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypedSubject {
    pub kind: SubjectKind,
    pub value: String,
    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
    pub extensions: BTreeMap<String, Value>,
}

impl TypedSubject {
    #[must_use]
    pub fn new(kind: impl Into<SubjectKind>, value: impl Into<String>) -> Self {
        Self {
            kind: kind.into(),
            value: value.into(),
            extensions: BTreeMap::new(),
        }
    }
}

/// Machine-comparable scope covered by a completeness claim.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EvidenceScope {
    pub kind: ScopeKind,
    pub value: String,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub roots: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub languages: Vec<String>,
    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
    pub extensions: BTreeMap<String, Value>,
}

impl EvidenceScope {
    #[must_use]
    pub fn new(kind: impl Into<ScopeKind>, value: impl Into<String>) -> Self {
        Self {
            kind: kind.into(),
            value: value.into(),
            roots: Vec::new(),
            languages: Vec::new(),
            extensions: BTreeMap::new(),
        }
    }
}

/// Planner and backend identity behind a completeness claim.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlannerIdentity {
    pub name: String,
    pub version: String,
    pub backend: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub backend_version: Option<String>,
    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
    pub extensions: BTreeMap<String, Value>,
}

impl PlannerIdentity {
    #[must_use]
    pub fn new(
        name: impl Into<String>,
        version: impl Into<String>,
        backend: impl Into<String>,
    ) -> Self {
        Self {
            name: name.into(),
            version: version.into(),
            backend: backend.into(),
            backend_version: None,
            extensions: BTreeMap::new(),
        }
    }
}

/// Typed proof identifying both the measured scope and its producer.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletenessProof {
    pub scope: EvidenceScope,
    pub planner: PlannerIdentity,
    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
    pub extensions: BTreeMap<String, Value>,
}

impl CompletenessProof {
    #[must_use]
    pub fn new(scope: EvidenceScope, planner: PlannerIdentity) -> Self {
        Self {
            scope,
            planner,
            extensions: BTreeMap::new(),
        }
    }
}

/// One explicitly unproven or uncovered reference.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UncertainReference {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub path: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub file: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub line: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub subject: Option<TypedSubject>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub kind: Option<UncertaintyCode>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reason: Option<UncertaintyCode>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub excerpt: Option<String>,
    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
    pub extensions: BTreeMap<String, Value>,
}

impl UncertainReference {
    #[must_use]
    pub fn repo_path(&self) -> Option<&str> {
        self.path.as_deref().or(self.file.as_deref())
    }
}

/// One intentionally omitted change with an evidence-backed explanation.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotModified {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub path: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub file: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub subject: Option<TypedSubject>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub operation_index: Option<u32>,
    pub reason: String,
    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
    pub extensions: BTreeMap<String, Value>,
}

/// Typed evidence flattened into the top-level refactor-plan object.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlanEvidence {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub created_at: Option<String>,
    #[serde(default, skip_serializing_if = "GraphRevision::is_missing")]
    pub graph_revision: GraphRevision,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub completeness_proof: Option<CompletenessProof>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uncertain_references: Option<Vec<UncertainReference>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub not_modified: Option<Vec<NotModified>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub warnings: Option<Vec<WarningCode>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub follow_up: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub syntax_check: Option<StatusCode>,
    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
    pub extensions: BTreeMap<String, Value>,
}

/// Compatibility name for the evidence type used by the former annotation API.
pub type PlanAnnotations = PlanEvidence;