CUBELinux-2: WordFlags/tri-channel 16-bit flag field, tag-14 + scan_by_word_flag, cubetrace capture→replay→golden→lineage, CUBE VP-tree balling index (cubecode::ballindex), cubefs-visible WordFlags, and cube-mvw (sharded multi-writer MVCC store with per-shard WALs + group-commit + compaction, impl CubeBackend => CubeStore<MvwBackend> DB). Adds show-flags/scan-word-flags/trace-*/golden-capture CLI; ~ tests green.
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
//! Execution-trace artifacts: store a captured machine-code trace as a
|
||||
//! first-class AI-artifact record (`Kind::Layer`) linked back to its source,
|
||||
//! and replay it to reproduce the run — so an agent can persist & reproduce
|
||||
//! interesting executions as ordinary CZYX records.
|
||||
//!
|
||||
//! A trace is the exact `Vec<Op>` a program actually executed ([`Vm::run_captured`]).
|
||||
//! We persist it as a record body (via [`crate::opcode::encode`]) with
|
||||
//! `doc_type = "layer"` (the AI-artifact kind) and a `linked_records` edge to
|
||||
//! the source cell. Loading it back (via [`CodeCell::from_record`]) decodes the
|
||||
//! ops and [`replay`] reproduces the observable behaviour with no access to the
|
||||
//! source program.
|
||||
|
||||
use crate::{opcode, replay, Kind, Op, RunResult, Vm};
|
||||
use cubecoords::{CubeHeader, Czyx};
|
||||
use cubestore::{CubeBackend, CubeStore};
|
||||
|
||||
/// Store a captured execution trace as an AI-artifact record at `coord`,
|
||||
/// linked to its `source` cell. `name` becomes the record title; the body is
|
||||
/// the encoded trace; `doc_type` is `Kind::Layer`.
|
||||
pub fn store_trace<B: CubeBackend>(
|
||||
store: &mut CubeStore<B>,
|
||||
coord: Czyx,
|
||||
source: Czyx,
|
||||
trace: &[Op],
|
||||
name: &str,
|
||||
) {
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some(name.to_string());
|
||||
h.doc_type = Some(Kind::Layer.as_str().to_string());
|
||||
h.linked_records = vec![source];
|
||||
h.refresh_flags();
|
||||
store.put_record(coord, &h, &opcode::encode(trace));
|
||||
}
|
||||
|
||||
/// Load a stored trace record at `coord` and replay it, reproducing the run's
|
||||
/// observable behaviour (data-stack result + `SYS_TRACE` output) from the
|
||||
/// stored record — no access to the original program. Returns `None` if the
|
||||
/// record is missing or its body is not a valid trace.
|
||||
pub fn replay_trace<B: CubeBackend>(
|
||||
store: &CubeStore<B>,
|
||||
coord: Czyx,
|
||||
) -> Option<(RunResult, Vec<u8>)> {
|
||||
let (_h, body) = store.get_record(&coord)?;
|
||||
let ops = opcode::decode(&body).ok()?;
|
||||
Some(replay(&ops))
|
||||
}
|
||||
|
||||
/// Capture a run and persist it as a "golden": the executed trace stored as a
|
||||
/// `Kind::Layer` record (linked to `source`) and the observed output stored as a
|
||||
/// companion `golden` record (linked to the trace). Returns the observed output.
|
||||
pub fn capture_golden<B: CubeBackend + Clone>(
|
||||
store: &mut CubeStore<B>,
|
||||
source: Czyx,
|
||||
trace_coord: Czyx,
|
||||
golden_coord: Czyx,
|
||||
name: &str,
|
||||
) -> Vec<u8> {
|
||||
let mut vm = Vm::new((*store).clone());
|
||||
let (_r, trace) = vm.run_captured(source);
|
||||
let out = vm.output().to_vec();
|
||||
store_trace(store, trace_coord, source, &trace, name);
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some(format!("golden:{name}"));
|
||||
h.doc_type = Some("golden".into());
|
||||
h.linked_records = vec![trace_coord];
|
||||
h.refresh_flags();
|
||||
store.put_record(golden_coord, &h, &out);
|
||||
out
|
||||
}
|
||||
|
||||
/// Golden-trace regression check: re-run `source` fresh AND replay the stored
|
||||
/// trace, and assert BOTH reproduce the recorded golden output.
|
||||
/// * replay vs golden -> the stored trace is a faithful reproduction;
|
||||
/// * fresh run vs golden -> the program has not drifted (or it would change
|
||||
/// the observed output).
|
||||
/// Returns `Ok(())` if the behavior is unchanged, else `Err(reason)`.
|
||||
pub fn verify_golden<B: CubeBackend + Clone>(
|
||||
store: &CubeStore<B>,
|
||||
source: Czyx,
|
||||
trace_coord: Czyx,
|
||||
golden_coord: Czyx,
|
||||
) -> Result<(), String> {
|
||||
let golden = store
|
||||
.get_record(&golden_coord)
|
||||
.map(|(_, b)| b)
|
||||
.ok_or("golden record missing")?;
|
||||
let (_, replay_out) = replay_trace(store, trace_coord).ok_or("trace record missing/invalid")?;
|
||||
if replay_out != golden {
|
||||
return Err(format!(
|
||||
"replay output differs from golden ({} vs {} bytes)",
|
||||
replay_out.len(),
|
||||
golden.len()
|
||||
));
|
||||
}
|
||||
let mut vm = Vm::new((*store).clone());
|
||||
let _ = vm.run(source);
|
||||
let fresh = vm.output().to_vec();
|
||||
if fresh != golden {
|
||||
return Err(format!(
|
||||
"fresh run differs from golden (program changed: {} vs {} bytes)",
|
||||
fresh.len(),
|
||||
golden.len()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A hop in the experiment-lineage graph: a record and the records it links to.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct LineageHop {
|
||||
/// The record's coordinate.
|
||||
pub coord: Czyx,
|
||||
/// Its `doc_type` (kind: fn/kernel/layer/checkpoint/variant/golden/...).
|
||||
pub doc_type: String,
|
||||
/// Its human title, if any.
|
||||
pub title: String,
|
||||
/// The CZYX records this record links to (its edges).
|
||||
pub links: Vec<Czyx>,
|
||||
}
|
||||
|
||||
/// Walk the linked-record graph starting at `start`, following
|
||||
/// `linked_records` edges (breadth-first) up to `depth` levels. Returns the
|
||||
/// ordered list of reachable records — the experiment lineage chain
|
||||
/// (source -> data -> weights -> trace -> golden, etc.). Each record is the
|
||||
/// kind the CUBE associates; provenance is a graph walk, not a lookup.
|
||||
pub fn lineage<B: CubeBackend>(
|
||||
store: &CubeStore<B>,
|
||||
start: Czyx,
|
||||
depth: usize,
|
||||
) -> Vec<LineageHop> {
|
||||
let mut out = Vec::new();
|
||||
let mut frontier = vec![start];
|
||||
let mut seen: std::collections::HashSet<Czyx> = std::collections::HashSet::new();
|
||||
for _ in 0..=depth {
|
||||
if frontier.is_empty() {
|
||||
break;
|
||||
}
|
||||
let mut next = Vec::new();
|
||||
for c in frontier {
|
||||
if !seen.insert(c) {
|
||||
continue;
|
||||
}
|
||||
if let Some((h, _)) = store.get_record(&c) {
|
||||
let links = h.linked_records.clone();
|
||||
out.push(LineageHop {
|
||||
coord: c,
|
||||
doc_type: h.doc_type.clone().unwrap_or_default(),
|
||||
title: h.title.clone().unwrap_or_default(),
|
||||
links: links.clone(),
|
||||
});
|
||||
next.extend(links);
|
||||
}
|
||||
}
|
||||
frontier = next;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{SYS_TRACE, Vm};
|
||||
use cubestore::HashBackend;
|
||||
|
||||
#[test]
|
||||
fn trace_stored_as_layer_and_replayed_without_source() {
|
||||
let mut store = CubeStore::new(HashBackend::new());
|
||||
let entry = Czyx::new(1, 1, 1, 7);
|
||||
let ops = [
|
||||
Op::Const(0), Op::Const(1), Op::Syscall(SYS_TRACE),
|
||||
Op::Const(1), Op::Const(1), Op::Syscall(SYS_TRACE),
|
||||
Op::Const(2), Op::Const(2), Op::Syscall(SYS_TRACE),
|
||||
Op::Halt,
|
||||
];
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some("solitaire".into());
|
||||
h.doc_type = Some("fn".into());
|
||||
h.refresh_flags();
|
||||
store.put_record(entry, &h, &opcode::encode(&ops));
|
||||
|
||||
// 1) Capture the run as a machine-code trace.
|
||||
let mut vm = Vm::new(store.clone());
|
||||
let (_r, trace) = vm.run_captured(entry);
|
||||
let produced = vm.output().to_vec();
|
||||
assert!(!trace.is_empty());
|
||||
|
||||
// 2) Store it as a Kind::Layer AI-artifact record linked to the source.
|
||||
let trace_coord = Czyx::new(9, 0, 0, 1);
|
||||
store_trace(&mut store, trace_coord, entry, &trace, "solitaire-run-1");
|
||||
|
||||
// 3) The record is a "layer" artifact with an edge back to the source.
|
||||
let (th, _tb) = store.get_record(&trace_coord).unwrap();
|
||||
assert_eq!(th.doc_type.as_deref(), Some("layer"));
|
||||
assert_eq!(th.linked_records, vec![entry]);
|
||||
|
||||
// 4) Reproduce the run purely from the stored trace record.
|
||||
let (result, replayed) = replay_trace(&store, trace_coord).unwrap();
|
||||
assert_eq!(result, RunResult::Halted { top: None });
|
||||
assert_eq!(replayed, produced, "stored trace must reproduce the run");
|
||||
assert!(replayed.contains(&b'\n'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn golden_regression_detects_drift() {
|
||||
let mut store = CubeStore::new(HashBackend::new());
|
||||
let entry = Czyx::new(1, 1, 1, 9);
|
||||
let mk = |val: u8| {
|
||||
vec![Op::Const(val), Op::Syscall(SYS_TRACE), Op::Halt]
|
||||
};
|
||||
let mut h = CubeHeader::new();
|
||||
h.doc_type = Some("fn".into());
|
||||
h.refresh_flags();
|
||||
store.put_record(entry, &h, &opcode::encode(&mk(7)));
|
||||
|
||||
let trace_c = Czyx::new(9, 0, 0, 1);
|
||||
let golden_c = Czyx::new(9, 0, 0, 2);
|
||||
let out = capture_golden(&mut store, entry, trace_c, golden_c, "regress");
|
||||
assert_eq!(out, b"\x07\n");
|
||||
assert!(verify_golden(&store, entry, trace_c, golden_c).is_ok());
|
||||
|
||||
// Drift the program: change the emitted value -> fresh run differs.
|
||||
store.put_record(entry, &h, &opcode::encode(&mk(9)));
|
||||
let r = verify_golden(&store, entry, trace_c, golden_c);
|
||||
assert!(r.is_err(), "program drift must be detected: {r:?}");
|
||||
// The stored trace is immutable: it still replays the ORIGINAL golden.
|
||||
let (_, rep) = replay_trace(&store, trace_c).unwrap();
|
||||
assert_eq!(rep, b"\x07\n", "stored trace still reproduces the original run");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lineage_walks_source_data_weights_trace() {
|
||||
let mut store = CubeStore::new(HashBackend::new());
|
||||
// Build experiment lineage: source -> data -> weights -> trace -> golden.
|
||||
let mut mk = |coord: Czyx, doc: &str, title: &str, links: Vec<Czyx>| {
|
||||
let mut h = CubeHeader::new();
|
||||
h.title = Some(title.into());
|
||||
h.doc_type = Some(doc.into());
|
||||
h.linked_records = links;
|
||||
h.refresh_flags();
|
||||
store.put_record(coord, &h, &[]);
|
||||
};
|
||||
let source = Czyx::new(1, 1, 1, 1);
|
||||
let data = Czyx::new(2, 0, 0, 1);
|
||||
let weights = Czyx::new(3, 0, 0, 1);
|
||||
let trace = Czyx::new(4, 0, 0, 1);
|
||||
let golden = Czyx::new(5, 0, 0, 1);
|
||||
mk(source, "fn", "train", vec![data, weights]);
|
||||
mk(data, "data", "batch", vec![]);
|
||||
mk(weights, "layer", "model-v1", vec![]);
|
||||
mk(trace, "layer", "trace:train", vec![source]);
|
||||
mk(golden, "golden", "golden:train", vec![trace]);
|
||||
|
||||
// Walk from the trace back to its lineage.
|
||||
let hops = lineage(&store, trace, 4);
|
||||
assert!(hops.iter().any(|h| h.coord == trace && h.doc_type == "layer"));
|
||||
assert!(hops.iter().any(|h| h.coord == source && h.doc_type == "fn"));
|
||||
// Depth-limited: from a leaf with no links, only itself.
|
||||
let leaf = lineage(&store, data, 3);
|
||||
assert_eq!(leaf.len(), 1);
|
||||
assert_eq!(leaf[0].coord, data);
|
||||
assert_eq!(leaf[0].links, vec![]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user