Skip to main content

ito_core/
capabilities.rs

1//! Compile-time capability reporting and configuration preflight.
2
3use std::fmt;
4
5use ito_config::types::{CoordinationStorage, ItoConfig};
6
7use crate::errors::{CoreError, CoreResult};
8
9/// Experimental implementation that may be absent from a compiled Ito binary.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CompiledFeature {
12    /// Remote backend repositories and backend service integration.
13    Backend,
14    /// Coordination-branch synchronization and worktree storage.
15    CoordinationBranch,
16}
17
18impl CompiledFeature {
19    /// Stable Cargo feature identifier used in diagnostics and machine output.
20    pub const fn as_str(self) -> &'static str {
21        match self {
22            Self::Backend => "backend",
23            Self::CoordinationBranch => "coordination-branch",
24        }
25    }
26
27    /// Whether this implementation is present in the current `ito-core` build.
28    ///
29    /// This observes dependency-crate features. Executables should report
30    /// their local feature boundary with [`CompiledCapabilities`] instead.
31    pub const fn is_compiled(self) -> bool {
32        match self {
33            Self::Backend => cfg!(feature = "backend"),
34            Self::CoordinationBranch => cfg!(feature = "coordination-branch"),
35        }
36    }
37}
38
39impl fmt::Display for CompiledFeature {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.write_str(self.as_str())
42    }
43}
44
45/// A caller-reported set of compiled capabilities.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct CompiledCapabilities {
48    /// Whether backend integration is compiled in.
49    pub backend: bool,
50    /// Whether coordination-branch integration is compiled in.
51    pub coordination_branch: bool,
52}
53
54impl CompiledCapabilities {
55    /// Report the features compiled into the current `ito-core` build.
56    pub const fn current() -> Self {
57        Self {
58            backend: cfg!(feature = "backend"),
59            coordination_branch: cfg!(feature = "coordination-branch"),
60        }
61    }
62
63    /// Return whether a particular feature is compiled in.
64    pub const fn contains(self, feature: CompiledFeature) -> bool {
65        match feature {
66            CompiledFeature::Backend => self.backend,
67            CompiledFeature::CoordinationBranch => self.coordination_branch,
68        }
69    }
70}
71
72/// How the caller intends to use the resolved configuration.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum CapabilityPreflight {
75    /// Ordinary command execution that may initialize mutable subsystems.
76    Stateful,
77    /// Read-only diagnostics or recovery that must remain available for migration.
78    Recovery,
79}
80
81/// Reject configuration that requests an implementation absent from this binary.
82///
83/// Call this after configuration resolution and before repository, audit, or
84/// coordination initialization. Recovery callers deliberately bypass the gate so
85/// they can explain or migrate legacy configuration without mutating through it.
86pub fn preflight_config(config: &ItoConfig, mode: CapabilityPreflight) -> CoreResult<()> {
87    preflight_config_with_capabilities(config, mode, CompiledCapabilities::current())
88}
89
90/// Reject configuration against the capabilities reported by the executable.
91///
92/// Cargo unifies dependency features across a workspace build. Executable
93/// crates should pass their own feature set here so another workspace member
94/// cannot accidentally make an experimental dependency appear available in a
95/// shipping binary.
96pub fn preflight_config_with_capabilities(
97    config: &ItoConfig,
98    mode: CapabilityPreflight,
99    capabilities: CompiledCapabilities,
100) -> CoreResult<()> {
101    if mode == CapabilityPreflight::Recovery {
102        return Ok(());
103    }
104
105    if config.backend.enabled && !capabilities.contains(CompiledFeature::Backend) {
106        return Err(CoreError::feature_unavailable(
107            CompiledFeature::Backend,
108            "backend.enabled",
109            "disable backend.enabled, or install an experimental build with the backend feature",
110        ));
111    }
112
113    let coordination = &config.changes.coordination_branch;
114    if (coordination.enabled.0 || coordination.storage == CoordinationStorage::Worktree)
115        && !capabilities.contains(CompiledFeature::CoordinationBranch)
116    {
117        let requested_by = if coordination.enabled.0 {
118            "changes.coordination_branch.enabled"
119        } else {
120            "changes.coordination_branch.storage=worktree"
121        };
122        return Err(CoreError::feature_unavailable(
123            CompiledFeature::CoordinationBranch,
124            requested_by,
125            "run `ito agent instruction migrate-to-main`, then disable coordination-branch synchronization",
126        ));
127    }
128
129    Ok(())
130}
131
132#[cfg(test)]
133#[path = "capabilities_tests.rs"]
134mod capabilities_tests;