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
14pub const SOURCE_FACT: &str = "source/public-api";
16pub const DISCLOSURE_FACT: &str = "policy/no-v3-disclosure";
18pub const SOURCE_CONCLUSION: &str = "conclusion/source-api";
20pub 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#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct WorldProjection {
29 pub result: ProjectionResult,
31 pub value: Datum,
33}
34
35#[derive(Debug)]
37pub enum WorldError {
38 Projection(ProjectionError),
40 Qualification(String),
42 UnsupportedPair {
44 kind: String,
46 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#[derive(Clone)]
74pub struct WorldProduct {
75 state: Arc<WorldState>,
76}
77
78impl WorldProduct {
79 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 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 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 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 #[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}