Skip to main content

spec_driven_docs/services/
status.rs

1//! Report an instance's state as data.
2//!
3//! Status answers what the verifier's text answers, but as one typed
4//! record a machine can branch on, and it treats a missing instance as an
5//! answer rather than an error. A corrupt manifest still raises: absence
6//! and breakage are different findings, and reporting a broken instance as
7//! absent would invite a destructive re-init.
8
9use camino::Utf8Path;
10use serde::Serialize;
11
12use crate::domain::profile::{DocsRoot, ProfileId};
13use crate::domain::version::CanonVersion;
14use crate::error::AppError;
15use crate::services::verifier;
16
17/// How an instance's canon version relates to this binary's.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
19#[serde(rename_all = "kebab-case")]
20pub enum Alignment {
21    /// The versions are equal.
22    Aligned,
23    /// The binary is newer; `sdd upgrade` moves the instance.
24    BinaryNewer,
25    /// The instance is newer; a newer `sdd` is required.
26    InstanceNewer,
27}
28
29/// One instance's state, as `sdd status` reports it.
30#[derive(Debug, Serialize)]
31pub struct StatusReport {
32    /// Whether the target carries an instance.
33    pub instance: bool,
34    /// The installed profile.
35    pub profile: Option<ProfileId>,
36    /// The instance's documentation root.
37    pub docs_root: Option<DocsRoot>,
38    /// The canon version that produced the instance.
39    pub canon_version: Option<CanonVersion>,
40    /// The version this binary carries.
41    pub binary_version: CanonVersion,
42    /// How the two versions relate.
43    pub alignment: Option<Alignment>,
44    /// Managed files missing, symlinked, or byte-drifted.
45    pub managed_drift: usize,
46    /// Adopted files awaiting reconciliation.
47    pub adopted_drift: usize,
48    /// Verification failures in total.
49    pub failures: usize,
50    /// Whether verification passes.
51    pub ok: Option<bool>,
52}
53
54fn absent() -> StatusReport {
55    StatusReport {
56        instance: false,
57        profile: None,
58        docs_root: None,
59        canon_version: None,
60        binary_version: CanonVersion::current(),
61        alignment: None,
62        managed_drift: 0,
63        adopted_drift: 0,
64        failures: 0,
65        ok: None,
66    }
67}
68
69/// Report the target's instance state.
70///
71/// # Errors
72///
73/// [`AppError::ManifestInvalid`] when a manifest exists but cannot be
74/// trusted, and I/O errors when the disk cannot be read. A missing
75/// manifest is a report, not an error.
76pub fn status(target: &Utf8Path) -> Result<StatusReport, AppError> {
77    let manifest = match verifier::read_manifest(target) {
78        Ok(manifest) => manifest,
79        Err(AppError::ManifestMissing(_)) => return Ok(absent()),
80        Err(error) => return Err(error),
81    };
82    let report = verifier::verify(target)?;
83    let binary = CanonVersion::current();
84    let alignment = match manifest.canon_version.cmp(&binary) {
85        std::cmp::Ordering::Equal => Alignment::Aligned,
86        std::cmp::Ordering::Less => Alignment::BinaryNewer,
87        std::cmp::Ordering::Greater => Alignment::InstanceNewer,
88    };
89    Ok(StatusReport {
90        instance: true,
91        profile: Some(manifest.profile),
92        docs_root: Some(manifest.docs_root),
93        canon_version: Some(manifest.canon_version),
94        binary_version: binary,
95        alignment: Some(alignment),
96        managed_drift: report.managed_drift,
97        adopted_drift: report.adopted_drift,
98        failures: report.failures,
99        ok: Some(report.failures == 0),
100    })
101}