1pub struct SessionKernel {
7 session_registry: SessionRegistry,
8 development_resources: DevelopmentResourceCatalog,
9 bundle_catalog: BundleCatalog,
10 mount_registry: MountRegistry,
11 sandbox_provider_registry: SandboxProviderRegistry,
12 sandbox_registry: SandboxRegistry,
13 runtime_factory: Rc<dyn Fn() -> Runtime>,
14 test_runner: String,
15 execution_backend: String,
16 #[cfg(not(target_arch = "wasm32"))]
17 source_catalog: Option<crate::project::SourceCatalog>,
18 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
19 native_source_cache: Option<SourceBytecodeCache>,
20 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
21 source_foundation_image: Option<Vec<u8>>,
22 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
23 source_language_spec_image: Option<Vec<u8>>,
24}
25
26#[derive(Default)]
27struct SessionRegistry {
28 entries: HashMap<String, Session>,
29}
30
31#[derive(Default)]
32struct DevelopmentResourceCatalog {
33 entries: HashMap<String, String>,
34}
35
36#[derive(Default)]
37struct BundleCatalog {
38 entries: HashMap<String, Vec<u8>>,
39}
40
41#[derive(Default)]
42struct MountRegistry {
43 entries: HashMap<u64, FilesystemMount>,
44 session_attachments: HashMap<String, u64>,
45 sandbox_attachments: HashMap<u64, u64>,
46 next_id: u64,
47}
48
49#[derive(Default)]
50struct SandboxProviderRegistry {
51 entries: HashMap<String, Rc<dyn SandboxProvider>>,
52}
53
54#[derive(Default)]
55struct SandboxRegistry {
56 entries: HashMap<u64, Sandbox>,
57 next_id: u64,
58}
59
60pub struct Session {
62 spec: SessionSpec,
63 runtime: Option<Runtime>,
64 state: SessionState,
65 filesystem: Option<AttachedFilesystem>,
66 authority: SessionAuthorityPolicy,
67 last_namespace: String,
68 live_sessions: SessionLiveRegistry,
69}
70
71struct AttachedFilesystem {
72 id: SessionMountId,
73 _provider: Rc<dyn core::FileProvider>,
74}
75
76impl Session {
77 #[cfg(test)]
78 fn new(name: &str, runtime: Runtime) -> Self {
79 let spec = SessionSpec::zero_authority(name)
80 .expect("Session::new requires a validated session name");
81 Self::open(spec, runtime)
82 }
83
84 fn open(spec: SessionSpec, runtime: Runtime) -> Self {
85 let authority = spec.authority;
86 let mut session = Self {
87 spec,
88 runtime: Some(runtime),
89 state: SessionState::New,
90 filesystem: None,
91 authority,
92 last_namespace: "user".into(),
93 live_sessions: SessionLiveRegistry::default(),
94 };
95 session.activate();
96 session
97 }
98
99 pub fn spec(&self) -> &SessionSpec {
100 &self.spec
101 }
102
103 pub fn id(&self) -> &SessionId {
104 &self.spec.id
105 }
106
107 pub fn name(&self) -> &str {
108 self.id().as_str()
109 }
110
111 pub fn state(&self) -> SessionState {
112 self.state
113 }
114
115 pub fn filesystem_mount(&self) -> Option<SessionMountId> {
116 self.filesystem.as_ref().map(|filesystem| filesystem.id)
117 }
118
119 #[cfg(test)]
120 pub(crate) fn module_revision(&self, name: &str) -> Result<u64, String> {
121 Ok(self.runtime()?.namespace_registry.module_revision(name))
122 }
123
124 fn ensure_active(&self) -> Result<(), String> {
125 match self.state {
126 SessionState::Active => Ok(()),
127 SessionState::Closed => Err(format!("SESSION_CLOSED {}", self.name())),
128 SessionState::New => Err(format!("SESSION_NOT_ACTIVE {} new", self.name())),
129 }
130 }
131
132 pub(crate) fn runtime(&self) -> Result<&Runtime, String> {
133 let name = self.spec.id.to_string();
134 self.runtime
135 .as_ref()
136 .ok_or_else(|| format!("SESSION_CLOSED {name}"))
137 }
138
139 pub(crate) fn runtime_mut(&mut self) -> Result<&mut Runtime, String> {
140 let name = self.spec.id.to_string();
141 self.runtime
142 .as_mut()
143 .ok_or_else(|| format!("SESSION_CLOSED {name}"))
144 }
145
146 fn activate(&mut self) {
147 assert_eq!(
148 self.state,
149 SessionState::New,
150 "session must start exactly once"
151 );
152 self.state = SessionState::Active;
153 }
154
155 fn release(&mut self) -> Option<SessionMountId> {
156 if self.state == SessionState::Closed {
157 return None;
158 }
159 self.live_sessions.dispose_all();
160 self.last_namespace = self
161 .runtime
162 .as_ref()
163 .map(Runtime::current_namespace)
164 .unwrap_or_else(|| self.last_namespace.clone());
165 if let Some(runtime) = self.runtime.as_mut() {
166 runtime.providers.set_file(None);
167 }
168 let mount = self.filesystem.take().map(|filesystem| filesystem.id);
169 self.runtime.take();
170 self.authority = SessionAuthorityPolicy::ZERO;
171 self.state = SessionState::Closed;
172 mount
173 }
174
175 pub fn eval(&mut self, source: &str) -> Result<String, String> {
176 self.ensure_active()?;
177 self.runtime_mut()?.eval_transfer_text(source)
178 }
179
180 pub fn current_namespace(&self) -> String {
181 self.runtime
182 .as_ref()
183 .map(Runtime::current_namespace)
184 .unwrap_or_else(|| self.last_namespace.clone())
185 }
186
187 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
188 pub fn native_execution_telemetry(
189 &self,
190 ) -> Result<crate::direct_native::NativeExecutionTelemetry, String> {
191 self.ensure_active()?;
192 Ok(self.runtime()?.native_execution_telemetry())
193 }
194
195 pub fn authority(&self) -> SessionAuthorityPolicy {
196 self.authority
197 }
198
199 #[cfg(not(target_arch = "wasm32"))]
200 pub fn install_native_socket_provider(&mut self) {
201 self.runtime_mut()
202 .expect("closed sessions cannot install providers")
203 .install_native_socket_provider();
204 self.authority.host_network = true;
205 }
206
207 #[cfg(not(target_arch = "wasm32"))]
208 pub fn install_native_process_provider(&mut self) {
209 self.runtime_mut()
210 .expect("closed sessions cannot install providers")
211 .install_native_process_provider();
212 self.authority.host_process = true;
213 }
214}
215
216impl crate::lang::protocol::IContext<&str> for Session {
217 type Output = Result<String, String>;
218
219 fn call(&mut self, source: &str) -> Self::Output {
220 self.eval(source)
221 }
222
223}
224
225impl crate::lang::protocol::IComponent for Session {
226 type Metadata = SessionMetadata;
227
228 fn props(&self) -> Self::Metadata {
229 SessionStatus {
230 name: self.id().clone(),
231 namespace: self.current_namespace(),
232 state: self.state,
233 filesystem: self.filesystem_mount(),
234 authority: self.authority,
235 }
236 }
237
238 fn status(&self) -> Self::Metadata {
239 self.props()
240 }
241
242 fn started(&self) -> bool {
243 self.state == SessionState::Active
244 }
245
246 fn stopped(&self) -> bool {
247 self.state == SessionState::Closed
248 }
249
250 fn start(&mut self) {
251 self.activate();
252 }
253
254 fn stop(&mut self) {
255 self.release();
256 }
257}
258
259impl<'a> crate::lang::protocol::IApplicable<Session, &'a str> for Session {
260 type Output = Result<String, String>;
261
262 fn apply_in(&self, runtime: &mut Session, source: &'a str) -> Self::Output {
263 self.ensure_active()?;
264 crate::lang::protocol::IContext::call(runtime, source)
265 }
266
267 fn apply_default(&mut self) -> &mut Session {
268 self
269 }
270
271 fn transform_in(&self, _runtime: &Session, source: &'a str) -> &'a str {
272 source
273 }
274
275 fn transform_out(
276 &self,
277 _runtime: &Session,
278 _source: &'a str,
279 value: Self::Output,
280 ) -> Self::Output {
281 value
282 }
283}
284
285struct FilesystemMount {
286 provider: Rc<dyn core::FileProvider>,
287 kind: &'static str,
288 key: String,
289 attachments: usize,
290}
291
292impl Default for SessionKernel {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298impl SessionKernel {
299 pub fn new() -> Self {
300 Self::with_runtime_factory(Runtime::new(), Rc::new(Runtime::new))
301 }
302
303 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
304 pub fn new_with_native_engine(engine: crate::direct_native::NativeEngine) -> Self {
308 let factory_engine = engine.clone();
309 Self::with_runtime_factory(
310 Runtime::with_native_engine(engine),
311 Rc::new(move || Runtime::with_native_engine(factory_engine.clone())),
312 )
313 }
314
315 pub(crate) fn with_runtime_factory(
316 root_runtime: Runtime,
317 runtime_factory: Rc<dyn Fn() -> Runtime>,
318 ) -> Self {
319 let root_id = SessionId::parse("ROOT").expect("ROOT is a valid session identifier");
320 let execution_backend = root_runtime.execution_backend.clone();
321 Self {
322 session_registry: SessionRegistry {
323 entries: HashMap::from([(
324 root_id.to_string(),
325 Session::open(
326 SessionSpec::new(root_id, SessionAuthorityPolicy::ZERO),
327 root_runtime,
328 ),
329 )]),
330 },
331 development_resources: DevelopmentResourceCatalog::default(),
332 bundle_catalog: BundleCatalog::default(),
333 mount_registry: MountRegistry {
334 next_id: 1,
335 ..MountRegistry::default()
336 },
337 sandbox_provider_registry: SandboxProviderRegistry::default(),
338 sandbox_registry: SandboxRegistry {
339 next_id: 1,
340 ..SandboxRegistry::default()
341 },
342 runtime_factory,
343 test_runner: "code.test".into(),
344 execution_backend,
345 #[cfg(not(target_arch = "wasm32"))]
346 source_catalog: None,
347 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
348 native_source_cache: None,
349 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
350 source_foundation_image: None,
351 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
352 source_language_spec_image: None,
353 }
354 }
355
356 pub fn set_test_runner(&mut self, runner: &str) -> Result<(), String> {
357 validate_test_runner(runner)?;
358 self.test_runner = runner.into();
359 for session in self.session_registry.entries.values_mut() {
360 session.runtime_mut()?.configure_test_runner(runner)?;
361 }
362 Ok(())
363 }
364
365 pub fn set_execution_backend(&mut self, backend: &str) -> Result<(), String> {
368 validate_execution_backend(backend)?;
369 for session in self.session_registry.entries.values_mut() {
370 session.runtime_mut()?.configure_execution_backend(backend)?;
371 }
372 self.execution_backend = backend.into();
373 Ok(())
374 }
375
376 #[cfg(not(target_arch = "wasm32"))]
379 pub fn register_source_catalog(&mut self, catalog: &crate::project::SourceCatalog) {
380 self.source_catalog = Some(catalog.clone());
381 for session in self.session_registry.entries.values_mut() {
382 session
383 .runtime_mut()
384 .expect("kernel cannot retain a closed session")
385 .register_source_catalog(catalog);
386 }
387 }
388
389 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
392 pub fn configure_native_source_cache(
393 &mut self,
394 root: &std::path::Path,
395 source_index_fingerprint: [u8; 32],
396 ) {
397 let cache = SourceBytecodeCache::new(root, source_index_fingerprint);
398 self.native_source_cache = Some(cache.clone());
399 for session in self.session_registry.entries.values_mut() {
400 session
401 .runtime_mut()
402 .expect("kernel cannot retain a closed session")
403 .set_direct_native_source_cache(cache.clone());
404 }
405 }
406
407 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
412 pub fn install_source_foundation_image(&mut self, image: &[u8]) -> Result<(), String> {
413 for session in self.session_registry.entries.values_mut() {
414 session
415 .runtime_mut()?
416 .bootstrap_source_foundation_image(image)?;
417 }
418 self.source_foundation_image = Some(image.to_vec());
419 Ok(())
420 }
421
422 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
425 pub fn install_source_language_spec_image(&mut self, image: &[u8]) -> Result<(), String> {
426 for session in self.session_registry.entries.values_mut() {
427 session
428 .runtime_mut()?
429 .install_source_language_spec_image(image)?;
430 }
431 self.source_language_spec_image = Some(image.to_vec());
432 Ok(())
433 }
434
435 #[cfg(feature = "bytecode-vm")]
440 pub fn install_bytecode_bundle(&mut self, bytes: &[u8]) -> Result<(), String> {
441 for session in self.session_registry.entries.values_mut() {
442 crate::vm::eval_bytecode_bundle(session.runtime_mut()?, bytes)?;
443 }
444 Ok(())
445 }
446
447 pub fn create_session(&mut self, id: SessionId) -> Result<(), String> {
448 let spec = SessionSpec::new(id, SessionAuthorityPolicy::ZERO);
449 if self.session_registry.entries.contains_key(spec.id.as_str()) {
450 return Err(format!("SESSION_EXISTS {}", spec.id));
451 }
452 let mut runtime = (self.runtime_factory)();
453 runtime.configure_test_runner(&self.test_runner)?;
454 runtime.configure_execution_backend(&self.execution_backend)?;
455 #[cfg(not(target_arch = "wasm32"))]
456 if let Some(source_catalog) = &self.source_catalog {
457 runtime.register_source_catalog(source_catalog);
458 }
459 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
460 if let Some(source_cache) = &self.native_source_cache {
461 runtime.set_direct_native_source_cache(source_cache.clone());
462 }
463 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
464 if let Some(image) = &self.source_foundation_image {
465 runtime.bootstrap_source_foundation_image(image)?;
466 }
467 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
468 if let Some(image) = &self.source_language_spec_image {
469 runtime.install_source_language_spec_image(image)?;
470 }
471 for (resource, source) in &self.development_resources.entries {
472 runtime.register_resource(resource, source);
473 }
474 self.session_registry
475 .entries
476 .insert(spec.id.as_str().into(), Session::open(spec, runtime));
477 Ok(())
478 }
479
480 pub fn session_names(&self) -> Vec<SessionId> {
481 let mut names = self
482 .session_registry
483 .entries
484 .values()
485 .map(|session| session.id().clone())
486 .collect::<Vec<_>>();
487 names.sort();
488 names
489 }
490
491 pub fn session(&self, id: &SessionId) -> Result<&Session, String> {
492 self.session_registry
493 .entries
494 .get(id.as_str())
495 .ok_or_else(|| format!("NO_SESSION {id}"))
496 }
497
498 pub fn session_mut(&mut self, id: &SessionId) -> Result<&mut Session, String> {
499 self.session_registry
500 .entries
501 .get_mut(id.as_str())
502 .ok_or_else(|| format!("NO_SESSION {id}"))
503 }
504
505 pub fn session_namespace(&self, id: &SessionId) -> Result<String, String> {
506 self.session_registry
507 .entries
508 .get(id.as_str())
509 .map(Session::current_namespace)
510 .ok_or_else(|| format!("NO_SESSION {id}"))
511 }
512
513 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
514 pub fn native_execution_telemetry(
515 &self,
516 id: &SessionId,
517 ) -> Result<crate::direct_native::NativeExecutionTelemetry, String> {
518 self.session_registry
519 .entries
520 .get(id.as_str())
521 .ok_or_else(|| format!("NO_SESSION {id}"))?
522 .native_execution_telemetry()
523 }
524
525 pub fn eval(&mut self, id: &SessionId, source: &str) -> Result<String, String> {
526 self.session_registry
527 .entries
528 .get_mut(id.as_str())
529 .ok_or_else(|| format!("NO_SESSION {id}"))?
530 .eval(source)
531 }
532
533 pub fn register_resource(&mut self, name: &str, source: &str) {
534 self.development_resources
535 .entries
536 .insert(name.into(), source.into());
537 for session in self.session_registry.entries.values_mut() {
538 session
539 .runtime_mut()
540 .expect("kernel cannot retain a closed session")
541 .register_resource(name, source);
542 }
543 }
544
545 pub fn remove_resource(&mut self, name: &str) -> bool {
546 self.development_resources.entries.remove(name).is_some()
547 }
548
549 pub fn resource_names(&self) -> Vec<String> {
550 let mut names = self
551 .development_resources
552 .entries
553 .keys()
554 .cloned()
555 .collect::<Vec<_>>();
556 names.sort();
557 names
558 }
559
560 pub fn register_bundle(&mut self, digest: &str, bytes: &[u8]) -> Result<(), String> {
561 match self.bundle_catalog.entries.get(digest) {
562 Some(current) if current == bytes => Ok(()),
563 Some(_) => Err(format!("BUNDLE_DIGEST_CONFLICT {digest}")),
564 None => {
565 self.bundle_catalog
566 .entries
567 .insert(digest.into(), bytes.into());
568 Ok(())
569 }
570 }
571 }
572
573 pub fn bundle(&self, digest: &str) -> Option<&[u8]> {
574 self.bundle_catalog.entries.get(digest).map(Vec::as_slice)
575 }
576
577 pub fn create_memory_filesystem(&mut self, root: &str) -> SessionMountId {
578 self.create_filesystem(Rc::new(core::MemoryFileProvider::new(root)), "memory", root)
579 }
580
581 #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
582 pub fn create_native_filesystem(&mut self, root: &str) -> SessionMountId {
583 self.create_filesystem(Rc::new(core::NativeFileProvider::new(root)), "native", root)
584 }
585
586 fn create_filesystem(
587 &mut self,
588 provider: Rc<dyn core::FileProvider>,
589 kind: &'static str,
590 key: &str,
591 ) -> SessionMountId {
592 let id = self.mount_registry.next_id;
593 self.mount_registry.next_id = self
594 .mount_registry
595 .next_id
596 .checked_add(1)
597 .expect("filesystem mount identifiers exhausted");
598 self.mount_registry.entries.insert(
599 id,
600 FilesystemMount {
601 provider,
602 kind,
603 key: key.into(),
604 attachments: 0,
605 },
606 );
607 SessionMountId::new(id)
608 }
609
610 pub fn attach_filesystem(
611 &mut self,
612 session: &SessionId,
613 mount_id: SessionMountId,
614 ) -> Result<(), String> {
615 if !self.session_registry.entries.contains_key(session.as_str()) {
616 return Err(format!("NO_SESSION {session}"));
617 }
618 let provider = self
619 .mount_registry
620 .entries
621 .get(&mount_id.get())
622 .ok_or_else(|| format!("NO_FILESYSTEM {mount_id}"))?
623 .provider
624 .clone();
625 if self
626 .mount_registry
627 .session_attachments
628 .get(session.as_str())
629 == Some(&mount_id.get())
630 {
631 return Ok(());
632 }
633 self.detach_filesystem(session)?;
634 self.mount_registry
635 .entries
636 .get_mut(&mount_id.get())
637 .unwrap()
638 .attachments += 1;
639 self.mount_registry
640 .session_attachments
641 .insert(session.to_string(), mount_id.get());
642 let session = self
643 .session_registry
644 .entries
645 .get_mut(session.as_str())
646 .unwrap();
647 session
648 .runtime_mut()?
649 .providers
650 .set_file(Some(provider.clone()));
651 session.filesystem = Some(AttachedFilesystem {
652 id: mount_id,
653 _provider: provider,
654 });
655 Ok(())
656 }
657
658 pub fn detach_filesystem(&mut self, session: &SessionId) -> Result<(), String> {
659 let session = self
660 .session_registry
661 .entries
662 .get_mut(session.as_str())
663 .ok_or_else(|| format!("NO_SESSION {session}"))?;
664 session.runtime_mut()?.providers.set_file(None);
665 session.filesystem.take();
666 if let Some(mount_id) = self
667 .mount_registry
668 .session_attachments
669 .remove(session.id().as_str())
670 {
671 if let Some(mount) = self.mount_registry.entries.get_mut(&mount_id) {
672 mount.attachments = mount.attachments.saturating_sub(1);
673 }
674 }
675 Ok(())
676 }
677
678 pub fn filesystem(&self, session: &SessionId) -> Option<SessionMountId> {
679 self.session_registry
680 .entries
681 .get(session.as_str())
682 .and_then(Session::filesystem_mount)
683 }
684
685 pub fn filesystem_info(&self, mount_id: SessionMountId) -> Result<(&str, &str, usize), String> {
686 self.mount_registry
687 .entries
688 .get(&mount_id.get())
689 .map(|mount| (mount.kind, mount.key.as_str(), mount.attachments))
690 .ok_or_else(|| format!("NO_FILESYSTEM {mount_id}"))
691 }
692
693 pub fn close_filesystem(&mut self, mount_id: SessionMountId) -> Result<(), String> {
694 let mount = self
695 .mount_registry
696 .entries
697 .get(&mount_id.get())
698 .ok_or_else(|| format!("NO_FILESYSTEM {mount_id}"))?;
699 if mount.attachments != 0 {
700 return Err(format!("FILESYSTEM_ATTACHED {mount_id}"));
701 }
702 self.mount_registry.entries.remove(&mount_id.get());
703 Ok(())
704 }
705
706 pub fn close_session(&mut self, id: &SessionId) -> Result<(), String> {
707 if id.as_str() == "ROOT" {
708 return Err("ROOT_CANNOT_CLOSE".into());
709 }
710 if !self.session_registry.entries.contains_key(id.as_str()) {
711 return Err(format!("NO_SESSION {id}"));
712 }
713 self.detach_filesystem(id)?;
714 if let Some(mut session) = self.session_registry.entries.remove(id.as_str()) {
715 crate::lang::protocol::IComponent::stop(&mut session);
716 }
717 Ok(())
718 }
719}
720
721fn validate_test_runner(runner: &str) -> Result<(), String> {
722 if matches!(runner, "code.test" | "native") {
723 Ok(())
724 } else {
725 Err("runtime test runner must be code.test or native".into())
726 }
727}
728
729fn reject_legacy_iterator_calls(form: &Form) -> Result<(), String> {
734 const LEGACY: &[&str] = &[
735 "iter-has?",
736 "iter-finite?",
737 "iter-materialize",
738 "iter-close",
739 "iter-map",
740 "iter-filter",
741 "iter-take-while",
742 "iter-drop-while",
743 "iter-mapcat",
744 "iter-keep",
745 "iter-interpose",
746 "iter-interleave",
747 "iter-every?",
748 "iter-any?",
749 "iter-take",
750 "iter-drop",
751 "iter-zip",
752 "iter-cycle",
753 "iter-partition-pair",
754 "iter-partition-all",
755 "iter-partition",
756 "iter-range",
757 "iter-constantly",
758 "iter-repeatedly",
759 "iter-iterate",
760 ];
761 match form {
762 Form::List(values) => {
763 if let Some(Form::Symbol(name)) = values.first() {
764 if LEGACY.contains(&name.as_str()) {
765 return Err(format!("unbound symbol: {name}"));
766 }
767 if name == "quote" {
768 return Ok(());
769 }
770 }
771 for value in values {
772 reject_legacy_iterator_calls(value)?;
773 }
774 }
775 Form::Vector(values) | Form::Set(values) => {
776 for value in values {
777 reject_legacy_iterator_calls(value)?;
778 }
779 }
780 Form::Map(entries) => {
781 for (key, value) in entries {
782 reject_legacy_iterator_calls(key)?;
783 reject_legacy_iterator_calls(value)?;
784 }
785 }
786 Form::Tagged(_, value) | Form::Metadata(_, value) => reject_legacy_iterator_calls(value)?,
787 _ => {}
788 }
789 Ok(())
790}
791
792#[cfg(test)]
793mod authority_tests {
794 use super::*;
795
796 fn session_id(name: &str) -> SessionId {
797 SessionId::parse(name).unwrap()
798 }
799
800 #[test]
801 fn named_sessions_start_with_zero_host_authority() {
802 let mut kernel = SessionKernel::new();
803 let root = session_id("ROOT");
804 #[cfg(not(target_arch = "wasm32"))]
805 {
806 kernel
807 .session_mut(&root)
808 .unwrap()
809 .install_native_socket_provider();
810 kernel
811 .session_mut(&root)
812 .unwrap()
813 .install_native_process_provider();
814 assert_eq!(
815 kernel.session(&root).unwrap().authority().profile(),
816 "explicit"
817 );
818 }
819
820 let child_id = session_id("child");
821 kernel.create_session(child_id.clone()).unwrap();
822 let child = kernel.session(&child_id).unwrap();
823 assert_eq!(child.authority(), SessionAuthorityPolicy::ZERO);
824 assert_eq!(
825 crate::lang::protocol::IComponent::props(child)
826 .authority
827 .profile(),
828 "zero"
829 );
830
831 for capability in ["filesystem", "network/socket", "process"] {
832 let error = kernel
833 .eval(
834 &child_id,
835 &format!(
836 "(std.protocol.ideref.IDeref/deref (std.native.Host/capability? \"{capability}\"))"
837 ),
838 )
839 .unwrap_err();
840 assert!(error.contains("std.native.Host/capability? requires capability :host-call"));
841 assert!(error.contains(":native/capability-denied"));
842 }
843 }
844
845 #[test]
846 fn session_status_uses_typed_identity_state_and_mount() {
847 use crate::lang::protocol::IComponent;
848
849 let mut kernel = SessionKernel::new();
850 let typed = session_id("typed");
851 kernel.create_session(typed.clone()).unwrap();
852 let initial = kernel.session(&typed).unwrap().props();
853 assert_eq!(initial.name.as_str(), "typed");
854 assert_eq!(initial.state, SessionState::Active);
855 assert_eq!(initial.filesystem, None);
856
857 let mount = kernel.create_memory_filesystem("/");
858 kernel.attach_filesystem(&typed, mount).unwrap();
859 let mounted = kernel.session(&typed).unwrap().props();
860 assert_eq!(mounted.filesystem, Some(mount));
861 assert_eq!(kernel.session(&typed).unwrap().spec().id, mounted.name);
862 }
863
864 #[test]
865 fn scoped_filesystem_mount_does_not_change_host_authority_profile() {
866 let mut kernel = SessionKernel::new();
867 let mounted = session_id("mounted");
868 kernel.create_session(mounted.clone()).unwrap();
869 let mount = kernel.create_memory_filesystem("/");
870 kernel.attach_filesystem(&mounted, mount).unwrap();
871 assert_eq!(
872 kernel.session(&mounted).unwrap().authority(),
873 SessionAuthorityPolicy::ZERO
874 );
875 assert_eq!(kernel.filesystem(&mounted), Some(mount));
876 }
877
878 #[test]
879 fn closing_releases_session_owned_runtime_and_filesystem_once() {
880 use crate::lang::protocol::IComponent;
881
882 let mut kernel = SessionKernel::new();
883 let child = session_id("owned");
884 kernel.create_session(child.clone()).unwrap();
885 let mount = kernel.create_memory_filesystem("/");
886 assert_eq!(
887 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
888 1
889 );
890
891 kernel.attach_filesystem(&child, mount).unwrap();
892 assert_eq!(
893 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
894 3
895 );
896
897 let mut session = kernel
898 .session_registry
899 .entries
900 .remove(child.as_str())
901 .unwrap();
902 let released_mount = session.release();
903 assert_eq!(released_mount, Some(mount));
904 assert_eq!(session.state(), SessionState::Closed);
905 assert!(session.runtime.is_none());
906 assert!(session.filesystem.is_none());
907 assert_eq!(
908 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
909 1
910 );
911
912 assert_eq!(session.release(), None);
913 session.stop();
914 assert_eq!(
915 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
916 1
917 );
918 }
919
920 #[test]
921 fn development_resources_and_sealed_bundles_use_distinct_catalogs() {
922 let mut kernel = SessionKernel::new();
923 kernel.register_resource("demo/value.hal", "(ns demo.value) (def value 42)");
924 assert_eq!(kernel.resource_names(), vec!["demo/value.hal"]);
925
926 kernel.register_bundle("sha256:demo", b"sealed").unwrap();
927 kernel.register_bundle("sha256:demo", b"sealed").unwrap();
928 assert_eq!(kernel.bundle("sha256:demo"), Some(b"sealed".as_slice()));
929 assert_eq!(
930 kernel
931 .register_bundle("sha256:demo", b"replacement")
932 .unwrap_err(),
933 "BUNDLE_DIGEST_CONFLICT sha256:demo"
934 );
935
936 assert!(kernel.remove_resource("demo/value.hal"));
937 assert!(kernel.resource_names().is_empty());
938 assert_eq!(kernel.bundle("sha256:demo"), Some(b"sealed".as_slice()));
939 }
940}