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