Skip to content

Safety Gateway en

EnerOS Bot edited this page Jul 5, 2026 · 1 revision

中文 | English

SafetyGateway & Real-Time Type-Level Safety

Maintained version: v0.51.2 | Last updated: 2026-07-06

SafetyGateway is the only channel from the general domain to the real-time domain in EnerOS's dual execution domain architecture. All commands from Agents / AI / planning optimization modules must pass through SafetyGateway's 7-stage decision pipeline, constraint validation, priority arbitration, and executor before being dispatched to the real-time domain. This page summarizes the type-level unbypassable mechanism; for the full decision record see ADR-0015.

Design Goals

Goal Implementation Verification
General domain cannot directly call RT API Type-level encapsulation + compile-time check clippy::disallowed_types rejects violations in CI
RT domain bans heap allocation / lock contention / dynamic dispatch NoAlloc marker + AllocTracker + lockfree default WCET framework + single-thread --test-threads=1 tests
Decisions are auditable and tamper-proof RepositoryAuditSink + HMAC-SHA256 chain HMAC chain break detection
Constraints cannot be downgraded Constraint as Kernel Law Compile-time trait seal + runtime rejection

7-Stage Decision Pipeline

[Agent/AI/Planning] → [Auth] → [Validate] → [Constraint] → [Priority]
                                ↓
                          [Decide] → [Execute] → [Audit]
Stage Responsibility Key Type
Auth API Key / JWT / mTLS authentication AuthMiddleware
Validate Type-level sealed command validation Validated<Command>
Constraint N-1 / thermal / voltage / frequency constraint validation ConstraintEngine
Priority Multi-Agent conflict priority arbitration PriorityArbiter
Decide Decision synthesis and signing DecisionPipeline
Execute Dispatch to real-time executor RtExecutor
Audit HMAC chain write to WORM audit log RepositoryAuditSink

Type-Level Unbypassable (v0.49.0+)

Validated newtype

#[derive(Debug, Clone)]
pub struct Validated<T>(T);

impl Validated<Command> {
    /// Only SafetyGateway can construct. Newer versions tighten via sealed trait
    pub fn new(cmd: Command) -> Self { Self(cmd) }
}

pub struct SafetyGateway { /* ... */ }
impl SafetyGateway {
    pub fn submit(&self, cmd: Command) -> Result<Validated<Command>, GatewayError> {
        // 7-stage pipeline...
        Ok(Validated::new(cmd))
    }
}

// RT API only accepts Validated<Command>, not bare Command
pub fn rt_execute(cmd: Validated<Command>) { /* ... */ }

External code cannot directly construct Validated<Command> — must go through SafetyGateway::submit.

clippy::disallowed_types RT lock ban

clippy.toml bans Mutex / RwLock / Box::new / Vec::new etc. on real-time domain paths:

[[disallowed-types]]
path = "std::sync::Mutex"
reason = "RT domain: lock contention is non-deterministic"

CI enforces cargo clippy --workspace --all-targets -- -D warnings; violations fail the build.

NoAlloc + AllocTracker

RT domain code is marked with NoAlloc trait; at runtime AllocTracker is injected to detect any heap allocation and panic:

pub trait NoAlloc {}

#[cfg(feature = "rt_check")]
pub struct AllocTracker { /* ... */ }

#[cfg(feature = "rt_check")]
impl AllocTracker {
    pub fn install() -> Self { /* inject global allocator hook */ }
    pub fn assert_no_alloc<F: FnOnce() -> R, R>(&self, f: F) -> R { /* ... */ }
}

WCET Framework

#[cfg(feature = "rt_check")]
pub fn wcet_bench<F: FnOnce() -> R, R>(label: &str, budget: Duration, f: F) -> R {
    let start = Instant::now();
    let r = f();
    let elapsed = start.elapsed();
    if elapsed > budget {
        panic!("WCET violation: {} {:?} > {:?}", label, elapsed, budget);
    }
    r
}

CI runs WCET tests with --test-threads=1 to avoid noise.

RepositoryAuditSink HMAC Chain

pub struct RepositoryAuditSink {
    repo: AuditRepository,
    hmac_secret: [u8; 32],
    last_hash: Vec<u8>,  // SHA-256 of previous entry
}

impl AuditSink for RepositoryAuditSink {
    fn write(&mut self, entry: &AuditEntry) -> Result<()> {
        let chain_hash = hmac_sha256(&self.hmac_secret, &[&self.last_hash, &entry.encode()]);
        self.repo.append(&entry, &chain_hash)?;
        self.last_hash = chain_hash;
        Ok(())
    }
}

Any tampering with historical records causes subsequent HMAC verification to fail.

Known Limitations (v0.51.2)

  • AllocTracker reentrancy bug bypassed in v0.49.1 via direct calls; full fix pending v0.52.0
  • HardwareWatchdog is not currently a trait; only CommandResult variant asserted
  • Validated::new currently uses #[doc(hidden)] pub instead of sealed trait; tightening pending v0.52.0
  • 67 unwrap/expect remain in non-test code (88 removed in v0.51.0; TODO v0.52.0)
  • WCET tests require --test-threads=1

Related Documentation

Clone this wiki locally