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}