rumpk/hal/ontology.zig

574 lines
15 KiB
Zig

//! SPEC-060: System Ontology - Event & Entity Structures
//! Component: core/ontology
//! Target: Ground Zero - Phase 2
const std = @import("std");
/// EventKind: Closed enumeration of system events (SPEC-060)
pub const EventKind = enum(u16) {
Null = 0,
// Lifecycle Events
SystemBoot = 1,
SystemShutdown = 2,
FiberSpawn = 3,
FiberTerminate = 4,
// Capability Events
CapabilityGrant = 10,
CapabilityRevoke = 11,
CapabilityDelegate = 12,
// I/O Events
ChannelOpen = 20,
ChannelClose = 21,
ChannelRead = 22,
ChannelWrite = 23,
// Memory Events
MemoryAllocate = 30,
MemoryFree = 31,
MemoryMap = 32,
// Network Events
NetworkPacketRx = 40,
NetworkPacketTx = 41,
// Security Events
AccessDenied = 50,
PolicyViolation = 51,
};
/// Event: Immutable record of a system occurrence (58 bytes, packed)
/// NOTE: Packed to 58 bytes for Zig compatibility (packed structs can't contain arrays)
pub const Event = packed struct {
kind: EventKind, // 2 bytes - Event type
_reserved: u16, // 2 bytes - Alignment
timestamp_ns: u64, // 8 bytes - Nanosecond timestamp
fiber_id: u64, // 8 bytes - Originating fiber
entity_id: u64, // 8 bytes - Target entity (SipHash)
cause_id: u64, // 8 bytes - Causal parent event ID
data0: u64, // 8 bytes - Event-specific data
data1: u64, // 8 bytes - Event-specific data
data2: u64, // 8 bytes - Event-specific data
// Total: 58 bytes (packed)
/// Create a null event
pub fn null_event() Event {
return .{
.kind = .Null,
._reserved = 0,
.timestamp_ns = 0,
.fiber_id = 0,
.entity_id = 0,
.cause_id = 0,
.data0 = 0,
.data1 = 0,
.data2 = 0,
};
}
/// Check if event is null
pub fn is_null(self: *const Event) bool {
return self.kind == .Null;
}
};
/// EntityKind: Types of system entities (SPEC-060)
pub const EntityKind = enum(u8) {
Null = 0,
Fiber = 1,
Channel = 2,
Memory = 3,
File = 4,
Network = 5,
Device = 6,
};
/// Entity: Represents a system resource (32 bytes, cache-aligned)
pub const Entity = extern struct {
kind: EntityKind, // 1 byte - Entity type
_reserved: [7]u8, // 7 bytes - Alignment
entity_id: u64, // 8 bytes - Unique ID (SipHash)
parent_id: u64, // 8 bytes - Parent entity (for hierarchy)
metadata: u64, // 8 bytes - Entity-specific metadata
comptime {
if (@sizeOf(Entity) != 32) {
@compileError("Entity must be exactly 32 bytes");
}
}
/// Create a null entity
pub fn null_entity() Entity {
return .{
.kind = .Null,
._reserved = [_]u8{0} ** 7,
.entity_id = 0,
.parent_id = 0,
.metadata = 0,
};
}
/// Check if entity is null
pub fn is_null(self: *const Entity) bool {
return self.kind == .Null;
}
};
/// System Truth Ledger: Append-only event log
pub const STL_SIZE = 4096; // Maximum events in ring buffer
pub const SystemTruthLedger = struct {
events: [STL_SIZE]Event,
head: u32, // Next write position
tail: u32, // Oldest event position
epoch: u32, // Wraparound counter
_padding: u32, // Alignment
/// Initialize empty STL
pub fn init() SystemTruthLedger {
var stl = SystemTruthLedger{
.events = undefined,
.head = 0,
.tail = 0,
.epoch = 0,
._padding = 0,
};
// Initialize all events to Null
for (&stl.events) |*event| {
event.* = Event.null_event();
}
return stl;
}
/// Append event to ledger (returns event ID)
pub fn append(self: *SystemTruthLedger, event: Event) u64 {
const idx = self.head;
self.events[idx] = event;
// Advance head
self.head = (self.head + 1) % STL_SIZE;
// If we wrapped, advance tail
if (self.head == self.tail) {
self.tail = (self.tail + 1) % STL_SIZE;
self.epoch +%= 1;
}
// Event ID = epoch << 32 | index
return (@as(u64, self.epoch) << 32) | @as(u64, idx);
}
/// Lookup event by ID
pub fn lookup(self: *const SystemTruthLedger, event_id: u64) ?*const Event {
const idx = @as(u32, @truncate(event_id & 0xFFFFFFFF));
const epoch = @as(u32, @truncate(event_id >> 32));
if (idx >= STL_SIZE) return null;
if (epoch != self.epoch and idx >= self.head) return null;
const event = &self.events[idx];
if (event.is_null()) return null;
return event;
}
/// Get current event count
pub fn count(self: *const SystemTruthLedger) u32 {
if (self.epoch > 0) return STL_SIZE;
return self.head;
}
};
/// Global System Truth Ledger
pub var global_stl: SystemTruthLedger = undefined;
pub var stl_initialized: bool = false;
/// Initialize STL subsystem
pub export fn stl_init() void {
if (stl_initialized) return;
global_stl = SystemTruthLedger.init();
stl_initialized = true;
}
/// Sovereign timer — canonical time source for all kernel timestamps
extern fn rumpk_timer_now_ns() u64;
/// Get current timestamp in nanoseconds since boot
fn get_timestamp_ns() u64 {
return rumpk_timer_now_ns();
}
/// Emit event to STL (C ABI)
pub export fn stl_emit(
kind: u16,
fiber_id: u64,
entity_id: u64,
cause_id: u64,
data0: u64,
data1: u64,
data2: u64,
) u64 {
if (!stl_initialized) return 0;
const event = Event{
.kind = @enumFromInt(kind),
._reserved = 0,
.timestamp_ns = get_timestamp_ns(),
.fiber_id = fiber_id,
.entity_id = entity_id,
.cause_id = cause_id,
.data0 = data0,
.data1 = data1,
.data2 = data2,
};
return global_stl.append(event);
}
/// Lookup event by ID (C ABI)
pub export fn stl_lookup(event_id: u64) ?*const Event {
if (!stl_initialized) return null;
return global_stl.lookup(event_id);
}
/// Get event count (C ABI)
pub export fn stl_count() u32 {
if (!stl_initialized) return 0;
return global_stl.count();
}
/// Query result structure for event filtering
pub const QueryResult = extern struct {
count: u32,
events: [64]*const Event, // Max 64 results per query
};
/// Query events by fiber ID (C ABI)
pub export fn stl_query_by_fiber(fiber_id: u64, result: *QueryResult) void {
if (!stl_initialized) {
result.count = 0;
return;
}
var count: u32 = 0;
var idx = global_stl.tail;
while (idx != global_stl.head and count < 64) : (idx = (idx + 1) % STL_SIZE) {
const event = &global_stl.events[idx];
if (!event.is_null() and event.fiber_id == fiber_id) {
result.events[count] = event;
count += 1;
}
}
result.count = count;
}
/// Query events by kind (C ABI)
pub export fn stl_query_by_kind(kind: u16, result: *QueryResult) void {
if (!stl_initialized) {
result.count = 0;
return;
}
var count: u32 = 0;
var idx = global_stl.tail;
while (idx != global_stl.head and count < 64) : (idx = (idx + 1) % STL_SIZE) {
const event = &global_stl.events[idx];
if (!event.is_null() and @intFromEnum(event.kind) == kind) {
result.events[count] = event;
count += 1;
}
}
result.count = count;
}
/// Get recent events (last N) (C ABI)
pub export fn stl_get_recent(max_count: u32, result: *QueryResult) void {
if (!stl_initialized) {
result.count = 0;
return;
}
const actual_count = @min(max_count, @min(global_stl.count(), 64));
var count: u32 = 0;
// Start from most recent (head - 1)
var idx: u32 = if (global_stl.head == 0) STL_SIZE - 1 else global_stl.head - 1;
while (count < actual_count) {
const event = &global_stl.events[idx];
if (!event.is_null()) {
result.events[count] = event;
count += 1;
}
if (idx == global_stl.tail) break;
idx = if (idx == 0) STL_SIZE - 1 else idx - 1;
}
result.count = count;
}
/// Lineage result structure for causal tracing
pub const LineageResult = extern struct {
count: u32,
event_ids: [16]u64, // Maximum depth of 16 for causal chains
};
/// Trace the causal lineage of an event (C ABI)
pub export fn stl_trace_lineage(event_id: u64, result: *LineageResult) void {
if (!stl_initialized) {
result.count = 0;
return;
}
var count: u32 = 0;
var current_id = event_id;
while (count < 16) {
const event = global_stl.lookup(current_id) orelse break;
result.event_ids[count] = current_id;
count += 1;
// Stop if we reach an event with no parent (or self-referencing parent)
if (event.cause_id == current_id) break;
// In our system, the root event (SystemBoot) has ID 0 and cause_id 0
if (current_id == 0 and event.cause_id == 0) break;
current_id = event.cause_id;
}
result.count = count;
}
/// Query events by time range (C ABI)
pub export fn stl_query_by_time_range(start_ns: u64, end_ns: u64, result: *QueryResult) void {
if (!stl_initialized) {
result.count = 0;
return;
}
var count: u32 = 0;
var idx = global_stl.tail;
while (idx != global_stl.head and count < 64) : (idx = (idx + 1) % STL_SIZE) {
const event = &global_stl.events[idx];
if (!event.is_null() and event.timestamp_ns >= start_ns and event.timestamp_ns <= end_ns) {
result.events[count] = event;
count += 1;
}
}
result.count = count;
}
/// System statistics structure
pub const SystemStats = extern struct {
total_events: u32,
boot_events: u32,
fiber_events: u32,
cap_events: u32,
io_events: u32,
mem_events: u32,
net_events: u32,
security_events: u32,
};
/// Get system statistics from STL (C ABI)
pub export fn stl_get_stats(stats: *SystemStats) void {
if (!stl_initialized) {
stats.* = .{
.total_events = 0,
.boot_events = 0,
.fiber_events = 0,
.cap_events = 0,
.io_events = 0,
.mem_events = 0,
.net_events = 0,
.security_events = 0,
};
return;
}
var s = SystemStats{
.total_events = global_stl.count(),
.boot_events = 0,
.fiber_events = 0,
.cap_events = 0,
.io_events = 0,
.mem_events = 0,
.net_events = 0,
.security_events = 0,
};
var idx = global_stl.tail;
while (idx != global_stl.head) : (idx = (idx + 1) % STL_SIZE) {
const event = &global_stl.events[idx];
if (event.is_null()) continue;
switch (event.kind) {
.SystemBoot, .SystemShutdown => s.boot_events += 1,
.FiberSpawn, .FiberTerminate => s.fiber_events += 1,
.CapabilityGrant, .CapabilityRevoke, .CapabilityDelegate => s.cap_events += 1,
.ChannelOpen, .ChannelClose, .ChannelRead, .ChannelWrite => s.io_events += 1,
.MemoryAllocate, .MemoryFree, .MemoryMap => s.mem_events += 1,
.NetworkPacketRx, .NetworkPacketTx => s.net_events += 1,
.AccessDenied, .PolicyViolation => s.security_events += 1,
else => {},
}
}
stats.* = s;
}
/// Binary Header for STL Export
pub const STLHeader = extern struct {
magic: u32 = 0x53544C21, // "STL!"
version: u16 = 1,
event_count: u16,
event_size: u8 = @sizeOf(Event),
_reserved: [23]u8 = [_]u8{0} ** 23, // Pad to 32 bytes for alignment
};
/// Export all events as a contiguous binary blob (C ABI)
/// Returns number of bytes written
pub export fn stl_export_binary(dest: [*]u8, max_size: usize) usize {
if (!stl_initialized) return 0;
const count = global_stl.count();
const required_size = @sizeOf(STLHeader) + (count * @sizeOf(Event));
if (max_size < required_size) return 0;
var ptr = dest;
// Write Header
const header = STLHeader{
.event_count = @as(u16, @intCast(count)),
};
@memcpy(ptr, @as([*]const u8, @ptrCast(&header))[0..@sizeOf(STLHeader)]);
ptr += @sizeOf(STLHeader);
// Write Events (in chronological order from tail to head)
var idx = global_stl.tail;
while (idx != global_stl.head) : (idx = (idx + 1) % STL_SIZE) {
const event = &global_stl.events[idx];
if (event.is_null()) continue;
@memcpy(ptr, @as([*]const u8, @ptrCast(event))[0..@sizeOf(Event)]);
ptr += @sizeOf(Event);
}
return required_size;
}
// Unit tests
test "Event creation and validation" {
const event = Event{
.kind = .FiberSpawn,
._reserved = 0,
.timestamp_ns = 1000,
.fiber_id = 42,
.entity_id = 0x1234,
.cause_id = 0,
.data0 = 0,
.data1 = 0,
.data2 = 0,
};
try std.testing.expect(!event.is_null());
try std.testing.expect(event.kind == .FiberSpawn);
try std.testing.expect(event.fiber_id == 42);
}
test "STL operations" {
var stl = SystemTruthLedger.init();
const event1 = Event{
.kind = .SystemBoot,
._reserved = 0,
.timestamp_ns = 1000,
.fiber_id = 0,
.entity_id = 0,
.cause_id = 0,
.data0 = 0,
.data1 = 0,
.data2 = 0,
};
// Append event
const id1 = stl.append(event1);
try std.testing.expect(id1 == 0);
try std.testing.expect(stl.count() == 1);
// Lookup event
const retrieved = stl.lookup(id1).?;
try std.testing.expect(retrieved.kind == .SystemBoot);
}
test "STL wraparound" {
var stl = SystemTruthLedger.init();
// Fill the buffer
var i: u32 = 0;
while (i < STL_SIZE + 10) : (i += 1) {
const event = Event{
.kind = .FiberSpawn,
._reserved = 0,
.timestamp_ns = i,
.fiber_id = i,
.entity_id = 0,
.cause_id = 0,
.data0 = 0,
.data1 = 0,
.data2 = 0,
};
_ = stl.append(event);
}
// Should have wrapped
try std.testing.expect(stl.epoch > 0);
try std.testing.expect(stl.count() == STL_SIZE);
}
test "STL binary export" {
var stl = SystemTruthLedger.init();
_ = stl.append(Event{
.kind = .SystemBoot,
._reserved = 0,
.timestamp_ns = 100,
.fiber_id = 0,
.entity_id = 0,
.cause_id = 0,
.data0 = 1,
.data1 = 2,
.data2 = 3,
});
// Mock global STL for export test (since export uses global_stl)
global_stl = stl;
stl_initialized = true;
var buf: [512]u8 align(16) = undefined;
const written = stl_export_binary(&buf, buf.len);
try std.testing.expect(written > @sizeOf(STLHeader));
const header = @as(*const STLHeader, @ptrCast(@alignCast(&buf))).*;
try std.testing.expect(header.magic == 0x53544C21);
try std.testing.expect(header.event_count == 1);
const first_ev = @as(*const Event, @ptrCast(@alignCast(&buf[@sizeOf(STLHeader)])));
try std.testing.expect(first_ev.kind == .SystemBoot);
try std.testing.expect(first_ev.data0 == 1);
}