Skip to main content

sim_incremental_core/projection/
builtin.rs

1use std::{collections::BTreeSet, sync::Arc};
2
3use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
4use sim_conformance_core::OwnerBindingId;
5use sim_kernel::{ContentId, Datum, Symbol};
6
7use super::{
8    PackageIdentity, ProjectionError, ProjectionInputs, ProjectionKindRef, ProjectionOutput,
9    ProjectionProvider, ProjectionRegistry,
10};
11
12/// Baseline open projection kinds required for source and release reasoning.
13pub const BASELINE_PROJECTION_KINDS: &[&str] = &[
14    "world/path-set-v1",
15    "world/manifest-dependencies-v1",
16    "world/public-api-v1",
17    "world/exact-command-environment-v1",
18    "world/generated-ownership-v1",
19    "world/package-assembly-v1",
20    "world/git-refs-v1",
21    "world/index-routes-v1",
22    "world/external-release-facts-v1",
23    "no-v3/disclosure-policy-v1",
24];
25
26/// Canonical include/glob/ignore semantics for logical world paths.
27///
28/// Paths use relative `/`-separated logical names. Absolute paths, parent
29/// traversal, backslashes, empty components, and `.` components are refused so
30/// host path spelling cannot enter semantic identity. Includes form a union;
31/// ignores subtract from it.
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub struct PathSelectionRules {
34    includes: Vec<String>,
35    ignores: Vec<String>,
36}
37
38impl PathSelectionRules {
39    /// Checks and canonicalizes an explicit ordered rule set.
40    pub fn new(
41        includes: impl IntoIterator<Item = String>,
42        ignores: impl IntoIterator<Item = String>,
43    ) -> Result<Self, ProjectionError> {
44        let includes = includes.into_iter().collect::<Vec<_>>();
45        let ignores = ignores.into_iter().collect::<Vec<_>>();
46        if includes.is_empty() {
47            return Err(ProjectionError::InvalidPathSelection(
48                "at least one include glob is required".to_owned(),
49            ));
50        }
51        compile_globs(&includes)?;
52        compile_globs(&ignores)?;
53        Ok(Self { includes, ignores })
54    }
55
56    /// Selects exact logical path fact identities in canonical order.
57    pub fn select(
58        &self,
59        paths: impl IntoIterator<Item = String>,
60    ) -> Result<BTreeSet<super::FactId>, ProjectionError> {
61        let includes = compile_globs(&self.includes)?;
62        let ignores = compile_globs(&self.ignores)?;
63        let mut selected = BTreeSet::new();
64        for path in paths {
65            validate_logical_path(&path)?;
66            if includes.is_match(&path) && !ignores.is_match(&path) {
67                selected.insert(super::FactId::new(format!("path/{path}"))?);
68            }
69        }
70        Ok(selected)
71    }
72
73    /// Returns the canonical checked configuration value bound into a digest.
74    #[must_use]
75    pub fn config(&self) -> Datum {
76        Datum::Node {
77            tag: Symbol::qualified("projection", "path-selection-v1"),
78            fields: vec![
79                (
80                    Symbol::new("include"),
81                    Datum::Vector(self.includes.iter().cloned().map(Datum::String).collect()),
82                ),
83                (
84                    Symbol::new("ignore"),
85                    Datum::Vector(self.ignores.iter().cloned().map(Datum::String).collect()),
86                ),
87            ],
88        }
89    }
90}
91
92fn compile_globs(patterns: &[String]) -> Result<GlobSet, ProjectionError> {
93    let mut builder = GlobSetBuilder::new();
94    for pattern in patterns {
95        if pattern.starts_with('/') || pattern.contains('\\') {
96            return Err(ProjectionError::InvalidPathSelection(format!(
97                "glob is not a canonical logical path pattern: {pattern}"
98            )));
99        }
100        let glob = GlobBuilder::new(pattern)
101            .literal_separator(true)
102            .backslash_escape(false)
103            .build()
104            .map_err(|error| ProjectionError::InvalidPathSelection(error.to_string()))?;
105        builder.add(glob);
106    }
107    builder
108        .build()
109        .map_err(|error| ProjectionError::InvalidPathSelection(error.to_string()))
110}
111
112fn validate_logical_path(path: &str) -> Result<(), ProjectionError> {
113    if path.is_empty()
114        || path.starts_with('/')
115        || path.contains('\\')
116        || path
117            .split('/')
118            .any(|part| part.is_empty() || part == "." || part == "..")
119    {
120        return Err(ProjectionError::InvalidPathSelection(format!(
121            "path is not a canonical relative logical path: {path}"
122        )));
123    }
124    Ok(())
125}
126
127/// Native baseline provider that projects all facts selected by its policy.
128///
129/// Each instance owns one open kind and one config Shape. Its output keeps fact
130/// identity beside each semantic value so two differently named inputs cannot
131/// alias even when their values are equal.
132pub struct SelectFactsProvider {
133    kind: ProjectionKindRef,
134    config_shape: ContentId,
135}
136
137impl SelectFactsProvider {
138    /// Constructs one provider instance for an open kind.
139    pub fn new(kind: impl Into<String>, config_shape: ContentId) -> Result<Self, ProjectionError> {
140        Ok(Self {
141            kind: ProjectionKindRef::new(kind)?,
142            config_shape,
143        })
144    }
145}
146
147impl ProjectionProvider for SelectFactsProvider {
148    fn kind(&self) -> &ProjectionKindRef {
149        &self.kind
150    }
151
152    fn config_shape(&self) -> &ContentId {
153        &self.config_shape
154    }
155
156    fn project(
157        &self,
158        inputs: &ProjectionInputs,
159        config: &Datum,
160    ) -> Result<ProjectionOutput, ProjectionError> {
161        let mut dependencies = BTreeSet::new();
162        let facts = inputs
163            .iter()
164            .map(|(id, value)| {
165                dependencies.insert(id.clone());
166                Datum::Node {
167                    tag: Symbol::qualified("projection", "fact-v1"),
168                    fields: vec![
169                        (Symbol::new("id"), Datum::String(id.as_str().to_owned())),
170                        (Symbol::new("value"), value.clone()),
171                    ],
172                }
173            })
174            .collect();
175        Ok(ProjectionOutput {
176            value: Datum::Node {
177                tag: Symbol::qualified("projection", "selected-facts-v1"),
178                fields: vec![
179                    (
180                        Symbol::new("kind"),
181                        Datum::String(self.kind.as_str().to_owned()),
182                    ),
183                    (Symbol::new("config"), config.clone()),
184                    (Symbol::new("facts"), Datum::Vector(facts)),
185                ],
186            },
187            dependencies,
188        })
189    }
190}
191
192/// Installs every baseline kind into an open registry, and grants each one
193/// real, honest `EvidenceGrade::Bootstrap` native-source admission.
194///
195/// Returns the [`PackageIdentity`] actually registered, which the caller
196/// must reuse for every later [`super::ProjectionSpec`] built against these
197/// kinds: its `code` is NOT `package.code` (a caller-supplied claim this
198/// function does not trust for admission purposes) but a fresh identity this
199/// function computes itself from `include_str!("builtin.rs")` -- the literal
200/// compiled source of [`SelectFactsProvider`]. Mutating that source changes
201/// this identity automatically; a caller cannot claim a code identity this
202/// function did not itself measure.
203///
204/// `dependencies` is likewise computed here, from this crate's own
205/// `CARGO_PKG_VERSION` -- always current by construction, never a
206/// hand-maintained string that can go stale. It does not enumerate the full
207/// transitive dependency closure; that is a known, named limitation, not a
208/// silent one.
209///
210/// # Errors
211/// Returns [`ProjectionError::UnqualifiedProjector`] if granting bootstrap
212/// admission fails for any kind (it should not, absent a bug here).
213pub fn install_baseline_providers(
214    registry: &mut ProjectionRegistry,
215    config_shape: ContentId,
216    package: PackageIdentity,
217) -> Result<PackageIdentity, ProjectionError> {
218    let code = builtin_source_identity()?;
219    let dependencies = builtin_dependency_identity()?;
220    let owner_binding = OwnerBindingId::from_text("sim-incremental-core/projection/builtin")
221        .map_err(|error| ProjectionError::UnqualifiedProjector(error.to_string()))?;
222    let registered = PackageIdentity { code, ..package };
223    for kind in BASELINE_PROJECTION_KINDS {
224        let kind_ref = ProjectionKindRef::new(*kind)?;
225        registry.register(
226            registered.clone(),
227            Arc::new(SelectFactsProvider::new(*kind, config_shape.clone())?),
228        )?;
229        registry
230            .admit_bootstrap_native(
231                &kind_ref,
232                owner_binding.clone(),
233                registered.code.clone(),
234                dependencies.clone(),
235            )
236            .map_err(|error| ProjectionError::UnqualifiedProjector(error.to_string()))?;
237    }
238    Ok(registered)
239}
240
241/// Hashes the literal compiled source of every file `SelectFactsProvider`'s
242/// real behavior actually depends on, not `builtin.rs` alone: its `project`
243/// calls into `ProjectionInputs`/`FactId` (`model.rs`), and the admission
244/// path it is registered through lives in `admission.rs`/`engine.rs`.
245///
246/// This still does not cover `sim-kernel`'s `Datum`/`Symbol` behavior, the
247/// Rust compiler, or any other external dependency -- those are covered
248/// only by version/registry identity (`builtin_dependency_identity`), the
249/// same limit ordinary Cargo-based supply-chain identity has everywhere
250/// else in this ecosystem, named here rather than left implicit.
251fn builtin_source_identity() -> Result<ContentId, ProjectionError> {
252    Datum::String(
253        [
254            include_str!("builtin.rs"),
255            include_str!("model.rs"),
256            include_str!("admission.rs"),
257            include_str!("engine.rs"),
258        ]
259        .concat(),
260    )
261    .content_id()
262    .map_err(|error| ProjectionError::UnqualifiedProjector(error.to_string()))
263}
264
265fn builtin_dependency_identity() -> Result<ContentId, ProjectionError> {
266    Datum::Node {
267        tag: Symbol::qualified("world", "native-dependency-closure-v1"),
268        fields: vec![(
269            Symbol::new("sim-incremental-core"),
270            Datum::String(env!("CARGO_PKG_VERSION").to_owned()),
271        )],
272    }
273    .content_id()
274    .map_err(|error| ProjectionError::UnqualifiedProjector(error.to_string()))
275}