Skip to main content

sim_lib_world/
product.rs

1use std::{collections::BTreeSet, sync::Arc};
2
3use sim_incremental_core::projection::{
4    ConclusionId, DeclaredInputSelector, DeterministicImportManifest, ExecutionSemantics, FactId,
5    FederatedClosure, NativeSourceEvidence, ObservedFact, ObservedWorld, OwnerProjectionGraph,
6    PackageIdentity, ProjectionBudget, ProjectionDigest, ProjectionEngine, ProjectionError,
7    ProjectionKindRef, ProjectionRegistry, ProjectionResult, ProjectionSpec, ProjectorPolicy,
8    ProjectorQualificationVerifier, install_baseline_providers,
9};
10use sim_kernel::{ContentId, Datum, Symbol};
11
12use crate::provider::{PROVIDER_SOURCE, WorldConfigShape, config, content_id};
13
14/// Semantic source fact used by the bundled reference world.
15pub const SOURCE_FACT: &str = "source/public-api";
16/// Audit-derived disclosure-policy fact used by the bundled reference world.
17pub const DISCLOSURE_FACT: &str = "policy/no-v3-disclosure";
18/// Source-only conclusion used to prove projection-scoped invalidation.
19pub const SOURCE_CONCLUSION: &str = "conclusion/source-api";
20/// Public-release conclusion which consumes the disclosure policy.
21pub const DISCLOSURE_CONCLUSION: &str = "conclusion/public-release";
22
23const SOURCE_KIND: &str = "world/public-api-v1";
24const DISCLOSURE_KIND: &str = "no-v3/disclosure-policy-v1";
25
26/// A checked projection together with its stable SIM record.
27#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct WorldProjection {
29    /// Complete qualified core result.
30    pub result: ProjectionResult,
31    /// Stable product record suitable for expression-codec rendering.
32    pub value: Datum,
33}
34
35/// Product-level construction or projection failure.
36#[derive(Debug)]
37pub enum WorldError {
38    /// Projection core rejected the request.
39    Projection(ProjectionError),
40    /// Qualification evidence did not close.
41    Qualification(String),
42    /// The requested reference kind/fact pair is not admitted.
43    UnsupportedPair {
44        /// Requested provider kind.
45        kind: String,
46        /// Requested fact identity.
47        fact: String,
48    },
49}
50
51impl std::fmt::Display for WorldError {
52    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        write!(formatter, "{self:?}")
54    }
55}
56
57impl std::error::Error for WorldError {}
58
59impl From<ProjectionError> for WorldError {
60    fn from(value: ProjectionError) -> Self {
61        Self::Projection(value)
62    }
63}
64
65struct WorldState {
66    registry: ProjectionRegistry,
67    shape: WorldConfigShape,
68    closure: FederatedClosure,
69    package: PackageIdentity,
70}
71
72/// Pure read-only product for semantic projection, diff, and explanation.
73#[derive(Clone)]
74pub struct WorldProduct {
75    state: Arc<WorldState>,
76}
77
78impl WorldProduct {
79    /// Constructs the bundled baseline provider from its exact authored source.
80    pub fn bundled() -> Result<Self, WorldError> {
81        let code = content_id(Datum::String(PROVIDER_SOURCE.to_owned()))?;
82        let shape_id = content_id(Datum::String("shape/world-config-v1".to_owned()))?;
83        let package = PackageIdentity {
84            name: env!("CARGO_PKG_NAME").to_owned(),
85            version: env!("CARGO_PKG_VERSION").to_owned(),
86            code,
87        };
88        let mut registry = ProjectionRegistry::new();
89        install_baseline_providers(&mut registry, shape_id.clone(), package.clone())?;
90        let source = FactId::new(SOURCE_FACT)?;
91        let disclosure = FactId::new(DISCLOSURE_FACT)?;
92        let closure = FederatedClosure::seal(
93            [source.clone(), disclosure.clone()],
94            [
95                OwnerProjectionGraph::new(
96                    "sim-runtime/sim-lib-world",
97                    [(
98                        ConclusionId::new(SOURCE_CONCLUSION)?,
99                        BTreeSet::from([source.clone()]),
100                    )],
101                )
102                .map_err(|error| WorldError::Qualification(error.to_string()))?,
103                OwnerProjectionGraph::new(
104                    "sim-private/disclosure",
105                    [(
106                        ConclusionId::new(DISCLOSURE_CONCLUSION)?,
107                        BTreeSet::from([source, disclosure]),
108                    )],
109                )
110                .map_err(|error| WorldError::Qualification(error.to_string()))?,
111            ],
112        )
113        .map_err(|error| WorldError::Qualification(error.to_string()))?;
114        Ok(Self {
115            state: Arc::new(WorldState {
116                registry,
117                shape: WorldConfigShape { id: shape_id },
118                closure,
119                package,
120            }),
121        })
122    }
123
124    /// Projects one caller-supplied observed semantic fact.
125    pub fn project(
126        &self,
127        kind: &str,
128        fact: &str,
129        semantic: Datum,
130        envelope: Option<Datum>,
131    ) -> Result<WorldProjection, WorldError> {
132        ensure_pair(kind, fact)?;
133        let fact_id = FactId::new(fact)?;
134        let world = ObservedWorld::new([(fact_id.clone(), ObservedFact { semantic, envelope })])?;
135        let policy = self.policy(fact_id);
136        let qualification = ProjectorQualificationVerifier::trusted_native(
137            &policy,
138            NativeSourceEvidence {
139                code: self.state.package.code.clone(),
140                dependencies: dependency_closure_id()?,
141                review: review_id()?,
142                source_and_dependencies_reviewed: true,
143                ambient_io_closed: true,
144                hidden_state_reviewed: true,
145                loaded_code_matches: true,
146            },
147        )
148        .map_err(|error| WorldError::Qualification(error.to_string()))?;
149        let spec = ProjectionSpec {
150            id: content_id(Datum::Node {
151                tag: Symbol::qualified("world", "projection-request-v1"),
152                fields: vec![
153                    (Symbol::new("kind"), Datum::String(kind.to_owned())),
154                    (Symbol::new("fact"), Datum::String(fact.to_owned())),
155                ],
156            })?,
157            kind: ProjectionKindRef::new(kind)?,
158            config: config(),
159            config_shape: self.state.shape.id.clone(),
160            provider: self.state.package.clone(),
161        };
162        let result =
163            ProjectionEngine::new(&self.state.registry, &self.state.shape, &self.state.closure)
164                .project(&world, &spec, &policy, Some(&qualification), None)?;
165        let value = projection_value(kind, fact, &result);
166        Ok(WorldProjection { result, value })
167    }
168
169    /// Compares two supplied semantic values under one qualified projection.
170    pub fn diff(
171        &self,
172        kind: &str,
173        fact: &str,
174        before: Datum,
175        after: Datum,
176    ) -> Result<Datum, WorldError> {
177        let before = self.project(kind, fact, before, None)?;
178        let after = self.project(kind, fact, after, None)?;
179        let changed = before.result.digest != after.result.digest;
180        let affected = if changed {
181            after
182                .result
183                .affected
184                .iter()
185                .map(|id| Datum::String(id.as_str().to_owned()))
186                .collect()
187        } else {
188            Vec::new()
189        };
190        Ok(Datum::Node {
191            tag: Symbol::qualified("world", "diff-v1"),
192            fields: vec![
193                (Symbol::new("before"), digest_datum(&before.result.digest)),
194                (Symbol::new("after"), digest_datum(&after.result.digest)),
195                (Symbol::new("changed"), Datum::Bool(changed)),
196                (Symbol::new("affected"), Datum::Vector(affected)),
197            ],
198        })
199    }
200
201    /// Explains one exact owner/conclusion/fact path from the sealed closure.
202    pub fn why(&self, conclusion: &str, fact: &str) -> Result<Datum, WorldError> {
203        let explanation = self
204            .state
205            .closure
206            .explain(&ConclusionId::new(conclusion)?, &FactId::new(fact)?)
207            .map_err(|error| WorldError::Qualification(error.to_string()))?;
208        Ok(Datum::Node {
209            tag: Symbol::qualified("world", "explanation-v1"),
210            fields: vec![
211                (
212                    Symbol::new("conclusion"),
213                    Datum::String(explanation.conclusion.as_str().to_owned()),
214                ),
215                (
216                    Symbol::new("fact"),
217                    Datum::String(explanation.fact.as_str().to_owned()),
218                ),
219                (
220                    Symbol::new("path"),
221                    Datum::Vector(explanation.path.into_iter().map(Datum::String).collect()),
222                ),
223            ],
224        })
225    }
226
227    /// Reports effect invocations owned by this product; the value is always zero.
228    #[must_use]
229    pub const fn effect_calls(&self) -> usize {
230        0
231    }
232
233    fn policy(&self, fact: FactId) -> ProjectorPolicy {
234        ProjectorPolicy {
235            input_shape: self.state.shape.id.clone(),
236            reads: DeclaredInputSelector::new([fact]),
237            imports: DeterministicImportManifest::default(),
238            execution: ExecutionSemantics {
239                id: "projection/trusted-native-v1".to_owned(),
240                canonical_nan: true,
241                canonical_collections: true,
242                fresh_instance: true,
243            },
244            budgets: ProjectionBudget {
245                max_inputs: 1,
246                max_output_bytes: 64 * 1024,
247                max_fuel: 1_000_000,
248                max_memory_bytes: 4 * 1024 * 1024,
249            },
250            requires_confinement: false,
251        }
252    }
253}
254
255fn ensure_pair(kind: &str, fact: &str) -> Result<(), WorldError> {
256    if matches!(
257        (kind, fact),
258        (SOURCE_KIND, SOURCE_FACT) | (DISCLOSURE_KIND, DISCLOSURE_FACT)
259    ) {
260        Ok(())
261    } else {
262        Err(WorldError::UnsupportedPair {
263            kind: kind.to_owned(),
264            fact: fact.to_owned(),
265        })
266    }
267}
268
269fn dependency_closure_id() -> Result<ContentId, ProjectionError> {
270    content_id(Datum::Node {
271        tag: Symbol::qualified("world", "native-dependency-closure-v1"),
272        fields: vec![
273            (
274                Symbol::new("sim-incremental-core"),
275                Datum::String("0.5.0".to_owned()),
276            ),
277            (Symbol::new("sim-kernel"), Datum::String("0.4.0".to_owned())),
278        ],
279    })
280}
281
282fn review_id() -> Result<ContentId, ProjectionError> {
283    content_id(Datum::Node {
284        tag: Symbol::qualified("world", "native-review-v1"),
285        fields: vec![
286            (
287                Symbol::new("source"),
288                Datum::String(PROVIDER_SOURCE.to_owned()),
289            ),
290            (Symbol::new("io-ports"), Datum::Vector(Vec::new())),
291            (Symbol::new("unsafe"), Datum::Bool(false)),
292            (Symbol::new("mutable-state"), Datum::Bool(false)),
293        ],
294    })
295}
296
297fn projection_value(kind: &str, fact: &str, result: &ProjectionResult) -> Datum {
298    Datum::Node {
299        tag: Symbol::qualified("world", "projection-v1"),
300        fields: vec![
301            (Symbol::new("kind"), Datum::String(kind.to_owned())),
302            (Symbol::new("fact"), Datum::String(fact.to_owned())),
303            (Symbol::new("digest"), digest_datum(&result.digest)),
304            (Symbol::new("value"), result.projection.clone()),
305            (
306                Symbol::new("affected"),
307                Datum::Vector(
308                    result
309                        .affected
310                        .iter()
311                        .map(|id| Datum::String(id.as_str().to_owned()))
312                        .collect(),
313                ),
314            ),
315        ],
316    }
317}
318
319fn digest_datum(digest: &ProjectionDigest) -> Datum {
320    Datum::Node {
321        tag: Symbol::qualified("core", "content-id-v1"),
322        fields: vec![
323            (
324                Symbol::new("algorithm"),
325                Datum::String(digest.0.algorithm.to_string()),
326            ),
327            (Symbol::new("bytes"), Datum::String(hex(&digest.0.bytes))),
328        ],
329    }
330}
331
332fn hex(bytes: &[u8]) -> String {
333    const DIGITS: &[u8; 16] = b"0123456789abcdef";
334    let mut out = String::with_capacity(bytes.len() * 2);
335    for byte in bytes {
336        out.push(char::from(DIGITS[usize::from(byte >> 4)]));
337        out.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
338    }
339    out
340}