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
This commit is contained in:
parent
3779197eb9
commit
8b109652ab
|
|
@ -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..<lineage.count:
|
||||
let eid = lineage.event_ids[i]
|
||||
let ev_ptr = stl_lookup(eid)
|
||||
|
||||
if i > 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)
|
||||
|
|
|
|||
161
hal/ontology.zig
161
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
Loading…
Reference in New Issue