spec_driven_docs/services/
status.rs1use camino::{Utf8Path, Utf8PathBuf};
10use serde::Serialize;
11
12use crate::domain::manifest::{DOCS_SCRATCH_VAR, PLAN_ZONE_VAR, PlanZone};
13use crate::domain::profile::{DocsRoot, ProfileId};
14use crate::domain::version::CanonVersion;
15use crate::error::AppError;
16use crate::services::verifier;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
20#[serde(rename_all = "kebab-case")]
21pub enum Alignment {
22 Aligned,
24 BinaryNewer,
26 InstanceNewer,
28}
29
30#[derive(Debug, Serialize)]
32pub struct StatusReport {
33 pub instance: bool,
35 pub profile: Option<ProfileId>,
37 pub docs_root: Option<DocsRoot>,
39 pub plan_zone: Option<PlanZone>,
41 pub docs_scratch: Option<Utf8PathBuf>,
43 pub plan_zone_env: Option<String>,
46 pub docs_scratch_env: Option<String>,
48 pub canon_version: Option<CanonVersion>,
50 pub binary_version: CanonVersion,
52 pub alignment: Option<Alignment>,
54 pub managed_drift: usize,
56 pub adopted_drift: usize,
58 pub failures: usize,
60 pub ok: Option<bool>,
62}
63
64fn variable(name: &str) -> Option<String> {
66 std::env::var(name)
67 .ok()
68 .map(|value| value.trim().to_string())
69 .filter(|value| !value.is_empty())
70}
71
72fn absent() -> StatusReport {
73 StatusReport {
74 instance: false,
75 profile: None,
76 docs_root: None,
77 plan_zone: None,
78 docs_scratch: None,
79 plan_zone_env: variable(PLAN_ZONE_VAR),
80 docs_scratch_env: variable(DOCS_SCRATCH_VAR),
81 canon_version: None,
82 binary_version: CanonVersion::current(),
83 alignment: None,
84 managed_drift: 0,
85 adopted_drift: 0,
86 failures: 0,
87 ok: None,
88 }
89}
90
91pub fn status(target: &Utf8Path) -> Result<StatusReport, AppError> {
99 let manifest = match verifier::read_manifest(target) {
100 Ok(manifest) => manifest,
101 Err(AppError::ManifestMissing(_)) => return Ok(absent()),
102 Err(error) => return Err(error),
103 };
104 let report = verifier::verify(target)?;
105 let binary = CanonVersion::current();
106 let alignment = match manifest.canon_version.cmp(&binary) {
107 std::cmp::Ordering::Equal => Alignment::Aligned,
108 std::cmp::Ordering::Less => Alignment::BinaryNewer,
109 std::cmp::Ordering::Greater => Alignment::InstanceNewer,
110 };
111 Ok(StatusReport {
112 instance: true,
113 profile: Some(manifest.profile),
114 docs_root: Some(manifest.docs_root),
115 plan_zone: Some(manifest.plan_zone),
116 docs_scratch: manifest.docs_scratch,
117 plan_zone_env: variable(PLAN_ZONE_VAR),
118 docs_scratch_env: variable(DOCS_SCRATCH_VAR),
119 canon_version: Some(manifest.canon_version),
120 binary_version: binary,
121 alignment: Some(alignment),
122 managed_drift: report.managed_drift,
123 adopted_drift: report.adopted_drift,
124 failures: report.failures,
125 ok: Some(report.failures == 0),
126 })
127}