Skip to main content

spec_driven_docs/commands/
front.rs

1//! What each compatibility verb is willing to serve.
2//!
3//! A front says what the operator meant to do. The target says what it is.
4//! Where the two disagree, the front refuses and names the verb that
5//! serves what was actually found, because landing seeds over a settled
6//! corpus starts a second convention beside the first and no flag makes
7//! that safe.
8
9use camino::Utf8Path;
10
11use crate::error::AppError;
12use crate::plan::classify::Intent;
13use crate::plan::observe::observe;
14
15/// Refuse where this verb does not serve what the target turned out to be.
16///
17/// # Errors
18///
19/// [`AppError::Refused`] naming the verb, the classification, and the next
20/// command. An unreadable target raises rather than being classified.
21pub fn serves(intent: Intent, target: &Utf8Path) -> Result<(), AppError> {
22    // An absent target is the landing verb's own business: it creates one.
23    if !target.exists() {
24        return Ok(());
25    }
26    let observation = observe(target)?;
27    let found = crate::plan::classify::classify(crate::plan::classify::Signals {
28        invalid: observation.invalid.is_some(),
29        installed: observation.installation.is_some(),
30        // The destination is this binary's own release for every front.
31        at_destination: observation.installation.as_ref().is_some_and(|installed| {
32            installed.canon_version == crate::domain::version::CanonVersion::current()
33        }),
34        drifted: observation
35            .installation
36            .as_ref()
37            .is_some_and(crate::plan::observe::Installation::drifted),
38        settled: observation.corpus.settled(),
39    });
40    intent
41        .accepts(found)
42        .map_err(|refusal| AppError::Refused(refusal.to_string()))
43}