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}
220
221impl crate::lang::protocol::IComponent for Session {
222 type Metadata = SessionMetadata;
223
224 fn props(&self) -> Self::Metadata {
225 SessionStatus {
226 name: self.id().clone(),
227 namespace: self.current_namespace(),
228 state: self.state,
229 filesystem: self.filesystem_mount(),
230 authority: self.authority,
231 }
232 }
233
234 fn status(&self) -> Self::Metadata {
235 self.props()
236 }
237
238 fn started(&self) -> bool {
239 self.state == SessionState::Active
240 }
241
242 fn stopped(&self) -> bool {
243 self.state == SessionState::Closed
244 }
245
246 fn start(&mut self) {
247 self.activate();
248 }
249
250 fn stop(&mut self) {
251 self.release();
252 }
253}
254
255impl<'a> crate::lang::protocol::IApplicable<Session, &'a str> for Session {
256 type Output = Result<String, String>;
257
258 fn apply_in(&self, runtime: &mut Session, source: &'a str) -> Self::Output {
259 self.ensure_active()?;
260 crate::lang::protocol::IContext::call(runtime, source)
261 }
262
263 fn apply_default(&mut self) -> &mut Session {
264 self
265 }
266
267 fn transform_in(&self, _runtime: &Session, source: &'a str) -> &'a str {
268 source
269 }
270
271 fn transform_out(
272 &self,
273 _runtime: &Session,
274 _source: &'a str,
275 value: Self::Output,
276 ) -> Self::Output {
277 value
278 }
279}
280
281struct FilesystemMount {
282 provider: Rc<dyn core::FileProvider>,
283 kind: &'static str,
284 key: String,
285 attachments: usize,
286}
287
288impl Default for SessionKernel {
289 fn default() -> Self {
290 Self::new()
291 }
292}
293
294impl SessionKernel {
295 pub fn new() -> Self {
296 Self::with_runtime_factory(Runtime::new(), Rc::new(Runtime::new))
297 }
298
299 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
300 pub fn new_with_native_engine(engine: crate::direct_native::NativeEngine) -> Self {
304 let factory_engine = engine.clone();
305 Self::with_runtime_factory(
306 Runtime::with_native_engine(engine),
307 Rc::new(move || Runtime::with_native_engine(factory_engine.clone())),
308 )
309 }
310
311 pub(crate) fn with_runtime_factory(
312 root_runtime: Runtime,
313 runtime_factory: Rc<dyn Fn() -> Runtime>,
314 ) -> Self {
315 let root_id = SessionId::parse("ROOT").expect("ROOT is a valid session identifier");
316 let execution_backend = root_runtime.execution_backend.clone();
317 Self {
318 session_registry: SessionRegistry {
319 entries: HashMap::from([(
320 root_id.to_string(),
321 Session::open(
322 SessionSpec::new(root_id, SessionAuthorityPolicy::ZERO),
323 root_runtime,
324 ),
325 )]),
326 },
327 development_resources: DevelopmentResourceCatalog::default(),
328 bundle_catalog: BundleCatalog::default(),
329 mount_registry: MountRegistry {
330 next_id: 1,
331 ..MountRegistry::default()
332 },
333 sandbox_provider_registry: SandboxProviderRegistry::default(),
334 sandbox_registry: SandboxRegistry {
335 next_id: 1,
336 ..SandboxRegistry::default()
337 },
338 runtime_factory,
339 test_runner: "code.test".into(),
340 execution_backend,
341 #[cfg(not(target_arch = "wasm32"))]
342 source_catalog: None,
343 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
344 native_source_cache: None,
345 }
346 }
347
348 pub fn set_test_runner(&mut self, runner: &str) -> Result<(), String> {
349 validate_test_runner(runner)?;
350 self.test_runner = runner.into();
351 for session in self.session_registry.entries.values_mut() {
352 session.runtime_mut()?.configure_test_runner(runner)?;
353 }
354 Ok(())
355 }
356
357 pub fn set_execution_backend(&mut self, backend: &str) -> Result<(), String> {
360 validate_execution_backend(backend)?;
361 for session in self.session_registry.entries.values_mut() {
362 session.runtime_mut()?.configure_execution_backend(backend)?;
363 }
364 self.execution_backend = backend.into();
365 Ok(())
366 }
367
368 #[cfg(not(target_arch = "wasm32"))]
371 pub fn register_source_catalog(&mut self, catalog: &crate::project::SourceCatalog) {
372 self.source_catalog = Some(catalog.clone());
373 for session in self.session_registry.entries.values_mut() {
374 session
375 .runtime_mut()
376 .expect("kernel cannot retain a closed session")
377 .register_source_catalog(catalog);
378 }
379 }
380
381 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
384 pub fn configure_native_source_cache(
385 &mut self,
386 root: &std::path::Path,
387 source_index_fingerprint: [u8; 32],
388 ) {
389 let cache = SourceBytecodeCache::new(root, source_index_fingerprint);
390 self.native_source_cache = Some(cache.clone());
391 for session in self.session_registry.entries.values_mut() {
392 session
393 .runtime_mut()
394 .expect("kernel cannot retain a closed session")
395 .set_direct_native_source_cache(cache.clone());
396 }
397 }
398
399 #[cfg(feature = "bytecode-vm")]
404 pub fn install_bytecode_bundle(&mut self, bytes: &[u8]) -> Result<(), String> {
405 for session in self.session_registry.entries.values_mut() {
406 crate::vm::eval_bytecode_bundle(session.runtime_mut()?, bytes)?;
407 }
408 Ok(())
409 }
410
411 pub fn create_session(&mut self, id: SessionId) -> Result<(), String> {
412 let spec = SessionSpec::new(id, SessionAuthorityPolicy::ZERO);
413 if self.session_registry.entries.contains_key(spec.id.as_str()) {
414 return Err(format!("SESSION_EXISTS {}", spec.id));
415 }
416 let mut runtime = (self.runtime_factory)();
417 runtime.configure_test_runner(&self.test_runner)?;
418 runtime.configure_execution_backend(&self.execution_backend)?;
419 #[cfg(not(target_arch = "wasm32"))]
420 if let Some(source_catalog) = &self.source_catalog {
421 runtime.register_source_catalog(source_catalog);
422 }
423 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
424 if let Some(source_cache) = &self.native_source_cache {
425 runtime.set_direct_native_source_cache(source_cache.clone());
426 }
427 for (resource, source) in &self.development_resources.entries {
428 runtime.register_resource(resource, source);
429 }
430 self.session_registry
431 .entries
432 .insert(spec.id.as_str().into(), Session::open(spec, runtime));
433 Ok(())
434 }
435
436 pub fn session_names(&self) -> Vec<SessionId> {
437 let mut names = self
438 .session_registry
439 .entries
440 .values()
441 .map(|session| session.id().clone())
442 .collect::<Vec<_>>();
443 names.sort();
444 names
445 }
446
447 pub fn session(&self, id: &SessionId) -> Result<&Session, String> {
448 self.session_registry
449 .entries
450 .get(id.as_str())
451 .ok_or_else(|| format!("NO_SESSION {id}"))
452 }
453
454 pub fn session_mut(&mut self, id: &SessionId) -> Result<&mut Session, String> {
455 self.session_registry
456 .entries
457 .get_mut(id.as_str())
458 .ok_or_else(|| format!("NO_SESSION {id}"))
459 }
460
461 pub fn session_namespace(&self, id: &SessionId) -> Result<String, String> {
462 self.session_registry
463 .entries
464 .get(id.as_str())
465 .map(Session::current_namespace)
466 .ok_or_else(|| format!("NO_SESSION {id}"))
467 }
468
469 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
470 pub fn native_execution_telemetry(
471 &self,
472 id: &SessionId,
473 ) -> Result<crate::direct_native::NativeExecutionTelemetry, String> {
474 self.session_registry
475 .entries
476 .get(id.as_str())
477 .ok_or_else(|| format!("NO_SESSION {id}"))?
478 .native_execution_telemetry()
479 }
480
481 pub fn eval(&mut self, id: &SessionId, source: &str) -> Result<String, String> {
482 self.session_registry
483 .entries
484 .get_mut(id.as_str())
485 .ok_or_else(|| format!("NO_SESSION {id}"))?
486 .eval(source)
487 }
488
489 pub fn register_resource(&mut self, name: &str, source: &str) {
490 self.development_resources
491 .entries
492 .insert(name.into(), source.into());
493 for session in self.session_registry.entries.values_mut() {
494 session
495 .runtime_mut()
496 .expect("kernel cannot retain a closed session")
497 .register_resource(name, source);
498 }
499 }
500
501 pub fn remove_resource(&mut self, name: &str) -> bool {
502 self.development_resources.entries.remove(name).is_some()
503 }
504
505 pub fn resource_names(&self) -> Vec<String> {
506 let mut names = self
507 .development_resources
508 .entries
509 .keys()
510 .cloned()
511 .collect::<Vec<_>>();
512 names.sort();
513 names
514 }
515
516 pub fn register_bundle(&mut self, digest: &str, bytes: &[u8]) -> Result<(), String> {
517 match self.bundle_catalog.entries.get(digest) {
518 Some(current) if current == bytes => Ok(()),
519 Some(_) => Err(format!("BUNDLE_DIGEST_CONFLICT {digest}")),
520 None => {
521 self.bundle_catalog
522 .entries
523 .insert(digest.into(), bytes.into());
524 Ok(())
525 }
526 }
527 }
528
529 pub fn bundle(&self, digest: &str) -> Option<&[u8]> {
530 self.bundle_catalog.entries.get(digest).map(Vec::as_slice)
531 }
532
533 pub fn create_memory_filesystem(&mut self, root: &str) -> SessionMountId {
534 self.create_filesystem(Rc::new(core::MemoryFileProvider::new(root)), "memory", root)
535 }
536
537 #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
538 pub fn create_native_filesystem(&mut self, root: &str) -> SessionMountId {
539 self.create_filesystem(Rc::new(core::NativeFileProvider::new(root)), "native", root)
540 }
541
542 fn create_filesystem(
543 &mut self,
544 provider: Rc<dyn core::FileProvider>,
545 kind: &'static str,
546 key: &str,
547 ) -> SessionMountId {
548 let id = self.mount_registry.next_id;
549 self.mount_registry.next_id = self
550 .mount_registry
551 .next_id
552 .checked_add(1)
553 .expect("filesystem mount identifiers exhausted");
554 self.mount_registry.entries.insert(
555 id,
556 FilesystemMount {
557 provider,
558 kind,
559 key: key.into(),
560 attachments: 0,
561 },
562 );
563 SessionMountId::new(id)
564 }
565
566 pub fn attach_filesystem(
567 &mut self,
568 session: &SessionId,
569 mount_id: SessionMountId,
570 ) -> Result<(), String> {
571 if !self.session_registry.entries.contains_key(session.as_str()) {
572 return Err(format!("NO_SESSION {session}"));
573 }
574 let provider = self
575 .mount_registry
576 .entries
577 .get(&mount_id.get())
578 .ok_or_else(|| format!("NO_FILESYSTEM {mount_id}"))?
579 .provider
580 .clone();
581 if self
582 .mount_registry
583 .session_attachments
584 .get(session.as_str())
585 == Some(&mount_id.get())
586 {
587 return Ok(());
588 }
589 self.detach_filesystem(session)?;
590 self.mount_registry
591 .entries
592 .get_mut(&mount_id.get())
593 .unwrap()
594 .attachments += 1;
595 self.mount_registry
596 .session_attachments
597 .insert(session.to_string(), mount_id.get());
598 let session = self
599 .session_registry
600 .entries
601 .get_mut(session.as_str())
602 .unwrap();
603 session
604 .runtime_mut()?
605 .providers
606 .set_file(Some(provider.clone()));
607 session.filesystem = Some(AttachedFilesystem {
608 id: mount_id,
609 _provider: provider,
610 });
611 Ok(())
612 }
613
614 pub fn detach_filesystem(&mut self, session: &SessionId) -> Result<(), String> {
615 let session = self
616 .session_registry
617 .entries
618 .get_mut(session.as_str())
619 .ok_or_else(|| format!("NO_SESSION {session}"))?;
620 session.runtime_mut()?.providers.set_file(None);
621 session.filesystem.take();
622 if let Some(mount_id) = self
623 .mount_registry
624 .session_attachments
625 .remove(session.id().as_str())
626 {
627 if let Some(mount) = self.mount_registry.entries.get_mut(&mount_id) {
628 mount.attachments = mount.attachments.saturating_sub(1);
629 }
630 }
631 Ok(())
632 }
633
634 pub fn filesystem(&self, session: &SessionId) -> Option<SessionMountId> {
635 self.session_registry
636 .entries
637 .get(session.as_str())
638 .and_then(Session::filesystem_mount)
639 }
640
641 pub fn filesystem_info(&self, mount_id: SessionMountId) -> Result<(&str, &str, usize), String> {
642 self.mount_registry
643 .entries
644 .get(&mount_id.get())
645 .map(|mount| (mount.kind, mount.key.as_str(), mount.attachments))
646 .ok_or_else(|| format!("NO_FILESYSTEM {mount_id}"))
647 }
648
649 pub fn close_filesystem(&mut self, mount_id: SessionMountId) -> Result<(), String> {
650 let mount = self
651 .mount_registry
652 .entries
653 .get(&mount_id.get())
654 .ok_or_else(|| format!("NO_FILESYSTEM {mount_id}"))?;
655 if mount.attachments != 0 {
656 return Err(format!("FILESYSTEM_ATTACHED {mount_id}"));
657 }
658 self.mount_registry.entries.remove(&mount_id.get());
659 Ok(())
660 }
661
662 pub fn close_session(&mut self, id: &SessionId) -> Result<(), String> {
663 if id.as_str() == "ROOT" {
664 return Err("ROOT_CANNOT_CLOSE".into());
665 }
666 if !self.session_registry.entries.contains_key(id.as_str()) {
667 return Err(format!("NO_SESSION {id}"));
668 }
669 self.detach_filesystem(id)?;
670 if let Some(mut session) = self.session_registry.entries.remove(id.as_str()) {
671 crate::lang::protocol::IComponent::stop(&mut session);
672 }
673 Ok(())
674 }
675}
676
677fn validate_test_runner(runner: &str) -> Result<(), String> {
678 if matches!(runner, "code.test" | "native") {
679 Ok(())
680 } else {
681 Err("runtime test runner must be code.test or native".into())
682 }
683}
684
685fn reject_legacy_iterator_calls(form: &Form) -> Result<(), String> {
690 const LEGACY: &[&str] = &[
691 "iter-has?",
692 "iter-finite?",
693 "iter-materialize",
694 "iter-close",
695 "iter-map",
696 "iter-filter",
697 "iter-take-while",
698 "iter-drop-while",
699 "iter-mapcat",
700 "iter-keep",
701 "iter-interpose",
702 "iter-interleave",
703 "iter-every?",
704 "iter-any?",
705 "iter-take",
706 "iter-drop",
707 "iter-zip",
708 "iter-cycle",
709 "iter-partition-pair",
710 "iter-partition-all",
711 "iter-partition",
712 "iter-range",
713 "iter-constantly",
714 "iter-repeatedly",
715 "iter-iterate",
716 ];
717 match form {
718 Form::List(values) => {
719 if let Some(Form::Symbol(name)) = values.first() {
720 if LEGACY.contains(&name.as_str()) {
721 return Err(format!("unbound symbol: {name}"));
722 }
723 if name == "quote" {
724 return Ok(());
725 }
726 }
727 for value in values {
728 reject_legacy_iterator_calls(value)?;
729 }
730 }
731 Form::Vector(values) | Form::Set(values) => {
732 for value in values {
733 reject_legacy_iterator_calls(value)?;
734 }
735 }
736 Form::Map(entries) => {
737 for (key, value) in entries {
738 reject_legacy_iterator_calls(key)?;
739 reject_legacy_iterator_calls(value)?;
740 }
741 }
742 Form::Tagged(_, value) | Form::Metadata(_, value) => reject_legacy_iterator_calls(value)?,
743 _ => {}
744 }
745 Ok(())
746}
747
748#[cfg(test)]
749mod authority_tests {
750 use super::*;
751
752 fn session_id(name: &str) -> SessionId {
753 SessionId::parse(name).unwrap()
754 }
755
756 #[test]
757 fn named_sessions_start_with_zero_host_authority() {
758 let mut kernel = SessionKernel::new();
759 let root = session_id("ROOT");
760 #[cfg(not(target_arch = "wasm32"))]
761 {
762 kernel
763 .session_mut(&root)
764 .unwrap()
765 .install_native_socket_provider();
766 kernel
767 .session_mut(&root)
768 .unwrap()
769 .install_native_process_provider();
770 assert_eq!(
771 kernel.session(&root).unwrap().authority().profile(),
772 "explicit"
773 );
774 }
775
776 let child_id = session_id("child");
777 kernel.create_session(child_id.clone()).unwrap();
778 let child = kernel.session(&child_id).unwrap();
779 assert_eq!(child.authority(), SessionAuthorityPolicy::ZERO);
780 assert_eq!(
781 crate::lang::protocol::IComponent::props(child)
782 .authority
783 .profile(),
784 "zero"
785 );
786
787 for capability in ["filesystem", "network/socket", "process"] {
788 let error = kernel
789 .eval(
790 &child_id,
791 &format!(
792 "(std.protocol.ideref.IDeref/deref (std.native.Host/capability? \"{capability}\"))"
793 ),
794 )
795 .unwrap_err();
796 assert!(error.contains("std.native.Host/capability? requires capability :host-call"));
797 assert!(error.contains(":native/capability-denied"));
798 }
799 }
800
801 #[test]
802 fn session_status_uses_typed_identity_state_and_mount() {
803 use crate::lang::protocol::IComponent;
804
805 let mut kernel = SessionKernel::new();
806 let typed = session_id("typed");
807 kernel.create_session(typed.clone()).unwrap();
808 let initial = kernel.session(&typed).unwrap().props();
809 assert_eq!(initial.name.as_str(), "typed");
810 assert_eq!(initial.state, SessionState::Active);
811 assert_eq!(initial.filesystem, None);
812
813 let mount = kernel.create_memory_filesystem("/");
814 kernel.attach_filesystem(&typed, mount).unwrap();
815 let mounted = kernel.session(&typed).unwrap().props();
816 assert_eq!(mounted.filesystem, Some(mount));
817 assert_eq!(kernel.session(&typed).unwrap().spec().id, mounted.name);
818 }
819
820 #[test]
821 fn scoped_filesystem_mount_does_not_change_host_authority_profile() {
822 let mut kernel = SessionKernel::new();
823 let mounted = session_id("mounted");
824 kernel.create_session(mounted.clone()).unwrap();
825 let mount = kernel.create_memory_filesystem("/");
826 kernel.attach_filesystem(&mounted, mount).unwrap();
827 assert_eq!(
828 kernel.session(&mounted).unwrap().authority(),
829 SessionAuthorityPolicy::ZERO
830 );
831 assert_eq!(kernel.filesystem(&mounted), Some(mount));
832 }
833
834 #[test]
835 fn closing_releases_session_owned_runtime_and_filesystem_once() {
836 use crate::lang::protocol::IComponent;
837
838 let mut kernel = SessionKernel::new();
839 let child = session_id("owned");
840 kernel.create_session(child.clone()).unwrap();
841 let mount = kernel.create_memory_filesystem("/");
842 assert_eq!(
843 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
844 1
845 );
846
847 kernel.attach_filesystem(&child, mount).unwrap();
848 assert_eq!(
849 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
850 3
851 );
852
853 let mut session = kernel
854 .session_registry
855 .entries
856 .remove(child.as_str())
857 .unwrap();
858 let released_mount = session.release();
859 assert_eq!(released_mount, Some(mount));
860 assert_eq!(session.state(), SessionState::Closed);
861 assert!(session.runtime.is_none());
862 assert!(session.filesystem.is_none());
863 assert_eq!(
864 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
865 1
866 );
867
868 assert_eq!(session.release(), None);
869 session.stop();
870 assert_eq!(
871 Rc::strong_count(&kernel.mount_registry.entries[&mount.get()].provider),
872 1
873 );
874 }
875
876 #[test]
877 fn development_resources_and_sealed_bundles_use_distinct_catalogs() {
878 let mut kernel = SessionKernel::new();
879 kernel.register_resource("demo/value.hal", "(ns demo.value) (def value 42)");
880 assert_eq!(kernel.resource_names(), vec!["demo/value.hal"]);
881
882 kernel.register_bundle("sha256:demo", b"sealed").unwrap();
883 kernel.register_bundle("sha256:demo", b"sealed").unwrap();
884 assert_eq!(kernel.bundle("sha256:demo"), Some(b"sealed".as_slice()));
885 assert_eq!(
886 kernel
887 .register_bundle("sha256:demo", b"replacement")
888 .unwrap_err(),
889 "BUNDLE_DIGEST_CONFLICT sha256:demo"
890 );
891
892 assert!(kernel.remove_resource("demo/value.hal"));
893 assert!(kernel.resource_names().is_empty());
894 assert_eq!(kernel.bundle("sha256:demo"), Some(b"sealed".as_slice()));
895 }
896}