Skip to main content

luft_core/contract/
mod.rs

1//! Frozen contracts (code-design ยง1). These types are the shared basis for all
2//! modules; once reviewed they should change rarely. A companion `CONTRACTS.md`
3//! may track the freeze.
4
5pub mod backend;
6pub mod cache;
7pub mod event;
8pub mod finding;
9pub mod ids;
10pub mod schema;
11
12// backend: AgentBackend (trait), AgentTask, AgentResult, RunContext, BackendError
13pub use backend::*;
14// cache: agent_cache_key (single named export; no wildcard re-export)
15pub use cache::agent_cache_key;
16// event: AgentEvent, EventSender, RunStatus
17pub use event::*;
18// finding: Finding, Severity, Location
19pub use finding::*;
20// ids: RunId, AgentId, PhaseId, TokenUsage
21pub use ids::*;
22// schema: validate_output, SchemaError
23pub use schema::*;
24
25#[cfg(test)]
26mod tests {
27    //! The `contract` module is the frozen public API surface of `luft-core`.
28    //! These tests are *compile-time* checks: they construct every re-exported
29    //! item so any future drift (e.g. accidentally removing a wildcard export,
30    //! renaming a type without updating this module) breaks the build before
31    //! it reaches downstream crates.
32
33    use super::*;
34
35    #[test]
36    fn reexports_are_accessible() {
37        // backend
38        let _: AgentCapabilities = AgentCapabilities::default();
39        let _: AgentStatus = AgentStatus::Ok;
40        let _: AgentTask;
41        let _: AgentResult;
42        let _: RunContext;
43        let _: BackendError = BackendError::Cancelled;
44        let _: ToolPolicy = ToolPolicy::default();
45        let _: McpEndpoint;
46        let _: Artifact;
47        let _: LogRef = LogRef::default();
48
49        // cache
50        let _: fn(&str, Option<&str>, &str, u32) -> String = agent_cache_key;
51
52        // event
53        let _: EventSender;
54        let _: AgentEvent;
55        let _: RunStatus = RunStatus::Completed;
56        let _: LogLevel = LogLevel::Info;
57        let _: PlanPhase;
58        let _: ProgressDelta;
59
60        // finding
61        let _: Finding;
62        let _: Severity = Severity::Info;
63        let _: Location;
64
65        // ids
66        let _: RunId = uuid::Uuid::nil();
67        let _: AgentId = uuid::Uuid::nil();
68        let _: PhaseId = 0u32;
69        let _: TokenUsage = TokenUsage::default();
70
71        // schema
72        let _: fn(&serde_json::Value, &serde_json::Value) -> Result<(), SchemaError> =
73            validate_output;
74    }
75
76    #[test]
77    fn submodule_paths_resolve() {
78        // Direct submodule paths must still resolve even though we also
79        // re-export with wildcards. This catches "moved submodule" breakage.
80        fn _assert_trait(_: &dyn backend::AgentBackend) {}
81        let _: event::AgentEvent;
82        let _: finding::Finding;
83        let _: ids::TokenUsage;
84        let _: schema::SchemaError;
85        // cache module exports the `agent_cache_key` function via its own path.
86        let _: fn(&str, Option<&str>, &str, u32) -> String = cache::agent_cache_key;
87    }
88
89    #[test]
90    fn backend_error_is_retryable_helper() {
91        assert!(BackendError::Timeout.is_retryable());
92        assert!(BackendError::Spawn("x".into()).is_retryable());
93        assert!(!BackendError::Cancelled.is_retryable());
94        assert!(!BackendError::Protocol("x".into()).is_retryable());
95        assert!(!BackendError::Config("x".into()).is_retryable());
96    }
97
98    #[test]
99    fn agent_status_as_str_is_consistent() {
100        // Spot-check that re-exported `AgentStatus` retains its `as_str()`
101        // helper exposed by the backend submodule.
102        assert_eq!(AgentStatus::Ok.as_str(), "ok");
103        assert_eq!(AgentStatus::TimedOut.as_str(), "timed_out");
104    }
105
106    #[test]
107    fn token_usage_default_is_zero() {
108        let t = TokenUsage::default();
109        assert_eq!(t.total(), 0);
110    }
111}