Skip to main content

scema_tools/
lib.rs

1//! # scema-tools — perception
2//!
3//! The only crate in the read path allowed to touch the outside world. [`Observer`] is the
4//! interface and [`RepoObserver`] is the first implementation.
5//!
6//! [`ImportObserver`] is the second, and it is what makes omni's domain-agnosticism
7//! operational rather than merely stated. A source tree can be perceived here because it is
8//! a filesystem walk in Rust. A running Solana bot, a set of Chainlink oracle feeds and a
9//! DOM cannot be — they live behind another lockfile, a Python package and a browser — and
10//! linking any of them would make this crate a hub of domain dependencies, which is exactly
11//! what the workspace note forbids. So the thing being observed **describes itself in
12//! `scema-world`'s vocabulary**, and this crate reads that. There are four producers on that
13//! contract now and only one of them is written in a language this crate can link.
14//!
15//! [`Workspace`] lives here too, and it belongs to the *read* path for a reason that is easy
16//! to miss: the CLI has an operator typing paths and needs no confinement, but the daemon
17//! and the MCP server take paths from a browser extension and a language model. "Observe
18//! this directory" from either of those is an instruction from somewhere the operator is
19//! not looking.
20//!
21//! Actuators (the write path) are not here yet. That is deliberate rather than unfinished:
22//! the loop is worth trusting with a keyboard only after the decision layer above it has
23//! been watched abstaining on real inputs for a while, and `scema execute` says so rather
24//! than pretending.
25
26pub mod conform;
27pub mod import;
28pub mod observer;
29pub mod repo;
30pub mod workspace;
31
32pub use conform::{conform, has_failure, Finding, Level};
33pub use import::ImportObserver;
34pub use observer::{resolve, Observer};
35pub use repo::RepoObserver;
36pub use workspace::Workspace;
37
38/// The observers compiled into this build, in resolution order.
39///
40/// Ordered, not scored: [`resolve`] takes the first that claims a locator. `RepoObserver`
41/// is last because it accepts almost any non-URL string, so a more specific observer added
42/// later must go in front of it.
43///
44/// [`ImportObserver`] is therefore first. Its grammar is deliberately narrow — `-` and a
45/// `.json` suffix — precisely because being first means anything it claims, the repo
46/// observer never sees.
47pub fn default_observers() -> Vec<&'static dyn Observer> {
48    static IMPORT: ImportObserver = ImportObserver;
49    static REPO: RepoObserver = RepoObserver;
50    vec![&IMPORT, &REPO]
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn the_import_observer_is_ahead_of_the_repo_observer() {
59        // First-match resolution, so order is the whole registry. If `RepoObserver` came
60        // first it would claim `mesh.json` as a directory to walk and fail with a confusing
61        // error rather than importing it.
62        let obs = default_observers();
63        assert_eq!(obs[0].name(), "import");
64        assert_eq!(resolve(&obs, "mesh.json").unwrap().name(), "import");
65        assert_eq!(resolve(&obs, "-").unwrap().name(), "import");
66    }
67
68    #[test]
69    fn a_directory_still_goes_to_the_repo_observer() {
70        // The thing that would break if the import grammar were widened.
71        let obs = default_observers();
72        assert_eq!(resolve(&obs, ".").unwrap().name(), "repo");
73        assert_eq!(resolve(&obs, "/some/project").unwrap().name(), "repo");
74    }
75}