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 import;
27pub mod observer;
28pub mod repo;
29pub mod workspace;
30
31pub use import::ImportObserver;
32pub use observer::{resolve, Observer};
33pub use repo::RepoObserver;
34pub use workspace::Workspace;
35
36/// The observers compiled into this build, in resolution order.
37///
38/// Ordered, not scored: [`resolve`] takes the first that claims a locator. `RepoObserver`
39/// is last because it accepts almost any non-URL string, so a more specific observer added
40/// later must go in front of it.
41///
42/// [`ImportObserver`] is therefore first. Its grammar is deliberately narrow — `-` and a
43/// `.json` suffix — precisely because being first means anything it claims, the repo
44/// observer never sees.
45pub fn default_observers() -> Vec<&'static dyn Observer> {
46    static IMPORT: ImportObserver = ImportObserver;
47    static REPO: RepoObserver = RepoObserver;
48    vec![&IMPORT, &REPO]
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn the_import_observer_is_ahead_of_the_repo_observer() {
57        // First-match resolution, so order is the whole registry. If `RepoObserver` came
58        // first it would claim `mesh.json` as a directory to walk and fail with a confusing
59        // error rather than importing it.
60        let obs = default_observers();
61        assert_eq!(obs[0].name(), "import");
62        assert_eq!(resolve(&obs, "mesh.json").unwrap().name(), "import");
63        assert_eq!(resolve(&obs, "-").unwrap().name(), "import");
64    }
65
66    #[test]
67    fn a_directory_still_goes_to_the_repo_observer() {
68        // The thing that would break if the import grammar were widened.
69        let obs = default_observers();
70        assert_eq!(resolve(&obs, ".").unwrap().name(), "repo");
71        assert_eq!(resolve(&obs, "/some/project").unwrap().name(), "repo");
72    }
73}