Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🦴 Kernel — Espinha Dorsal

O Kernel é o Bounded Context que implementa o loop GALS (Globally Asynchronous, Locally Synchronous) do nó. Ele é a única peça que conhece rede, threads e tempo — o restante dos BCs (biology, economy, capiba, learning, etc.) é puro domínio e se comunica com o Kernel via troca de mensagens.

flowchart LR
    subgraph Adapters [adapters/ · E/S]
        NET[NetworkPort<br/>libp2p]
        TIME[TimePort<br/>tokio::time]
        RES[ResourcePort<br/>sysinfo]
    end
    subgraph Ports [ports/ · traits de I/O]
        P1[NetworkPort]
        P2[TimePort]
        P3[ResourcePort]
    end
    subgraph Types [ports/ · tipos canônicos]
        T1[PeerId]
        T2[MetabolicMessage]
        T3[SystemPressure]
    end
    subgraph Actor [actor/ · loop GALS]
        KA[KernelActor<br/>mpsc receiver]
    end
    subgraph Domain [domain/ · núcleo puro]
        M[Metabolism<br/>peer health +<br/>token bucket]
    end
    NET -.implementa.-> P1
    TIME -.implementa.-> P2
    RES -.implementa.-> P3
    P1 --> KA
    P2 --> KA
    P3 --> KA
    KA -->|handle_message<br/>carrega T1, T2, T3| M
    M -->|MetabolicAction| KA
    KA -->|send_message| P1

Regra de dependência: domain/ nunca importa de actor/, ports/ ou adapters/. A seta vai do actor para o domain — nunca o contrário. Esta é a aplicação do Dogma 1 (Isolamento Hexagonal) ao Kernel.

0.1. O coração metabólico: Token Bucket

O Metabolism é o algoritmo central do GALS: um token bucket canônico com MAX_TOKENS=100 e recarga de 0.1 tokens/ms. Cada mensagem consome 1 token; sem tokens, o ator não bloqueia — ele marca a mensagem como saturada, dispara sinal algedônico e segue. É a implementação concreta do princípio de Backpressure universal.

flowchart LR
    I[Inbound msg] --> R[Refill<br/>tokens = min MAX, t + tokens]
    R --> C{tokens >= 1?}
    C -- não --> S[Saturated<br/>drop + algedonic]
    C -- sim --> H[handle_message]
    H --> T[tokens -= 1]
    T --> A[MetabolicAction<br/>processa e despacha]
    S -.sinal.-> ALG[AlgedonicSensor]
    A --> O[Outbound msg]

1. Anatomia do crate

crates/kernel/src/
├── lib.rs           # exports públicos
├── actor/
│   └── mod.rs       # KernelActor, KernelCommand, KernelHandle
├── domain/
│   └── mod.rs       # Metabolism, MetabolicAction, PeerHealth
├── ports/
│   └── mod.rs       # PeerId, MetabolicMessage, NetworkPort, TimePort, ResourcePort, SystemPressure
└── adapters/
    └── mod.rs       # (vazio por enquanto — implementações concretas virão aqui)
MóduloCamada hexagonalResponsabilidade
domain/NúcleoLógica pura: tracking de peers, backpressure, decisão metabólica
ports/PortasTraits assíncronos que o domínio “vê” do mundo
actor/AplicaçãoLoop GALS que conecta domínio a portas
adapters/AdaptadoresImplementações concretas de portas (rede, tempo, recursos)

2. Domínio (crates/kernel/src/domain/)

Metabolism

A única estrutura que carrega estado mutável do domínio. Não tem I/O, não tem threads, não tem tokio. É testável em isolamento absoluto com um relógio mockado.

#![allow(unused)]
fn main() {
pub struct Metabolism {
    node_id: PeerId,
    known_peers: HashMap<PeerId, PeerHealth>,
    tokens: f32,            // token bucket (backpressure)
    last_refill_ms: u64,
}
}

Constantes de metabolismo:

ConstanteValorSignificado
MAX_TOKENS100.0Capacidade máxima do bucket
REFILL_RATE_PER_MS0.1100 tokens / segundo

Algoritmo:

  1. Em cada handle_message, primeiro reabastece o bucket proporcional a now_ms - last_refill_ms.
  2. Se tokens < 1.0, retorna MetabolicAction::Saturated (sem consumir).
  3. Senão, consome 1 token e processa a mensagem.
  4. Para PulseRequest: registra/atualiza o PeerHealth do emissor e agenda PulseResponse.
  5. Para PulseResponse: atualiza o health_score do emissor.

MetabolicAction

#![allow(unused)]
fn main() {
pub enum MetabolicAction {
    SendResponse { target: PeerId, msg: MetabolicMessage },
    UpdateInternalState,
    Saturated,   // backpressure sinalizou overload
    None,
}
}

Invariante: Metabolism::handle_message é puro — dado o mesmo (sender, msg, now_ms) e o mesmo estado inicial, produz a mesma MetabolicAction. Isso é verificável por proptest.

PeerHealth

#![allow(unused)]
fn main() {
pub struct PeerHealth {
    pub last_seen_ms: u64,
    pub health_score: f32,
}
}

health_score ∈ [0.0, 1.0]; valores perto de 0 indicam peer degradado e disparam o sensor algedônico (futuro: roteamento adaptativo).


3. Portas (crates/kernel/src/ports/)

Definem tudo que o domínio vê do mundo, sem saber quem implementa.

PeerId

#![allow(unused)]
fn main() {
pub struct PeerId([u8; 32]);
}

32 bytes opacos. Display mostra os primeiros 8 bytes em hex (para logs).

MetabolicMessage

#![allow(unused)]
fn main() {
pub enum MetabolicMessage {
    PulseRequest  { timestamp: u64 },
    PulseResponse { timestamp: u64, health: f32 },
}
}

Por enquanto, apenas duas variantes — hello da malha. O conjunto crescerá conforme os outros BCs (biology, economy) enviarem mensagens de domínio. Cada nova variante é uma mudança compatível para trás (apenas adição), preservável por versionamento de envelope.

NetworkPort (assíncrono)

#![allow(unused)]
fn main() {
#[async_trait]
pub trait NetworkPort: Send + Sync {
    /// Envia uma mensagem metabólica para um peer específico.
    async fn send_message(&self, target: PeerId, msg: MetabolicMessage) -> Result<(), &'static str>;

    /// Broadcast de uma mensagem para todos os peers conhecidos.
    async fn broadcast(&self, msg: MetabolicMessage) -> Result<(), &'static str>;
}
}

Implementação concreta atual: apps/node/src/network.rs (libp2p).

TimePort (síncrono)

#![allow(unused)]
fn main() {
pub trait TimePort: Send + Sync {
    fn now_ms(&self) -> u64;
}
}

Síncrono de propósito: facilita proptest (testes determinísticos bancam o tempo com um contador). Não usar Instant::now() diretamente no domínio.

ResourcePort (síncrono)

#![allow(unused)]
fn main() {
pub trait ResourcePort: Send + Sync {
    fn get_current_pressure(&self) -> SystemPressure;
}
}

SystemPressure é o que alimenta o sensor algedônico do nó (local, sem supervisor global).

#![allow(unused)]
fn main() {
pub struct SystemPressure {
    pub cpu_usage: f32,     // [0.0, 1.0]
    pub memory_usage: f32,  // [0.0, 1.0]
    pub disk_usage: f32,    // [0.0, 1.0]
}
}

4. Ator (crates/kernel/src/actor/)

KernelCommand

Mensagens que o Node (camada de fora) envia para o ator:

#![allow(unused)]
fn main() {
pub enum KernelCommand {
    IncomingMessage { sender: PeerId, message: MetabolicMessage },
    TriggerPulse,
    Shutdown,
}
}

KernelActor

#![allow(unused)]
fn main() {
pub struct KernelActor {
    metabolism: Metabolism,
    network_port: Arc<dyn NetworkPort>,
    time_port:   Arc<dyn TimePort>,
    resource_port: Arc<dyn ResourcePort>,
    receiver:    mpsc::Receiver<KernelCommand>,
}
}

O loop principal:

#![allow(unused)]
fn main() {
pub async fn run(mut self) {
    while let Some(command) = self.receiver.recv().await {
        match command {
            KernelCommand::IncomingMessage { sender, message } => {
                let now = self.time_port.now_ms();
                let action = self.metabolism.handle_message(sender, message, now);
                self.execute_action(action).await;
            }
            KernelCommand::TriggerPulse => { /* pulso metabólico */ }
            KernelCommand::Shutdown => break,
        }
    }
}
}

Regra do Dogma 2 (GALS): o loop do ator nunca bloqueia em I/O. Toda chamada externa é async e awaitada; o mpsc::Receiver é o único ponto de suspensão.

KernelHandle

pub use em lib.rs — é a handle que o Node usa para enviar comandos ao ator:

#![allow(unused)]
fn main() {
pub struct KernelHandle {
    sender: mpsc::Sender<KernelCommand>,
}
}
#![allow(unused)]
fn main() {
impl KernelHandle {
    pub async fn incoming(&self, sender: PeerId, msg: MetabolicMessage) -> Result<(), ...>;
    pub async fn trigger_pulse(&self) -> Result<(), ...>;
    pub async fn shutdown(&self) -> Result<(), ...>;
}
}

5. Adaptadores (crates/kernel/src/adapters/)

Por enquanto apenas:

#![allow(unused)]
fn main() {
pub mod networking {}
}

A implementação concreta está em apps/node/src/network.rs — uma decisão consciente: o Kernel é uma biblioteca no_std-friendly que não inclui libp2p. A integração com a rede física vive no Node (binário). Em fases futuras, a tendência é mover implementações para cá para reuso em outros front-ends (CLI, playground, simulator).


6. Fluxo end-to-end (exemplo)

Uma mensagem chegando do libp2p:

sequenceDiagram
    participant N as Network (libp2p)
    participant H as KernelHandle
    participant A as KernelActor (loop)
    participant D as Metabolism (domain)
    participant R as ResourcePort
    N->>H: incoming(sender, PulseRequest)
    H->>A: mpsc::send(IncomingMessage)
    A->>R: get_current_pressure()
    A->>D: handle_message(sender, msg, now)
    D-->>A: MetabolicAction::SendResponse
    A->>N: network_port.send_message(...)
    N-->>Sender: PulseResponse

Garantia: todo o caminho passa por um único thread (o loop do ator). Não há Mutex no Metabolism — a imutabilidade estrutural do actor é o “lock”.

Transporte e descoberta do pulso

A integração libp2p concreta vive em apps/node/src/network.rs. O pulso trafega em gossipsub (tópico paebiru-metabolism), que é o canal de publicação efêmero e ponto-a-ponto. Os peers que assinam este tópico são descobertos por duas camadas complementares:

  • mDNS (LAN) — plug-and-play local, sem configuração.
  • Kademlia DHT (global) — anel de peers roteáveis, alimentado por PAEBIRU_BOOTNODES. O gate F1 do Roadmap (“dois nós trocam pulse ponta-a-ponta via libp2p Kademlia”) é satisfeito pela combinação Kademlia-para-descoberta + gossipsub-para-transporte; o pulso não é armazenado como Kademlia Record — ele é efêmero por design (ver Pulse no Dicionário).

O Dogma 1 (Hexagonal) é preservado: este BC não conhece libp2p, mDNS ou Kademlia. Toda a complexidade de transporte vive no Node (apps/node), satisfazendo o NetworkPort que o KernelActor consome. O Dogma 3 (no_std) também permanece intacto — o paebiru-kernel continua compilável para riscv32imc-unknown-none-elf e thumbv7em-none-eabihf.


7. Como testar

Tipo de testeOndeO que cobre
Unitário (#[test])crates/kernel/src/domain/testsMetabolism puro — proptest! sobre (sender, msg, now)
Integraçãocrates/kernel/tests/actor_loop.rsKernelActor::run com NetworkPort mockado
Property-basedcrates/kernel/proptest-recipesInvariante: tokens ∈ [0, MAX_TOKENS]
no_stdcargo check --target riscv32imc-unknown-none-elfdomain/ compila sem std

Cobertura-alvo: ≥ 90 % (enforçada por make coverage).


8. O que não é Kernel

  • Persistênciacapiba.md (outro BC).
  • Aprendizado / modelos → BC learning.
  • Crédito / Loteria Joule → BC economy.
  • Agentes BDI / SNN → BC biology.
  • Definição de plasmídeos → BC plasmids.

Se a mudança que você quer fazer não é loop de ator, porta ou adaptação de E/S, provavelmente é outro BC.


9. Veja também