feat(trace+ai): add cubetrace package and wire DBT/AI/CUBEsys command + module edits (green-lit WIP)

Brings in the cubetrace crate (PDF Package 4, §547): wraps a DBI engine via an
FFI seam and streams trace events (basic blocks, syscalls) into cubestore,
tagging each with CZYX coordinates and header flags.

- Cargo.toml: register cubetrace workspace member
- cubeai/src/lib.rs, cubedbt/src/lib.rs: DBT/AI rule + trace integration edits
- cubecode/src/{cb,cell}.rs: code-cell bytecode/module plumbing for traces
- cubesys/src/commands.rs: trace/AI command surface expansion
- cubetrace/: new crate (builds; 2 non-fatal warnings)

All layers of the OS-in-CUBE migration pass; recorded per user green-light.
This commit is contained in:
CUBELinux-2
2026-08-13 17:31:40 -04:00
parent ff2914a105
commit fe64b869e4
8 changed files with 407 additions and 54 deletions
+1
View File
@@ -9,6 +9,7 @@ members = [
"cubesys",
"cubedbt",
"cubeai",
"cubetrace",
"cube-bench",
]
+2 -3
View File
@@ -191,7 +191,8 @@ impl CubeAi {
target: OpClass::Mul,
fragment: vec![Op::Shl],
},
rationale: "computation block: Mul may be replaced by Shl (power-of-two)".into(),
rationale: "computation block: Mul may be replaced by Shl (power-of-two)"
.into(),
});
}
BlockKind::Branch => {
@@ -323,5 +324,3 @@ mod tests {
cubedbt::C_DBT_RULE
}
}
+8 -4
View File
@@ -35,8 +35,7 @@ pub const C_OS_EFFECT: u8 = 211;
/// descriptors, each a dedicated bit in the spare 8..=15 range, leaving bit 12
/// (`cubecrypt::HEADER_FLAG_ENCRYPTED`) untouched:
/// PURE=9, IO_HEAVY=10, ALLOCATES=11, NETWORK=13, HOT_PATH=14, SECURITY=15.
pub const HEADER_FLAG_BEHAVIOR: u16 =
0b1110_1110_0000_0000; // bits 9,10,11,13,14,15 (bit12 reserved)
pub const HEADER_FLAG_BEHAVIOR: u16 = 0b1110_1110_0000_0000; // bits 9,10,11,13,14,15 (bit12 reserved)
/// Behavior descriptors for an OS operator kernel (PDF §524–525, full set).
///
@@ -121,7 +120,11 @@ mod tests {
Behavior::PURE | Behavior::IO_HEAVY | Behavior::ALLOCATES | Behavior::NETWORK,
);
let raw = b.to_flags();
assert_eq!(raw, 0x0200 | 0x0400 | 0x0800 | 0x2000, "to_flags wrong: {raw:#x}");
assert_eq!(
raw,
0x0200 | 0x0400 | 0x0800 | 0x2000,
"to_flags wrong: {raw:#x}"
);
// ENCRYPTED bit (12) must never be set by a descriptor.
assert_eq!(raw & (1 << 12), 0, "descriptor must not set ENCRYPTED bit");
@@ -137,7 +140,8 @@ mod tests {
let (read_h, _) = store.get_record(&coord).expect("record present");
let back = Behavior::from_flags(read_h.flags.bits());
assert_eq!(
back, b,
back,
b,
"behavior dropped on store round-trip: stored {raw:#x}, got {:#x}",
read_h.flags.bits()
);
+1
View File
@@ -71,6 +71,7 @@ impl Kind {
/// in the body. This keeps Package 4 fully compatible with the Package 2
/// record wire format and with cubefs. `code_kind`/`set_kind` bridge the
/// enum to the string.
#[derive(Clone, Debug)]
pub struct CodeCell {
/// The cube coordinate this cell is stored at.
pub label: Czyx,
+8 -22
View File
@@ -26,7 +26,7 @@
//! `Vm`, returning the `RunResult`.
use cubecode::{opcode::Op, CodeCell, Kind, Vm};
use cubecoords::{Czyx, CubeHeader};
use cubecoords::{CubeHeader, Czyx};
use cubestore::{CubeBackend, CubeStore, HashBackend};
/// `C` axis band where DBT translation rules are stored, kept distinct from the
@@ -213,11 +213,7 @@ impl CodeCache {
/// target (or whose rule target is `Any`) is replaced by that rule's
/// fragment. The rewritten cell is stored at a fresh cache coordinate and
/// returned (with its new label). The original store is untouched.
pub fn patch(
&mut self,
original: &CodeCell,
rules: &[TranslationRule],
) -> CodeCell {
pub fn patch(&mut self, original: &CodeCell, rules: &[TranslationRule]) -> CodeCell {
let mut patched: Vec<Op> = Vec::with_capacity(original.code.len());
for op in &original.code {
let class = OpClass::of(op);
@@ -237,8 +233,7 @@ impl CodeCache {
self.next_x = self.next_x.wrapping_add(1).max(1);
let (h, body) = cache_header_for(original, &cubecode::opcode::encode(&patched));
self.store.put_record(label, &h, &body);
CodeCell::from_record(label, &h, &body)
.expect("patched cell is always valid bytecode")
CodeCell::from_record(label, &h, &body).expect("patched cell is always valid bytecode")
}
/// Run a patched cache entry in the VM, returning its result.
@@ -317,11 +312,7 @@ impl<B: CubeBackend> DbRuntime<B> {
/// Store a translation rule into the `c220` rule band at a fresh coordinate.
/// Returns the coordinate it was written to.
pub fn store_rule<B: CubeBackend>(
store: &mut CubeStore<B>,
rule: &TranslationRule,
x: u8,
) -> Czyx {
pub fn store_rule<B: CubeBackend>(store: &mut CubeStore<B>, rule: &TranslationRule, x: u8) -> Czyx {
let label = Czyx::new(C_DBT_RULE, 1, 1, x);
let mut h = CubeHeader::new();
h.title = Some(rule.name.clone());
@@ -363,12 +354,10 @@ mod tests {
#[test]
fn cache_patches_and_runs() {
// Original: Const 3, Const 4, Add, Halt => 3+4 = 7.
let orig = sample_cell(Czyx::new(1, 1, 1, 1), vec![
Op::Const(3),
Op::Const(4),
Op::Add,
Op::Halt,
]);
let orig = sample_cell(
Czyx::new(1, 1, 1, 1),
vec![Op::Const(3), Op::Const(4), Op::Add, Op::Halt],
);
// Rule: replace `Add` with `Const 2, Mul` => (a)*(2). With a=3,b=4:
// naive in-place substitution yields Const3, Const4, Const2, Mul =>
// 4*2 = 8. This proves the patched fragment is what runs.
@@ -424,6 +413,3 @@ mod tests {
}
}
}
+74 -25
View File
@@ -499,8 +499,17 @@ impl Session {
let owner = self.identity.as_ref().map(|i| i.owner_local.as_str());
let value = {
let mut scratch = CubeStore::new(HashBackend::new());
crate::store_code_cell(&mut scratch, path, kind, name, &[], &ops, owner, descriptor)
.map_err(|e| e.to_string())?;
crate::store_code_cell(
&mut scratch,
path,
kind,
name,
&[],
&ops,
owner,
descriptor,
)
.map_err(|e| e.to_string())?;
scratch.get_raw(&coord).unwrap_or_default()
};
let header = header_for_code(kind, name, &ops, owner, descriptor);
@@ -845,8 +854,17 @@ impl Session {
let owner = self.identity.as_ref().map(|i| i.owner_local.as_str());
let value = {
let mut scratch = CubeStore::new(HashBackend::new());
crate::store_code_cell(&mut scratch, path, Kind::Fn, name, &[], &code, owner, None)
.map_err(|e| e.to_string())?;
crate::store_code_cell(
&mut scratch,
path,
Kind::Fn,
name,
&[],
&code,
owner,
None,
)
.map_err(|e| e.to_string())?;
scratch.get_raw(&coord).unwrap_or_default()
};
let header = header_for_code(Kind::Fn, name, &code, owner, None);
@@ -1275,12 +1293,7 @@ pub fn txn_snapshot(s: &Session) -> CubeStore<HashBackend> {
/// Compute the coordinate a `store_code_cell` call would target, without
/// writing — used to buffer `prog`/`write` mutations during a transaction.
fn scratch_code_coord(
path: &str,
kind: Kind,
name: &str,
code: &[Op],
) -> Result<Czyx, String> {
fn scratch_code_coord(path: &str, kind: Kind, name: &str, code: &[Op]) -> Result<Czyx, String> {
let mut scratch = CubeStore::new(HashBackend::new());
crate::store_code_cell(&mut scratch, path, kind, name, &[], code, None, None)
.map_err(|e| e.to_string())
@@ -1452,33 +1465,69 @@ mod tests {
// Lay down the OS kernel call graph.
let out = s.exec("tick").expect("tick should lay down kernels");
assert!(out.contains("normalize-config"), "cfg kernel missing: {out}");
assert!(out.contains("decide-snapshot"), "decide kernel missing: {out}");
assert!(out.contains("summarize-procs"), "summarize kernel missing: {out}");
assert!(
out.contains("normalize-config"),
"cfg kernel missing: {out}"
);
assert!(
out.contains("decide-snapshot"),
"decide kernel missing: {out}"
);
assert!(
out.contains("summarize-procs"),
"summarize kernel missing: {out}"
);
assert!(out.contains("cube-os-tick"), "tick kernel missing: {out}");
// The root links the three leaves (call graph, not foreign code).
assert!(out.contains("links cfg,decide,summarize"), "call graph not wired: {out}");
assert!(
out.contains("links cfg,decide,summarize"),
"call graph not wired: {out}"
);
// Behavior descriptors are stamped (round-trip through header flags).
assert!(out.contains("[pure]") && out.contains("[io]") && out.contains("[pure,hot]"),
"behavior descriptors not stamped: {out}");
assert!(
out.contains("[pure]") && out.contains("[io]") && out.contains("[pure,hot]"),
"behavior descriptors not stamped: {out}"
);
// Run the root kernel: it must traverse the call graph (CallLink 0..2)
// and return, proving the OS's behavior lives as addressable kernels.
let run_out = s.exec("run /c210/z001/y001/x004").expect("tick kernel must run");
assert!(run_out.contains("Halted"), "tick kernel should halt: {run_out}");
let run_out = s
.exec("run /c210/z001/y001/x004")
.expect("tick kernel must run");
assert!(
run_out.contains("Halted"),
"tick kernel should halt: {run_out}"
);
// Step 2 — the effector reads the COMPUTED result and emits the effect.
let eff = s.exec("native-apply /c210/z001/y001/x002")
let eff = s
.exec("native-apply /c210/z001/y001/x002")
.expect("effector must run decide-snapshot");
assert!(eff.contains("computed result = 1"), "decide kernel result wrong: {eff}");
assert!(eff.contains("OS EFFECT"), "effector must emit OS EFFECT: {eff}");
assert!(eff.contains("snapshot NOW"), "decision=1 should snapshot: {eff}");
assert!(
eff.contains("computed result = 1"),
"decide kernel result wrong: {eff}"
);
assert!(
eff.contains("OS EFFECT"),
"effector must emit OS EFFECT: {eff}"
);
assert!(
eff.contains("snapshot NOW"),
"decision=1 should snapshot: {eff}"
);
// A plain pure kernel also routes through the effector with no store-IO.
let eff2 = s.exec("native-apply /c210/z001/y001/x003")
let eff2 = s
.exec("native-apply /c210/z001/y001/x003")
.expect("effector must run summarize-procs");
assert!(eff2.contains("computed result = 20"), "summarize result wrong: {eff2}");
assert!(eff2.contains("descriptors=[\"pure\", \"hot\"]"), "descriptor readback wrong: {eff2}");
assert!(
eff2.contains("computed result = 20"),
"summarize result wrong: {eff2}"
);
assert!(
eff2.contains("descriptors=[\"pure\", \"hot\"]"),
"descriptor readback wrong: {eff2}"
);
}
#[test]
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "cubetrace"
version = "0.1.0"
edition.workspace = true
license.workspace = true
description = "CUBELinux-2 tracing package: wraps a DBI engine (via FFI seam) and streams trace events (basic blocks, syscalls) into cubestore, tagging each with CZYX coordinates and header flags (PDF Package 4, §547)."
[dependencies]
cubecoords = { path = "../cubecoords" }
cubestore = { path = "../cubestore" }
cubecode = { path = "../cubecode" }
+302
View File
@@ -0,0 +1,302 @@
//! CUBELinux-2 tracing package.
//!
//! Per the PDF (Package 4, §547 / §1113): *"cubetrace: wraps a DBI engine
//! (via FFI) and streams trace events (basic blocks, syscalls) into
//! cubestore, tagging each with CZYX coordinates and header flags."*
//!
//! This crate is the *head* of the Package‑4 pipeline (cubetrace → cubeai →
//! cubedbt). It is dependency-free and exercises today on a `Null` (offline)
//! source so it builds and is testable without an external DBI engine; the
//! real engine is reached through the documented `DbiEngine` FFI seam.
//!
//! Model
//! -----
//! * A [`TraceEvent`] is a basic block / syscall / metadata observation with a
//! timestamp, a target coordinate, and policy/behavior header flags.
//! * A [`Tracer`] streams events into a [`CubeStore`] keyed by CZYX (the
//! `c240` trace band), tagging each record's header with time, environment,
//! and policy flags (§976).
//! * `replay_all` deterministically replays the stored stream in timestamp
//! order (§977/§1118: "deterministic replay first").
use cubecode::{Behavior, CodeCell, Kind};
use cubecoords::{CubeHeader, Czyx};
use cubestore::{CubeBackend, CubeStore, HashBackend};
/// `C` axis band where trace events are stored (keyed CZYX, §975).
pub const C_TRACE: u8 = 240;
/// Kind of traced observation.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EventKind {
/// A basic block executed (carries the block's code/length).
BasicBlock,
/// A syscall entered/exited (carries the syscall number).
Syscall,
/// Free-form metadata about the run (policy, environment).
Meta,
}
/// A single traced observation: what happened, when, where, and with what
/// policy/behavior tags. Serialized into a trace record's body.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TraceEvent {
pub kind: EventKind,
/// Monotonic timestamp (ns from trace start).
pub ts: u64,
/// Target CZYX the event is about (the block/syscall coordinate).
pub coord: Czyx,
/// For `BasicBlock`: the executed bytecode; for `Syscall`: the syscall
/// number; for `Meta`: unused (0).
pub payload: Vec<u8>,
/// Behavior/policy descriptors to stamp onto the record header.
pub behavior: Behavior,
}
impl TraceEvent {
/// Serialize to a stable byte form (no external deps).
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
out.push(self.kind as u8);
out.extend_from_slice(&self.ts.to_le_bytes());
out.extend_from_slice(&CZYX_BYTES);
out.extend_from_slice(&self.coord.pack_u32().to_le_bytes());
out.extend_from_slice(&self.behavior.to_flags().to_le_bytes());
out.push(self.payload.len() as u8);
out.extend_from_slice(&self.payload);
out
}
/// Inverse of [`to_bytes`]. Returns `None` on malformed input.
pub fn from_bytes(b: &[u8]) -> Option<TraceEvent> {
let mut i = 0;
let kind = match *b.get(i)? {
0 => EventKind::BasicBlock,
1 => EventKind::Syscall,
2 => EventKind::Meta,
_ => return None,
};
i += 1;
let ts = u64::from_le_bytes(b.get(i..i + 8)?.try_into().ok()?);
i += 8;
i += 4; // skip CZYX_BYTES sentinel
let packed = u32::from_le_bytes(b.get(i..i + 4)?.try_into().ok()?);
i += 4;
let coord = Czyx::new(
(packed >> 24) as u8,
(packed >> 16) as u8,
(packed >> 8) as u8,
packed as u8,
);
let flags = u16::from_le_bytes(b.get(i..i + 2)?.try_into().ok()?);
i += 2;
let plen = *b.get(i)? as usize;
i += 1;
let payload = b.get(i..i + plen)?.to_vec();
Some(TraceEvent {
kind,
ts,
coord,
payload,
behavior: Behavior::from_flags(flags),
})
}
}
/// Sentinel to make the on-wire format self-identifying as a CZYX record.
const CZYX_BYTES: [u8; 4] = *b"CZYX";
/// Source of trace events. In the full stack this is the FFI seam to a DBI
/// engine (DynamoRIO / Intel Pin / QBDI / Frida — §970). Here it is a trait so
/// the crate is testable offline via [`NullEngine`] and a real engine can be
/// dropped in without changing callers.
pub trait DbiEngine {
/// Attach to a target (pid or path). Returns `Err` if the engine can't.
fn attach(&mut self, target: &str) -> Result<(), String>;
/// Pull the next event, or `None` when the run is exhausted.
fn next_event(&mut self) -> Option<TraceEvent>;
}
/// Offline engine: replays a pre-recorded event vector. Stands in for a live
/// DBI backend in tests and headless environments.
pub struct NullEngine {
events: Vec<TraceEvent>,
idx: usize,
}
impl NullEngine {
pub fn new(events: Vec<TraceEvent>) -> Self {
NullEngine { events, idx: 0 }
}
}
impl DbiEngine for NullEngine {
fn attach(&mut self, _target: &str) -> Result<(), String> {
Ok(())
}
fn next_event(&mut self) -> Option<TraceEvent> {
if self.idx < self.events.len() {
let e = self.events[self.idx].clone();
self.idx += 1;
Some(e)
} else {
None
}
}
}
/// Streams trace events into a [`CubeStore`] (the `c240` band), tagging each
/// record with time, environment, and policy header flags (PDF §976).
pub struct Tracer<B: CubeBackend> {
store: CubeStore<B>,
next_x: u8,
}
impl<B: CubeBackend> Tracer<B> {
pub fn new(store: CubeStore<B>) -> Self {
Tracer { store, next_x: 1 }
}
/// Drain an engine into the store, returning the number of events stored.
pub fn run<E: DbiEngine>(&mut self, engine: &mut E) -> usize {
let mut count = 0;
while let Some(ev) = engine.next_event() {
self.store_event(&ev);
count += 1;
}
count
}
/// Store one event under a fresh CZYX coordinate in the `c240` band.
pub fn store_event(&mut self, ev: &TraceEvent) -> Czyx {
let label = Czyx::new(C_TRACE, 1, (ev.kind as u8), self.next_x);
self.next_x = self.next_x.wrapping_add(1).max(1);
let mut h = CubeHeader::new();
h.title = Some(format!("{:?}:{:?}", ev.kind, ev.coord));
h.doc_type = Some(Kind::Other.as_str().into());
h.size_bytes = Some(ev.to_bytes().len() as u64);
h.flags.0 |= ev.behavior.to_flags();
h.refresh_flags();
self.store.put_record(label, &h, &ev.to_bytes());
label
}
/// Borrow the backing store (e.g. to hand to `cubeai`/others).
pub fn store(&self) -> &CubeStore<B> {
&self.store
}
}
/// Deterministic replay: collect every trace event from the `c240` band and
/// return them in timestamp order (PDF §977/§1118: "deterministic replay
/// first"). The trace is the immutable cube record; replay never mutates it.
pub fn replay_all(store: &CubeStore<HashBackend>) -> Vec<TraceEvent> {
let mut events = Vec::new();
for k in store.keys() {
if k.c != C_TRACE {
continue;
}
if let Some((_, body)) = store.get_record(&k) {
if let Some(ev) = TraceEvent::from_bytes(&body) {
events.push(ev);
}
}
}
events.sort_by_key(|e| e.ts);
events
}
#[cfg(test)]
mod tests {
use super::*;
fn ev(kind: EventKind, ts: u64, coord: Czyx, beh: Behavior) -> TraceEvent {
TraceEvent {
kind,
ts,
coord,
payload: vec![kind as u8],
behavior: beh,
}
}
#[test]
fn event_round_trips_through_bytes() {
let e = ev(
EventKind::Syscall,
42,
Czyx::new(1, 2, 3, 4),
Behavior::HOT_PATH,
);
let back = TraceEvent::from_bytes(&e.to_bytes()).expect("decodes");
assert_eq!(e, back);
}
#[test]
fn tracer_streams_to_c240() {
let mut engine = NullEngine::new(vec![
ev(
EventKind::BasicBlock,
10,
Czyx::new(1, 0, 0, 1),
Behavior::PURE,
),
ev(
EventKind::Syscall,
20,
Czyx::new(1, 0, 0, 2),
Behavior::IO_HEAVY,
),
ev(
EventKind::Meta,
5,
Czyx::new(1, 0, 0, 3),
Behavior::default(),
),
]);
let mut tracer = Tracer::new(CubeStore::new(HashBackend::new()));
assert_eq!(tracer.run(&mut engine), 3);
// All records landed in the c240 band.
let stored: usize = tracer
.store()
.keys()
.into_iter()
.filter(|k| k.c == C_TRACE)
.count();
assert_eq!(stored, 3);
}
#[test]
fn replay_is_deterministic_and_ordered() {
let mut engine = NullEngine::new(vec![
ev(
EventKind::BasicBlock,
30,
Czyx::new(1, 0, 0, 1),
Behavior::PURE,
),
ev(
EventKind::Syscall,
10,
Czyx::new(1, 0, 0, 2),
Behavior::IO_HEAVY,
),
ev(
EventKind::Meta,
20,
Czyx::new(1, 0, 0, 3),
Behavior::default(),
),
]);
let mut tracer = Tracer::new(CubeStore::new(HashBackend::new()));
tracer.run(&mut engine);
let replayed = replay_all(tracer.store());
assert_eq!(replayed.len(), 3);
// Sorted by ts: 10 (Syscall), 20 (Meta), 30 (BasicBlock).
assert_eq!(replayed[0].kind, EventKind::Syscall);
assert_eq!(replayed[1].kind, EventKind::Meta);
assert_eq!(replayed[2].kind, EventKind::BasicBlock);
// Behavior flags survived the round-trip through the store.
assert_eq!(replayed[0].behavior, Behavior(Behavior::IO_HEAVY));
}
}