feat(core): M4 security — CSpace, Pledge, STL, budget enforcement, BKDL manifests

This commit is contained in:
Markus Maiwald 2026-02-15 19:59:07 +01:00
parent 8d4b581519
commit 0c598ce0bd
11 changed files with 872 additions and 268 deletions

View File

@ -27,6 +27,7 @@ proc cspace_grant_cap*(
proc cspace_lookup*(fiber_id: uint64, slot: uint): pointer {.importc, cdecl.}
proc cspace_revoke*(fiber_id: uint64, slot: uint) {.importc, cdecl.}
proc cspace_check_perm*(fiber_id: uint64, slot: uint, perm_bits: uint8): bool {.importc, cdecl.}
proc cspace_check_channel*(fiber_id: uint64, channel_id: uint64, perm_bits: uint8): bool {.importc, cdecl.}
## Capability Types (Mirror from cspace.zig)
type
@ -80,10 +81,10 @@ proc fiber_grant_memory*(
end_addr
)
proc fiber_check_channel_access*(fiber_id: uint64, slot: uint, write: bool): bool =
## Check if fiber has channel access via capability
proc fiber_check_channel_access*(fiber_id: uint64, channel_id: uint64, write: bool): bool =
## Check if fiber has Channel capability for given channel_id
let perm = if write: PERM_WRITE else: PERM_READ
return cspace_check_perm(fiber_id, slot, perm)
return cspace_check_channel(fiber_id, channel_id, perm)
proc fiber_revoke_capability*(fiber_id: uint64, slot: uint) =
## Revoke a capability from a fiber

View File

@ -9,7 +9,7 @@
# MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
# Rumpk Phase 10: Multitasking & Context Switching
#
#
# Responsibilities:
# - Define the Fiber abstraction (Hardware Context + Stack)
# - Abstract the ISA-specific context switch mechanism
@ -24,6 +24,10 @@ when defined(riscv64):
const ARCH_NAME* = "riscv64"
const CONTEXT_SIZE* = 128
const RET_ADDR_INDEX* = 0 # Offset in stack for RA
elif defined(arm64):
const ARCH_NAME* = "aarch64"
const CONTEXT_SIZE* = 96 # 6 register pairs (x19-x30) * 16 bytes
const RET_ADDR_INDEX* = 11 # x30 (LR) at [sp + 88] = index 11
elif defined(amd64) or defined(x86_64):
const ARCH_NAME* = "amd64"
const CONTEXT_SIZE* = 64
@ -112,7 +116,7 @@ const STACK_SIZE* = 4096
# Fiber State
# =========================================================
var main_fiber: FiberObject
var main_fiber*: FiberObject
var current_fiber* {.global.}: Fiber = addr main_fiber
# =========================================================
@ -135,6 +139,9 @@ proc fiber_trampoline() {.cdecl, exportc, noreturn.} =
when defined(riscv64):
while true:
{.emit: "asm volatile(\"wfi\");".}
elif defined(arm64):
while true:
{.emit: "asm volatile(\"wfe\");".}
else:
while true: discard

View File

@ -37,7 +37,7 @@ type
CMD_GET_GPU_STATUS = 0x102
CMD_FS_OPEN = 0x200
CMD_FS_READ = 0x201
CMD_FS_READDIR = 0x202
CMD_FS_READDIR = 0x202
CMD_FS_WRITE = 0x203
CMD_FS_MOUNT = 0x204
CMD_ION_FREE = 0x300
@ -79,7 +79,7 @@ type
SysTable* = object
magic*: uint32 # 0x4E585553
reserved*: uint32
reserved*: uint32
s_rx*: ptr HAL_Ring[IonPacket]
s_tx*: ptr HAL_Ring[IonPacket]
s_event*: ptr HAL_Ring[IonPacket]
@ -91,9 +91,11 @@ type
fn_vfs_list*: proc(buf: pointer, max_len: uint64): int64 {.cdecl.}
fn_vfs_write*: proc(fd: int32, buf: pointer, count: uint64): int64 {.cdecl.}
fn_vfs_close*: proc(fd: int32): int32 {.cdecl.}
fn_vfs_dup*: proc(fd: int32, min_fd: int32): int32 {.cdecl.}
fn_vfs_dup2*: proc(old_fd, new_fd: int32): int32 {.cdecl.}
fn_log*: pointer
fn_pledge*: proc(promises: uint64): int32 {.cdecl.}
# Framebuffer
fb_addr*: uint64
fb_width*: uint32
@ -115,14 +117,18 @@ type
# Phase 36.3: Shared ION (16 bytes)
fn_ion_alloc*: proc(out_id: ptr uint16): uint64 {.cdecl.}
fn_ion_free*: proc(id: uint16) {.cdecl.}
# Phase 36.4: I/O Multiplexing (8 bytes)
fn_wait_multi*: proc(mask: uint64): int32 {.cdecl.}
# Phase 36.5: Network Hardware Info (8 bytes)
net_mac*: array[6, byte]
reserved_mac*: array[2, byte]
# Project LibWeb: LWF Sovereign Channel (16 bytes)
s_lwf_rx*: ptr HAL_Ring[IonPacket] # Kernel Producer -> User Consumer (LWF frames)
s_lwf_tx*: ptr HAL_Ring[IonPacket] # User Producer -> Kernel Consumer (LWF frames)
include invariant
# --- Sovereign Logic ---
@ -167,6 +173,12 @@ var net_rx_hal: HAL_Ring[IonPacket]
var net_tx_hal: HAL_Ring[IonPacket]
var netswitch_rx_hal: HAL_Ring[IonPacket]
# Project LibWeb: LWF Sovereign Channels
var chan_lwf_rx*: SovereignChannel[IonPacket] # Kernel -> User (LWF frames)
var chan_lwf_tx*: SovereignChannel[IonPacket] # User -> Kernel (LWF frames)
var lwf_rx_hal: HAL_Ring[IonPacket]
var lwf_tx_hal: HAL_Ring[IonPacket]
proc ion_init_input*() {.exportc, cdecl.} =
guest_input_hal.head = 0
guest_input_hal.tail = 0
@ -181,7 +193,7 @@ proc ion_init_network*() {.exportc, cdecl.} =
net_rx_hal.tail = 0
net_rx_hal.mask = 255
chan_net_rx.ring = addr net_rx_hal
net_tx_hal.head = 0
net_tx_hal.tail = 0
net_tx_hal.mask = 255
@ -191,7 +203,18 @@ proc ion_init_network*() {.exportc, cdecl.} =
netswitch_rx_hal.tail = 0
netswitch_rx_hal.mask = 255
chan_netswitch_rx.ring = addr netswitch_rx_hal
# Project LibWeb: LWF Rings
lwf_rx_hal.head = 0
lwf_rx_hal.tail = 0
lwf_rx_hal.mask = 255
chan_lwf_rx.ring = addr lwf_rx_hal
lwf_tx_hal.head = 0
lwf_tx_hal.tail = 0
lwf_tx_hal.mask = 255
chan_lwf_tx.ring = addr lwf_tx_hal
# Initialize user slab
ion_user_slab_init()
@ -218,4 +241,4 @@ proc ion_user_free_systable*(id: uint16) {.exportc, cdecl.} =
static: doAssert(sizeof(IonPacket) == 24, "IonPacket size mismatch!")
static: doAssert(sizeof(CmdPacket) == 32, "CmdPacket size mismatch!")
static: doAssert(sizeof(SysTable) == 208, "SysTable size mismatch! (Expected 208 after MAC+pad)")
static: doAssert(sizeof(SysTable) == 240, "SysTable size mismatch! (Expected 240 after LibWeb LWF channels)")

View File

@ -13,17 +13,24 @@
import ../ring
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}
proc dbg(s: string) =
console_write(unsafeAddr s[0], csize_t(s.len))
var nl = "\n"
console_write(unsafeAddr nl[0], csize_t(1))
var NEWLINE_BUF: array[2, char] = ['\n', '\0']
proc dbg(s: cstring) {.inline.} =
if s != nil:
var i = 0
let p = cast[ptr UncheckedArray[char]](s)
while p[i] != '\0': inc i
console_write(cast[pointer](s), csize_t(i))
console_write(addr NEWLINE_BUF[0], csize_t(1))
const
SLAB_SIZE* = 2048 # Max Packet Size (Ethernet Frame + Headroom)
POOL_COUNT* = 1024 # Number of packets in the pool (2MB total RAM)
POOL_ALIGN* = 4096 # VirtIO/Page Alignment
SYSTABLE_BASE = 0x83000000'u64
SYSTABLE_BASE = when defined(arm64): 0x50000000'u64
else: 0x83000000'u64
USER_SLAB_OFFSET = 0x10000'u64 # Offset within SYSTABLE
USER_SLAB_BASE* = SYSTABLE_BASE + USER_SLAB_OFFSET # 0x83010000
USER_SLAB_COUNT = 512 # 512 packets to cover RX Ring (256) + TX
@ -53,9 +60,11 @@ proc ion_pool_init*() {.exportc.} =
dbg("[ION] Initializing Pool...")
# 1. Get the VIRTUAL address of the static buffer
dbg("[ION] Step 1: Getting virt addr...")
let virt_addr = cast[uint64](addr global_pool.buffer[0])
# 2. Translate to PHYSICAL (Identity Mapped for Phase 7)
dbg("[ION] Step 2: Setting base phys...")
global_pool.base_phys = virt_addr
# Tracing for Phase 37
@ -64,12 +73,16 @@ proc ion_pool_init*() {.exportc.} =
kprint_hex(global_pool.base_phys)
dbg("")
dbg("[ION] Ring Init...")
dbg("[ION] Step 3: Ring Init (free_ring)...")
dbg(" 3a: About to call init()")
global_pool.free_ring.init()
dbg(" 3b: free_ring init complete")
dbg("[ION] Step 4: Ring Init (tx_ring)...")
global_tx_ring.init()
dbg(" 4a: tx_ring init complete")
# Fill the free ring with all indices [0..1023]
dbg("[ION] Filling Slabs...")
dbg("[ION] Step 5: Filling Slabs...")
var count = 0
for i in 0 ..< POOL_COUNT:
if global_pool.free_ring.push(uint16(i)):
@ -77,6 +90,8 @@ proc ion_pool_init*() {.exportc.} =
dbg("[ION] Pool Ready.")
proc ion_alloc*(): IonPacket {.exportc.} =
## O(1) Allocation. Returns an empty packet struct.
## If OOM, returns packet with data = nil
@ -197,7 +212,7 @@ proc ion_user_slab_init*() {.exportc.} =
proc ion_alloc_shared*(out_id: ptr uint16): uint64 {.exportc, cdecl.} =
## Allocate a buffer from the user-visible slab (Kernel Side, Shared Bitmap)
let bitmap = cast[ptr array[64, byte]](USER_BITMAP_ADDR)
for byteIdx in 0 ..< 64:
if bitmap[byteIdx] != 0xFF:
for bitIdx in 0 ..< 8:
@ -207,7 +222,7 @@ proc ion_alloc_shared*(out_id: ptr uint16): uint64 {.exportc, cdecl.} =
bitmap[byteIdx] = bitmap[byteIdx] or mask
let idx = byteIdx * 8 + bitIdx
if idx >= USER_SLAB_COUNT: return 0
out_id[] = uint16(idx) or 0x8000
return USER_SLAB_BASE + uint64(idx) * USER_PKT_SIZE
return 0

File diff suppressed because it is too large Load Diff

View File

@ -67,6 +67,76 @@ proc kload*(path: string): uint64 =
return ehdr.e_entry
# --- M4.4: BKDL Manifest Extraction ---
proc streq_n(a: ptr UncheckedArray[byte], b: cstring, maxlen: int): bool =
## Compare byte array against C string, bounded by maxlen
var i = 0
while i < maxlen:
if b[i] == '\0':
return true # b ended, all matched
if a[i] != byte(b[i]):
return false
i += 1
return false
proc kload_manifest*(file_content: openArray[byte]): ManifestResult =
## Scan ELF section headers for .nexus.manifest containing BKDL data.
## Returns header=nil if no manifest found.
result.header = nil
result.caps = nil
result.count = 0
if file_content.len < int(sizeof(Elf64_Ehdr)):
return
let ehdr = cast[ptr Elf64_Ehdr](unsafeAddr file_content[0])
let base = cast[uint64](unsafeAddr file_content[0])
let file_len = uint64(file_content.len)
# Validate section header table is within file
if ehdr.e_shoff == 0 or ehdr.e_shnum == 0:
return
if ehdr.e_shoff + uint64(ehdr.e_shnum) * uint64(ehdr.e_shentsize) > file_len:
return
# Get string table section (shstrtab)
if ehdr.e_shstrndx >= ehdr.e_shnum:
return
let strtab_shdr = cast[ptr Elf64_Shdr](base + ehdr.e_shoff + uint64(ehdr.e_shstrndx) * uint64(ehdr.e_shentsize))
if strtab_shdr.sh_offset + strtab_shdr.sh_size > file_len:
return
let strtab = cast[ptr UncheckedArray[byte]](base + strtab_shdr.sh_offset)
# Scan sections for .nexus.manifest
let target = cstring(".nexus.manifest")
for i in 0 ..< int(ehdr.e_shnum):
let shdr = cast[ptr Elf64_Shdr](base + ehdr.e_shoff + uint64(i) * uint64(ehdr.e_shentsize))
if shdr.sh_name < uint32(strtab_shdr.sh_size):
let name_ptr = cast[ptr UncheckedArray[byte]](cast[uint64](strtab) + uint64(shdr.sh_name))
let remaining = int(strtab_shdr.sh_size) - int(shdr.sh_name)
if streq_n(name_ptr, target, remaining):
# Found .nexus.manifest section
if shdr.sh_offset + shdr.sh_size > file_len:
return # Section data out of bounds
if shdr.sh_size < uint64(sizeof(BkdlHeader)):
return # Too small
let hdr = cast[ptr BkdlHeader](base + shdr.sh_offset)
if hdr.magic != BKDL_MAGIC or hdr.version != BKDL_VERSION:
kprintln("[Manifest] Invalid BKDL magic/version")
return
let expected_size = uint64(sizeof(BkdlHeader)) + uint64(hdr.cap_count) * uint64(sizeof(CapDescriptor))
if expected_size > shdr.sh_size:
kprintln("[Manifest] BKDL cap_count exceeds section size")
return
result.header = hdr
result.caps = cast[ptr UncheckedArray[CapDescriptor]](base + shdr.sh_offset + uint64(sizeof(BkdlHeader)))
result.count = int(hdr.cap_count)
return
proc kexec*(path: string) =
let entry = kload(path)
if entry != 0:

View File

@ -37,8 +37,48 @@ type
p_memsz*: uint64
p_align*: uint64
Elf64_Shdr* {.packed.} = object
sh_name*: uint32
sh_type*: uint32
sh_flags*: uint64
sh_addr*: uint64
sh_offset*: uint64
sh_size*: uint64
sh_link*: uint32
sh_info*: uint32
sh_addralign*: uint64
sh_entsize*: uint64
const
PT_LOAD* = 1
PT_NOTE* = 4
PF_X* = 1
PF_W* = 2
PF_R* = 4
# SPEC-071: BKDL (Binary Manifest) types
const
BKDL_MAGIC* = 0x4E585553'u32 # "NXUS" (little-endian)
BKDL_VERSION* = 1'u16
type
BkdlHeader* {.packed.} = object
magic*: uint32
version*: uint16
flags*: uint16
signature*: array[64, uint8] # Ed25519 (unchecked in dev mode)
pubkey_hash*: array[32, uint8] # SHA-256 of signing key
cap_count*: uint16
blob_size*: uint32
entry_point*: uint64 # 0 = use ELF e_entry
CapDescriptor* {.packed.} = object
cap_type*: uint8 # CapType enum value
perms*: uint8 # Permission bitmask
reserved*: uint16 # Alignment padding
resource_id*: uint64 # SipHash of resource name
ManifestResult* = object
header*: ptr BkdlHeader
caps*: ptr UncheckedArray[CapDescriptor]
count*: int

View File

@ -9,7 +9,7 @@
# core/rumpk/core/netswitch.nim
# Phase 36.2: The Traffic Cop (Network Membrane Layer 2 Switch)
#
#
# Responsibilities:
# - Poll VirtIO-Net hardware for incoming packets
# - Route RX packets to s_net_rx ring (Kernel -> Userland)
@ -34,6 +34,11 @@ proc kprint(s: cstring) {.importc, cdecl.}
proc kprint_hex(v: uint64) {.importc, cdecl.}
proc get_now_ns(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
# Project LibWeb: LWF Adapter (Zig FFI)
proc lwf_validate(data: pointer, len: uint16): uint8 {.importc, cdecl.}
const ETHERTYPE_LWF = 0x4C57'u16 # "LW" — Libertaria Wire Frame
# Membrane Infrastructure (LwIP Glue)
proc membrane_init*() {.importc, cdecl.}
proc pump_membrane_stack*() {.importc, cdecl.}
@ -73,19 +78,39 @@ proc netswitch_process_packet(pkt: IonPacket): bool =
case etype:
of 0x0800, 0x0806, 0x86DD: # IPv4, ARP, IPv6
# NOTE(LibWeb): IPv6 is the first-class citizen for sovereign mesh.
# Most chapter nodes sit behind consumer NAT — IPv6 provides
# end-to-end addressability without traversal hacks.
# IPv4 is the fallback, not the default. Membrane/Transport
# layer enforces preference order (IPv6 → IPv4).
# Route to Legacy/LwIP Membrane
if not chan_net_rx.send(pkt):
# Ring full (Backpressure)
ion_free(pkt)
return false
return true
of 0x88B5: # Sovereign UTCP (SPEC-700)
# TODO: Route to dedicated UTCP channel
# kprintln("[NetSwitch] UTCP Sovereign Packet Identified")
ion_free(pkt)
return true # Handled (dropped)
of ETHERTYPE_LWF: # Project LibWeb: Libertaria Wire Frame
# Validate LWF magic before routing (Fast Drop)
let lwf_data = cast[pointer](cast[uint64](pkt.data) + 14) # Skip Eth header
let lwf_len = if pkt.len > 14: uint16(pkt.len - 14) else: 0'u16
if lwf_len >= 88 and lwf_validate(lwf_data, lwf_len) == 1:
# Valid LWF — route to dedicated LWF channel
if not chan_lwf_rx.send(pkt):
ion_free(pkt) # Backpressure
return false
return true
else:
# Invalid LWF frame — drop
ion_free(pkt)
return false
else:
# Drop unknown EtherTypes (Security/Sovereignty)
ion_free(pkt)
@ -93,30 +118,30 @@ proc netswitch_process_packet(pkt: IonPacket): bool =
proc fiber_netswitch_entry*() {.cdecl.} =
kprintln("[NetSwitch] Fiber Entry - The Traffic Cop is ON DUTY")
var rx_activity: bool = false
var tx_activity: bool = false
var rx_count: uint64 = 0
var tx_count: uint64 = 0
var last_stat_print: uint64 = 0
while true:
rx_activity = false
tx_activity = false
# 1. Drive the hardware poll (fills chan_netswitch_rx)
virtio_net_poll()
# [Cleaned] Driven by Userland now
# 2. Consume from the Driver -> Switch internal ring
var raw_pkt: IonPacket
while chan_netswitch_rx.recv(raw_pkt):
if netswitch_process_packet(raw_pkt):
rx_activity = true
inc rx_count
# 3. TX PATH: Userland -> Hardware
# 3. TX PATH: Userland -> Hardware (Legacy/LwIP)
var tx_pkt: IonPacket
while chan_net_tx.recv(tx_pkt):
if tx_pkt.data != nil and tx_pkt.len > 0:
@ -124,7 +149,16 @@ proc fiber_netswitch_entry*() {.cdecl.} =
inc tx_count
ion_free(tx_pkt)
tx_activity = true
# 3b. TX PATH: LWF Egress (Project LibWeb)
var lwf_pkt: IonPacket
while chan_lwf_tx.recv(lwf_pkt):
if lwf_pkt.data != nil and lwf_pkt.len > 0:
virtio_net_send(cast[pointer](lwf_pkt.data), uint32(lwf_pkt.len))
inc tx_count
ion_free(lwf_pkt)
tx_activity = true
# 4. Periodically print stats
let now = get_now_ns()
if now - last_stat_print > 5000000000'u64: # 5 seconds

View File

@ -161,6 +161,24 @@ proc emit_access_denied*(
0, 0
)
proc emit_policy_violation*(
fiber_id: uint64,
budget_ns: uint64,
actual_ns: uint64,
violation_count: uint32,
cause_id: uint64 = 0
): uint64 {.exportc, cdecl.} =
## Emit budget violation event to STL (The Ratchet, M4.5)
return stl_emit(
uint16(EvPolicyViolation),
fiber_id,
budget_ns,
cause_id,
actual_ns,
uint64(violation_count),
0
)
## Initialization
proc init_stl_subsystem*() =
## Initialize the STL subsystem (call from kmain)
@ -180,24 +198,24 @@ proc stl_print_summary*() {.exportc, cdecl.} =
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("")
# Demonstrate Causal Graph for the last event
if stats.total_events > 0:
let last_id = uint64(stats.total_events - 1)
let last_id = uint64(stats.total_events - 1)
var lineage: LineageResult
stl_trace_lineage(last_id, lineage)
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:
# Kind is at offset 0 (2 bytes)
let kind_val = cast[ptr uint16](ev_ptr)[]

View File

@ -175,10 +175,17 @@ proc pty_write_slave*(fd: int, data: ptr byte, len: int): int {.exportc, cdecl.}
if ring_push(pty.slave_to_master, pty.stm_head, pty.stm_tail, b):
written += 1
# Mirror to UART console
var c_buf: array[2, char]
c_buf[0] = char(b)
c_buf[1] = '\0'
kprint(cast[cstring](addr c_buf[0]))
# Also render to FB terminal
term_putc(char(b))
else:
break
# Render frame after batch write
if written > 0:

View File

@ -22,26 +22,38 @@ import fiber
# We need a centralized registry or a way to iterate.
#
# For the first pass, we can replicate the logic in kernel.nim which explicitly checks
# the known fibers, but structured as the Spectrum loop.
# the known fibers, but structured as the Spectrum loop.
# Or we can make kernel.nim pass the fibers to us.
#
# Let's keep it simple and stateless in sched.nim if possible, or have it manage the queue.
# Since kernel.nim holds the variables, sched.nim should probably define the *Strategy*
# Since kernel.nim holds the variables, sched.nim should probably define the *Strategy*
# and kernel.nim calls it, OR sched.nim should import kernel (circular!).
#
# Better: fiber.nim holds a linked list of fibers?
# Better: fiber.nim holds a linked list of fibers?
# Or sched.nim is just a helper module that kernel.nim uses.
# Let's define the Strategy here.
# To avoid circular imports, kernel.nim will likely INCLUDE sched.nim or sched.nim
# will act on a passed context.
# To avoid circular imports, kernel.nim will likely INCLUDE sched.nim or sched.nim
# will act on a passed context.
# BUT, SPEC-102 implies sched.nim *is* the logic.
#
# Let's define the Harmonic logic.
# Let's define the Harmonic logic.
# We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper).
proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}
proc console_write(p: pointer, len: csize_t) {.importc: "hal_console_write", cdecl.}
proc uart_print_hex(v: uint64) {.importc: "uart_print_hex", cdecl.}
proc uart_print_hex8(v: uint8) {.importc: "uart_print_hex8", cdecl.}
# M4.5: STL emission for budget violations (The Ratchet)
proc emit_policy_violation*(fiber_id, budget_ns, actual_ns: uint64,
violation_count: uint32, cause_id: uint64): uint64 {.importc, cdecl.}
# Forward declaration — implementation is in THE RATCHET section below
proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64)
var photon_idx, matter_idx, gravity_idx, void_idx: int
# Forward declaration for channel data check (provided by kernel/channels)
proc fiber_can_run_on_channels*(id: uint64, mask: uint64): bool {.importc, cdecl.}
@ -58,16 +70,22 @@ proc is_runnable(f: ptr FiberObject, now: uint64): bool =
proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
let now = sched_get_now_ns()
# =========================================================
# Phase 1: PHOTON (Hard Real-Time / Hardware Driven)
# =========================================================
var run_photon = false
for f in fibers:
for i in 0..<fibers.len:
let idx = (photon_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Photon:
if is_runnable(f, now):
if f != current_fiber:
switch(f); return true
photon_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else:
run_photon = true
if run_photon: return true
@ -76,11 +94,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 2: MATTER (Interactive / Latency Sensitive)
# =========================================================
var run_matter = false
for f in fibers:
for i in 0..<fibers.len:
let idx = (matter_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Matter:
if is_runnable(f, now):
if f != current_fiber:
switch(f); return true
matter_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else:
run_matter = true
if run_matter: return true
@ -89,11 +113,17 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# Phase 3: GRAVITY (Throughput / Background)
# =========================================================
var run_gravity = false
for f in fibers:
for i in 0..<fibers.len:
let idx = (gravity_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Gravity:
if is_runnable(f, now):
if f != current_fiber:
switch(f); return true
gravity_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else:
run_gravity = true
if run_gravity: return true
@ -101,15 +131,20 @@ proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
# =========================================================
# Phase 4: VOID (Scavenger)
# =========================================================
for f in fibers:
for i in 0..<fibers.len:
let idx = (void_idx + i) mod fibers.len
let f = fibers[idx]
if f != nil and f.getSpectrum() == Spectrum.Void:
if is_runnable(f, now):
if f != current_fiber:
void_idx = (idx + 1) mod fibers.len
let t0 = sched_get_now_ns()
switch(f)
sched_analyze_burst(f, sched_get_now_ns() - t0)
return true
else:
return true
# =========================================================
# THE SILENCE
# =========================================================
@ -124,15 +159,25 @@ proc sched_get_next_wakeup*(fibers: openArray[ptr FiberObject]): uint64 =
if f != nil and f.sleep_until > now:
if f.sleep_until < min_wakeup:
min_wakeup = f.sleep_until
return min_wakeup
# =========================================================
# M4.5: Budget Defaults Per Spectrum Tier
# =========================================================
proc default_budget_for_spectrum*(s: Spectrum): uint64 =
## Return the default budget_ns for a given Spectrum tier
case s
of Spectrum.Photon: return 2_000_000'u64 # 2ms — hard real-time
of Spectrum.Matter: return 10_000_000'u64 # 10ms — interactive
of Spectrum.Gravity: return 50_000_000'u64 # 50ms — batch
of Spectrum.Void: return 0'u64 # unlimited — scavenger
# =========================================================
# THE RATCHET (Post-Execution Analysis)
# =========================================================
proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}
proc sched_log(msg: string) =
if msg.len > 0:
console_write(unsafeAddr msg[0], csize_t(msg.len))
@ -157,20 +202,29 @@ proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64) =
## Analyze the burst duration of a fiber after it yields/switches
## Implements the 3-strike rule for budget violations
if f == nil: return
f.last_burst_ns = burst_ns
# Update moving average (exponential: 75% old, 25% new)
if f.avg_burst == 0:
f.avg_burst = burst_ns
else:
f.avg_burst = (f.avg_burst * 3 + burst_ns) div 4
# Budget enforcement
if f.budget_ns > 0 and burst_ns > f.budget_ns:
f.violations += 1
console_write(cstring("[Ratchet] Violation: fiber exceeded budget\n"), 42)
discard emit_policy_violation(f.id, f.budget_ns, burst_ns, f.violations, 0)
console_write(cstring("[Ratchet] Budget violation fiber=0x"), 33)
uart_print_hex(f.id)
console_write(cstring(" burst="), 7)
uart_print_hex(burst_ns)
console_write(cstring(" budget="), 8)
uart_print_hex(f.budget_ns)
console_write(cstring(" strikes="), 9)
uart_print_hex(uint64(f.violations))
console_write(cstring("\n"), 1)
if f.violations >= 3:
sched_demote(f)
f.violations = 0 # Reset after demotion