Skip to main content

v0.2.0 Release Notes

EnerOS v0.2.0

Release Date: February 12, 2024 Codename: Foundation Git Tag: v0.2.0 Support Status: Internal Preview Total Crates: 1 Test Cases: 186

Version Overview

EnerOS v0.2.0 “Foundation” is a key step for the project from “engineering skeleton” to “operating system kernel”. v0.1.0 only provided a prototype of the eneros-core crate, while v0.2.0 formally established the Kernel ABI, Syscall Interface, Error Code System, and Tracing Integration. These four components constitute the essential difference between EnerOS as an “operating system” rather than a “regular library” — they define the boundary between user mode and kernel mode, and all upper-layer capabilities (topology, power flow, Agent, gateway) must access kernel resources through syscalls.

The kernel abstraction layer design draws on the experience of Rust microkernels such as seL4 and Hubris, but is deeply tailored for power system scenarios: First, syscalls must meet deterministic latency requirements, avoiding tail latency caused by GC or dynamic allocation; Second, syscalls must be aware of electrical semantics, for example, ReadBus, WriteSwitch and other interfaces directly correspond to power domain operations; Third, syscalls must support the capability model, and each syscall call must carry a capability token to implement the principle of least privilege. This design enables EnerOS to reject unauthorized operations at the kernel level — for example, a read-only Agent cannot call WriteSwitch.

Another important work of v0.2.0 is the introduction of the tracing ecosystem as the unified logging and observability infrastructure. Power systems are critical infrastructure, and the traceability of the operational process is a hard requirement for compliance audits (NERC CIP, IEC 62443). The advantage of tracing over the traditional log crate lies in structured logging and distributed tracing, which can record the complete call chain of a grid operation, facilitating post-incident review and fault localization. This version also established the EnerOSResult<T> unified return type; all fallible functions return this type, with error codes aligned with HTTP status codes and gRPC status codes for easy subsequent REST/gRPC layer integration.

Key Data

MetricValueDescription
Syscall count24Basic syscalls
Error code count486 major categories
Lines of code4,210127% growth over v0.1.0
Test cases186Coverage 78%
Kernel call latency P99380 nsSynchronous syscall
tracing event throughput850,000/secSingle thread

New Features

1. Kernel Abstraction Layer (Kernel ABI)

Define the binary interface (ABI) between the EnerOS kernel and user mode. The Kernel ABI adopts a capability-based security model; each kernel resource (bus, switch, Agent) is associated with a capability token, and user mode must hold the corresponding capability to access it.

// crates/eneros-core/src/abi/mod.rs
//! EnerOS Kernel Abstraction Layer
//!
//! Defines the interface contract between user mode and kernel mode.

use std::fmt;

/// Kernel capability token
///
/// Each capability token corresponds to a kernel resource access permission.
/// Capability tokens cannot be forged and can only be issued by the kernel.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Capability(pub u64);

impl Capability {
    /// Create a root capability (kernel boot only)
    pub(crate) fn root() -> Self {
        Capability(0xFFFF_FFFF_FFFF_FFFF)
    }

    /// Check whether the capability contains a permission
    pub fn grants(&self, perm: Permission) -> bool {
        // Simplified capability check implementation
        (self.0 & perm.mask()) != 0
    }
}

/// Permission enum
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Permission {
    Read = 0x01,
    Write = 0x02,
    Execute = 0x04,
    Admin = 0x08,
}

impl Permission {
    pub fn mask(&self) -> u64 {
        1u64 << (*self as u32)
    }
}

/// Kernel object handle
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Handle(pub u32);

impl fmt::Display for Handle {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "handle:{}", self.0)
    }
}

Capability delegation example:

// Kernel issues capability to user-mode Agent
let agent_cap = kernel.grant_capability(
    resource: ResourceId::Bus(1),
    permissions: Permission::Read | Permission::Write,
    ttl: Duration::from_secs(3600),
)?;

// Agent uses capability to access resource
let bus_state = kernel.syscall(Syscall::ReadBus {
    handle: bus_handle,
    cap: agent_cap,
})?;

2. Syscall Interface

Define 24 basic syscalls, covering four major categories: resource management, topology access, Agent control, and time operations. Syscalls use the form of ID + parameter struct, making it easy to extend to real trap instructions in the future.

// crates/eneros-core/src/syscall/mod.rs
//! EnerOS Syscall Interface

/// Syscall ID
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum SyscallId {
    // Resource management (0x00-0x0F)
    CreateResource = 0x00,
    DestroyResource = 0x01,
    OpenResource = 0x02,
    CloseResource = 0x03,
    GrantCapability = 0x04,
    RevokeCapability = 0x05,

    // Topology access (0x10-0x1F)
    ReadBus = 0x10,
    WriteBus = 0x11,
    ReadBranch = 0x12,
    ReadSwitch = 0x13,
    WriteSwitch = 0x14,
    GetTopology = 0x15,

    // Agent control (0x20-0x2F)
    SpawnAgent = 0x20,
    KillAgent = 0x21,
    SendMessage = 0x22,
    RecvMessage = 0x23,
    AgentSleep = 0x24,
    AgentWake = 0x25,

    // Time operations (0x30-0x3F)
    GetTime = 0x30,
    Sleep = 0x31,
    SetTimer = 0x32,
    CancelTimer = 0x33,

    // Diagnostics (0x40-0x4F)
    Log = 0x40,
    Metric = 0x41,
    Trace = 0x42,
}

/// Syscall parameters and return values
#[derive(Debug)]
pub enum Syscall {
    ReadBus { handle: Handle, cap: Capability },
    WriteBus { handle: Handle, cap: Capability, value: BusState },
    WriteSwitch { handle: Handle, cap: Capability, state: SwitchState },
    SpawnAgent { manifest: AgentManifest },
    SendMessage { target: Handle, msg: Message },
    GetTime,
    // ...
}

/// Syscall return value
#[derive(Debug)]
pub enum SyscallResult {
    Bus(BusState),
    Switch(SwitchState),
    AgentHandle(Handle),
    Time(SystemTime),
    Ack,
    Err(ErrorCode),
}

Syscall invocation example:

use eneros_core::syscall::{Syscall, SyscallId, SyscallResult};

// Read the state of bus 1
let result = kernel.syscall(Syscall::ReadBus {
    handle: bus_handle,
    cap: read_cap,
})?;

match result {
    SyscallResult::Bus(state) => {
        tracing::info!(bus_id = 1, voltage = state.voltage, "Read bus state");
    }
    SyscallResult::Err(code) => {
        tracing::error!(code = ?code, "Failed to read bus");
        return Err(code.into());
    }
    _ => unreachable!(),
}

3. Error Code System

Establish a unified error code system, divided into 6 major categories with 48 error codes. Error codes use the format E + category + number, aligned with HTTP status codes and gRPC status codes.

// crates/eneros-core/src/error.rs
use thiserror::Error;

/// EnerOS unified error type
#[derive(Debug, Clone, Error)]
pub enum ErrorCode {
    // General errors (E1xxx)
    #[error("E1001: Internal error - {0}")]
    Internal(String),

    #[error("E1002: Invalid argument - {0}")]
    InvalidArgument(String),

    #[error("E1003: Resource not found - {0}")]
    NotFound(String),

    #[error("E1004: Resource already exists - {0}")]
    AlreadyExists(String),

    // Permission errors (E2xxx)
    #[error("E2001: Permission denied - requires {required}")]
    PermissionDenied { required: Permission },

    #[error("E2002: Invalid capability token")]
    InvalidCapability,

    #[error("E2003: Capability token expired")]
    CapabilityExpired,

    // Topology errors (E3xxx)
    #[error("E3001: Bus does not exist - id={0}")]
    BusNotFound(u32),

    #[error("E3002: Topology disconnected")]
    TopologyDisconnected,

    #[error("E3003: Electrical island is empty")]
    EmptyIsland,

    // Power flow errors (E4xxx)
    #[error("E4001: Power flow not converged - iterated {iter} times")]
    PowerFlowNotConverged { iter: usize },

    #[error("E4002: Singular Jacobian matrix")]
    SingularJacobian,

    // Agent errors (E5xxx)
    #[error("E5001: Agent does not exist - {0}")]
    AgentNotFound(String),

    #[error("E5002: Invalid Agent state - current {current}")]
    InvalidAgentState { current: AgentState },

    // Time series errors (E6xxx)
    #[error("E6001: Timestamp out of range - {0}")]
    TimestampOutOfRange(i64),

    #[error("E6002: Time series data discontinuous")]
    TimeSeriesDiscontinuous,
}

/// EnerOS unified return type
pub type EnerOSResult<T> = Result<T, ErrorCode>;

Error code category summary:

CategoryPrefixRangeCountHTTP Mapping
General errorsE1xxxE1001-E109912500/400/404/409
Permission errorsE2xxxE2001-E20998403/401
Topology errorsE3xxxE3001-E309910404/422
Power flow errorsE4xxxE4001-E40996422/500
Agent errorsE5xxxE5001-E50998404/409
Time series errorsE6xxxE6001-E60994422

4. Logging System (tracing Integration)

Integrate tracing and tracing-subscriber to provide structured logging and distributed tracing capabilities. EnerOS defines a standard log field specification; all log events must include trace_id, span_id, agent_id (if applicable).

// crates/eneros-core/src/log.rs
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

/// Initialize EnerOS logging system
pub fn init_log() {
    let env_filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info"));

    let fmt_layer = fmt::layer()
        .with_target(true)
        .with_thread_ids(true)
        .with_file(true)
        .with_line_number(true)
        .json();

    tracing_subscriber::registry()
        .with(env_filter)
        .with(fmt_layer)
        .init();
}

/// Standard log fields
pub mod fields {
    pub const TRACE_ID: &str = "trace_id";
    pub const SPAN_ID: &str = "span_id";
    pub const AGENT_ID: &str = "agent_id";
    pub const BUS_ID: &str = "bus_id";
    pub const VOLTAGE: &str = "voltage";
    pub const DURATION_US: &str = "duration_us";
}

Usage example:

use tracing::{info_span, info, instrument};

#[instrument(skip(kernel), fields(bus_id = %bus_id))]
pub fn read_bus_voltage(kernel: &Kernel, bus_id: BusId) -> EnerOSResult<f64> {
    let span = info_span!("syscall_read_bus", bus_id = %bus_id);
    let _enter = span.enter();

    let result = kernel.syscall(Syscall::ReadBus {
        handle: kernel.open(bus_id)?,
        cap: kernel.current_capability(),
    })?;

    match result {
        SyscallResult::Bus(state) => {
            tracing::info!(
                bus_id = %bus_id,
                voltage = state.voltage,
                "Successfully read bus voltage"
            );
            Ok(state.voltage)
        }
        SyscallResult::Err(code) => Err(code),
        _ => unreachable!(),
    }
}

Improvements

  • Build speed: Enabled sccache caching, CI full build reduced from 1m12s to 48s
  • Test parallelization: Using cargo nextest, test execution time reduced from 18s to 7s
  • Documentation generation: Integrated cargo-doc and mdbook, auto-generating API docs and user manual
  • Dependency audit: Introduced cargo-deny, preventing GPL/AGPL dependencies from being mixed in
  • Code coverage: Integrated tarpaulin, enforcing coverage not lower than 75% in CI

Bug Fixes

  • Fixed Version::parse failing to parse pre-release labels like 0.1.0-alpha.1 (#23)
  • Fixed Capability::grants returning incorrect results for combined permissions (#27)
  • Fixed tracing ANSI color output not working on Windows (#31)
  • Fixed ErrorCode’s Display implementation not including error code numbers (#34)
  • Fixed cargo clippy in CI not enabling --all-targets (#38)

Breaking Changes

  • eneros_core::version::Version: Added pre: Option<String> field; the original Version::new signature remains unchanged, but direct struct construction needs updating
  • eneros_core::error: EnerOSError renamed to ErrorCode, implementing std::error::Error trait
  • Logging macros: Removed dependency on log crate, unified use of tracing macros

Migration example:

// v0.1.0 (old)
use eneros_core::error::EnerOSError;
fn foo() -> Result<(), EnerOSError> { ... }

// v0.2.0 (new)
use eneros_core::error::{ErrorCode, EnerOSResult};
fn foo() -> EnerOSResult<()> { ... }

Performance Improvements

v0.2.0 made several optimizations on the syscall path; compared to v0.1.0’s “direct function call” baseline, the additional overhead from introducing the ABI boundary is kept within acceptable range:

Operationv0.1.0v0.2.0Change
Version parsing142 ns138 ns-2.8%
Syscall (ReadBus)N/A380 nsNew
Capability checkN/A18 nsNew
Error code construction24 ns16 ns-33%
tracing::info!N/A1.2 μsNew
CI build1m12s48s-33%
Test execution18s7s-61%

Syscall performance benchmark:

// benches/syscall_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use eneros_core::syscall::{Syscall, SyscallId};

fn bench_syscall_dispatch(c: &mut Criterion) {
    let kernel = Kernel::mock();
    let cap = Capability::root();
    let handle = Handle(1);

    c.bench_function("syscall_read_bus", |b| {
        b.iter(|| {
            kernel.syscall(black_box(Syscall::ReadBus {
                handle,
                cap,
            }))
        })
    });
}

criterion_group!(benches, bench_syscall_dispatch);
criterion_main!(benches);

Contributors

v0.2.0 has 5 contributors:

ContributorRoleCommits
@eneros-foundationArchitect42
@grid-rustaceanRust Engineer31
@kernel-hackerKernel Engineer18
@powerdomain-reviewerPower Domain Expert9
@observability-engObservability Engineer7

Upgrade Guide

Upgrading from v0.1.0

# 1. Pull latest code
git fetch origin
git checkout v0.2.0

# 2. Update dependencies
cargo update

# 3. Build the project
cargo build --release

# 4. Run tests (note breaking changes)
cargo test --all

Code Migration Checklist

Change ItemImpactMigration Method
EnerOSErrorErrorCodeAll error handling codeGlobal replace type name
Version added pre fieldDirect struct constructionUse Version::new or complete the field
logtracingAll log callslog::info!tracing::info!

Configuring Log Output

From v0.2.0, log level is configured via environment variables:

# Set log level
export RUST_LOG="eneros_core=debug,eneros_toplogy=info,warn"

# Output JSON format logs
export ENEROS_LOG_FORMAT=json

# Enable OpenTelemetry export
export ENEROS_OTEL_ENDPOINT=http://otel-collector:4317

Next Steps

v0.3.0 “Topology” will introduce the grid topology data model; Bus, Branch, Generator, and other core node types will be formally implemented. It is recommended to read the IEC 61970 CIM standard in advance to understand the data model design of subsequent versions.