From 8b109652abe451fdb3ecb32c44e47901f8051cd4 Mon Sep 17 00:00:00 2001 From: Markus Maiwald Date: Tue, 6 Jan 2026 10:13:59 +0100 Subject: [PATCH] feat(nexshell): implement Visual Causal Graph Viewer - Added 'stl graph' command to NexShell for ASCII causal visualization - Integrated Causal Graph Audit into kernel boot summary - Optimized STL list command to show absolute event IDs - Fixed Nim kernel crashes by avoiding dynamic string allocations in STL summary - Hardened HAL-to-NexShell interface with proper extern declarations --- core/ontology.nim | 79 +++++++++++------- hal/ontology.zig | 161 ++++++++++++++++++++++++++++++++++-- src/npl/system/nexshell.zig | 155 ++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+), 33 deletions(-) diff --git a/core/ontology.nim b/core/ontology.nim index f8a93e0..e6e16b2 100644 --- a/core/ontology.nim +++ b/core/ontology.nim @@ -37,6 +37,7 @@ type proc stl_query_by_fiber*(fiber_id: uint64, result: var QueryResult) {.importc, cdecl.} proc stl_query_by_kind*(kind: uint16, result: var QueryResult) {.importc, cdecl.} proc stl_get_recent*(max_count: uint32, result: var QueryResult) {.importc, cdecl.} +proc stl_query_by_time_range*(start_ns: uint64, end_ns: uint64, result: var QueryResult) {.importc, cdecl.} type LineageResult* = object @@ -45,6 +46,20 @@ type proc stl_trace_lineage*(event_id: uint64, result: var LineageResult) {.importc, cdecl.} +type + SystemStats* = object + total_events*: uint32 + boot_events*: uint32 + fiber_events*: uint32 + cap_events*: uint32 + io_events*: uint32 + mem_events*: uint32 + net_events*: uint32 + security_events*: uint32 + +proc stl_get_stats*(stats: var SystemStats) {.importc, cdecl.} +proc stl_export_binary*(dest: pointer, max_size: uint64): uint64 {.importc, cdecl.} + ## Event Types (Mirror from ontology.zig) type EventKind* = enum @@ -153,42 +168,50 @@ proc init_stl_subsystem*() = kprintln("[STL] System Truth Ledger initialized") ## Query API -proc stl_print_summary*() = +proc stl_print_summary*() {.exportc, cdecl.} = ## Print a summary of the STL ledger to the console - kprintln("\n[STL] Event Summary:") - let total = stl_count() - kprint("[STL] Total Events: "); kprint_hex(uint64(total)); kprintln("") + var stats: SystemStats + stl_get_stats(stats) + + kprintln("\n[STL] System Truth Ledger Summary:") + kprint("[STL] Total Events: "); kprint_hex(uint64(stats.total_events)); kprintln("") + kprint("[STL] Lifecycle: "); kprint_hex(uint64(stats.boot_events + stats.fiber_events)); kprintln("") + kprint("[STL] Capabilities: "); kprint_hex(uint64(stats.cap_events)); kprintln("") + kprint("[STL] I/O & Channels: "); kprint_hex(uint64(stats.io_events)); kprintln("") + kprint("[STL] Memory: "); kprint_hex(uint64(stats.mem_events)); kprintln("") + kprint("[STL] Security/Policy: "); kprint_hex(uint64(stats.security_events)); kprintln("") - var res: QueryResult - - # Boot Events - stl_query_by_kind(uint16(EvSystemBoot), res) - kprint("[STL] SystemBoot: "); kprint_hex(uint64(res.count)); kprintln("") - - # Fiber Spawn - stl_query_by_kind(uint16(EvFiberSpawn), res) - kprint("[STL] FiberSpawn: "); kprint_hex(uint64(res.count)); kprintln("") - - # Cap Grants - stl_query_by_kind(uint16(EvCapabilityGrant), res) - kprint("[STL] CapGrant: "); kprint_hex(uint64(res.count)); kprintln("") - - # Demonstrate Lineage Tracing for the last event - if total > 0: - let last_id = uint64(total - 1) + # Demonstrate Causal Graph for the last event + if stats.total_events > 0: + let last_id = uint64(stats.total_events - 1) var lineage: LineageResult stl_trace_lineage(last_id, lineage) - kprint("[STL] Lineage Trace for Event "); kprint_hex(last_id); kprintln(":") + kprintln("\n[STL] Causal Graph Audit:"); + kprint("[STL] Target: "); kprint_hex(last_id); kprintln("") + for i in 0.. 0: kprintln(" |") + + kprint(" +-- ["); kprint_hex(eid); kprint("] ") + if ev_ptr != nil: - # We can't easily access fields of Event struct from Nim if it's packed in Zig - # but we know kind is at offset 0 (2 bytes) - let kind = cast[ptr uint16](ev_ptr)[] - kprint(" <- ["); kprint_hex(eid); kprint("] Kind="); kprint_hex(uint64(kind)); kprintln("") + # Kind is at offset 0 (2 bytes) + let kind_val = cast[ptr uint16](ev_ptr)[] + if kind_val == uint16(EvSystemBoot): kprintln("SystemBoot") + elif kind_val == uint16(EvFiberSpawn): kprintln("FiberSpawn") + elif kind_val == uint16(EvCapabilityGrant): kprintln("CapGrant") + elif kind_val == uint16(EvAccessDenied): kprintln("AccessDenied") + else: + kprint("Kind="); kprint_hex(uint64(kind_val)); kprintln("") else: - kprint(" <- ["); kprint_hex(eid); kprintln("] (Lookup Failed)") + kprintln("Unknown") - kprintln("[STL] Summary complete.") + kprintln("\n[STL] Summary complete.") + +proc export_stl_binary*(dest: pointer, max_size: uint64): uint64 = + ## Export STL events to a binary buffer + return stl_export_binary(dest, max_size) diff --git a/hal/ontology.zig b/hal/ontology.zig index ee8488e..1e02185 100644 --- a/hal/ontology.zig +++ b/hal/ontology.zig @@ -178,11 +178,8 @@ pub const SystemTruthLedger = struct { /// Get current event count pub fn count(self: *const SystemTruthLedger) u32 { - if (self.head >= self.tail) { - return self.head - self.tail; - } else { - return (STL_SIZE - self.tail) + self.head; - } + if (self.epoch > 0) return STL_SIZE; + return self.head; } }; @@ -350,6 +347,127 @@ pub export fn stl_trace_lineage(event_id: u64, result: *LineageResult) void { 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{ @@ -418,3 +536,36 @@ test "STL wraparound" { 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); +} diff --git a/src/npl/system/nexshell.zig b/src/npl/system/nexshell.zig index b4a62de..b98db26 100644 --- a/src/npl/system/nexshell.zig +++ b/src/npl/system/nexshell.zig @@ -223,6 +223,85 @@ fn process_command(cmd_text: []const u8, cmd_ring: *RingBuffer(CmdPacket)) void print(" - NetSwitch (Traffic Engine)\n"); print(" - Subject (Userland Loader)\n"); print(" - Kernel (Main)\n"); + } else if (std.mem.eql(u8, cmd_text, "stl summary")) { + stl_print_summary(); + } else if (std.mem.eql(u8, cmd_text, "stl list")) { + print("[NexShell] Recent Events:\n"); + const total = stl_count(); + const start = if (total > 10) total - 10 else 0; + var id = total - 1; + while (id >= start) { + const ev = stl_lookup(id); + if (ev) |e| { + print(" ["); + print_u64_hex(id); + print("] Kind="); + print_u16_hex(@intFromEnum(e.kind)); + print(" Fiber="); + print_u16_hex(@as(u16, @intCast(e.fiber_id))); + print("\n"); + } + if (id == 0) break; + id -= 1; + } + } else if (std.mem.startsWith(u8, cmd_text, "stl graph") or std.mem.eql(u8, cmd_text, "stl tree")) { + var lineage: LineageResult = undefined; + const total = stl_count(); + if (total == 0) { + print("[NexShell] No events to graph.\n"); + } else { + // Default to last event + const last_id = @as(u64, total - 1); + stl_trace_lineage(last_id, &lineage); + print("[NexShell] Causal Graph for Event "); + print_u64_hex(last_id); + print(":\n\n"); + + var i: u32 = 0; + while (i < lineage.count) : (i += 1) { + const eid = lineage.event_ids[i]; + const ev = stl_lookup(eid); + + if (i > 0) print(" |\n ▼\n"); + + print("["); + print_u64_hex(eid); + print("] "); + + if (ev) |e| { + switch (e.kind) { + .SystemBoot => print("SystemBoot"), + .FiberSpawn => print("FiberSpawn"), + .CapabilityGrant => print("CapGrant"), + .AccessDenied => print("AccessDenied"), + else => { + print("Kind="); + print_u16_hex(@intFromEnum(e.kind)); + }, + } + } else { + print("???"); + } + print("\n"); + } + print("\n"); + } + } else if (std.mem.eql(u8, cmd_text, "stl dump")) { + var dump_buf: [4096]u8 = undefined; + const written = stl_export_binary(&dump_buf, dump_buf.len); + if (written > 0) { + print("[NexShell] STL Binary Dump ("); + print_u64_hex(written); + print(" bytes):\n"); + var i: usize = 0; + while (i < written) : (i += 1) { + print_hex(dump_buf[i]); + if ((i + 1) % 32 == 0) print("\n"); + } + print("\n[NexShell] Dump Complete.\n"); + } else { + print("[NexShell] Dump Failed (Buffer too small or STL not ready)\n"); + } } else if (std.mem.eql(u8, cmd_text, "mem")) { print("[NexShell] Memory Status:\n"); print(" Ion Pool: 32MB allocated\n"); @@ -247,6 +326,7 @@ fn process_command(cmd_text: []const u8, cmd_ring: *RingBuffer(CmdPacket)) void } else if (std.mem.eql(u8, cmd_text, "help")) { print("[NexShell] Kernel Commands:\n"); print(" System: ps, fibers, mem, uptime, reboot, clear\n"); + print(" STL: stl summary, stl list, stl dump, stl graph\n"); print(" IO: io stop, ion stop\n"); print(" Matrix: matrix on/off/status\n"); print(" Shell: subject, nipbox, help\n"); @@ -281,6 +361,81 @@ extern fn fiber_sleep(ms: u64) void; extern fn fiber_yield() void; extern fn debug_uart_lsr() u8; +// STL Externs +extern fn stl_count() u32; +extern fn stl_print_summary() void; +extern fn stl_get_recent(max_count: u32, result: *QueryResult) void; +extern fn stl_export_binary(dest: [*]u8, max_size: usize) usize; +extern fn stl_trace_lineage(event_id: u64, result: *LineageResult) void; +extern fn stl_lookup(event_id: u64) ?*const Event; + +const LineageResult = extern struct { + count: u32, + event_ids: [16]u64, +}; + +const EventKind = enum(u16) { + Null = 0, + SystemBoot = 1, + SystemShutdown = 2, + FiberSpawn = 3, + FiberTerminate = 4, + CapabilityGrant = 10, + CapabilityRevoke = 11, + CapabilityDelegate = 12, + ChannelOpen = 20, + ChannelClose = 21, + ChannelRead = 22, + ChannelWrite = 23, + MemoryAllocate = 30, + MemoryFree = 31, + MemoryMap = 32, + NetworkPacketRx = 40, + NetworkPacketTx = 41, + AccessDenied = 50, + PolicyViolation = 51, +}; + +const Event = packed struct { + kind: EventKind, + _reserved: u8 = 0, + timestamp_ns: u64, + fiber_id: u64, + entity_id: u64, + cause_id: u64, + data0: u64, + data1: u64, + data2: u64, +}; + +const QueryResult = extern struct { + count: u32, + events: [64]*const Event, +}; + +fn print_u64_hex(val: u64) void { + const chars = "0123456789ABCDEF"; + var buf: [16]u8 = undefined; + var v = val; + var i: usize = 0; + while (i < 16) : (i += 1) { + buf[15 - i] = chars[v & 0xF]; + v >>= 4; + } + print(&buf); +} + +fn print_u16_hex(val: u16) void { + const chars = "0123456789ABCDEF"; + const buf = [4]u8{ + chars[(val >> 12) & 0xF], + chars[(val >> 8) & 0xF], + chars[(val >> 4) & 0xF], + chars[val & 0xF], + }; + print(&buf); +} + fn print_hex(val: u8) void { const chars = "0123456789ABCDEF"; const hi = chars[(val >> 4) & 0xF];