Skip to main content

koi_compose/
lib.rs

1//! Koi composition layer — the single place that constructs domain cores, installs the
2//! cross-domain integration bridges, runs the container orchestrator, assembles
3//! capability status, and tears everything down in order.
4//!
5//! Three consumers share it: the `koi` daemon (`daemon_mode`), the Windows service
6//! (`run_service`), and `koi-embedded`. Building the composition once makes Windows and
7//! embedded parity true *by construction* — the verified `koi install` defect (a weaker
8//! Windows daemon missing the orchestrator + certmesh background loops) cannot recur,
9//! because all three call the same code.
10//!
11//! This is a **composition crate**, not a domain crate: it depends on every domain it
12//! wires. Nothing depends on it except the top-level consumers, so the `koi-common`
13//! kernel and the domain crates keep clean dependency closures.
14
15/// Per-host mDNS announce records (`_http._tcp` self-announcement with the ADR-020 trust
16/// stamp, `_mcp._tcp` transport descriptor) shared by the daemon, the Windows service, and
17/// embedded so the stamp is present by construction on every boot path.
18pub mod announce;
19
20/// The posture-reactive self-announce supervisor: keeps the `_http._tcp` posture stamp current
21/// across Open↔Authenticated flips and owns the `_mcp._tcp` lifecycle. Spawned identically by
22/// all three boot paths (mirrors the trust-plane's `_certmesh._tcp` reactivity).
23pub mod self_announce;
24
25/// The cross-domain integration-trait bridges (moved from the binary's `integrations.rs`).
26pub mod bridges;
27
28/// Certmesh role-driven background loops + the enrollment-approval pump (moved from the
29/// binary's `main.rs`). Shared so Windows-service and embedded daemons reach parity.
30pub mod certmesh;
31
32/// The container-runtime orchestrator: translates runtime lifecycle events into
33/// mDNS/DNS/health/proxy operations (moved from the binary's `orchestrator.rs`). Shared so
34/// Windows-service and embedded daemons can spawn it too.
35pub mod orchestrator;
36
37/// Daemon core composition: `build_cores` (the one core+bridge construction graph the
38/// daemon and the Windows service share), `init_certmesh_core`, and `ordered_shutdown`.
39pub mod cores;
40
41/// Unified capability-status assembly (`assemble_capabilities`) — the single capability
42/// ladder shared by `/v1/status`, the dashboard snapshot, and the embedded snapshot.
43pub mod status;
44
45/// The rich dashboard snapshot (`build_dashboard_snapshot`) — the one detail projection of
46/// the live cores shared by the daemon's dashboard adapter and the embedded snapshot.
47pub mod snapshot;
48
49#[cfg(test)]
50mod parity_tests {
51    //! Acceptance proof for the `koi install` parity fix (P07).
52    //!
53    //! The Windows service (`run_service`) and the foreground daemon (`daemon_mode`) now
54    //! spawn certmesh background tasks + the orchestrator through these exact composition
55    //! functions. Asserting the spawned-task inventory here — with no SCM and no network —
56    //! proves Windows gets the same task set the daemon does (the verified defect was a
57    //! structurally weaker Windows daemon missing precisely these tasks).
58
59    use std::sync::Arc;
60
61    use tokio::task::JoinHandle;
62    use tokio_util::sync::CancellationToken;
63
64    fn test_certmesh() -> Arc<koi_certmesh::CertmeshCore> {
65        let dir = std::env::temp_dir().join(format!("koi-compose-parity-{}", std::process::id()));
66        let paths = koi_certmesh::CertmeshPaths::with_data_dir(dir);
67        Arc::new(koi_certmesh::CertmeshCore::uninitialized_with_paths(paths))
68    }
69
70    fn test_runtime() -> Arc<koi_runtime::RuntimeCore> {
71        // Constructed but never `start_watching`'d — no backend connection, no network.
72        Arc::new(koi_runtime::RuntimeCore::new(koi_runtime::RuntimeConfig {
73            backend_kind: koi_runtime::RuntimeBackendKind::Auto,
74            socket_path: None,
75        }))
76    }
77
78    #[tokio::test]
79    async fn certmesh_role_loops_spawn_one_task() {
80        // ADR-017 F6: a single member-pull renewal loop. CA failover is manual
81        // (`koi certmesh promote`) and the old broken health-heartbeat loop was
82        // removed, so there is exactly one background loop, needing no mDNS.
83        let certmesh = test_certmesh();
84        let cancel = CancellationToken::new();
85        let mut tasks: Vec<JoinHandle<()>> = Vec::new();
86
87        crate::certmesh::spawn_certmesh_background_tasks(&certmesh, &cancel, &mut tasks);
88        assert_eq!(
89            tasks.len(),
90            1,
91            "expected the single member-pull renewal loop"
92        );
93
94        cancel.cancel();
95        for task in tasks {
96            let _ = task.await;
97        }
98    }
99
100    #[tokio::test]
101    async fn windows_parity_full_task_inventory() {
102        // Mirror the exact spawn sequence windows.rs run_service now uses with certmesh +
103        // runtime enabled: 1 approval pump + 1 certmesh renewal loop + 1 orchestrator = 3.
104        let certmesh = test_certmesh();
105        let runtime = test_runtime();
106        let cancel = CancellationToken::new();
107        let mut tasks: Vec<JoinHandle<()>> = Vec::new();
108
109        crate::certmesh::spawn_enrollment_approval(
110            &certmesh,
111            crate::certmesh::deny_and_log_decider(),
112            &cancel,
113            &mut tasks,
114        )
115        .await;
116        crate::certmesh::spawn_certmesh_background_tasks(&certmesh, &cancel, &mut tasks);
117        tasks.push(crate::orchestrator::spawn_orchestrator(
118            &runtime,
119            crate::orchestrator::OrchestrationTargets {
120                mdns: None,
121                dns: None,
122                health: None,
123                proxy: None,
124            },
125            cancel.clone(),
126        ));
127
128        assert_eq!(
129            tasks.len(),
130            3,
131            "Windows parity: 1 approval + 1 certmesh renewal loop + 1 orchestrator"
132        );
133
134        cancel.cancel();
135        for task in tasks {
136            let _ = task.await;
137        }
138    }
139}