1use std::sync::Arc;
8use std::time::Instant;
9
10use serde::{Deserialize, Serialize};
11use tracing::{error, info, warn};
12
13use clawft_core::bootstrap::AppContext;
14use clawft_core::bus::MessageBus;
15use clawft_platform::Platform;
16use clawft_types::config::Config;
17
18#[cfg(feature = "native")]
19use crate::a2a::A2ARouter;
20use crate::app::AppManager;
21use crate::capability::AgentCapabilities;
22#[cfg(feature = "native")]
23use crate::capability::CapabilityChecker;
24use crate::cluster::{ClusterConfig, ClusterMembership};
25use crate::console::{BootEvent, BootLog, BootPhase, KernelEventLog};
26use crate::error::{KernelError, KernelResult};
27use crate::health::HealthSystem;
28use crate::ipc::KernelIpc;
29use crate::process::{ProcessEntry, ProcessState, ProcessTable, ResourceUsage};
30use crate::service::ServiceRegistry;
31use crate::supervisor::AgentSupervisor;
32#[cfg(feature = "native")]
33use crate::topic::TopicRouter;
34use clawft_types::config::KernelConfig;
35
36#[cfg(feature = "ecc")]
40const DISKANN_FEATURE_MISMATCH_WARNING: &str = "vector.backend requires the \
41 `diskann` cargo feature (DiskAnn or Hybrid) but this kernel binary was \
42 built WITHOUT it — DiskAnnBackend is silently degrading to a \
43 brute-force O(n) linear-scan stub instead of real Vamana-graph ANN \
44 search. Rebuild with `--features diskann` for the real backend, or set \
45 `vector.strict = true` in kernel config to turn this into a boot error \
46 instead of a silent degrade.";
47
48#[cfg(feature = "ecc")]
51const DISKANN_FEATURE_MISMATCH_ERROR: &str = "vector.backend requires the \
52 `diskann` cargo feature (DiskAnn or Hybrid) but this kernel binary was \
53 built WITHOUT it, and `vector.strict = true` refuses to silently fall \
54 back to the brute-force O(n) stub. Rebuild with `--features diskann`, \
55 or set `vector.strict = false` to accept the degraded stub.";
56
57#[non_exhaustive]
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub enum KernelState {
61 Booting,
63 Running,
65 ShuttingDown,
67 Halted,
69}
70
71impl std::fmt::Display for KernelState {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 KernelState::Booting => write!(f, "booting"),
75 KernelState::Running => write!(f, "running"),
76 KernelState::ShuttingDown => write!(f, "shutting_down"),
77 KernelState::Halted => write!(f, "halted"),
78 }
79 }
80}
81
82#[cfg(feature = "exochain")]
91pub struct ChainSubsystem {
92 pub(crate) chain_manager: Option<Arc<crate::chain::ChainManager>>,
93 pub(crate) tree_manager: Option<Arc<crate::tree_manager::TreeManager>>,
94 pub(crate) governance_gate: Option<Arc<dyn crate::gate::GateBackend>>,
95 pub(crate) chain_anchor: Option<Arc<crate::chain_anchor::AnchoringController>>,
97 pub(crate) overlay: Option<Arc<crate::overlay_runtime::OverlayRuntime>>,
100 pub(crate) _chain_lock: Option<crate::chain_storage::ChainLock>,
102}
103
104#[cfg(feature = "ecc")]
107pub struct EccSubsystem {
108 pub(crate) hnsw: Option<Arc<crate::hnsw_service::HnswService>>,
109 pub(crate) causal: Option<Arc<crate::causal::CausalGraph>>,
110 pub(crate) tick: Option<Arc<crate::cognitive_tick::CognitiveTick>>,
111 pub(crate) crossrefs: Option<Arc<crate::crossref::CrossRefStore>>,
112 pub(crate) impulses: Option<Arc<crate::impulse::ImpulseQueue>>,
113 pub(crate) calibration: Option<crate::calibration::EccCalibration>,
114 pub(crate) vector_backend: Option<Arc<dyn crate::vector_backend::VectorBackend>>,
115 pub(crate) eml_coherence:
116 Option<Arc<std::sync::Mutex<crate::eml_coherence::EmlCoherenceModel>>>,
117 pub(crate) spatial: Option<Arc<crate::spatial_service::SpatialService>>,
120}
121
122#[cfg(feature = "os-patterns")]
125pub struct ObservabilitySubsystem {
126 pub(crate) metrics_registry: Option<Arc<crate::metrics::MetricsRegistry>>,
127 pub(crate) log_service: Option<Arc<crate::log_service::LogService>>,
128 pub(crate) timer_service: Option<Arc<crate::timer::TimerService>>,
129 pub(crate) dead_letter_queue: Option<Arc<crate::dead_letter::DeadLetterQueue>>,
130}
131
132enum NodeBinding {
134 Key(Option<[u8; 32]>),
136 Service(String),
138}
139
140pub struct Kernel<P: Platform> {
152 state: KernelState,
153 config: KernelConfig,
154 app_context: Option<AppContext<P>>,
155 bus: Arc<MessageBus>,
156 process_table: Arc<ProcessTable>,
157 service_registry: Arc<ServiceRegistry>,
158 ipc: Arc<KernelIpc>,
159 #[cfg(feature = "native")]
160 a2a_router: Arc<A2ARouter>,
161 #[cfg(feature = "native")]
162 cron_service: Arc<crate::cron::CronService>,
163 #[cfg(feature = "native")]
164 assessment_service: Arc<crate::assessment::AssessmentService>,
165 health: HealthSystem,
166 supervisor: AgentSupervisor<P>,
167 boot_log: BootLog,
168 event_log: Arc<KernelEventLog>,
169 boot_time: Instant,
170 cluster_membership: Arc<ClusterMembership>,
171 app_manager: Arc<AppManager>,
173 revocation_list: Arc<crate::revocation::RevocationList>,
174 #[cfg(feature = "native")]
175 agent_registry: crate::agent_registry::AgentRegistry,
176 #[cfg(feature = "native")]
177 node_registry: crate::node_registry::NodeRegistry,
178 #[cfg(feature = "native")]
179 substrate_service: crate::substrate_service::SubstrateService,
180 #[cfg(feature = "exochain")]
181 chain: ChainSubsystem,
182 #[cfg(feature = "ecc")]
183 ecc: EccSubsystem,
184 #[cfg(feature = "os-patterns")]
185 observability: ObservabilitySubsystem,
186}
187
188impl<P: Platform> Kernel<P> {
189 pub async fn boot(
203 config: Config,
204 kernel_config: KernelConfig,
205 platform: Arc<P>,
206 ) -> KernelResult<Self> {
207 Self::boot_with_node_key(config, kernel_config, platform, None).await
208 }
209
210 pub async fn boot_with_node_key(
224 config: Config,
225 kernel_config: KernelConfig,
226 platform: Arc<P>,
227 node_key_seed: Option<[u8; 32]>,
228 ) -> KernelResult<Self> {
229 Self::boot_bound(config, kernel_config, platform, NodeBinding::Key(node_key_seed)).await
230 }
231
232 pub async fn boot_in_service_mode(
243 config: Config,
244 kernel_config: KernelConfig,
245 platform: Arc<P>,
246 service_node_id: String,
247 ) -> KernelResult<Self> {
248 if !crate::node_id::is_node_id(&service_node_id) {
249 return Err(KernelError::Boot(format!(
250 "service mode needs the mesh service's node id, got {service_node_id:?}"
251 )));
252 }
253 Self::boot_bound(
254 config,
255 kernel_config,
256 platform,
257 NodeBinding::Service(service_node_id),
258 )
259 .await
260 }
261
262 async fn boot_bound(
263 config: Config,
264 mut kernel_config: KernelConfig,
265 platform: Arc<P>,
266 binding: NodeBinding,
267 ) -> KernelResult<Self> {
268 let (node_key_seed, service_node_id) = match binding {
269 NodeBinding::Key(seed) => (seed, None),
270 NodeBinding::Service(id) => (None, Some(id)),
271 };
272 #[cfg(feature = "mesh")]
276 if service_node_id.is_none()
277 && let Some(m) = kernel_config.mesh.as_ref()
278 && m.enabled
279 && m.service == clawft_types::config::MeshServicePolicy::Required
280 {
281 return Err(KernelError::Boot(
282 "kernel.mesh.service = \"required\" but this boot did not link a machine mesh \
283 service (start the user daemon with the service running, or set \
284 kernel.mesh.service = \"auto\")"
285 .into(),
286 ));
287 }
288 let boot_time = Instant::now();
289 let mut boot_log = BootLog::new();
290 let pin = crate::chain_storage::pin_chain_storage_noted(&mut kernel_config);
294 if let Some(msg) = pin.refusal {
297 return Err(if pin.refusal_transient {
300 KernelError::Boot(msg)
301 } else {
302 KernelError::BootRefused(msg)
303 });
304 }
305 let (pinned_chain, chain_note) = (pin.path, pin.warning);
306 #[cfg(feature = "exochain")]
309 let chain_lock = match pinned_chain.as_deref() {
310 Some(ckpt) => Some(
311 crate::chain_storage::ChainLock::try_acquire(ckpt).map_err(|e| match e {
312 crate::chain_storage::ChainLockError::InUse(m) => KernelError::BootRefused(m),
313 crate::chain_storage::ChainLockError::Other(m) => KernelError::Boot(m),
314 })?,
315 ),
316 None => None,
317 };
318
319 info!("WeftOS kernel booting");
320 boot_log.push(BootEvent::info(
321 BootPhase::Init,
322 format!("WeftOS v{} booting...", env!("CARGO_PKG_VERSION")),
323 ));
324 if let Some(ref p) = pinned_chain {
325 boot_log.push(BootEvent::info(
326 BootPhase::Init,
327 format!("Chain storage: {}", p.display()),
328 ));
329 }
330 let runtime_paths = crate::chain_storage::boot_runtime_paths(pinned_chain.as_deref());
333 boot_log.push(BootEvent::info(
334 BootPhase::Init,
335 format!("Runtime dir: {}", runtime_paths.root().display()),
336 ));
337 let project_profile = kernel_config.profile.is_some();
340 #[cfg(feature = "exochain")]
341 let mut overlay_prepared = if project_profile {
342 let p = crate::overlay_runtime::prepare(&runtime_paths)
343 .map_err(|e| KernelError::BootRefused(e.boot_message()))?;
344 p.apply_limits(&mut kernel_config);
345 Some(p)
346 } else {
347 let p = crate::overlay_runtime::prepare_nested()
348 .map_err(|e| KernelError::BootRefused(e.boot_message()))?;
349 if let Some(p) = &p { p.apply_limits(&mut kernel_config); }
350 p
351 };
352 #[cfg(not(feature = "exochain"))]
353 if project_profile {
354 return Err(KernelError::BootRefused(
355 "profile `project` needs the exochain feature (governance overlay)".into(),
356 ));
357 }
358 #[cfg(feature = "exochain")]
359 let mut overlay_runtime: Option<Arc<crate::overlay_runtime::OverlayRuntime>> = None;
360 if let Some(w) = chain_note {
361 warn!("{w}");
362 boot_log.push(BootEvent::warn(BootPhase::Init, w));
363 }
364 boot_log.push(BootEvent::info(BootPhase::Init, "PID 0 (kernel)"));
365 if let Some(msg) = clawft_plugin::sandbox::sandbox_platform_banner_warning() {
368 warn!(%msg, "sandbox platform restriction");
369 boot_log.push(BootEvent::warn(BootPhase::Init, msg));
370 }
371
372 let process_table = Arc::new(ProcessTable::new(kernel_config.max_processes));
374 let service_registry = Arc::new(ServiceRegistry::new());
375
376 boot_log.push(BootEvent::info(
377 BootPhase::Config,
378 format!("Max processes: {}", kernel_config.max_processes),
379 ));
380 boot_log.push(BootEvent::info(
381 BootPhase::Config,
382 format!(
383 "Health check interval: {}s",
384 kernel_config.health_check_interval_secs
385 ),
386 ));
387
388 let app_context = AppContext::new(config, platform)
390 .await
391 .map_err(|e| KernelError::Boot(format!("AppContext init failed: {e}")))?;
392
393 let bus = app_context.bus().clone();
395 let ipc = Arc::new(KernelIpc::new(bus.clone()));
396
397 let health = HealthSystem::new(kernel_config.health_check_interval_secs);
399
400 let kernel_entry = ProcessEntry {
402 pid: 0,
403 agent_id: "kernel".to_owned(),
404 state: ProcessState::Running,
405 capabilities: AgentCapabilities::default(),
406 resource_usage: ResourceUsage::default(),
407 cancel_token: crate::process::CancellationToken::new(),
408 parent_pid: None,
409 };
410 process_table
411 .insert_with_pid(kernel_entry)
412 .map_err(|e| KernelError::Boot(format!("failed to register kernel process: {e}")))?;
413
414 boot_log.push(BootEvent::info(
415 BootPhase::Services,
416 "Service registry ready",
417 ));
418
419 #[cfg(feature = "native")]
421 let capability_checker = Arc::new(CapabilityChecker::new(process_table.clone()));
422 #[cfg(feature = "native")]
423 let topic_router = Arc::new(TopicRouter::new(process_table.clone()));
424 #[cfg(feature = "native")]
425 let a2a_router = Arc::new(A2ARouter::new(
426 process_table.clone(),
427 capability_checker,
428 topic_router,
429 ));
430
431 #[cfg(feature = "native")]
432 boot_log.push(BootEvent::info(BootPhase::Services, "A2A router ready"));
433
434 #[cfg(feature = "native")]
436 let cron_svc = Arc::new(crate::cron::CronService::new());
437 #[cfg(feature = "native")]
438 if let Err(e) = service_registry.register(cron_svc.clone()) {
439 error!(error = %e, "failed to register cron service");
440 } else {
441 boot_log.push(BootEvent::info(
442 BootPhase::Services,
443 "Cron service registered",
444 ));
445 }
446
447 let node_id: String = if let Some(id) = service_node_id.clone() {
451 id
454 } else {
455 #[cfg(any(feature = "mesh", feature = "exochain"))]
456 {
457 let identity = match node_key_seed {
458 Some(seed) => crate::cluster::NodeIdentity::from_signing_key(
459 ed25519_dalek::SigningKey::from_bytes(&seed),
460 ),
461 None => {
462 warn!("no persisted node key supplied; using an ephemeral node identity");
463 crate::cluster::NodeIdentity::generate()
464 }
465 };
466 identity.node_id().to_owned()
467 }
468 #[cfg(not(any(feature = "mesh", feature = "exochain")))]
469 {
470 let seed = node_key_seed.unwrap_or_else(|| {
474 let mut b = [0u8; 32];
475 b[..16].copy_from_slice(uuid::Uuid::new_v4().as_bytes());
476 b[16..].copy_from_slice(uuid::Uuid::new_v4().as_bytes());
477 b
478 });
479 crate::node_id::node_id_from_pubkey(&seed)
480 }
481 };
482
483 #[cfg(all(feature = "native", feature = "mesh"))]
490 let mesh_config_early = kernel_config.mesh.clone().unwrap_or_default();
491 #[cfg(all(feature = "native", feature = "mesh"))]
492 let mesh_node_id: Option<String> = if mesh_config_early.enabled {
493 Some(node_id.clone())
494 } else {
495 None
496 };
497 #[cfg(all(feature = "native", feature = "mesh"))]
498 let mesh_project_name = kernel_config
499 .cluster
500 .as_ref()
501 .and_then(|c| c.node_name.clone())
502 .unwrap_or_else(|| "local".into());
503 #[cfg(all(feature = "native", feature = "mesh"))]
504 let assess_mesh_coord: Option<Arc<crate::assessment::mesh::MeshCoordinator>> =
505 mesh_node_id.as_ref().map(|id| {
506 Arc::new(crate::assessment::mesh::MeshCoordinator::new(
507 id.clone(),
508 mesh_project_name.clone(),
509 ))
510 });
511
512 #[cfg(feature = "native")]
514 let assessment_svc = {
515 #[cfg(feature = "mesh")]
516 {
517 if let Some(ref coord) = assess_mesh_coord {
518 Arc::new(crate::assessment::AssessmentService::with_mesh_coordinator(
519 Arc::clone(coord),
520 ))
521 } else {
522 Arc::new(crate::assessment::AssessmentService::new())
523 }
524 }
525 #[cfg(not(feature = "mesh"))]
526 {
527 Arc::new(crate::assessment::AssessmentService::new())
528 }
529 };
530 #[cfg(feature = "native")]
531 if let Err(e) = service_registry.register(assessment_svc.clone()) {
532 error!(error = %e, "failed to register assessment service");
533 } else {
534 let mesh_note = {
535 #[cfg(feature = "mesh")]
536 {
537 if assess_mesh_coord.is_some() {
538 " (mesh coordination enabled)"
539 } else {
540 ""
541 }
542 }
543 #[cfg(not(feature = "mesh"))]
544 {
545 ""
546 }
547 };
548 boot_log.push(BootEvent::info(
549 BootPhase::Services,
550 format!("Assessment service registered{mesh_note}"),
551 ));
552 }
553
554 let container_manager = std::sync::Arc::new(crate::container::ContainerManager::new(
556 crate::container::ContainerConfig::default(),
557 ));
558 let container_service = std::sync::Arc::new(crate::container::ContainerService::new(
559 container_manager.clone(),
560 ));
561 if let Err(e) = service_registry.register(container_service) {
562 error!(error = %e, "failed to register container service");
563 } else {
564 boot_log.push(BootEvent::info(
565 BootPhase::Services,
566 "Container service registered",
567 ));
568 }
569
570 let revocation_list = Arc::new(crate::revocation::RevocationList::load(
573 runtime_paths.revoked_hosts(),
574 ));
575
576 #[cfg(all(feature = "native", feature = "mesh", feature = "exochain"))]
579 let mesh_verdicts = Arc::new(crate::mesh_admit::GateVerdictSource::late());
580 #[cfg(all(feature = "native", feature = "mesh", feature = "exochain"))]
581 let mesh_closed_without_gate = mesh_config_early.admission
582 == clawft_types::config::MeshAdmissionMode::Enforce
583 && !mesh_config_early.admission_open_membership;
584
585 #[cfg(all(feature = "native", feature = "mesh"))]
589 let mesh_runtime = {
590 let mesh_config = mesh_config_early;
591 if service_node_id.is_none()
592 && crate::mesh_mode::select(&mesh_config) == crate::mesh_mode::MeshMode::Collapsed
593 {
594 let node_id = mesh_node_id
595 .clone()
596 .expect("mesh enabled implies mesh_node_id");
597 let mut runtime = if mesh_config.discovery {
598 let kad_id = blake3::hash(node_id.as_bytes());
599 crate::mesh_runtime::MeshRuntime::with_discovery(
600 node_id.clone(),
601 *kad_id.as_bytes(),
602 )
603 } else {
604 crate::mesh_runtime::MeshRuntime::new(node_id.clone())
605 };
606
607 runtime.set_local_router(Arc::clone(&a2a_router));
609
610 let runtime = Arc::new(runtime);
611 if let Some(seed) = node_key_seed {
615 let leaf_root = runtime_paths.root().join("leaf");
616 if leaf_root.join("registry").is_dir() {
617 let ingress = crate::mesh_leaf::LeafIngress::open(
618 &leaf_root,
619 ed25519_dalek::SigningKey::from_bytes(&seed),
620 ).map_err(|e| KernelError::Boot(format!("leaf ingress: {e}")))?;
621 runtime.set_leaf_ingress(Arc::new(ingress)).map_err(|_|
622 KernelError::Boot("leaf ingress already installed".into()))?;
623 }
624 }
625
626 if let Some(ref coord) = assess_mesh_coord {
630 let assess_transport =
631 Arc::new(crate::mesh_assess::AssessmentTransport::new(Arc::clone(
632 coord,
633 )));
634 runtime.set_assessment_transport(Arc::clone(&assess_transport));
635
636 let rt_tick = Arc::clone(&runtime);
639 let assess_for_tick = Arc::clone(&assessment_svc);
640 let transport_for_tick = Arc::clone(&assess_transport);
641 tokio::spawn(async move {
642 let mut interval =
643 tokio::time::interval(std::time::Duration::from_secs(15));
644 loop {
645 interval.tick().await;
646 if let Some(report) = assess_for_tick.get_latest() {
649 transport_for_tick.publish_report(report);
650 }
651 let pushed = rt_tick.push_pending_assessment().await;
653 if pushed == 0 {
654 let _ = rt_tick.assessment_gossip_tick().await;
655 }
656 }
657 });
658
659 boot_log.push(BootEvent::info(
660 BootPhase::Network,
661 "AssessmentTransport wired into mesh event loop",
662 ));
663 }
664
665 a2a_router.set_mesh_runtime(Arc::clone(&runtime));
676 runtime.set_enforcing(
677 mesh_config.admission == clawft_types::config::MeshAdmissionMode::Enforce,
678 );
679
680 let seed_peers = mesh_config.seed_peers.clone();
682 let transport_name_for_seeds = mesh_config.transport.clone();
683 let transport_display = mesh_config.transport.clone();
684 let listen_display = mesh_config.listen_addr.clone();
685 let noise_enabled = mesh_config.noise;
686
687 let noise_config = if noise_enabled {
689 let private_key: [u8; 32] = if let Some(ref key_path) =
690 mesh_config.noise_key_path
691 {
692 let key_bytes = std::fs::read(key_path).unwrap_or_else(|_| {
693 tracing::warn!("noise key file not found, generating ephemeral key");
694 let mut key = [0u8; 32];
695 use rand::RngCore;
696 rand::thread_rng().fill_bytes(&mut key);
697 key.to_vec()
698 });
699 let mut arr = [0u8; 32];
700 arr.copy_from_slice(&key_bytes[..32.min(key_bytes.len())]);
701 arr
702 } else {
703 let mut key = [0u8; 32];
704 use rand::RngCore;
705 rand::thread_rng().fill_bytes(&mut key);
706 key
707 };
708 Some(Arc::new(crate::mesh_noise::NoiseConfig {
709 pattern: crate::mesh_noise::NoisePattern::XX,
710 local_private_key: private_key,
711 remote_static_key: None,
712 }))
713 } else {
714 None
715 };
716 let noise_for_seeds = noise_config.clone();
717
718 let listen_addr = mesh_config.listen_addr.clone();
721 let transport = crate::mesh_serve::transport_for(&mesh_config.transport, None);
722
723 let listener = transport.listen(&listen_addr).await.map_err(|e| {
727 KernelError::Boot(format!(
728 "mesh enabled but the listener could not bind {listen_addr} ({e}); \
729 another kernel on this machine is probably already using that port. \
730 Stop it, set kernel.mesh.listen_addr to a free address, or set \
731 kernel.mesh.enabled = false"
732 ))
733 })?;
734
735 let pinned = mesh_config
738 .genesis_hash
739 .as_deref()
740 .map(|h| {
741 crate::mesh_admit::hex_decode(h)
742 .and_then(|b| <[u8; 32]>::try_from(b).ok())
743 .ok_or_else(|| {
744 KernelError::Boot(
745 "kernel.mesh.genesis_hash must be 64 hex characters".into(),
746 )
747 })
748 })
749 .transpose()?;
750 let dial_identity: Option<Arc<crate::mesh_admit::DialIdentity>> =
751 match (mesh_config.admission, pinned, node_key_seed) {
752 (clawft_types::config::MeshAdmissionMode::Off, _, _) => None,
753 (_, Some(genesis), Some(seed)) => {
754 Some(Arc::new(crate::mesh_admit::DialIdentity {
755 key: ed25519_dalek::SigningKey::from_bytes(&seed),
756 genesis,
757 platform: std::env::consts::OS.to_owned(),
758 capabilities: vec![],
759 }))
760 }
761 _ => None,
762 };
763 let admission_gate: Arc<dyn crate::mesh_admit::AdmissionGate> = {
764 use clawft_types::config::MeshAdmissionMode as Adm;
765 match (mesh_config.admission, pinned) {
766 (Adm::Off, _) | (Adm::Observe, None) => {
767 Arc::new(crate::mesh_admit::AllowAll)
768 }
769 (Adm::Enforce, None) => {
770 return Err(KernelError::Boot(
771 "kernel.mesh.admission = \"enforce\" requires kernel.mesh.genesis_hash"
772 .into(),
773 ));
774 }
775 (mode, Some(genesis)) => {
776 #[cfg(feature = "exochain")]
777 let verdicts: Arc<dyn crate::mesh_admit::VerdictSource> =
778 mesh_verdicts.clone();
779 #[cfg(not(feature = "exochain"))]
780 let verdicts: Arc<dyn crate::mesh_admit::VerdictSource> = {
781 if mode == Adm::Enforce && !mesh_config.admission_open_membership {
782 return Err(KernelError::Boot(
783 "kernel.mesh.admission = \"enforce\" needs a governance \
784 gate; this build has none. Set \
785 kernel.mesh.admission_open_membership = true to admit \
786 any valid hello"
787 .into(),
788 ));
789 }
790 Arc::new(crate::mesh_admit::OpenVerdicts)
791 };
792 Arc::new(crate::mesh_admit::CryptoGate::new(
793 genesis,
794 Arc::clone(&revocation_list),
795 verdicts,
796 mode,
797 ))
798 }
799 }
800 };
801
802 let require_authenticated_seeds = crate::mesh_admit_gate::nested_peer_ceiling_active();
803 if let (Some(identity), Some(noise)) = (dial_identity.as_ref(), noise_config.as_ref()) {
804 let noise_static = crate::mesh_noise::noise_static_public(&noise.local_private_key)
805 .ok_or_else(|| KernelError::Boot("cannot derive mesh Noise key".into()))?;
806 runtime.set_authentication(crate::mesh_runtime::MeshAuthentication {
807 gate: Arc::clone(&admission_gate), identity: Arc::clone(identity),
808 noise_static, require_authenticated_seeds,
809 });
810 } else if require_authenticated_seeds {
811 return Err(KernelError::Boot("nested mesh requires reciprocal authenticated admission".into()));
812 }
813 let rt = Arc::clone(&runtime);
814 let conn_limits = crate::mesh_limits::Limits {
815 first_frame: std::time::Duration::from_secs(
816 mesh_config.first_frame_timeout_secs.max(1),
817 ),
818 per_ip: mesh_config.max_connections_per_ip.max(1),
819 ..crate::mesh_limits::Limits::default()
820 };
821 let listener_task = tokio::spawn(async move {
824 crate::mesh_serve::serve_listener_with(
825 rt,
826 listener,
827 noise_config,
828 transport.name(),
829 &listen_addr,
830 admission_gate,
831 conn_limits,
832 )
833 .await;
834 });
835
836 let mut mesh_tasks = crate::mesh_serve::connect_seeds(
838 &runtime,
839 &seed_peers,
840 &transport_name_for_seeds,
841 noise_for_seeds,
842 dial_identity,
843 );
844 mesh_tasks.push(listener_task);
845
846 let mesh_svc = Arc::new(crate::mesh_system_service::MeshService::with_endpoints(
851 Arc::clone(&runtime),
852 listen_display.clone(),
853 transport_display.clone(),
854 ));
855 mesh_svc.adopt_tasks(mesh_tasks);
856 if let Err(e) = service_registry.register(mesh_svc) {
857 error!(error = %e, "failed to register mesh service");
858 } else {
859 boot_log.push(BootEvent::info(
860 BootPhase::Network,
861 "Mesh service registered (SystemService lifecycle)",
862 ));
863 }
864
865 boot_log.push(BootEvent::info(
866 BootPhase::Network,
867 format!(
868 "Mesh transport started ({} on {}, {} seed peers)",
869 transport_display,
870 listen_display,
871 seed_peers.len(),
872 ),
873 ));
874
875 Some(runtime)
876 } else {
877 boot_log.push(BootEvent::info(
878 BootPhase::Network,
879 if service_node_id.is_some() {
880 "Mesh transport owned by the machine mesh service"
881 } else {
882 "Mesh transport disabled"
883 },
884 ));
885 None
886 }
887 };
888
889 let cluster_config = ClusterConfig {
891 node_id: node_id.clone(),
892 node_name: kernel_config
893 .cluster
894 .as_ref()
895 .and_then(|c| c.node_name.clone())
896 .unwrap_or_else(|| "local".into()),
897 heartbeat_interval_secs: kernel_config
898 .cluster
899 .as_ref()
900 .map(|c| c.heartbeat_interval_secs)
901 .unwrap_or(5),
902 ..ClusterConfig::default()
903 };
904 let cluster_peers_path = runtime_paths.cluster_peers();
906 let cluster_membership =
907 Arc::new(ClusterMembership::new(cluster_config).with_persist_path(&cluster_peers_path));
908
909 boot_log.push(BootEvent::info(
910 BootPhase::Network,
911 format!(
912 "Cluster membership ready (node {}, {} peer(s) rehydrated)",
913 cluster_membership.local_node_id(),
914 cluster_membership.len(),
915 ),
916 ));
917
918 let apps_persist_path =
921 runtime_paths.apps();
922 let app_manager = Arc::new(AppManager::new().with_persist_path(&apps_persist_path));
923 boot_log.push(BootEvent::info(
924 BootPhase::Services,
925 format!(
926 "AppManager ready ({} app(s) rehydrated from {})",
927 app_manager.len(),
928 apps_persist_path.display(),
929 ),
930 ));
931
932 {
935 let count = revocation_list.len();
936 if count > 0 {
937 boot_log.push(BootEvent::info(
938 BootPhase::Network,
939 format!("Host revocation list loaded ({count} banned hosts)"),
940 ));
941 } else {
942 boot_log.push(BootEvent::info(
943 BootPhase::Network,
944 "Host revocation list ready (empty)",
945 ));
946 }
947 }
948
949 #[cfg(feature = "cluster")]
953 {
954 use crate::cluster::ClusterService;
955 use ruvector_cluster::StaticDiscovery;
956
957 let net_config = kernel_config.cluster.clone().unwrap_or_default();
958 let seed_addrs: Vec<std::net::SocketAddr> = net_config
959 .seed_nodes
960 .iter()
961 .filter_map(|s| s.parse().ok())
962 .collect();
963 let seed_nodes: Vec<ruvector_cluster::ClusterNode> = seed_addrs
964 .into_iter()
965 .map(|addr| ruvector_cluster::ClusterNode::new(addr.to_string(), addr))
966 .collect();
967 let discovery = Box::new(StaticDiscovery::new(seed_nodes));
968 let node_id = cluster_membership.local_node_id().to_owned();
969
970 match ClusterService::new(
971 net_config,
972 node_id,
973 discovery,
974 Arc::clone(&cluster_membership),
975 ) {
976 Ok(cluster_svc) => {
977 let svc = Arc::new(cluster_svc);
978
979 #[cfg(all(feature = "native", feature = "mesh"))]
981 {
982 if let Some(ref mr) = mesh_runtime {
983 let rx = mr.subscribe_peer_events();
984 svc.spawn_mesh_peer_listener(rx);
985 boot_log.push(BootEvent::info(
986 BootPhase::Network,
987 "ClusterService subscribed to mesh peer-event stream",
988 ));
989 }
990 }
991
992 if let Err(e) = service_registry.register(svc) {
993 error!(error = %e, "failed to register cluster service");
994 } else {
995 boot_log.push(BootEvent::info(
996 BootPhase::Network,
997 "Cluster service registered (ruvector)",
998 ));
999 }
1000 }
1001 Err(e) => {
1002 error!(error = %e, "failed to create cluster service");
1003 boot_log.push(BootEvent::info(
1004 BootPhase::Network,
1005 format!("Cluster service failed: {e}"),
1006 ));
1007 }
1008 }
1009 }
1010
1011 #[cfg(all(feature = "native", feature = "mesh", not(feature = "cluster")))]
1014 {
1015 if let Some(ref mr) = mesh_runtime {
1016 let rx = mr.subscribe_peer_events();
1017 cluster_membership.spawn_mesh_peer_listener(rx);
1018 boot_log.push(BootEvent::info(
1019 BootPhase::Network,
1020 "ClusterMembership subscribed to mesh peer-event stream",
1021 ));
1022 }
1023 }
1024
1025 service_registry
1027 .start_all()
1028 .await
1029 .map_err(|e| KernelError::Boot(format!("service start failed: {e}")))?;
1030
1031 #[cfg(feature = "exochain")]
1034 let chain_manager = {
1035 let chain_config = kernel_config.chain.clone().unwrap_or_default();
1036 if chain_config.enabled {
1037 let signing_key = if project_profile {
1043 let child = crate::overlay_runtime::child_paths(&runtime_paths)
1044 .map_err(|e| KernelError::BootRefused(e.boot_message()))?;
1045 let path = child.project_key().ok_or_else(|| {
1046 KernelError::BootRefused("profile `project` has no project key path".into())
1047 })?;
1048 let key = crate::project_identity::project_chain_key(&path, node_key_seed)
1049 .map_err(|e| KernelError::BootRefused(e.to_string()))?;
1050 boot_log.push(BootEvent::info(
1051 BootPhase::Services,
1052 format!("Ed25519 signing key is the project key: {}", path.display()),
1053 ));
1054 Some(key)
1055 } else if let Some(ref ckpt_path) =
1056 chain_config.effective_checkpoint_path()
1057 {
1058 let key_path = clawft_types::config::chain_paths::chain_key_for_checkpoint(
1061 std::path::Path::new(ckpt_path),
1062 );
1063 #[cfg(unix)]
1067 if key_path.file_name().is_some_and(|n| n == "user.key") {
1068 use std::os::unix::fs::PermissionsExt;
1069 if let Ok(m) = std::fs::symlink_metadata(&key_path)
1070 && (!m.file_type().is_file() || m.permissions().mode() & 0o077 != 0)
1071 {
1072 return Err(KernelError::Boot(format!(
1073 "{} must be a regular file readable only by its owner (chmod 600); refusing to sign the chain with it",
1074 key_path.display()
1075 )));
1076 }
1077 }
1078 match crate::chain::ChainManager::load_or_create_key(&key_path) {
1079 Ok(key) => {
1080 boot_log.push(BootEvent::info(
1081 BootPhase::Services,
1082 format!("Ed25519 signing key loaded: {}", key_path.display()),
1083 ));
1084 Some(key)
1085 }
1086 Err(e) => {
1087 warn!(error = %e, "failed to load/create signing key");
1088 boot_log.push(BootEvent::info(
1089 BootPhase::Services,
1090 format!("Signing key unavailable: {e} — chain will be unsigned"),
1091 ));
1092 None
1093 }
1094 }
1095 } else {
1096 None
1097 };
1098
1099 let cm = if let Some(ref ckpt_path) = chain_config.effective_checkpoint_path() {
1100 let json_path = std::path::PathBuf::from(ckpt_path);
1101 let rvf_path = json_path.with_extension("rvf");
1103
1104 if rvf_path.exists() {
1105 match crate::chain::ChainManager::load_from_rvf(
1107 &rvf_path,
1108 chain_config.checkpoint_interval,
1109 ) {
1110 Ok(restored) => {
1111 let seq = restored.sequence();
1112 boot_log.push(BootEvent::info(
1113 BootPhase::Services,
1114 format!(
1115 "Chain restored from RVF (seq={}, chain_id={})",
1116 seq, chain_config.chain_id,
1117 ),
1118 ));
1119 Arc::new(restored)
1120 }
1121 Err(e) => {
1122 error!(error = %e, "failed to restore RVF chain, trying JSON fallback");
1123 if json_path.exists() {
1125 match crate::chain::ChainManager::load_from_file(
1126 &json_path,
1127 chain_config.checkpoint_interval,
1128 ) {
1129 Ok(restored) => {
1130 let seq = restored.sequence();
1131 boot_log.push(BootEvent::info(
1132 BootPhase::Services,
1133 format!(
1134 "Chain restored from JSON fallback (seq={}, chain_id={})",
1135 seq, chain_config.chain_id,
1136 ),
1137 ));
1138 Arc::new(restored)
1139 }
1140 Err(e2) => {
1141 error!(error = %e2, "JSON fallback also failed; refusing to boot");
1142 return Err(KernelError::Boot(unrestorable_chain_msg(
1143 &rvf_path,
1144 &format!("RVF: {e}; JSON: {e2}"),
1145 )));
1146 }
1147 }
1148 } else {
1149 return Err(KernelError::Boot(unrestorable_chain_msg(
1150 &rvf_path,
1151 &e.to_string(),
1152 )));
1153 }
1154 }
1155 }
1156 } else if json_path.exists() {
1157 match crate::chain::ChainManager::load_from_file(
1159 &json_path,
1160 chain_config.checkpoint_interval,
1161 ) {
1162 Ok(restored) => {
1163 let seq = restored.sequence();
1164 boot_log.push(BootEvent::info(
1165 BootPhase::Services,
1166 format!(
1167 "Chain restored from JSON (seq={}, chain_id={}, will migrate to RVF)",
1168 seq, chain_config.chain_id,
1169 ),
1170 ));
1171 Arc::new(restored)
1172 }
1173 Err(e) => {
1174 error!(error = %e, "failed to restore chain; refusing to boot");
1175 return Err(KernelError::Boot(unrestorable_chain_msg(
1176 &json_path,
1177 &e.to_string(),
1178 )));
1179 }
1180 }
1181 } else {
1182 Arc::new(crate::chain::ChainManager::new(
1183 chain_config.chain_id,
1184 chain_config.checkpoint_interval,
1185 ))
1186 }
1187 } else {
1188 Arc::new(crate::chain::ChainManager::new(
1189 chain_config.chain_id,
1190 chain_config.checkpoint_interval,
1191 ))
1192 };
1193
1194 let mut cm = cm;
1196 if let Some(key) = signing_key
1197 && let Some(inner) = Arc::get_mut(&mut cm)
1198 {
1199 let ml_dsa_seed = key.to_bytes();
1201 let (ml_key, _ml_vk) = weftos_rvf_crypto::MlDsa65Key::generate(&ml_dsa_seed);
1202 inner.set_signing_key(key);
1203 inner.set_ml_dsa_key(ml_key);
1204 boot_log.push(BootEvent::info(
1205 BootPhase::Services,
1206 "Dual signing enabled (Ed25519 + ML-DSA-65)",
1207 ));
1208 }
1209
1210 boot_log.push(BootEvent::info(
1211 BootPhase::Services,
1212 format!(
1213 "Local chain ready (chain_id={}, seq={}, signed={})",
1214 chain_config.chain_id,
1215 cm.sequence(),
1216 cm.has_signing_key(),
1217 ),
1218 ));
1219
1220 if let Some(p) = overlay_prepared.as_ref() {
1223 p.commit(&cm)
1224 .map_err(|e| KernelError::BootRefused(e.boot_message()))?;
1225 p.install_provider(&cm);
1226 }
1227
1228 cm.append(
1230 "kernel",
1231 "boot.init",
1232 Some(serde_json::json!({"version": "0.1.0"})),
1233 );
1234 cm.append(
1235 "kernel",
1236 "boot.config",
1237 Some(serde_json::json!({
1238 "max_processes": kernel_config.max_processes,
1239 "health_interval": kernel_config.health_check_interval_secs,
1240 })),
1241 );
1242 cm.append(
1243 "kernel",
1244 "boot.services",
1245 Some(serde_json::json!({
1246 "count": service_registry.len(),
1247 })),
1248 );
1249
1250 Some(cm)
1251 } else {
1252 boot_log.push(BootEvent::info(BootPhase::Services, "Local chain disabled"));
1253 None
1254 }
1255 };
1256
1257 #[cfg(feature = "exochain")]
1262 if let Some(ref cm) = chain_manager {
1263 crate::workload_governance::chain_revocations(&revocation_list, Arc::clone(cm));
1264 }
1265
1266 #[cfg(feature = "exochain")]
1270 let chain_anchor = {
1271 let chain_config = kernel_config.chain.clone().unwrap_or_default();
1272 if !chain_config.enabled {
1273 None
1274 } else if let Some(ref acfg) = chain_config.external_anchor {
1275 match crate::chain_anchor::AnchoringController::from_config(acfg) {
1276 Ok(Some(ctrl)) => {
1277 boot_log.push(BootEvent::info(
1278 BootPhase::Services,
1279 format!(
1280 "Chain external anchor ready (backend={}, min_interval={}s, min_events={})",
1281 ctrl.backend_name(),
1282 acfg.min_interval_secs,
1283 acfg.min_events_between,
1284 ),
1285 ));
1286 if let Some(ref cm) = chain_manager {
1288 match ctrl.try_anchor_chain_head(cm) {
1289 Ok(Some(receipt)) => {
1290 boot_log.push(BootEvent::info(
1291 BootPhase::Services,
1292 format!(
1293 "Chain head anchored at boot (tx_id={})",
1294 receipt.tx_id
1295 ),
1296 ));
1297 }
1298 Ok(None) => {
1299 }
1302 Err(e) => {
1303 warn!(error = %e, "boot-time chain head anchor failed");
1304 }
1305 }
1306 }
1307 Some(Arc::new(ctrl))
1308 }
1309 Ok(None) => {
1310 boot_log.push(BootEvent::info(
1311 BootPhase::Services,
1312 "Chain external anchor configured as none — skipped",
1313 ));
1314 None
1315 }
1316 Err(e) => {
1317 warn!(error = %e, "failed to build chain external anchor");
1318 boot_log.push(BootEvent::info(
1319 BootPhase::Services,
1320 format!("Chain external anchor unavailable: {e}"),
1321 ));
1322 None
1323 }
1324 }
1325 } else {
1326 None
1327 }
1328 };
1329
1330 #[cfg(all(feature = "native", feature = "mesh", feature = "exochain"))]
1336 if let (Some(mr), Some(cm)) = (mesh_runtime.as_ref(), chain_manager.as_ref()) {
1337 mr.set_chain_manager(Arc::clone(cm));
1338 }
1339
1340 #[cfg(feature = "exochain")]
1343 let tree_manager = {
1344 let rt_config = kernel_config.resource_tree.clone().unwrap_or_default();
1345 if rt_config.enabled {
1346 if let Some(ref cm) = chain_manager {
1347 let tm = Arc::new(crate::tree_manager::TreeManager::new(Arc::clone(cm)));
1348
1349 let chain_cfg = kernel_config.chain.clone().unwrap_or_default();
1351 let tree_ckpt_path = chain_cfg
1352 .effective_checkpoint_path()
1353 .map(|p| std::path::PathBuf::from(p).with_extension("tree.json"));
1354
1355 let mut restored_from_checkpoint = false;
1356 if let Some(ref tree_path) = tree_ckpt_path
1357 && tree_path.exists()
1358 {
1359 match tm.load_checkpoint(tree_path) {
1360 Ok(()) => {
1361 let stats = tm.stats();
1362 let chain_tree_hash = cm.last_tree_root_hash();
1364 if let Some(ref expected) = chain_tree_hash {
1365 if stats.root_hash == *expected {
1366 boot_log.push(BootEvent::info(
1367 BootPhase::ResourceTree,
1368 format!(
1369 "Resource tree restored from checkpoint ({} nodes, root={}..., hash verified)",
1370 stats.node_count,
1371 &stats.root_hash[..12],
1372 ),
1373 ));
1374 restored_from_checkpoint = true;
1375 } else {
1376 warn!(
1377 expected = %expected,
1378 actual = %stats.root_hash,
1379 "tree checkpoint root hash mismatch — falling back to fresh bootstrap"
1380 );
1381 boot_log.push(BootEvent::info(
1382 BootPhase::ResourceTree,
1383 format!(
1384 "Tree checkpoint hash mismatch (expected={}..., got={}...), bootstrapping fresh",
1385 &expected[..std::cmp::min(12, expected.len())],
1386 &stats.root_hash[..12],
1387 ),
1388 ));
1389 }
1391 } else {
1392 boot_log.push(BootEvent::info(
1394 BootPhase::ResourceTree,
1395 format!(
1396 "Resource tree restored from checkpoint ({} nodes, root={}...)",
1397 stats.node_count,
1398 &stats.root_hash[..12],
1399 ),
1400 ));
1401 restored_from_checkpoint = true;
1402 }
1403 }
1404 Err(e) => {
1405 error!(error = %e, "failed to restore tree checkpoint, bootstrapping fresh");
1406 }
1407 }
1408 }
1409
1410 if !restored_from_checkpoint {
1411 if let Err(e) = tm.bootstrap() {
1412 error!(error = %e, "failed to bootstrap resource tree");
1413 } else {
1415 let stats = tm.stats();
1416 boot_log.push(BootEvent::info(
1417 BootPhase::ResourceTree,
1418 format!(
1419 "Resource tree bootstrapped ({} nodes, root={}...)",
1420 stats.node_count,
1421 &stats.root_hash[..12],
1422 ),
1423 ));
1424 }
1425 }
1426
1427 if let Err(e) = tm.register_service_with_manifest("cron", "scheduler") {
1429 tracing::debug!(error = %e, "failed to register cron in tree (may already exist)");
1430 }
1431
1432 {
1434 use exo_resource_tree::model::{ResourceId, ResourceKind};
1435 if let Err(e) = tm.insert(
1436 ResourceId::new("/kernel/containers"),
1437 ResourceKind::Namespace,
1438 ResourceId::new("/kernel"),
1439 ) {
1440 tracing::debug!(
1441 error = %e,
1442 "failed to register containers namespace (may already exist)"
1443 );
1444 }
1445 }
1446
1447 #[cfg(feature = "ecc")]
1449 {
1450 use exo_resource_tree::model::{ResourceId, ResourceKind};
1451 let ecc_namespaces = [
1452 ("/kernel/services/ecc", "/kernel/services"),
1453 ("/kernel/services/ecc/hnsw", "/kernel/services/ecc"),
1454 ("/kernel/services/ecc/causal", "/kernel/services/ecc"),
1455 ("/kernel/services/ecc/tick", "/kernel/services/ecc"),
1456 ("/kernel/services/ecc/calibration", "/kernel/services/ecc"),
1457 ("/kernel/services/ecc/crossrefs", "/kernel/services/ecc"),
1458 ];
1459 for (path, parent) in &ecc_namespaces {
1460 if let Err(e) = tm.insert(
1461 ResourceId::new(*path),
1462 ResourceKind::Namespace,
1463 ResourceId::new(*parent),
1464 ) {
1465 tracing::debug!(
1466 error = %e, path = *path,
1467 "failed to register ECC namespace (may already exist)"
1468 );
1469 }
1470 }
1471 boot_log.push(BootEvent::info(
1472 BootPhase::Ecc,
1473 "ECC resource tree namespaces registered",
1474 ));
1475 }
1476
1477 {
1479 use exo_resource_tree::model::{ResourceId, ResourceKind};
1480 let tool_namespaces = [
1481 ("/kernel/tools", "/kernel"),
1482 ("/kernel/tools/fs", "/kernel/tools"),
1483 ("/kernel/tools/agent", "/kernel/tools"),
1484 ("/kernel/tools/sys", "/kernel/tools"),
1485 ("/kernel/tools/ipc", "/kernel/tools"),
1486 #[cfg(feature = "ecc")]
1487 ("/kernel/tools/ecc", "/kernel/tools"),
1488 ];
1489 for (path, parent) in &tool_namespaces {
1490 if let Err(e) = tm.insert(
1491 ResourceId::new(*path),
1492 ResourceKind::Namespace,
1493 ResourceId::new(*parent),
1494 ) {
1495 tracing::debug!(
1496 error = %e, path = *path,
1497 "failed to register tool namespace (may already exist)"
1498 );
1499 }
1500 }
1501 let catalog = crate::wasm_runner::builtin_tool_catalog();
1503 for spec in &catalog {
1504 let cat_path = match spec.category {
1505 crate::wasm_runner::ToolCategory::Filesystem => "/kernel/tools/fs",
1506 crate::wasm_runner::ToolCategory::Agent => "/kernel/tools/agent",
1507 crate::wasm_runner::ToolCategory::System => "/kernel/tools/sys",
1508 crate::wasm_runner::ToolCategory::Ecc => "/kernel/tools/ecc",
1509 crate::wasm_runner::ToolCategory::User => "/kernel/tools",
1510 };
1511 let tool_path = format!("{}/{}", cat_path, spec.name.replace('.', "/"));
1512 if let Err(e) = tm.insert(
1513 ResourceId::new(&tool_path),
1514 ResourceKind::Tool,
1515 ResourceId::new(cat_path),
1516 ) {
1517 tracing::debug!(
1518 error = %e, tool = %spec.name,
1519 "failed to register tool node (may already exist)"
1520 );
1521 }
1522 }
1523 boot_log.push(BootEvent::info(
1524 BootPhase::ResourceTree,
1525 format!(
1526 "Registered {} built-in tools in resource tree",
1527 catalog.len()
1528 ),
1529 ));
1530 }
1531
1532 Some(tm)
1533 } else {
1534 boot_log.push(BootEvent::info(
1535 BootPhase::ResourceTree,
1536 "Resource tree requires chain — skipped",
1537 ));
1538 None
1539 }
1540 } else {
1541 boot_log.push(BootEvent::info(
1542 BootPhase::ResourceTree,
1543 "Resource tree disabled",
1544 ));
1545 None
1546 }
1547 };
1548
1549 #[cfg(feature = "exochain")]
1557 let governance_gate: Option<Arc<dyn crate::gate::GateBackend>> = {
1558 if let Some(ref cm) = chain_manager {
1559 use crate::gate::GovernanceGate;
1560 use crate::governance::{GovernanceBranch, GovernanceRule, RuleSeverity};
1561
1562 let (risk_threshold, human_approval) = overlay_prepared
1564 .as_ref()
1565 .map_or((0.7, false), |p| p.engine_params());
1566
1567 let mut gate =
1568 GovernanceGate::new(risk_threshold, human_approval).with_chain(Arc::clone(cm));
1569
1570 let genesis_rules = vec![
1577 GovernanceRule {
1579 id: "GOV-001".into(),
1580 description: "High-risk operations require elevated review".into(),
1581 branch: GovernanceBranch::Judicial,
1582 severity: RuleSeverity::Blocking,
1583 active: true,
1584 reference_url: None,
1585 sop_category: None,
1586 rule_type: Default::default(),
1587 action_selector: None,
1588 tool_selector: None,
1589 force_on_match: false,
1590 },
1591 GovernanceRule {
1592 id: "GOV-002".into(),
1593 description: "Security-sensitive actions must not exceed security threshold".into(),
1594 branch: GovernanceBranch::Judicial,
1595 severity: RuleSeverity::Blocking,
1596 active: true,
1597 reference_url: None,
1598 sop_category: None,
1599 rule_type: Default::default(),
1600 action_selector: None,
1601 tool_selector: None,
1602 force_on_match: false,
1603 },
1604 GovernanceRule {
1605 id: "GOV-003".into(),
1606 description: "Privacy-impacting operations flagged for review".into(),
1607 branch: GovernanceBranch::Legislative,
1608 severity: RuleSeverity::Warning,
1609 active: true,
1610 reference_url: None,
1611 sop_category: None,
1612 rule_type: Default::default(),
1613 action_selector: None,
1614 tool_selector: None,
1615 force_on_match: false,
1616 },
1617 GovernanceRule {
1618 id: "GOV-004".into(),
1619 description: "Novel/unprecedented actions require advisory logging".into(),
1620 branch: GovernanceBranch::Executive,
1621 severity: RuleSeverity::Advisory,
1622 active: true,
1623 reference_url: None,
1624 sop_category: None,
1625 rule_type: Default::default(),
1626 action_selector: None,
1627 tool_selector: None,
1628 force_on_match: false,
1629 },
1630 GovernanceRule {
1631 id: "GOV-005".into(),
1632 description: "Filesystem write operations scored for risk".into(),
1633 branch: GovernanceBranch::Legislative,
1634 severity: RuleSeverity::Warning,
1635 active: true,
1636 reference_url: None,
1637 sop_category: None,
1638 rule_type: Default::default(),
1639 action_selector: None,
1640 tool_selector: None,
1641 force_on_match: false,
1642 },
1643 GovernanceRule {
1644 id: "GOV-006".into(),
1645 description: "Agent spawn operations require governance clearance".into(),
1646 branch: GovernanceBranch::Executive,
1647 severity: RuleSeverity::Blocking,
1648 active: true,
1649 reference_url: None,
1650 sop_category: None,
1651 rule_type: Default::default(),
1652 action_selector: None,
1653 tool_selector: None,
1654 force_on_match: false,
1655 },
1656 GovernanceRule {
1657 id: "GOV-007".into(),
1658 description: "IPC messages between agents logged for audit trail".into(),
1659 branch: GovernanceBranch::Judicial,
1660 severity: RuleSeverity::Advisory,
1661 active: true,
1662 reference_url: None,
1663 sop_category: None,
1664 rule_type: Default::default(),
1665 action_selector: None,
1666 tool_selector: None,
1667 force_on_match: false,
1668 },
1669 GovernanceRule {
1671 id: "SOP-L001".into(),
1672 description: "AI-IRB approval required before high-impact deployments".into(),
1673 branch: GovernanceBranch::Legislative,
1674 severity: RuleSeverity::Blocking,
1675 active: true,
1676 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1300-01-AI_IRB_Approval.md".into()),
1677 sop_category: Some("governance".into()),
1678 rule_type: Default::default(),
1679 action_selector: None,
1680 tool_selector: None,
1681 force_on_match: false,
1682 },
1683 GovernanceRule {
1684 id: "SOP-L002".into(),
1685 description: "Version control and branching policies must be enforced".into(),
1686 branch: GovernanceBranch::Legislative,
1687 severity: RuleSeverity::Warning,
1688 active: true,
1689 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1003-01-AI_Version_Control.md".into()),
1690 sop_category: Some("governance".into()),
1691 rule_type: Default::default(),
1692 action_selector: None,
1693 tool_selector: None,
1694 force_on_match: false,
1695 },
1696 GovernanceRule {
1697 id: "SOP-L003".into(),
1698 description: "Requirements must include AI-IRB ethical review".into(),
1699 branch: GovernanceBranch::Legislative,
1700 severity: RuleSeverity::Warning,
1701 active: true,
1702 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1040-01-AI_Requirements.md".into()),
1703 sop_category: Some("engineering".into()),
1704 rule_type: Default::default(),
1705 action_selector: None,
1706 tool_selector: None,
1707 force_on_match: false,
1708 },
1709 GovernanceRule {
1710 id: "SOP-L004".into(),
1711 description: "Release planning must follow structured lifecycle gates".into(),
1712 branch: GovernanceBranch::Legislative,
1713 severity: RuleSeverity::Advisory,
1714 active: true,
1715 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1005-01-AI_Release_Planning.md".into()),
1716 sop_category: Some("lifecycle".into()),
1717 rule_type: Default::default(),
1718 action_selector: None,
1719 tool_selector: None,
1720 force_on_match: false,
1721 },
1722 GovernanceRule {
1723 id: "SOP-L005".into(),
1724 description: "Data protection and PII handling must comply with policy".into(),
1725 branch: GovernanceBranch::Legislative,
1726 severity: RuleSeverity::Blocking,
1727 active: true,
1728 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1303-01-AI_Data_Protection.md".into()),
1729 sop_category: Some("ethics".into()),
1730 rule_type: Default::default(),
1731 action_selector: None,
1732 tool_selector: None,
1733 force_on_match: false,
1734 },
1735 GovernanceRule {
1736 id: "SOP-L006".into(),
1737 description: "Risk register must be maintained and reviewed".into(),
1738 branch: GovernanceBranch::Legislative,
1739 severity: RuleSeverity::Warning,
1740 active: true,
1741 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1062-01-AI_Risk_Register.md".into()),
1742 sop_category: Some("governance".into()),
1743 rule_type: Default::default(),
1744 action_selector: None,
1745 tool_selector: None,
1746 force_on_match: false,
1747 },
1748 GovernanceRule {
1750 id: "SOP-E001".into(),
1751 description: "Secure coding standards must be followed".into(),
1752 branch: GovernanceBranch::Executive,
1753 severity: RuleSeverity::Warning,
1754 active: true,
1755 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1200-01-AI_Secure_Coding.md".into()),
1756 sop_category: Some("engineering".into()),
1757 rule_type: Default::default(),
1758 action_selector: None,
1759 tool_selector: None,
1760 force_on_match: false,
1761 },
1762 GovernanceRule {
1763 id: "SOP-E002".into(),
1764 description: "Deployment requires governance clearance checkpoint".into(),
1765 branch: GovernanceBranch::Executive,
1766 severity: RuleSeverity::Blocking,
1767 active: true,
1768 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1220-01-AI_Deployment_Clearance.md".into()),
1769 sop_category: Some("lifecycle".into()),
1770 rule_type: Default::default(),
1771 action_selector: None,
1772 tool_selector: None,
1773 force_on_match: false,
1774 },
1775 GovernanceRule {
1776 id: "SOP-E003".into(),
1777 description: "Incident response procedures must be documented and followed".into(),
1778 branch: GovernanceBranch::Executive,
1779 severity: RuleSeverity::Warning,
1780 active: true,
1781 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1008-01-AI_Incident_Response.md".into()),
1782 sop_category: Some("security".into()),
1783 rule_type: Default::default(),
1784 action_selector: None,
1785 tool_selector: None,
1786 force_on_match: false,
1787 },
1788 GovernanceRule {
1789 id: "SOP-E004".into(),
1790 description: "Decommissioning must follow structured teardown procedure".into(),
1791 branch: GovernanceBranch::Executive,
1792 severity: RuleSeverity::Advisory,
1793 active: true,
1794 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1011-01-AI_Decommissioning.md".into()),
1795 sop_category: Some("lifecycle".into()),
1796 rule_type: Default::default(),
1797 action_selector: None,
1798 tool_selector: None,
1799 force_on_match: false,
1800 },
1801 GovernanceRule {
1802 id: "SOP-E005".into(),
1803 description: "Third-party AI procurement requires screening".into(),
1804 branch: GovernanceBranch::Executive,
1805 severity: RuleSeverity::Advisory,
1806 active: true,
1807 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1004-01-AI_Procurement_Screening.md".into()),
1808 sop_category: Some("governance".into()),
1809 rule_type: Default::default(),
1810 action_selector: None,
1811 tool_selector: None,
1812 force_on_match: false,
1813 },
1814 GovernanceRule {
1816 id: "SOP-J001".into(),
1817 description: "Bias and fairness assessments required for model outputs".into(),
1818 branch: GovernanceBranch::Judicial,
1819 severity: RuleSeverity::Blocking,
1820 active: true,
1821 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1301-01-AI_Bias_Fairness.md".into()),
1822 sop_category: Some("ethics".into()),
1823 rule_type: Default::default(),
1824 action_selector: None,
1825 tool_selector: None,
1826 force_on_match: false,
1827 },
1828 GovernanceRule {
1829 id: "SOP-J002".into(),
1830 description: "Explainability documentation required for decision systems".into(),
1831 branch: GovernanceBranch::Judicial,
1832 severity: RuleSeverity::Warning,
1833 active: true,
1834 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1302-01-AI_Explainability.md".into()),
1835 sop_category: Some("ethics".into()),
1836 rule_type: Default::default(),
1837 action_selector: None,
1838 tool_selector: None,
1839 force_on_match: false,
1840 },
1841 GovernanceRule {
1842 id: "SOP-J003".into(),
1843 description: "Model drift detection and monitoring must be active".into(),
1844 branch: GovernanceBranch::Judicial,
1845 severity: RuleSeverity::Warning,
1846 active: true,
1847 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-1009-01-AI_Drift_Detection.md".into()),
1848 sop_category: Some("lifecycle".into()),
1849 rule_type: Default::default(),
1850 action_selector: None,
1851 tool_selector: None,
1852 force_on_match: false,
1853 },
1854 GovernanceRule {
1855 id: "SOP-J004".into(),
1856 description: "Quality records must be maintained for audit compliance".into(),
1857 branch: GovernanceBranch::Judicial,
1858 severity: RuleSeverity::Advisory,
1859 active: true,
1860 reference_url: Some("https://github.com/AISDLC/AI-SDLC-SOPs/blob/main/sops/SOP-2002-01-AI_Quality_Records.md".into()),
1861 sop_category: Some("quality".into()),
1862 rule_type: Default::default(),
1863 action_selector: None,
1864 tool_selector: None,
1865 force_on_match: false,
1866 },
1867 ]
1872 .into_iter()
1873 .chain(crate::governance::browser_policy_default_rules())
1874 .chain(crate::governance::binding_thread_default_rules())
1876 .chain(crate::workload_governance::default_rules())
1878 .collect::<Vec<_>>();
1879 let genesis_rules = match overlay_prepared.as_ref() {
1882 Some(p) => p.rules().to_vec(),
1883 None => genesis_rules,
1884 };
1885
1886 let genesis_seq = cm.sequence();
1888 let rules_json: Vec<serde_json::Value> = genesis_rules
1889 .iter()
1890 .map(|r| {
1891 serde_json::json!({
1892 "id": r.id,
1893 "description": r.description,
1894 "branch": format!("{}", r.branch),
1895 "severity": format!("{}", r.severity),
1896 "reference_url": r.reference_url,
1897 "sop_category": r.sop_category,
1898 "rule_type": format!("{}", r.rule_type),
1899 })
1900 })
1901 .collect();
1902
1903 cm.append(
1906 "governance",
1907 "governance.genesis",
1908 Some(serde_json::json!({
1909 "version": "2.0.0",
1910 "risk_threshold": risk_threshold,
1911 "human_approval_required": human_approval,
1912 "rules": rules_json,
1913 "rule_count": genesis_rules.len(),
1914 "genesis_seq": genesis_seq,
1915 })),
1916 );
1917
1918 for rule in &genesis_rules {
1920 cm.append(
1921 "governance",
1922 "governance.rule",
1923 Some(serde_json::json!({
1924 "rule_id": rule.id,
1925 "branch": format!("{}", rule.branch),
1926 "severity": format!("{}", rule.severity),
1927 "genesis_seq": genesis_seq,
1928 })),
1929 );
1930 }
1931
1932 for rule in genesis_rules {
1934 gate = gate.add_rule(rule);
1935 }
1936
1937 boot_log.push(BootEvent::info(
1938 BootPhase::Services,
1939 format!(
1940 "Governance genesis anchored (seq={}, {} rules, threshold={:.1})",
1941 genesis_seq,
1942 gate.engine().rule_count(),
1943 risk_threshold,
1944 ),
1945 ));
1946
1947 match overlay_prepared.take() {
1948 Some(p) => {
1949 let (gate, rt) = p.into_runtime(gate, Arc::clone(cm));
1950 overlay_runtime = Some(rt);
1951 Some(gate)
1952 }
1953 None => Some(Arc::new(gate) as Arc<dyn crate::gate::GateBackend>),
1954 }
1955 } else {
1956 if overlay_prepared.is_some() {
1957 return Err(KernelError::BootRefused(
1958 "profile `project` needs a chain: governance cannot run open".into(),
1959 ));
1960 }
1961 boot_log.push(BootEvent::info(
1962 BootPhase::Services,
1963 "Governance: no chain available, using open governance",
1964 ));
1965 None
1966 }
1967 };
1968
1969 #[cfg(all(feature = "native", feature = "mesh", feature = "exochain"))]
1970 match &governance_gate {
1971 Some(g) => mesh_verdicts.bind(Arc::clone(g)),
1972 None if mesh_closed_without_gate => {
1973 error!(
1974 "mesh admission = enforce but no governance gate exists: every peer will be \
1975 refused. Enable the chain/governance, or set \
1976 kernel.mesh.admission_open_membership = true to admit any valid hello"
1977 );
1978 mesh_verdicts.bind_closed();
1979 }
1980 None => {
1981 warn!("mesh admission_open_membership: admitting any peer with a valid hello");
1982 mesh_verdicts.bind_open();
1983 }
1984 }
1985
1986 #[cfg(feature = "exochain")]
1990 if let Some(ref gate) = governance_gate {
1991 a2a_router.set_gate(Arc::clone(gate));
1992 }
1993
1994 #[cfg(feature = "exochain")]
1996 if let Some(ref cm) = chain_manager {
1997 cm.append(
1998 "kernel",
1999 "boot.cluster",
2000 Some(serde_json::json!({
2001 "node_id": cluster_membership.local_node_id(),
2002 })),
2003 );
2004
2005 let elapsed_ms = boot_time.elapsed().as_millis() as u64;
2006 let mut ready_payload = serde_json::json!({
2007 "elapsed_ms": elapsed_ms,
2008 "processes": process_table.len(),
2009 "services": service_registry.len(),
2010 });
2011
2012 if let Some(ref tm) = tree_manager {
2013 let root_hash = tm.stats().root_hash;
2014 ready_payload
2015 .as_object_mut()
2016 .unwrap()
2017 .insert("tree_root_hash".to_string(), serde_json::json!(root_hash));
2018 }
2019
2020 cm.append("kernel", "boot.ready", Some(ready_payload));
2021
2022 let mut manifest = serde_json::json!({
2025 "version": env!("CARGO_PKG_VERSION"),
2026 "node_id": cluster_membership.local_node_id(),
2027 "process_count": process_table.len(),
2028 "service_count": service_registry.len(),
2029 "chain_sequence": cm.sequence(),
2030 "boot_elapsed_ms": boot_time.elapsed().as_millis() as u64,
2031 });
2032 if let Some(ref tm) = tree_manager {
2033 let stats = tm.stats();
2034 manifest.as_object_mut().unwrap().insert(
2035 "tree_root_hash".to_string(),
2036 serde_json::json!(stats.root_hash),
2037 );
2038 manifest.as_object_mut().unwrap().insert(
2039 "tree_node_count".to_string(),
2040 serde_json::json!(stats.node_count),
2041 );
2042 }
2043 cm.append("kernel", "boot.manifest", Some(manifest));
2044 }
2045
2046 #[cfg(feature = "ecc")]
2048 let (
2049 ecc_hnsw,
2050 ecc_causal,
2051 ecc_tick,
2052 ecc_crossrefs,
2053 ecc_impulses,
2054 ecc_calibration,
2055 ecc_vector_backend,
2056 ecc_eml_model,
2057 ecc_spatial,
2058 ) = {
2059 use crate::calibration::{EccCalibrationConfig, run_calibration};
2060 use crate::causal::CausalGraph;
2061 use crate::cognitive_tick::{CognitiveTick, CognitiveTickConfig};
2062 use crate::crossref::CrossRefStore;
2063 use crate::hnsw_service::{HnswService, HnswServiceConfig};
2064 use crate::impulse::ImpulseQueue;
2065 use crate::service::SystemService;
2066 use crate::spatial_service::SpatialService;
2067 use crate::vector_backend::VectorBackend;
2068 use crate::vector_diskann::{
2069 DiskAnnBackend, DiskAnnConfig, DiskAnnFeatureStatus, diskann_feature_status,
2070 };
2071 use crate::vector_hnsw::HnswBackend;
2072 use crate::vector_hybrid::{HybridBackend, HybridConfig};
2073
2074 boot_log.push(BootEvent::info(
2075 BootPhase::Ecc,
2076 "Initializing ECC cognitive substrate",
2077 ));
2078
2079 let hnsw = Arc::new(HnswService::new(HnswServiceConfig::default()));
2080 let causal = Arc::new(CausalGraph::new());
2081 let crossrefs = Arc::new(CrossRefStore::new());
2082 let impulses = Arc::new(ImpulseQueue::new());
2083
2084 let vector_config = kernel_config.vector.clone();
2086 let vector_strict = vector_config.as_ref().map(|v| v.strict).unwrap_or(false);
2087 let vector_backend: Arc<dyn VectorBackend> =
2088 match vector_config.as_ref().map(|v| v.backend) {
2089 Some(clawft_types::config::VectorBackendKind::DiskAnn) => {
2090 match diskann_feature_status(
2091 clawft_types::config::VectorBackendKind::DiskAnn,
2092 cfg!(feature = "diskann"),
2093 vector_strict,
2094 ) {
2095 DiskAnnFeatureStatus::StubRejected => {
2096 return Err(KernelError::BootRefused(
2097 DISKANN_FEATURE_MISMATCH_ERROR.to_string(),
2098 ));
2099 }
2100 DiskAnnFeatureStatus::StubDegraded => {
2101 boot_log.push(BootEvent::warn(
2102 BootPhase::Ecc,
2103 DISKANN_FEATURE_MISMATCH_WARNING,
2104 ));
2105 }
2106 DiskAnnFeatureStatus::Ok => {}
2107 }
2108 let da_cfg = vector_config
2109 .as_ref()
2110 .and_then(|v| v.diskann.as_ref())
2111 .map(|d| DiskAnnConfig {
2112 max_points: d.max_points,
2113 dimensions: d.dimensions,
2114 num_neighbors: d.num_neighbors,
2115 search_list_size: d.search_list_size,
2116 data_path: d.data_path.clone(),
2117 use_pq: d.use_pq,
2118 pq_num_chunks: d.pq_num_chunks,
2119 })
2120 .unwrap_or_default();
2121 boot_log.push(BootEvent::info(
2122 BootPhase::Ecc,
2123 if cfg!(feature = "diskann") {
2124 "Vector backend: DiskANN (real: Vamana graph + PQ)"
2125 } else {
2126 "Vector backend: DiskANN (STUB: brute-force O(n) linear scan)"
2127 },
2128 ));
2129 Arc::new(DiskAnnBackend::new(da_cfg))
2130 }
2131 Some(clawft_types::config::VectorBackendKind::Hybrid) => {
2132 match diskann_feature_status(
2133 clawft_types::config::VectorBackendKind::Hybrid,
2134 cfg!(feature = "diskann"),
2135 vector_strict,
2136 ) {
2137 DiskAnnFeatureStatus::StubRejected => {
2138 return Err(KernelError::BootRefused(
2139 DISKANN_FEATURE_MISMATCH_ERROR.to_string(),
2140 ));
2141 }
2142 DiskAnnFeatureStatus::StubDegraded => {
2143 boot_log.push(BootEvent::warn(
2144 BootPhase::Ecc,
2145 DISKANN_FEATURE_MISMATCH_WARNING,
2146 ));
2147 }
2148 DiskAnnFeatureStatus::Ok => {}
2149 }
2150 let hnsw_cfg = vector_config
2151 .as_ref()
2152 .and_then(|v| v.hnsw.as_ref())
2153 .map(|h| HnswServiceConfig {
2154 ef_construction: h.ef_construction,
2155 ef_search: 100,
2156 default_dimensions: 384,
2157 })
2158 .unwrap_or_default();
2159 let da_cfg = vector_config
2160 .as_ref()
2161 .and_then(|v| v.diskann.as_ref())
2162 .map(|d| DiskAnnConfig {
2163 max_points: d.max_points,
2164 dimensions: d.dimensions,
2165 num_neighbors: d.num_neighbors,
2166 search_list_size: d.search_list_size,
2167 data_path: d.data_path.clone(),
2168 use_pq: d.use_pq,
2169 pq_num_chunks: d.pq_num_chunks,
2170 })
2171 .unwrap_or_default();
2172 let hybrid_cfg = vector_config
2173 .as_ref()
2174 .and_then(|v| v.hybrid.as_ref())
2175 .map(|h| HybridConfig {
2176 hot_capacity: h.hot_capacity,
2177 promotion_threshold: h.promotion_threshold,
2178 eviction_policy: crate::vector_hybrid::EvictionPolicy::Lru,
2179 })
2180 .unwrap_or_default();
2181 boot_log.push(BootEvent::info(
2182 BootPhase::Ecc,
2183 format!(
2184 "Vector backend: Hybrid (hot={}, threshold={}, cold={})",
2185 hybrid_cfg.hot_capacity,
2186 hybrid_cfg.promotion_threshold,
2187 if cfg!(feature = "diskann") {
2188 "DiskANN real"
2189 } else {
2190 "DiskANN STUB"
2191 },
2192 ),
2193 ));
2194 Arc::new(HybridBackend::new(hnsw_cfg, da_cfg, hybrid_cfg))
2195 }
2196 _ => {
2197 boot_log.push(BootEvent::info(
2199 BootPhase::Ecc,
2200 "Vector backend: HNSW (in-memory)",
2201 ));
2202 Arc::new(HnswBackend::with_defaults())
2203 }
2204 };
2205
2206 let cal_config = EccCalibrationConfig::default();
2208 let calibration = run_calibration(&hnsw, &causal, &cal_config);
2209
2210 boot_log.push(BootEvent::info(
2211 BootPhase::Ecc,
2212 format!(
2213 "ECC calibration complete (p50={}us, p95={}us, tick={}ms, spectral={})",
2214 calibration.compute_p50_us,
2215 calibration.compute_p95_us,
2216 calibration.tick_interval_ms,
2217 calibration.spectral_capable,
2218 ),
2219 ));
2220
2221 let tick_config = CognitiveTickConfig {
2223 tick_interval_ms: calibration.tick_interval_ms,
2224 ..CognitiveTickConfig::default()
2225 };
2226 let tick = Arc::new(CognitiveTick::new(tick_config));
2227
2228 if let Err(e) = service_registry.register(hnsw.clone()) {
2230 tracing::debug!(error = %e, "failed to register HNSW service");
2231 }
2232 if let Err(e) = service_registry.register(tick.clone()) {
2233 tracing::debug!(error = %e, "failed to register cognitive tick service");
2234 }
2235
2236 if let Err(e) = tick.start().await {
2240 tracing::warn!(error = %e, "failed to start cognitive tick service");
2241 }
2242
2243 let eml_model = Arc::new(std::sync::Mutex::new(
2248 crate::eml_coherence::EmlCoherenceModel::new(),
2249 ));
2250 {
2251 let tick_clone = tick.clone();
2252 let causal_clone = causal.clone();
2253 let hnsw_clone = hnsw.clone();
2254 let eml_clone = eml_model.clone();
2255 tokio::spawn(async move {
2256 crate::cognitive_tick::run_democritus_loop(
2257 tick_clone,
2258 causal_clone,
2259 hnsw_clone,
2260 eml_clone,
2261 )
2262 .await;
2263 });
2264 }
2265 boot_log.push(BootEvent::info(
2266 BootPhase::Ecc,
2267 "DEMOCRITUS two-tier coherence loop spawned",
2268 ));
2269
2270 #[cfg(feature = "exochain")]
2272 if let Some(ref cm) = chain_manager {
2273 cm.append(
2274 "ecc",
2275 "ecc.boot.calibration",
2276 Some(serde_json::json!({
2277 "compute_p50_us": calibration.compute_p50_us,
2278 "compute_p95_us": calibration.compute_p95_us,
2279 "tick_interval_ms": calibration.tick_interval_ms,
2280 "spectral_capable": calibration.spectral_capable,
2281 })),
2282 );
2283 }
2284
2285 let spatial_svc: Option<Arc<SpatialService>> = {
2287 let spatial_cfg = kernel_config.spatial.clone().unwrap_or_default();
2288 if spatial_cfg.enabled {
2289 let svc = Arc::new(SpatialService::from_spatial_config(&spatial_cfg));
2290 #[cfg(feature = "exochain")]
2291 if let Some(ref cm) = chain_manager {
2292 svc.attach_chain(cm.clone());
2293 }
2294 if let Err(e) = service_registry.register(svc.clone()) {
2295 tracing::debug!(error = %e, "failed to register spatial service");
2296 }
2297 if let Err(e) = svc.start().await {
2298 tracing::warn!(error = %e, "failed to start spatial service");
2299 }
2300 boot_log.push(BootEvent::info(
2301 BootPhase::Ecc,
2302 format!(
2303 "Spatial BVH ready (backend={}, max_leaves={})",
2304 svc.backend().backend_name(),
2305 spatial_cfg.max_leaves,
2306 ),
2307 ));
2308 Some(svc)
2309 } else {
2310 boot_log.push(BootEvent::info(
2311 BootPhase::Ecc,
2312 "Spatial BVH disabled (set kernel.spatial.enabled = true)",
2313 ));
2314 None
2315 }
2316 };
2317
2318 boot_log.push(BootEvent::info(
2319 BootPhase::Ecc,
2320 format!(
2321 "ECC ready (hnsw={}, causal={} nodes, tick={}ms, vector={}, spatial={})",
2322 hnsw.len(),
2323 causal.node_count(),
2324 calibration.tick_interval_ms,
2325 vector_backend.backend_name(),
2326 spatial_svc
2327 .as_ref()
2328 .map(|s| s.backend().backend_name())
2329 .unwrap_or("off"),
2330 ),
2331 ));
2332
2333 (
2334 Some(hnsw),
2335 Some(causal),
2336 Some(tick),
2337 Some(crossrefs),
2338 Some(impulses),
2339 Some(calibration),
2340 Some(vector_backend),
2341 Some(eml_model),
2342 spatial_svc,
2343 )
2344 };
2345
2346 #[cfg(feature = "os-patterns")]
2348 let (metrics_registry, log_svc, timer_svc, dead_letter_queue) = {
2349 use crate::dead_letter::DeadLetterQueue;
2350 use crate::log_service::LogService;
2351 use crate::metrics::MetricsRegistry;
2352 use crate::timer::TimerService;
2353
2354 boot_log.push(BootEvent::info(
2355 BootPhase::Services,
2356 "Initializing os-patterns observability modules",
2357 ));
2358
2359 let registry = Arc::new(MetricsRegistry::with_builtins());
2361 registry.gauge_set("kernel.process.count", process_table.len() as i64);
2363 registry.gauge_set("kernel.agent.count", 0);
2364 registry.gauge_set("kernel.ipc.messages_sent", 0);
2365 registry.counter_add("kernel.ipc.messages_failed", 0);
2366 registry.gauge_set("kernel.chain.height", 0);
2367 registry.gauge_set("kernel.uptime_secs", 0);
2368
2369 boot_log.push(BootEvent::info(
2370 BootPhase::Services,
2371 "MetricsRegistry ready (built-in + kernel gauges)",
2372 ));
2373
2374 let log_svc = Arc::new(LogService::with_default_capacity());
2376 boot_log.push(BootEvent::info(BootPhase::Services, "LogService ready"));
2377
2378 let timer_svc = Arc::new(TimerService::new());
2380 boot_log.push(BootEvent::info(BootPhase::Services, "TimerService ready"));
2381
2382 let dlq = Arc::new(DeadLetterQueue::with_default_capacity());
2384 boot_log.push(BootEvent::info(
2385 BootPhase::Services,
2386 "DeadLetterQueue ready",
2387 ));
2388
2389 a2a_router.set_dead_letter_queue(Arc::clone(&dlq));
2391
2392 (Some(registry), Some(log_svc), Some(timer_svc), Some(dlq))
2393 };
2394
2395 let elapsed = boot_time.elapsed();
2396 boot_log.push(BootEvent::info(
2397 BootPhase::Ready,
2398 format!(
2399 "Boot complete in {:.1}s ({} processes, {} services)",
2400 elapsed.as_secs_f64(),
2401 process_table.len(),
2402 service_registry.len(),
2403 ),
2404 ));
2405
2406 info!(
2407 elapsed_ms = elapsed.as_millis(),
2408 processes = process_table.len(),
2409 services = service_registry.len(),
2410 "kernel boot complete"
2411 );
2412
2413 let supervisor = AgentSupervisor::new(
2415 process_table.clone(),
2416 ipc.clone(),
2417 AgentCapabilities::default(),
2418 );
2419
2420 #[cfg(feature = "native")]
2422 let supervisor = supervisor.with_a2a_router(a2a_router.clone(), cron_svc.clone());
2423
2424 #[cfg(feature = "exochain")]
2426 let supervisor = supervisor.with_exochain(tree_manager.clone(), chain_manager.clone());
2427
2428 let event_log = Arc::new(KernelEventLog::new());
2430 event_log.ingest_boot_log(&boot_log);
2431
2432 Ok(Self {
2433 state: KernelState::Running,
2434 config: kernel_config,
2435 app_context: Some(app_context),
2436 bus,
2437 process_table,
2438 service_registry,
2439 ipc,
2440 #[cfg(feature = "native")]
2441 a2a_router,
2442 #[cfg(feature = "native")]
2443 cron_service: cron_svc,
2444 #[cfg(feature = "native")]
2445 assessment_service: assessment_svc,
2446 health,
2447 supervisor,
2448 boot_log,
2449 event_log,
2450 boot_time,
2451 cluster_membership,
2452 app_manager,
2453 revocation_list,
2454 #[cfg(feature = "native")]
2455 agent_registry: crate::agent_registry::AgentRegistry::new(),
2456 #[cfg(feature = "native")]
2457 node_registry: crate::node_registry::NodeRegistry::new(),
2458 #[cfg(feature = "native")]
2459 substrate_service: crate::substrate_service::SubstrateService::new(),
2460 #[cfg(feature = "exochain")]
2461 chain: ChainSubsystem {
2462 chain_manager,
2463 tree_manager,
2464 governance_gate,
2465 chain_anchor,
2466 overlay: overlay_runtime,
2467 _chain_lock: chain_lock,
2468 },
2469 #[cfg(feature = "ecc")]
2470 ecc: EccSubsystem {
2471 hnsw: ecc_hnsw,
2472 causal: ecc_causal,
2473 tick: ecc_tick,
2474 crossrefs: ecc_crossrefs,
2475 impulses: ecc_impulses,
2476 calibration: ecc_calibration,
2477 vector_backend: ecc_vector_backend,
2478 eml_coherence: ecc_eml_model,
2479 spatial: ecc_spatial,
2480 },
2481 #[cfg(feature = "os-patterns")]
2482 observability: ObservabilitySubsystem {
2483 metrics_registry,
2484 log_service: log_svc,
2485 timer_service: timer_svc,
2486 dead_letter_queue,
2487 },
2488 })
2489 }
2490
2491 pub async fn shutdown(&mut self) -> KernelResult<()> {
2496 if self.state != KernelState::Running {
2497 return Err(KernelError::WrongState {
2498 expected: "Running".into(),
2499 actual: self.state.to_string(),
2500 });
2501 }
2502
2503 info!("kernel shutting down");
2504 self.state = KernelState::ShuttingDown;
2505 self.event_log.info("kernel", "shutdown initiated");
2506
2507 if let Err(e) = self.service_registry.stop_all().await {
2509 error!(error = %e, "error stopping services during shutdown");
2510 }
2511
2512 #[cfg(feature = "exochain")]
2514 if let Some(ref tm) = self.chain.tree_manager
2515 && let Some(ref cm) = self.chain.chain_manager
2516 {
2517 let stats = tm.stats();
2518 cm.append(
2519 "kernel",
2520 "shutdown",
2521 Some(serde_json::json!({
2522 "tree_root_hash": stats.root_hash,
2523 "chain_seq": cm.sequence(),
2524 "tree_nodes": stats.node_count,
2525 })),
2526 );
2527 }
2528
2529 #[cfg(feature = "native")]
2531 self.supervisor.abort_all();
2532
2533 for entry in self.process_table.list() {
2535 if entry.pid == 0 {
2536 continue; }
2538 entry.cancel_token.cancel();
2539
2540 #[cfg(feature = "exochain")]
2542 if let Some(ref tm) = self.chain.tree_manager {
2543 let _ = tm.unregister_agent(&entry.agent_id, entry.pid, 0);
2544 }
2545
2546 let _ = self
2547 .process_table
2548 .update_state(entry.pid, ProcessState::Exited(0));
2549 }
2550
2551 #[cfg(feature = "exochain")]
2553 if let Some(ref cm) = self.chain.chain_manager {
2554 let chain_config = self.config.chain.clone().unwrap_or_default();
2555 if let Some(ref ckpt_path) = chain_config.effective_checkpoint_path() {
2556 let json_path = std::path::PathBuf::from(ckpt_path);
2557 let rvf_path = json_path.with_extension("rvf");
2558
2559 match cm.save_to_rvf(&rvf_path) {
2561 Ok(()) => info!(path = %rvf_path.display(), "chain saved to RVF checkpoint"),
2562 Err(e) => {
2563 error!(error = %e, "failed to save RVF checkpoint, falling back to JSON");
2564 match cm.save_to_file(&json_path) {
2566 Ok(()) => {
2567 info!(path = %json_path.display(), "chain saved to JSON checkpoint (fallback)")
2568 }
2569 Err(e2) => {
2570 error!(error = %e2, "failed to save JSON checkpoint fallback")
2571 }
2572 }
2573 }
2574 }
2575
2576 if let Some(ref tm) = self.chain.tree_manager {
2578 let tree_path = json_path.with_extension("tree.json");
2579 match tm.save_checkpoint(&tree_path) {
2580 Ok(()) => {
2581 info!(path = %tree_path.display(), "tree checkpoint saved");
2582 cm.append(
2583 "tree",
2584 "tree.checkpoint",
2585 Some(serde_json::json!({
2586 "path": tree_path.display().to_string(),
2587 "root_hash": tm.stats().root_hash,
2588 })),
2589 );
2590 }
2591 Err(e) => error!(error = %e, "failed to save tree checkpoint"),
2592 }
2593 }
2594 }
2595 }
2596
2597 self.state = KernelState::Halted;
2598 self.event_log.info("kernel", "halted");
2599 info!("kernel halted");
2600 Ok(())
2601 }
2602
2603 pub fn state(&self) -> &KernelState {
2605 &self.state
2606 }
2607
2608 pub fn kernel_config(&self) -> &KernelConfig {
2610 &self.config
2611 }
2612
2613 pub fn process_table(&self) -> &Arc<ProcessTable> {
2615 &self.process_table
2616 }
2617
2618 pub fn services(&self) -> &Arc<ServiceRegistry> {
2620 &self.service_registry
2621 }
2622
2623 pub fn ipc(&self) -> &Arc<KernelIpc> {
2625 &self.ipc
2626 }
2627
2628 pub fn bus(&self) -> &Arc<MessageBus> {
2630 &self.bus
2631 }
2632
2633 #[cfg(feature = "native")]
2635 pub fn a2a_router(&self) -> &Arc<A2ARouter> {
2636 &self.a2a_router
2637 }
2638
2639 #[cfg(feature = "native")]
2646 pub fn agent_registry(&self) -> &crate::agent_registry::AgentRegistry {
2647 &self.agent_registry
2648 }
2649
2650 #[cfg(feature = "native")]
2659 pub fn node_registry(&self) -> &crate::node_registry::NodeRegistry {
2660 &self.node_registry
2661 }
2662
2663 #[cfg(feature = "native")]
2668 pub fn substrate_service(&self) -> &crate::substrate_service::SubstrateService {
2669 &self.substrate_service
2670 }
2671
2672 #[cfg(feature = "native")]
2674 pub fn cron_service(&self) -> &Arc<crate::cron::CronService> {
2675 &self.cron_service
2676 }
2677
2678 #[cfg(feature = "native")]
2680 pub fn assessment_service(&self) -> &Arc<crate::assessment::AssessmentService> {
2681 &self.assessment_service
2682 }
2683
2684 pub fn health(&self) -> &HealthSystem {
2686 &self.health
2687 }
2688
2689 pub fn supervisor(&self) -> &AgentSupervisor<P> {
2691 &self.supervisor
2692 }
2693
2694 pub fn boot_log(&self) -> &BootLog {
2696 &self.boot_log
2697 }
2698
2699 pub fn event_log(&self) -> &Arc<KernelEventLog> {
2701 &self.event_log
2702 }
2703
2704 pub fn uptime(&self) -> std::time::Duration {
2706 self.boot_time.elapsed()
2707 }
2708
2709 pub fn cluster_membership(&self) -> &Arc<ClusterMembership> {
2711 &self.cluster_membership
2712 }
2713
2714 pub fn app_manager(&self) -> &Arc<AppManager> {
2716 &self.app_manager
2717 }
2718
2719 pub fn revocation_list(&self) -> &Arc<crate::revocation::RevocationList> {
2721 &self.revocation_list
2722 }
2723
2724 #[cfg(feature = "exochain")]
2726 pub fn chain_manager(&self) -> Option<&Arc<crate::chain::ChainManager>> {
2727 self.chain.chain_manager.as_ref()
2728 }
2729
2730 #[cfg(feature = "exochain")]
2732 pub fn governance_overlay(&self) -> Option<&Arc<crate::overlay_runtime::OverlayRuntime>> {
2733 self.chain.overlay.as_ref()
2734 }
2735
2736 #[cfg(feature = "exochain")]
2738 pub fn tree_manager(&self) -> Option<&Arc<crate::tree_manager::TreeManager>> {
2739 self.chain.tree_manager.as_ref()
2740 }
2741
2742 #[cfg(feature = "exochain")]
2744 pub fn governance_gate(&self) -> Option<&Arc<dyn crate::gate::GateBackend>> {
2745 self.chain.governance_gate.as_ref()
2746 }
2747
2748 #[cfg(feature = "exochain")]
2750 pub fn chain_anchor(&self) -> Option<&Arc<crate::chain_anchor::AnchoringController>> {
2751 self.chain.chain_anchor.as_ref()
2752 }
2753
2754 #[cfg(feature = "ecc")]
2756 pub fn ecc_hnsw(&self) -> Option<&Arc<crate::hnsw_service::HnswService>> {
2757 self.ecc.hnsw.as_ref()
2758 }
2759
2760 #[cfg(feature = "ecc")]
2765 pub fn ecc_vector_backend(&self) -> Option<&Arc<dyn crate::vector_backend::VectorBackend>> {
2766 self.ecc.vector_backend.as_ref()
2767 }
2768
2769 #[cfg(feature = "ecc")]
2771 pub fn ecc_spatial(&self) -> Option<&Arc<crate::spatial_service::SpatialService>> {
2772 self.ecc.spatial.as_ref()
2773 }
2774
2775 #[cfg(feature = "ecc")]
2777 pub fn ecc_causal(&self) -> Option<&Arc<crate::causal::CausalGraph>> {
2778 self.ecc.causal.as_ref()
2779 }
2780
2781 #[cfg(feature = "ecc")]
2783 pub fn ecc_tick(&self) -> Option<&Arc<crate::cognitive_tick::CognitiveTick>> {
2784 self.ecc.tick.as_ref()
2785 }
2786
2787 #[cfg(feature = "ecc")]
2789 pub fn ecc_calibration(&self) -> Option<&crate::calibration::EccCalibration> {
2790 self.ecc.calibration.as_ref()
2791 }
2792
2793 #[cfg(feature = "ecc")]
2795 pub fn ecc_crossrefs(&self) -> Option<&Arc<crate::crossref::CrossRefStore>> {
2796 self.ecc.crossrefs.as_ref()
2797 }
2798
2799 #[cfg(feature = "ecc")]
2801 pub fn ecc_impulses(&self) -> Option<&Arc<crate::impulse::ImpulseQueue>> {
2802 self.ecc.impulses.as_ref()
2803 }
2804
2805 #[cfg(feature = "ecc")]
2807 pub fn ecc_eml_coherence(
2808 &self,
2809 ) -> Option<&Arc<std::sync::Mutex<crate::eml_coherence::EmlCoherenceModel>>> {
2810 self.ecc.eml_coherence.as_ref()
2811 }
2812
2813 #[cfg(feature = "os-patterns")]
2815 pub fn metrics_registry(&self) -> Option<&Arc<crate::metrics::MetricsRegistry>> {
2816 self.observability.metrics_registry.as_ref()
2817 }
2818
2819 #[cfg(feature = "os-patterns")]
2821 pub fn log_service(&self) -> Option<&Arc<crate::log_service::LogService>> {
2822 self.observability.log_service.as_ref()
2823 }
2824
2825 #[cfg(feature = "os-patterns")]
2827 pub fn timer_service(&self) -> Option<&Arc<crate::timer::TimerService>> {
2828 self.observability.timer_service.as_ref()
2829 }
2830
2831 #[cfg(feature = "os-patterns")]
2833 pub fn dead_letter_queue(&self) -> Option<&Arc<crate::dead_letter::DeadLetterQueue>> {
2834 self.observability.dead_letter_queue.as_ref()
2835 }
2836
2837 pub fn take_app_context(&mut self) -> Option<AppContext<P>> {
2843 self.app_context.take()
2844 }
2845}
2846
2847#[cfg(feature = "exochain")]
2855fn unrestorable_chain_msg(path: &std::path::Path, cause: &str) -> String {
2856 format!(
2857 "chain at {} failed to restore ({cause}); refusing to start a fresh chain over it. \
2858 Move or rename {} (and its .rvf/.json sibling) aside, then restart; \
2859 if a newer binary wrote it, upgrade",
2860 path.display(),
2861 path.display()
2862 )
2863}
2864
2865#[cfg(test)]
2866mod tests {
2867 use super::*;
2868 use clawft_platform::NativePlatform;
2869 use clawft_types::config::{AgentDefaults, AgentsConfig};
2870
2871 fn test_config() -> Config {
2872 Config {
2873 agents: AgentsConfig {
2874 defaults: AgentDefaults {
2875 workspace: "~/.clawft/workspace".into(),
2876 model: "test/model".into(),
2877 max_tokens: 1024,
2878 temperature: 0.5,
2879 max_tool_iterations: 5,
2880 memory_window: 10,
2881 },
2882 ..AgentsConfig::default()
2883 },
2884 ..Config::default()
2885 }
2886 }
2887
2888 fn test_kernel_config() -> KernelConfig {
2889 KernelConfig {
2890 governance: Default::default(),
2891 profile: None,
2892 shared_services: Default::default(),
2893 enabled: true,
2894 max_processes: 16,
2895 health_check_interval_secs: 5,
2896 cluster: None,
2897 chain: None,
2898 resource_tree: None,
2899 vector: None,
2900 spatial: None,
2901 profiles: None,
2902 pairing: None,
2903 mesh: None,
2904 anchor: None,
2905 ipc_tcp: None,
2906 llm: None,
2907 agent: None,
2908 brand: ::clawft_types::config::DEFAULT_BRAND.to_string(),
2909 }
2910 }
2911
2912 #[cfg(all(feature = "native", feature = "mesh"))]
2914 #[tokio::test]
2915 async fn boot_registers_mesh_system_service() {
2916 use clawft_types::config::MeshConfig;
2917 use crate::health::HealthStatus;
2918
2919 let platform = Arc::new(NativePlatform::new());
2920 let mut kconfig = test_kernel_config();
2921 kconfig.mesh = Some(MeshConfig {
2922 enabled: true,
2923 transport: "tcp".into(),
2924 listen_addr: "127.0.0.1:0".into(),
2926 discovery: false,
2927 seed_peers: vec![],
2928 noise: false,
2929 noise_key_path: None,
2930 ..MeshConfig::default()
2931 });
2932
2933 let mut kernel = Kernel::boot(test_config(), kconfig, platform)
2934 .await
2935 .expect("boot with mesh");
2936
2937 let mesh_svc = kernel
2938 .services()
2939 .get("mesh")
2940 .expect("mesh SystemService should be registered when mesh.enabled");
2941 assert_eq!(mesh_svc.name(), "mesh");
2942 assert_eq!(
2943 mesh_svc.health_check().await,
2944 HealthStatus::Healthy,
2945 "start_all should have started mesh"
2946 );
2947
2948 kernel.shutdown().await.unwrap();
2949 assert!(
2951 matches!(
2952 mesh_svc.health_check().await,
2953 HealthStatus::Unhealthy(_)
2954 ),
2955 "mesh should be unhealthy after shutdown stop"
2956 );
2957 }
2958
2959 #[cfg(all(feature = "native", feature = "mesh"))]
2961 #[tokio::test]
2962 async fn boot_fails_when_mesh_listener_cannot_bind() {
2963 use clawft_types::config::MeshConfig;
2964
2965 let taken = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
2966 let addr = taken.local_addr().unwrap().to_string();
2967
2968 let mut kconfig = test_kernel_config();
2969 kconfig.mesh = Some(MeshConfig {
2970 enabled: true,
2971 listen_addr: addr.clone(),
2972 ..MeshConfig::default()
2973 });
2974 let err = Kernel::boot(test_config(), kconfig, Arc::new(NativePlatform::new()))
2975 .await
2976 .err()
2977 .expect("boot must fail when the mesh port is taken");
2978 let msg = err.to_string();
2979 assert!(msg.contains(&addr), "error should name the address: {msg}");
2980 assert!(msg.contains("another kernel"), "error should name the cause: {msg}");
2981 }
2982
2983 #[cfg(all(feature = "native", feature = "exochain", unix))]
2986 #[tokio::test]
2987 async fn user_key_symlink_or_loose_mode_is_refused_at_boot() {
2988 use clawft_types::config::ChainConfig;
2989 use std::os::unix::fs::PermissionsExt;
2990
2991 let home = tempfile::tempdir().unwrap();
2992 let weftos = home.path().join(".weftos");
2993 let chain_dir = weftos.join("chain");
2994 std::fs::create_dir_all(&chain_dir).unwrap();
2995 let mk = |ckpt: &std::path::Path| {
2996 let mut kc = test_kernel_config();
2997 kc.chain = Some(ChainConfig {
2998 enabled: true,
2999 checkpoint_path: Some(ckpt.display().to_string()),
3000 ..ChainConfig::default()
3001 });
3002 kc
3003 };
3004 let ckpt = chain_dir.join("chain.json");
3005
3006 let user_key = weftos.join("user.key");
3008 std::fs::write(&user_key, [3u8; 32]).unwrap();
3009 std::fs::set_permissions(&user_key, std::fs::Permissions::from_mode(0o644)).unwrap();
3010 let err = Kernel::boot(test_config(), mk(&ckpt), Arc::new(NativePlatform::new()))
3011 .await
3012 .err()
3013 .expect("loose user.key must refuse boot");
3014 assert!(err.to_string().contains("user.key"), "{err}");
3015
3016 std::fs::remove_file(&user_key).unwrap();
3018 let target = home.path().join("elsewhere.key");
3019 std::fs::write(&target, [3u8; 32]).unwrap();
3020 std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o600)).unwrap();
3021 std::os::unix::fs::symlink(&target, &user_key).unwrap();
3022 let err = Kernel::boot(test_config(), mk(&ckpt), Arc::new(NativePlatform::new()))
3023 .await
3024 .err()
3025 .expect("symlinked user.key must refuse boot");
3026 assert!(err.to_string().contains("user.key"), "{err}");
3027 }
3028
3029 #[cfg(all(feature = "native", feature = "mesh"))]
3032 #[tokio::test]
3033 async fn service_mode_uses_the_service_node_id_and_binds_nothing() {
3034 use clawft_types::config::MeshConfig;
3035
3036 let taken = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
3037 let addr = taken.local_addr().unwrap().to_string();
3038 let service_id = crate::node_id::node_id_from_pubkey(&[42u8; 32]);
3039
3040 let mut kconfig = test_kernel_config();
3041 kconfig.mesh = Some(MeshConfig {
3042 enabled: true,
3043 listen_addr: addr,
3044 ..MeshConfig::default()
3045 });
3046 let mut kernel = Kernel::boot_in_service_mode(
3047 test_config(),
3048 kconfig,
3049 Arc::new(NativePlatform::new()),
3050 service_id.clone(),
3051 )
3052 .await
3053 .expect("service mode must not try to bind the taken port");
3054 assert_eq!(kernel.cluster_membership().local_node_id(), service_id);
3055 assert!(kernel.services().get("mesh").is_none(), "no in-kernel mesh service");
3056 kernel.shutdown().await.unwrap();
3057 }
3058
3059 #[cfg(all(feature = "native", feature = "mesh"))]
3062 #[tokio::test]
3063 async fn service_mode_refuses_a_malformed_node_id() {
3064 let err = Kernel::boot_in_service_mode(
3065 test_config(),
3066 test_kernel_config(),
3067 Arc::new(NativePlatform::new()),
3068 "nope".into(),
3069 )
3070 .await
3071 .err()
3072 .expect("must fail");
3073 assert!(err.to_string().contains("node id"), "{err}");
3074 }
3075
3076 #[cfg(all(feature = "native", feature = "mesh"))]
3079 #[tokio::test]
3080 async fn required_service_fails_a_boot_without_a_link() {
3081 use clawft_types::config::{MeshConfig, MeshServicePolicy};
3082
3083 let mut kconfig = test_kernel_config();
3084 kconfig.mesh = Some(MeshConfig {
3085 enabled: true,
3086 listen_addr: "127.0.0.1:0".into(),
3087 service: MeshServicePolicy::Required,
3088 ..MeshConfig::default()
3089 });
3090 let err = Kernel::boot(test_config(), kconfig, Arc::new(NativePlatform::new()))
3091 .await
3092 .err()
3093 .expect("required without a service link must fail");
3094 assert!(err.to_string().contains("required"), "{err}");
3095 }
3096
3097 #[cfg(all(feature = "native", feature = "mesh"))]
3099 #[tokio::test]
3100 async fn boot_with_mesh_disabled_never_binds() {
3101 use clawft_types::config::MeshConfig;
3102
3103 let taken = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
3104 let mut kconfig = test_kernel_config();
3105 kconfig.mesh = Some(MeshConfig {
3106 enabled: false,
3107 listen_addr: taken.local_addr().unwrap().to_string(),
3108 ..MeshConfig::default()
3109 });
3110 let mut kernel = Kernel::boot(test_config(), kconfig, Arc::new(NativePlatform::new()))
3111 .await
3112 .expect("mesh-off boot must not touch the listener");
3113 kernel.shutdown().await.unwrap();
3114 }
3115
3116 #[cfg(all(feature = "native", feature = "mesh"))]
3118 #[tokio::test]
3119 async fn node_id_derives_from_node_key_and_survives_reboot() {
3120 let seed = [9u8; 32];
3121 let expected = crate::node_id::node_id_from_pubkey(
3122 &ed25519_dalek::SigningKey::from_bytes(&seed)
3123 .verifying_key()
3124 .to_bytes(),
3125 );
3126 for _ in 0..2 {
3127 let mut kernel = Kernel::boot_with_node_key(
3128 test_config(),
3129 test_kernel_config(),
3130 Arc::new(NativePlatform::new()),
3131 Some(seed),
3132 )
3133 .await
3134 .expect("boot with node key");
3135 assert_eq!(kernel.cluster_membership().local_node_id(), expected);
3136 kernel.shutdown().await.unwrap();
3137 }
3138 }
3139
3140 #[cfg(all(feature = "native", feature = "mesh"))]
3142 #[tokio::test]
3143 async fn mesh_node_id_equals_cluster_node_id() {
3144 use clawft_types::config::MeshConfig;
3145
3146 let mut kconfig = test_kernel_config();
3147 kconfig.mesh = Some(MeshConfig {
3148 enabled: true,
3149 listen_addr: "127.0.0.1:0".into(),
3150 ..MeshConfig::default()
3151 });
3152 let mut kernel = Kernel::boot_with_node_key(
3153 test_config(),
3154 kconfig,
3155 Arc::new(NativePlatform::new()),
3156 Some([3u8; 32]),
3157 )
3158 .await
3159 .expect("boot with mesh");
3160 let mesh_id = kernel
3161 .a2a_router()
3162 .mesh_runtime()
3163 .expect("mesh runtime attached")
3164 .node_id()
3165 .to_owned();
3166 assert_eq!(mesh_id, kernel.cluster_membership().local_node_id());
3167 assert!(crate::node_id::is_node_id(&mesh_id));
3168 kernel.shutdown().await.unwrap();
3169 }
3170
3171 #[tokio::test]
3172 async fn boot_and_shutdown() {
3173 let platform = Arc::new(NativePlatform::new());
3174 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3175 .await
3176 .unwrap();
3177
3178 assert_eq!(*kernel.state(), KernelState::Running);
3179 let _uptime = kernel.uptime();
3181
3182 let kernel_proc = kernel.process_table().get(0).unwrap();
3184 assert_eq!(kernel_proc.agent_id, "kernel");
3185 assert_eq!(kernel_proc.state, ProcessState::Running);
3186
3187 kernel.shutdown().await.unwrap();
3188 assert_eq!(*kernel.state(), KernelState::Halted);
3189 }
3190
3191 #[tokio::test]
3192 async fn double_shutdown_fails() {
3193 let platform = Arc::new(NativePlatform::new());
3194 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3195 .await
3196 .unwrap();
3197
3198 kernel.shutdown().await.unwrap();
3199 let result = kernel.shutdown().await;
3200 assert!(result.is_err());
3201 }
3202
3203 #[tokio::test]
3204 async fn boot_log_has_events() {
3205 let platform = Arc::new(NativePlatform::new());
3206 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3207 .await
3208 .unwrap();
3209
3210 let log = kernel.boot_log();
3211 assert!(!log.is_empty());
3212
3213 let formatted = log.format_all();
3214 assert!(formatted.contains(&format!("WeftOS v{}", env!("CARGO_PKG_VERSION"))));
3215 assert!(formatted.contains("Boot complete"));
3216 }
3217
3218 #[tokio::test]
3219 async fn process_table_accessible() {
3220 let platform = Arc::new(NativePlatform::new());
3221 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3222 .await
3223 .unwrap();
3224
3225 let pt = kernel.process_table();
3226 assert_eq!(pt.len(), 1); assert_eq!(pt.max_processes(), 16);
3228 }
3229
3230 #[tokio::test]
3231 async fn services_accessible() {
3232 let platform = Arc::new(NativePlatform::new());
3233 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3234 .await
3235 .unwrap();
3236
3237 let count = kernel.services().len();
3239 #[cfg(all(feature = "ecc", feature = "cluster"))]
3240 assert_eq!(
3241 count, 6,
3242 "expected cron+containers+assess+cluster+hnsw+cognitive_tick"
3243 );
3244 #[cfg(all(feature = "ecc", not(feature = "cluster")))]
3245 assert_eq!(
3246 count, 5,
3247 "expected cron+containers+assess+hnsw+cognitive_tick"
3248 );
3249 #[cfg(all(not(feature = "ecc"), feature = "cluster"))]
3250 assert_eq!(count, 4, "expected cron+containers+assess+cluster");
3251 #[cfg(all(not(feature = "ecc"), not(feature = "cluster")))]
3252 assert_eq!(count, 3, "expected cron+containers+assess");
3253 }
3254
3255 #[tokio::test]
3256 async fn take_app_context() {
3257 let platform = Arc::new(NativePlatform::new());
3258 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3259 .await
3260 .unwrap();
3261
3262 let ctx = kernel.take_app_context();
3263 assert!(ctx.is_some());
3264
3265 let ctx2 = kernel.take_app_context();
3267 assert!(ctx2.is_none());
3268 }
3269
3270 #[tokio::test]
3271 async fn ipc_accessible() {
3272 let platform = Arc::new(NativePlatform::new());
3273 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3274 .await
3275 .unwrap();
3276
3277 let ipc = kernel.ipc();
3278 assert!(Arc::ptr_eq(ipc.bus(), kernel.bus()));
3279 }
3280
3281 #[test]
3282 fn kernel_state_display() {
3283 assert_eq!(KernelState::Booting.to_string(), "booting");
3284 assert_eq!(KernelState::Running.to_string(), "running");
3285 assert_eq!(KernelState::ShuttingDown.to_string(), "shutting_down");
3286 assert_eq!(KernelState::Halted.to_string(), "halted");
3287 }
3288
3289 #[cfg(all(feature = "exochain", feature = "ecc", feature = "wasm-sandbox"))]
3295 fn test_kernel_config_full_stack() -> KernelConfig {
3296 use clawft_types::config::{ChainConfig, ResourceTreeConfig};
3297 KernelConfig {
3298 enabled: true,
3299 max_processes: 32,
3300 health_check_interval_secs: 5,
3301 cluster: None,
3302 chain: Some(ChainConfig {
3303 enabled: true,
3304 checkpoint_interval: 10_000,
3305 chain_id: 0,
3306 checkpoint_path: None,
3307 external_anchor: None,
3308 }),
3309 resource_tree: Some(ResourceTreeConfig {
3310 enabled: true,
3311 checkpoint_path: None,
3312 }),
3313 vector: None,
3314 spatial: None,
3315 profiles: None,
3316 pairing: None,
3317 mesh: None,
3318 anchor: None,
3319 ipc_tcp: None,
3320 llm: None,
3321 agent: None,
3322 brand: ::clawft_types::config::DEFAULT_BRAND.to_string(),
3323 ..KernelConfig::default()
3324 }
3325 }
3326
3327 #[tokio::test]
3332 #[cfg(all(feature = "exochain", feature = "ecc", feature = "wasm-sandbox"))]
3333 async fn integration_full_stack_kernel() {
3334 let platform = Arc::new(NativePlatform::new());
3335 let kernel = Kernel::boot(test_config(), test_kernel_config_full_stack(), platform)
3336 .await
3337 .unwrap();
3338
3339 assert_eq!(*kernel.state(), KernelState::Running);
3341
3342 let svc_count = kernel.services().len();
3345 assert!(svc_count >= 4, "expected >= 4 services, got {svc_count}");
3346
3347 let spawn_result = kernel.supervisor().spawn(crate::supervisor::SpawnRequest {
3349 agent_id: "integration-agent-1".into(),
3350 capabilities: None,
3351 parent_pid: None,
3352 env: std::collections::HashMap::new(),
3353 backend: None, });
3355 assert!(
3356 spawn_result.is_ok(),
3357 "native agent spawn failed: {:?}",
3358 spawn_result.err()
3359 );
3360 let agent_pid = spawn_result.unwrap().pid;
3361 assert!(agent_pid > 0, "agent should get PID > 0");
3362
3363 let processes = kernel.process_table().list();
3365 assert!(
3366 processes.iter().any(|p| p.pid == agent_pid),
3367 "agent not in process table"
3368 );
3369
3370 let a2a = kernel.a2a_router();
3372 let _inbox = a2a.create_inbox(agent_pid);
3373
3374 let msg = crate::ipc::KernelMessage::new(
3376 0, crate::ipc::MessageTarget::Process(agent_pid),
3378 crate::ipc::MessagePayload::Json(serde_json::json!({"cmd": "ping"})),
3379 );
3380 let send_result = a2a.send(msg).await;
3381 assert!(
3383 send_result.is_ok(),
3384 "A2A send failed: {:?}",
3385 send_result.err()
3386 );
3387
3388 let wasm_config = crate::wasm_runner::WasmSandboxConfig::default();
3390 let wasm_runner = crate::wasm_runner::WasmToolRunner::new(wasm_config);
3391
3392 let noop_wat = r#"(module (func (export "_start")))"#;
3394 let result = wasm_runner
3395 .execute_bytes(
3396 "integration-tool",
3397 noop_wat.as_bytes(),
3398 serde_json::json!({}),
3399 )
3400 .await;
3401 assert!(result.is_ok(), "WASM execution failed: {:?}", result.err());
3402 let wasm_result = result.unwrap();
3403 assert_eq!(wasm_result.exit_code, 0, "WASM tool should exit cleanly");
3404 assert!(wasm_result.fuel_consumed > 0, "WASM should consume fuel");
3405
3406 let wasm_runner_arc = Arc::new(crate::wasm_runner::WasmToolRunner::new(
3408 crate::wasm_runner::WasmSandboxConfig::default(),
3409 ));
3410 let mut registry = crate::wasm_runner::ToolRegistry::new();
3411 registry
3412 .register_wasm_tool(
3413 "demo-tool",
3414 "A demo WASM tool for integration testing",
3415 noop_wat.as_bytes().to_vec(),
3416 wasm_runner_arc,
3417 )
3418 .unwrap();
3419 assert!(
3420 registry.get("demo-tool").is_some(),
3421 "WASM tool should be in registry"
3422 );
3423 let tool_list = registry.list();
3424 assert!(
3425 tool_list.contains(&"demo-tool".to_string()),
3426 "WASM tool should appear in listing"
3427 );
3428
3429 let svc_list = kernel.services().list();
3431 assert!(
3432 svc_list.iter().any(|(name, _)| name == "containers"),
3433 "container service should be registered"
3434 );
3435
3436 let ecc_hnsw = kernel.ecc_hnsw().expect("HNSW service should be present");
3438 assert_eq!(
3440 ecc_hnsw.len(),
3441 0,
3442 "HNSW should be empty after calibration cleanup"
3443 );
3444
3445 let ecc_causal = kernel.ecc_causal().expect("causal graph should be present");
3446 assert_eq!(
3447 ecc_causal.node_count(),
3448 0,
3449 "causal graph should be empty after cleanup"
3450 );
3451
3452 let _ecc_tick = kernel.ecc_tick().expect("cognitive tick should be present");
3453
3454 let calibration = kernel.ecc_calibration().expect("calibration should exist");
3455 assert!(
3456 calibration.compute_p95_us > 0,
3457 "calibration should have run"
3458 );
3459 assert!(
3460 calibration.tick_interval_ms > 0,
3461 "tick interval should be auto-calibrated"
3462 );
3463
3464 ecc_hnsw.insert(
3466 "test-doc".into(),
3467 vec![1.0, 0.0, 0.0, 0.0],
3468 serde_json::json!({"text": "hello"}),
3469 );
3470 assert_eq!(ecc_hnsw.len(), 1);
3471 let results = ecc_hnsw.search(&[1.0, 0.0, 0.0, 0.0], 5);
3472 assert_eq!(results.len(), 1);
3473 assert_eq!(results[0].id, "test-doc");
3474
3475 let node1 = ecc_causal.add_node("concept-A".into(), serde_json::json!({}));
3477 let node2 = ecc_causal.add_node("concept-B".into(), serde_json::json!({}));
3478 ecc_causal.link(
3479 node1,
3480 node2,
3481 crate::causal::CausalEdgeType::Causes,
3482 1.0,
3483 0,
3484 0,
3485 );
3486 assert_eq!(ecc_causal.node_count(), 2);
3487 assert_eq!(ecc_causal.edge_count(), 1);
3488 let path = ecc_causal.find_path(node1, node2, 5);
3489 assert!(path.is_some(), "should find path between linked nodes");
3490
3491 let chain = kernel.chain_manager().expect("chain should exist");
3493 assert!(
3494 chain.sequence() > 5,
3495 "chain should have boot events, got seq={}",
3496 chain.sequence()
3497 );
3498
3499 let verify_result = chain.verify_integrity();
3501 assert!(
3502 verify_result.valid,
3503 "chain should be valid: {:?}",
3504 verify_result.errors
3505 );
3506 assert!(verify_result.errors.is_empty(), "no integrity errors");
3507
3508 let _governance = kernel
3510 .governance_gate()
3511 .expect("governance gate should exist");
3512 let all_events = chain.tail(0);
3513 let genesis_events: Vec<_> = all_events
3514 .iter()
3515 .filter(|e| e.kind == "governance.genesis")
3516 .collect();
3517 assert!(
3518 !genesis_events.is_empty(),
3519 "governance genesis should be on chain"
3520 );
3521
3522 let genesis_payload = genesis_events
3525 .iter()
3526 .filter_map(|e| e.payload.as_ref())
3527 .find(|p| p.get("version").and_then(|v| v.as_str()) == Some("2.0.0"))
3528 .expect("should find v2.0.0 governance genesis on chain");
3529 assert_eq!(
3530 genesis_payload["rule_count"].as_u64().unwrap(),
3531 26,
3532 "genesis should contain 26 rules (22 constitutional/SOP + 3 browser_policy + 1 binding_thread)"
3533 );
3534 assert_eq!(
3535 genesis_payload["version"].as_str().unwrap(),
3536 "2.0.0",
3537 "genesis version should be 2.0.0"
3538 );
3539
3540 let rule_events: Vec<_> = all_events
3542 .iter()
3543 .filter(|e| e.kind == "governance.rule")
3544 .collect();
3545 assert!(
3546 rule_events.len() >= 26,
3547 "at least 26 genesis rules should be individually anchored, got {}",
3548 rule_events.len(),
3549 );
3550
3551 let rule_ids: Vec<&str> = rule_events
3553 .iter()
3554 .filter_map(|e| e.payload.as_ref()?.get("rule_id")?.as_str())
3555 .collect();
3556 for expected_id in &[
3557 "GOV-001", "GOV-002", "GOV-003", "GOV-004", "GOV-005", "GOV-006", "GOV-007",
3558 "SOP-L001", "SOP-L002", "SOP-L003", "SOP-L004", "SOP-L005", "SOP-L006", "SOP-E001",
3559 "SOP-E002", "SOP-E003", "SOP-E004", "SOP-E005", "SOP-J001", "SOP-J002", "SOP-J003",
3560 "SOP-J004", "BP-001", "BP-002", "BP-003", "soul.binding_thread_intact",
3561 ] {
3562 assert!(
3563 rule_ids.contains(expected_id),
3564 "{expected_id} should be anchored on chain"
3565 );
3566 }
3567
3568 let ecc_events: Vec<_> = all_events
3570 .iter()
3571 .filter(|e| e.kind.starts_with("ecc."))
3572 .collect();
3573 assert!(
3574 !ecc_events.is_empty(),
3575 "ECC boot calibration should be logged to chain"
3576 );
3577
3578 let tree = kernel.tree_manager().expect("tree should exist");
3580 let stats = tree.stats();
3581 assert!(
3582 stats.node_count > 20,
3583 "tree should have many nodes (got {})",
3584 stats.node_count
3585 );
3586
3587 assert!(
3590 kernel.services().get("cron").is_some(),
3591 "cron service should be accessible"
3592 );
3593 assert!(
3594 kernel.services().get("containers").is_some(),
3595 "container service should be accessible"
3596 );
3597
3598 let mut kernel = kernel;
3600 kernel.shutdown().await.unwrap();
3601 assert_eq!(*kernel.state(), KernelState::Halted);
3602 }
3603
3604 #[test]
3610 #[cfg(feature = "wasm-sandbox")]
3611 fn integration_cross_backend_tools() {
3612 use crate::governance::EffectVector;
3613 use crate::wasm_runner::{
3614 BuiltinTool, BuiltinToolSpec, ToolCategory, ToolError, ToolRegistry, WasmSandboxConfig,
3615 WasmToolRunner,
3616 };
3617
3618 struct EchoTool;
3620
3621 impl BuiltinTool for EchoTool {
3622 fn name(&self) -> &str {
3623 "native.echo"
3624 }
3625 fn spec(&self) -> &BuiltinToolSpec {
3626 static SPEC: std::sync::OnceLock<BuiltinToolSpec> = std::sync::OnceLock::new();
3628 SPEC.get_or_init(|| BuiltinToolSpec {
3629 name: "native.echo".into(),
3630 category: ToolCategory::System,
3631 description: "Echoes input back".into(),
3632 parameters: serde_json::json!({}),
3633 gate_action: "tool.native.echo".into(),
3634 effect: EffectVector::default(),
3635 native: true,
3636 })
3637 }
3638 fn execute(&self, args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
3639 Ok(serde_json::json!({"echo": args}))
3640 }
3641 }
3642
3643 let mut registry = ToolRegistry::new();
3645
3646 registry.register(Arc::new(EchoTool));
3648
3649 let runner = Arc::new(WasmToolRunner::new(WasmSandboxConfig::default()));
3651 let noop_wat = r#"(module (func (export "_start")))"#;
3652 registry
3653 .register_wasm_tool(
3654 "wasm.noop",
3655 "A no-op WASM tool",
3656 noop_wat.as_bytes().to_vec(),
3657 runner,
3658 )
3659 .unwrap();
3660
3661 assert_eq!(registry.list().len(), 2);
3663
3664 let native = registry
3665 .get("native.echo")
3666 .expect("native tool should exist");
3667 assert!(native.spec().native, "native tool should be marked native");
3668
3669 let wasm = registry.get("wasm.noop").expect("wasm tool should exist");
3670 assert!(!wasm.spec().native, "wasm tool should NOT be marked native");
3671
3672 let result = native
3674 .execute(serde_json::json!({"hello": "world"}))
3675 .unwrap();
3676 assert_eq!(result["echo"]["hello"], "world");
3677
3678 let parent = Arc::new(registry);
3681 let mut child = ToolRegistry::with_parent(parent);
3682
3683 assert!(
3685 child.get("native.echo").is_some(),
3686 "child sees parent native tool"
3687 );
3688 assert!(
3689 child.get("wasm.noop").is_some(),
3690 "child sees parent wasm tool"
3691 );
3692
3693 struct OverrideTool;
3695 impl BuiltinTool for OverrideTool {
3696 fn name(&self) -> &str {
3697 "native.echo"
3698 }
3699 fn spec(&self) -> &BuiltinToolSpec {
3700 static SPEC: std::sync::OnceLock<BuiltinToolSpec> = std::sync::OnceLock::new();
3701 SPEC.get_or_init(|| BuiltinToolSpec {
3702 name: "native.echo".into(),
3703 category: ToolCategory::System,
3704 description: "Overridden echo".into(),
3705 parameters: serde_json::json!({}),
3706 gate_action: "tool.native.echo".into(),
3707 effect: EffectVector::default(),
3708 native: true,
3709 })
3710 }
3711 fn execute(&self, _args: serde_json::Value) -> Result<serde_json::Value, ToolError> {
3712 Ok(serde_json::json!({"overridden": true}))
3713 }
3714 }
3715
3716 child.register(Arc::new(OverrideTool));
3717 let result = child
3718 .get("native.echo")
3719 .unwrap()
3720 .execute(serde_json::json!({}))
3721 .unwrap();
3722 assert_eq!(
3723 result["overridden"], true,
3724 "child should shadow parent tool"
3725 );
3726
3727 assert!(
3729 child.get("wasm.noop").is_some(),
3730 "WASM tool still reachable via parent"
3731 );
3732 }
3733
3734 #[tokio::test]
3737 async fn boot_reaches_running_state() {
3738 let platform = Arc::new(NativePlatform::new());
3739 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3740 .await
3741 .unwrap();
3742 assert_eq!(*kernel.state(), KernelState::Running);
3743 }
3744
3745 #[tokio::test]
3746 async fn boot_registers_cron_service() {
3747 let platform = Arc::new(NativePlatform::new());
3748 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3749 .await
3750 .unwrap();
3751 assert!(
3752 kernel.services().get("cron").is_some(),
3753 "cron service must be registered at boot"
3754 );
3755 }
3756
3757 #[tokio::test]
3758 async fn boot_registers_container_service() {
3759 let platform = Arc::new(NativePlatform::new());
3760 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3761 .await
3762 .unwrap();
3763 assert!(
3764 kernel.services().get("containers").is_some(),
3765 "container service must be registered at boot"
3766 );
3767 }
3768
3769 #[cfg(feature = "exochain")]
3770 fn test_kernel_config_exochain() -> KernelConfig {
3771 use clawft_types::config::{ChainConfig, ResourceTreeConfig};
3772 KernelConfig {
3773 governance: Default::default(),
3774 profile: None,
3775 shared_services: Default::default(),
3776 enabled: true,
3777 max_processes: 16,
3778 health_check_interval_secs: 5,
3779 cluster: None,
3780 chain: Some(ChainConfig {
3781 enabled: true,
3782 checkpoint_interval: 10_000,
3783 chain_id: 0,
3784 checkpoint_path: None,
3785 external_anchor: None,
3786 }),
3787 resource_tree: Some(ResourceTreeConfig {
3788 enabled: true,
3789 checkpoint_path: None,
3790 }),
3791 vector: None,
3792 spatial: None,
3793 profiles: None,
3794 pairing: None,
3795 mesh: None,
3796 anchor: None,
3797 ipc_tcp: None,
3798 llm: None,
3799 agent: None,
3800 brand: ::clawft_types::config::DEFAULT_BRAND.to_string(),
3801 }
3802 }
3803
3804 #[cfg(all(feature = "exochain", unix))]
3806 #[tokio::test]
3807 async fn boot_refuses_a_chain_held_by_another_kernel() {
3808 let dir = tempfile::tempdir().unwrap();
3809 let ckpt = dir.path().join("chain.json");
3810 let _holder = crate::chain_storage::ChainLock::acquire(&ckpt).unwrap();
3811 let mut kc = test_kernel_config_exochain();
3812 kc.chain.as_mut().unwrap().checkpoint_path = Some(ckpt.to_string_lossy().into_owned());
3813 let platform = Arc::new(NativePlatform::new());
3814 let err = Kernel::boot(test_config(), kc, platform)
3815 .await
3816 .err()
3817 .expect("boot must refuse a held chain");
3818 let msg = err.to_string();
3819 assert!(msg.contains("in use by another kernel"), "{msg}");
3820 assert!(msg.contains("--new-chain"), "{msg}");
3821 }
3822
3823 #[cfg(all(feature = "exochain", unix))]
3827 #[tokio::test]
3828 async fn boot_refuses_an_unrestorable_chain_and_keeps_the_file() {
3829 for rvf in [false, true] {
3830 let dir = tempfile::tempdir().unwrap();
3831 let ckpt = dir.path().join("chain.json");
3832 let chain_file = if rvf { ckpt.with_extension("rvf") } else { ckpt.clone() };
3833 let cm = crate::chain::ChainManager::new(0, 0);
3834 cm.append("s", "k", Some(serde_json::json!({"a": 1})));
3835 if rvf {
3836 cm.save_to_rvf(&chain_file).unwrap();
3837 } else {
3838 cm.save_to_file(&chain_file).unwrap();
3839 }
3840 let mut bytes = std::fs::read(&chain_file).unwrap();
3841 let mid = bytes.len() / 2;
3842 bytes[mid] ^= 0xFF; std::fs::write(&chain_file, &bytes).unwrap();
3844
3845 let mut kc = test_kernel_config_exochain();
3846 kc.chain.as_mut().unwrap().checkpoint_path =
3847 Some(ckpt.to_string_lossy().into_owned());
3848 let platform = Arc::new(NativePlatform::new());
3849 let err = Kernel::boot(test_config(), kc, platform)
3850 .await
3851 .err()
3852 .expect("boot must refuse an unrestorable chain");
3853 let msg = err.to_string();
3854 assert!(msg.contains("refusing to start a fresh chain"), "rvf={rvf}: {msg}");
3855 assert!(
3856 msg.contains("Move or rename") && msg.contains(".rvf/.json sibling") && msg.contains("upgrade"),
3857 "rvf={rvf}: {msg}"
3858 );
3859 assert!(!msg.contains("--new-chain"), "rvf={rvf}: {msg}");
3860 assert_eq!(std::fs::read(&chain_file).unwrap(), bytes, "rvf={rvf}: file changed");
3861 }
3862 }
3863
3864 #[cfg(feature = "exochain")]
3865 #[tokio::test]
3866 async fn boot_exochain_creates_chain_manager() {
3867 let platform = Arc::new(NativePlatform::new());
3868 let kernel = Kernel::boot(test_config(), test_kernel_config_exochain(), platform)
3869 .await
3870 .unwrap();
3871 assert!(
3872 kernel.chain_manager().is_some(),
3873 "chain manager must be present with exochain feature and enabled chain config"
3874 );
3875 }
3876
3877 #[cfg(feature = "exochain")]
3878 #[tokio::test]
3879 async fn boot_exochain_creates_tree_manager() {
3880 let platform = Arc::new(NativePlatform::new());
3881 let kernel = Kernel::boot(test_config(), test_kernel_config_exochain(), platform)
3882 .await
3883 .unwrap();
3884 assert!(
3885 kernel.tree_manager().is_some(),
3886 "tree manager must be present with exochain feature and enabled config"
3887 );
3888 }
3889
3890 #[cfg(feature = "exochain")]
3891 #[tokio::test]
3892 async fn boot_exochain_chain_has_boot_events() {
3893 let platform = Arc::new(NativePlatform::new());
3894 let kernel = Kernel::boot(test_config(), test_kernel_config_exochain(), platform)
3895 .await
3896 .unwrap();
3897 let chain = kernel.chain_manager().unwrap();
3898 assert!(
3900 chain.sequence() >= 6,
3901 "chain should have at least 6 boot events, got {}",
3902 chain.sequence()
3903 );
3904 }
3905
3906 #[cfg(feature = "exochain")]
3907 #[tokio::test]
3908 async fn boot_exochain_governance_gate_present() {
3909 let platform = Arc::new(NativePlatform::new());
3910 let kernel = Kernel::boot(test_config(), test_kernel_config_exochain(), platform)
3911 .await
3912 .unwrap();
3913 assert!(
3914 kernel.governance_gate().is_some(),
3915 "governance gate should be present when chain is enabled"
3916 );
3917 }
3918
3919 #[cfg(feature = "ecc")]
3920 #[tokio::test]
3921 async fn boot_ecc_registers_hnsw_service() {
3922 let platform = Arc::new(NativePlatform::new());
3923 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3924 .await
3925 .unwrap();
3926 assert!(
3927 kernel.ecc_hnsw().is_some(),
3928 "HNSW service must be present with ecc feature"
3929 );
3930 }
3931
3932 #[cfg(feature = "ecc")]
3933 #[tokio::test]
3934 async fn boot_ecc_registers_cognitive_tick() {
3935 let platform = Arc::new(NativePlatform::new());
3936 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3937 .await
3938 .unwrap();
3939 assert!(
3940 kernel.ecc_tick().is_some(),
3941 "cognitive tick must be present with ecc feature"
3942 );
3943 }
3944
3945 #[cfg(feature = "ecc")]
3946 #[tokio::test]
3947 async fn boot_ecc_calibration_has_valid_results() {
3948 let platform = Arc::new(NativePlatform::new());
3949 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3950 .await
3951 .unwrap();
3952 let cal = kernel.ecc_calibration().unwrap();
3953 assert!(cal.tick_interval_ms > 0, "tick interval must be positive");
3954 assert!(cal.compute_p50_us > 0, "p50 latency must be measured");
3955 assert!(cal.compute_p95_us >= cal.compute_p50_us, "p95 >= p50");
3956 }
3957
3958 #[cfg(feature = "ecc")]
3959 #[tokio::test]
3960 async fn boot_ecc_causal_graph_accessible() {
3961 let platform = Arc::new(NativePlatform::new());
3962 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3963 .await
3964 .unwrap();
3965 assert!(
3966 kernel.ecc_causal().is_some(),
3967 "causal graph must be accessible"
3968 );
3969 assert_eq!(
3970 kernel.ecc_causal().unwrap().node_count(),
3971 0,
3972 "causal graph starts empty"
3973 );
3974 }
3975
3976 #[cfg(feature = "ecc")]
3977 #[tokio::test]
3978 async fn boot_ecc_crossrefs_and_impulses_accessible() {
3979 let platform = Arc::new(NativePlatform::new());
3980 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
3981 .await
3982 .unwrap();
3983 assert!(
3984 kernel.ecc_crossrefs().is_some(),
3985 "cross-ref store must be accessible"
3986 );
3987 assert!(
3988 kernel.ecc_impulses().is_some(),
3989 "impulse queue must be accessible"
3990 );
3991 }
3992
3993 #[cfg(all(feature = "ecc", not(feature = "diskann")))]
4001 #[tokio::test]
4002 async fn boot_vector_strict_diskann_without_feature_fails() {
4003 let platform = Arc::new(NativePlatform::new());
4004 let mut kconfig = test_kernel_config();
4005 kconfig.vector = Some(clawft_types::config::VectorConfig {
4006 backend: clawft_types::config::VectorBackendKind::DiskAnn,
4007 strict: true,
4008 ..Default::default()
4009 });
4010 let result = Kernel::boot(test_config(), kconfig, platform).await;
4011 assert!(
4012 result.is_err(),
4013 "boot must fail when vector.strict=true and the diskann feature \
4014 is not compiled in"
4015 );
4016 }
4017
4018 #[cfg(all(feature = "ecc", not(feature = "diskann")))]
4019 #[tokio::test]
4020 async fn boot_vector_strict_hybrid_without_feature_fails() {
4021 let platform = Arc::new(NativePlatform::new());
4022 let mut kconfig = test_kernel_config();
4023 kconfig.vector = Some(clawft_types::config::VectorConfig {
4024 backend: clawft_types::config::VectorBackendKind::Hybrid,
4025 strict: true,
4026 ..Default::default()
4027 });
4028 let result = Kernel::boot(test_config(), kconfig, platform).await;
4029 assert!(
4030 result.is_err(),
4031 "boot must fail for Hybrid too — it wraps DiskAnnBackend as its \
4032 cold tier"
4033 );
4034 }
4035
4036 #[cfg(all(feature = "ecc", not(feature = "diskann")))]
4037 #[tokio::test]
4038 async fn boot_vector_non_strict_diskann_without_feature_warns_and_boots() {
4039 let platform = Arc::new(NativePlatform::new());
4040 let mut kconfig = test_kernel_config();
4041 kconfig.vector = Some(clawft_types::config::VectorConfig {
4042 backend: clawft_types::config::VectorBackendKind::DiskAnn,
4043 strict: false,
4044 ..Default::default()
4045 });
4046 let kernel = Kernel::boot(test_config(), kconfig, platform)
4047 .await
4048 .unwrap();
4049 let warned = kernel.boot_log().events().iter().any(|e| {
4050 e.level == crate::console::LogLevel::Warn && e.message.contains("diskann")
4051 });
4052 assert!(
4053 warned,
4054 "expected a Warn boot event naming the diskann feature mismatch"
4055 );
4056 }
4057
4058 #[tokio::test]
4059 async fn boot_cluster_membership_accessible() {
4060 let platform = Arc::new(NativePlatform::new());
4061 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4062 .await
4063 .unwrap();
4064 let cm = kernel.cluster_membership();
4065 assert!(
4066 !cm.local_node_id().is_empty(),
4067 "cluster membership should have a node ID"
4068 );
4069 }
4070
4071 #[tokio::test]
4072 async fn shutdown_transitions_to_halted() {
4073 let platform = Arc::new(NativePlatform::new());
4074 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4075 .await
4076 .unwrap();
4077 assert_eq!(*kernel.state(), KernelState::Running);
4078 kernel.shutdown().await.unwrap();
4079 assert_eq!(*kernel.state(), KernelState::Halted);
4080 }
4081
4082 #[tokio::test]
4083 async fn shutdown_from_halted_fails() {
4084 let platform = Arc::new(NativePlatform::new());
4085 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4086 .await
4087 .unwrap();
4088 kernel.shutdown().await.unwrap();
4089 let err = kernel.shutdown().await.unwrap_err();
4090 match err {
4091 KernelError::WrongState { expected, actual } => {
4092 assert_eq!(expected, "Running");
4093 assert_eq!(actual, "halted");
4094 }
4095 other => panic!("expected WrongState, got: {other:?}"),
4096 }
4097 }
4098
4099 #[tokio::test]
4100 async fn process_table_has_kernel_pid_zero() {
4101 let platform = Arc::new(NativePlatform::new());
4102 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4103 .await
4104 .unwrap();
4105 let entry = kernel.process_table().get(0).expect("PID 0 should exist");
4106 assert_eq!(entry.agent_id, "kernel");
4107 assert_eq!(entry.state, ProcessState::Running);
4108 assert_eq!(entry.pid, 0);
4109 }
4110
4111 #[tokio::test]
4112 async fn a2a_router_accessible_after_boot() {
4113 let platform = Arc::new(NativePlatform::new());
4114 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4115 .await
4116 .unwrap();
4117 let a2a = kernel.a2a_router();
4118 let _inbox = a2a.create_inbox(0); }
4122
4123 #[tokio::test]
4124 async fn boot_log_contains_expected_phases() {
4125 let platform = Arc::new(NativePlatform::new());
4126 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4127 .await
4128 .unwrap();
4129 let formatted = kernel.boot_log().format_all();
4130 assert!(
4131 formatted.contains(&format!("WeftOS v{}", env!("CARGO_PKG_VERSION"))),
4132 "should contain version"
4133 );
4134 assert!(
4135 formatted.contains("Service registry ready"),
4136 "should have service phase"
4137 );
4138 assert!(
4139 formatted.contains("A2A router ready"),
4140 "should have A2A phase"
4141 );
4142 assert!(
4143 formatted.contains("Boot complete"),
4144 "should have ready phase"
4145 );
4146 }
4147
4148 #[tokio::test]
4149 async fn event_log_populated_after_boot() {
4150 let platform = Arc::new(NativePlatform::new());
4151 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4152 .await
4153 .unwrap();
4154 let events = kernel.event_log();
4156 assert!(
4158 !events.is_empty(),
4159 "event log should have entries after boot"
4160 );
4161 }
4162
4163 #[tokio::test]
4164 async fn health_system_accessible() {
4165 let platform = Arc::new(NativePlatform::new());
4166 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4167 .await
4168 .unwrap();
4169 let _health = kernel.health();
4170 }
4172
4173 #[tokio::test]
4174 async fn uptime_is_positive_after_boot() {
4175 let platform = Arc::new(NativePlatform::new());
4176 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4177 .await
4178 .unwrap();
4179 let uptime = kernel.uptime();
4180 assert!(uptime.as_nanos() > 0, "uptime should be positive");
4181 }
4182
4183 #[tokio::test]
4184 async fn cron_service_accessible_and_empty() {
4185 let platform = Arc::new(NativePlatform::new());
4186 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4187 .await
4188 .unwrap();
4189 assert_eq!(kernel.cron_service().job_count(), 0, "no cron jobs at boot");
4190 }
4191
4192 #[tokio::test]
4193 async fn max_processes_matches_config() {
4194 let platform = Arc::new(NativePlatform::new());
4195 let mut kconfig = test_kernel_config();
4196 kconfig.max_processes = 42;
4197 let kernel = Kernel::boot(test_config(), kconfig, platform)
4198 .await
4199 .unwrap();
4200 assert_eq!(kernel.process_table().max_processes(), 42);
4201 }
4202
4203 #[test]
4206 fn kernel_state_serde_roundtrip_all_variants() {
4207 for state in [
4208 KernelState::Booting,
4209 KernelState::Running,
4210 KernelState::ShuttingDown,
4211 KernelState::Halted,
4212 ] {
4213 let json = serde_json::to_string(&state).unwrap();
4214 let restored: KernelState = serde_json::from_str(&json).unwrap();
4215 assert_eq!(restored, state);
4216 }
4217 }
4218
4219 #[test]
4220 fn kernel_state_equality() {
4221 assert_eq!(KernelState::Running, KernelState::Running);
4222 assert_ne!(KernelState::Running, KernelState::Halted);
4223 }
4224
4225 #[tokio::test]
4226 async fn kernel_state_is_running_after_boot() {
4227 let platform = Arc::new(NativePlatform::new());
4228 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4229 .await
4230 .unwrap();
4231 assert_eq!(kernel.state(), &KernelState::Running);
4232 }
4233
4234 #[tokio::test]
4235 async fn boot_log_has_entries() {
4236 let platform = Arc::new(NativePlatform::new());
4237 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4238 .await
4239 .unwrap();
4240 let log = kernel.boot_log();
4241 assert!(!log.events().is_empty(), "boot log should have events");
4242 }
4243
4244 #[tokio::test]
4245 async fn bus_is_accessible_after_boot() {
4246 let platform = Arc::new(NativePlatform::new());
4247 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4248 .await
4249 .unwrap();
4250 let _bus = kernel.bus();
4252 }
4253
4254 #[tokio::test]
4255 async fn a2a_router_has_inboxes_after_boot() {
4256 let platform = Arc::new(NativePlatform::new());
4257 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4258 .await
4259 .unwrap();
4260 let _router = kernel.a2a_router();
4261 }
4263
4264 #[tokio::test]
4265 async fn ipc_accessible_after_boot() {
4266 let platform = Arc::new(NativePlatform::new());
4267 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4268 .await
4269 .unwrap();
4270 let _ipc = kernel.ipc();
4271 }
4272
4273 #[tokio::test]
4276 async fn supervisor_accessible_after_boot() {
4277 let platform = Arc::new(NativePlatform::new());
4278 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4279 .await
4280 .unwrap();
4281 let _supervisor = kernel.supervisor();
4282 }
4283
4284 #[tokio::test]
4285 async fn kernel_config_accessor_returns_boot_config() {
4286 let platform = Arc::new(NativePlatform::new());
4287 let mut kconfig = test_kernel_config();
4288 kconfig.max_processes = 99;
4289 kconfig.health_check_interval_secs = 42;
4290 let kernel = Kernel::boot(test_config(), kconfig, platform)
4291 .await
4292 .unwrap();
4293 assert_eq!(kernel.kernel_config().max_processes, 99);
4294 assert_eq!(kernel.kernel_config().health_check_interval_secs, 42);
4295 }
4296
4297 #[tokio::test]
4298 async fn shutdown_exits_all_agent_processes() {
4299 let platform = Arc::new(NativePlatform::new());
4300 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4301 .await
4302 .unwrap();
4303
4304 let spawn1 = kernel.supervisor().spawn(crate::supervisor::SpawnRequest {
4306 agent_id: "test-agent-1".into(),
4307 capabilities: None,
4308 parent_pid: None,
4309 env: std::collections::HashMap::new(),
4310 backend: None,
4311 });
4312 let spawn2 = kernel.supervisor().spawn(crate::supervisor::SpawnRequest {
4313 agent_id: "test-agent-2".into(),
4314 capabilities: None,
4315 parent_pid: None,
4316 env: std::collections::HashMap::new(),
4317 backend: None,
4318 });
4319 assert!(spawn1.is_ok());
4320 assert!(spawn2.is_ok());
4321 let pid1 = spawn1.unwrap().pid;
4322 let pid2 = spawn2.unwrap().pid;
4323
4324 assert!(kernel.process_table().get(pid1).is_some());
4326 assert!(kernel.process_table().get(pid2).is_some());
4327
4328 kernel.shutdown().await.unwrap();
4329
4330 if let Some(entry1) = kernel.process_table().get(pid1) {
4336 assert!(
4337 !matches!(entry1.state, ProcessState::Running),
4338 "agent 1 should not be Running after shutdown, got: {}",
4339 entry1.state
4340 );
4341 }
4342 if let Some(entry2) = kernel.process_table().get(pid2) {
4343 assert!(
4344 !matches!(entry2.state, ProcessState::Running),
4345 "agent 2 should not be Running after shutdown, got: {}",
4346 entry2.state
4347 );
4348 }
4349 }
4350
4351 #[tokio::test]
4352 async fn shutdown_stops_services() {
4353 let platform = Arc::new(NativePlatform::new());
4354 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4355 .await
4356 .unwrap();
4357
4358 let svc_count_before = kernel.services().len();
4360 assert!(svc_count_before > 0, "should have services before shutdown");
4361
4362 kernel.shutdown().await.unwrap();
4363 assert_eq!(*kernel.state(), KernelState::Halted);
4366 }
4367
4368 #[tokio::test]
4369 async fn boot_with_custom_max_processes() {
4370 let platform = Arc::new(NativePlatform::new());
4371 let mut kconfig = test_kernel_config();
4372 kconfig.max_processes = 128;
4373 let kernel = Kernel::boot(test_config(), kconfig, platform)
4374 .await
4375 .unwrap();
4376 assert_eq!(kernel.process_table().max_processes(), 128);
4377 }
4378
4379 #[tokio::test]
4380 async fn boot_creates_a2a_router_with_topic_router() {
4381 let platform = Arc::new(NativePlatform::new());
4382 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4383 .await
4384 .unwrap();
4385 let _topic_router = kernel.a2a_router().topic_router();
4387 }
4388
4389 #[tokio::test]
4390 async fn event_log_has_shutdown_events() {
4391 let platform = Arc::new(NativePlatform::new());
4392 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4393 .await
4394 .unwrap();
4395
4396 let events_before = kernel.event_log().len();
4397 kernel.shutdown().await.unwrap();
4398
4399 let events_after = kernel.event_log().len();
4401 assert!(
4402 events_after > events_before,
4403 "event log should grow during shutdown (before={events_before}, after={events_after})"
4404 );
4405 }
4406
4407 #[tokio::test]
4408 async fn boot_log_contains_max_processes() {
4409 let platform = Arc::new(NativePlatform::new());
4410 let mut kconfig = test_kernel_config();
4411 kconfig.max_processes = 64;
4412 let kernel = Kernel::boot(test_config(), kconfig, platform)
4413 .await
4414 .unwrap();
4415 let formatted = kernel.boot_log().format_all();
4416 assert!(
4417 formatted.contains("Max processes: 64"),
4418 "boot log should contain max_processes config value"
4419 );
4420 }
4421
4422 #[tokio::test]
4423 async fn boot_log_contains_health_interval() {
4424 let platform = Arc::new(NativePlatform::new());
4425 let mut kconfig = test_kernel_config();
4426 kconfig.health_check_interval_secs = 15;
4427 let kernel = Kernel::boot(test_config(), kconfig, platform)
4428 .await
4429 .unwrap();
4430 let formatted = kernel.boot_log().format_all();
4431 assert!(
4432 formatted.contains("Health check interval: 15s"),
4433 "boot log should contain health check interval"
4434 );
4435 }
4436
4437 #[tokio::test]
4438 async fn take_app_context_returns_some_then_none() {
4439 let platform = Arc::new(NativePlatform::new());
4440 let mut kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4441 .await
4442 .unwrap();
4443
4444 assert!(
4445 kernel.take_app_context().is_some(),
4446 "first take should succeed"
4447 );
4448 assert!(
4449 kernel.take_app_context().is_none(),
4450 "second take should return None"
4451 );
4452 assert!(
4453 kernel.take_app_context().is_none(),
4454 "third take should also return None"
4455 );
4456 }
4457
4458 #[tokio::test]
4459 async fn kernel_pid_zero_has_default_capabilities() {
4460 let platform = Arc::new(NativePlatform::new());
4461 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4462 .await
4463 .unwrap();
4464 let entry = kernel.process_table().get(0).unwrap();
4465 assert_eq!(entry.agent_id, "kernel");
4466 assert!(entry.parent_pid.is_none(), "kernel should have no parent");
4467 assert!(entry.capabilities.can_ipc, "kernel should be able to IPC");
4469 }
4470
4471 #[tokio::test]
4472 async fn cluster_membership_has_nonempty_node_id() {
4473 let platform = Arc::new(NativePlatform::new());
4474 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4475 .await
4476 .unwrap();
4477 let node_id = kernel.cluster_membership().local_node_id();
4478 assert!(!node_id.is_empty(), "cluster node ID should not be empty");
4479 assert!(
4481 crate::node_id::is_node_id(node_id),
4482 "node ID should be hex(SHA-256(pubkey)[..16]), got {node_id}"
4483 );
4484 }
4485
4486 #[tokio::test]
4487 async fn boot_log_contains_cluster_info() {
4488 let platform = Arc::new(NativePlatform::new());
4489 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4490 .await
4491 .unwrap();
4492 let formatted = kernel.boot_log().format_all();
4493 assert!(
4494 formatted.contains("Cluster membership ready"),
4495 "boot log should mention cluster membership"
4496 );
4497 }
4498
4499 #[cfg(feature = "exochain")]
4500 #[tokio::test]
4501 async fn shutdown_with_exochain_completes() {
4502 let platform = Arc::new(NativePlatform::new());
4503 let mut kernel = Kernel::boot(test_config(), test_kernel_config_exochain(), platform)
4504 .await
4505 .unwrap();
4506
4507 assert!(kernel.chain_manager().is_some());
4508 kernel.shutdown().await.unwrap();
4509 assert_eq!(*kernel.state(), KernelState::Halted);
4510 }
4511
4512 #[cfg(feature = "exochain")]
4513 #[tokio::test]
4514 async fn exochain_chain_integrity_valid_after_boot() {
4515 let platform = Arc::new(NativePlatform::new());
4516 let kernel = Kernel::boot(test_config(), test_kernel_config_exochain(), platform)
4517 .await
4518 .unwrap();
4519 let chain = kernel.chain_manager().unwrap();
4520 let result = chain.verify_integrity();
4521 assert!(result.valid, "chain integrity should be valid after boot");
4522 assert!(result.errors.is_empty(), "no integrity errors expected");
4523 }
4524
4525 #[cfg(feature = "exochain")]
4526 #[tokio::test]
4527 async fn exochain_boot_manifest_event_present() {
4528 let platform = Arc::new(NativePlatform::new());
4529 let kernel = Kernel::boot(test_config(), test_kernel_config_exochain(), platform)
4530 .await
4531 .unwrap();
4532 let chain = kernel.chain_manager().unwrap();
4533 let all_events = chain.tail(0);
4534 let manifest_events: Vec<_> = all_events
4535 .iter()
4536 .filter(|e| e.kind == "boot.manifest")
4537 .collect();
4538 assert!(
4539 !manifest_events.is_empty(),
4540 "boot.manifest event should be on chain"
4541 );
4542 }
4543
4544 #[tokio::test]
4547 #[cfg(feature = "os-patterns")]
4548 async fn boot_creates_metrics_registry() {
4549 let platform = Arc::new(NativePlatform::new());
4550 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4551 .await
4552 .unwrap();
4553
4554 let registry = kernel.metrics_registry();
4555 assert!(
4556 registry.is_some(),
4557 "MetricsRegistry should be created at boot"
4558 );
4559 let r = registry.unwrap();
4561 let val = r.gauge_get("kernel.process.count");
4562 assert!(val >= 1, "kernel.process.count gauge should be seeded");
4563 }
4564
4565 #[tokio::test]
4566 #[cfg(feature = "os-patterns")]
4567 async fn boot_creates_log_service() {
4568 let platform = Arc::new(NativePlatform::new());
4569 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4570 .await
4571 .unwrap();
4572
4573 assert!(
4574 kernel.log_service().is_some(),
4575 "LogService should be created at boot"
4576 );
4577 }
4578
4579 #[tokio::test]
4580 #[cfg(feature = "os-patterns")]
4581 async fn boot_creates_timer_service() {
4582 let platform = Arc::new(NativePlatform::new());
4583 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4584 .await
4585 .unwrap();
4586
4587 assert!(
4588 kernel.timer_service().is_some(),
4589 "TimerService should be created at boot"
4590 );
4591 }
4592
4593 #[tokio::test]
4594 #[cfg(feature = "os-patterns")]
4595 async fn boot_creates_dead_letter_queue() {
4596 let platform = Arc::new(NativePlatform::new());
4597 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4598 .await
4599 .unwrap();
4600
4601 let dlq = kernel.dead_letter_queue();
4602 assert!(dlq.is_some(), "DeadLetterQueue should be created at boot");
4603 assert!(dlq.unwrap().is_empty(), "DLQ should start empty");
4604 }
4605
4606 #[tokio::test]
4607 #[cfg(feature = "os-patterns")]
4608 async fn kernel_metrics_accessor_returns_registry() {
4609 let platform = Arc::new(NativePlatform::new());
4610 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4611 .await
4612 .unwrap();
4613
4614 let registry = kernel.metrics_registry().unwrap();
4615 registry.counter_inc(crate::metrics::METRIC_MESSAGES_SENT);
4617 assert_eq!(
4618 registry.counter_get(crate::metrics::METRIC_MESSAGES_SENT),
4619 1
4620 );
4621 }
4622
4623 #[tokio::test]
4624 #[cfg(feature = "os-patterns")]
4625 async fn dlq_accessible_via_kernel_accessor() {
4626 let platform = Arc::new(NativePlatform::new());
4627 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4628 .await
4629 .unwrap();
4630
4631 let dlq = kernel.dead_letter_queue().unwrap();
4632 assert_eq!(dlq.capacity(), crate::dead_letter::DEFAULT_DLQ_CAPACITY);
4633 }
4634
4635 #[tokio::test]
4636 #[cfg(feature = "os-patterns")]
4637 async fn failed_a2a_send_routes_to_dlq() {
4638 use crate::ipc::{KernelMessage, MessageTarget};
4639
4640 let platform = Arc::new(NativePlatform::new());
4641 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4642 .await
4643 .unwrap();
4644
4645 let sender_entry = ProcessEntry {
4647 pid: 0,
4648 agent_id: "dlq-sender".to_owned(),
4649 state: ProcessState::Running,
4650 capabilities: AgentCapabilities::default(),
4651 resource_usage: ResourceUsage::default(),
4652 cancel_token: crate::process::CancellationToken::new(),
4653 parent_pid: None,
4654 };
4655 let sender_pid = kernel.process_table().insert(sender_entry).unwrap();
4656 kernel.a2a_router().create_inbox(sender_pid);
4657
4658 let msg = KernelMessage::text(sender_pid, MessageTarget::Process(9999), "dead letter test");
4660 let result = kernel.a2a_router().send(msg).await;
4661 assert!(result.is_err(), "send to unknown PID should fail");
4662
4663 let dlq = kernel.dead_letter_queue().unwrap();
4665 assert_eq!(dlq.len(), 1, "DLQ should contain 1 dead letter");
4666 }
4667
4668 #[tokio::test]
4669 #[cfg(feature = "os-patterns")]
4670 async fn kernel_metrics_gauge_seeded_at_boot() {
4671 let platform = Arc::new(NativePlatform::new());
4672 let kernel = Kernel::boot(test_config(), test_kernel_config(), platform)
4673 .await
4674 .unwrap();
4675
4676 let registry = kernel.metrics_registry().unwrap();
4677 assert!(
4679 registry.gauge_get("kernel.process.count") >= 1,
4680 "kernel.process.count should be >= 1 at boot"
4681 );
4682 assert_eq!(
4684 registry.gauge_get("kernel.uptime_secs"),
4685 0,
4686 "kernel.uptime_secs gauge should exist and be 0 at boot"
4687 );
4688 assert_eq!(
4689 registry.gauge_get("kernel.chain.height"),
4690 0,
4691 "kernel.chain.height gauge should exist and be 0 at boot"
4692 );
4693 }
4694}