574 lines
15 KiB
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);
|
|
}
|