engenho_runtime/lib.rs
1//! # engenho-runtime
2//!
3//! The single-node assembly layer. [`Runtime`] is the ONE struct that
4//! boots the entire single-node engenho control + data plane in ONE
5//! process over a durable [`engenho_store::StoreMesh`], so a POSTed
6//! `apps/v1` Deployment converges to a running container. The `engenho`
7//! binary is a thin launcher over [`Runtime::start`].
8//!
9//! ## Why a lib crate (not just `main.rs`)
10//!
11//! The convergence proof — boot every subsystem, POST a Deployment over
12//! real HTTP, watch the chain converge autonomously — must be an
13//! integration test that doesn't go through `main`. A lib crate makes
14//! [`Runtime`] integration-testable; the binary depends on it.
15//!
16//! ## What gets assembled (the spine)
17//!
18//! ```text
19//! ONE StoreMesh (durable fjall, or ephemeral in tests)
20//! │ ← every subsystem holds a clone; the apiserver translates
21//! │ HTTP→ResourceCommand proposals, controllers read via
22//! │ list/get/watch + write via propose. Same catalog.
23//! ├── ApiServer (HTTP K8s API; handlers_from_catalog)
24//! ├── Node/<node_name> (self-registered at boot; schedulable)
25//! ├── DeploymentController (Deployment → ReplicaSet)
26//! ├── ReplicaSetController (ReplicaSet → Pod)
27//! ├── EndpointsController (Service → Endpoints)
28//! ├── GcController (orphan owner-ref GC)
29//! ├── Scheduler (pending Pod → spec.nodeName)
30//! └── Kubelet (bound Pod → container via backend)
31//! ```
32//!
33//! Each controller/scheduler/kubelet is wrapped in a
34//! [`engenho_controllers::WatchDriver`] with a per-controller
35//! [`engenho_controllers::KindFilter`] so the chain converges in ms,
36//! with a periodic fallback as the safety net.
37//!
38//! ## Boot order (strict)
39//!
40//! 1. `config.validate()`
41//! 2. StoreMesh start (durable `start_or_resume`, or ephemeral) +
42//! `wait_for_leadership` — leadership MUST precede any `propose`
43//! 3. register `Node/<node_name>` (the missing brick; no other code
44//! does this, and the scheduler hard-requires a schedulable Node)
45//! 4. `ApiServer::start`
46//! 5. spawn controllers / scheduler / kubelet drivers
47//!
48//! ## Shutdown (the tricky bit)
49//!
50//! [`Runtime::shutdown`] aborts + awaits every driver JoinHandle (so
51//! the controller/scheduler/kubelet tasks drop their `Arc<StoreMesh>`
52//! clones), shuts the apiserver down (2s grace, severs open watches —
53//! which drops the handler clones), THEN `Arc::try_unwrap`s the store
54//! and calls `terminate` (which consumes `StoreMesh` and needs the sole
55//! strong ref). The Runtime holds the last clone; once the tasks +
56//! handlers drop theirs, the unwrap succeeds.
57
58#![warn(clippy::pedantic)]
59#![allow(clippy::module_name_repetitions)]
60
61mod error;
62mod runtime;
63
64pub use error::RuntimeError;
65pub use runtime::Runtime;