Skip to content

Commit db6b571

Browse files
committed
fix(ffi): read next_node/record_id from committer.live_state, not engine.state
When a WAL event_log is configured, engine.state is never mutated — all writes go through committer.live_state. Reading next_record_id / next_node_id from engine.state always returned 0 after the first insert, causing ShadowApply(InvalidOperation) on every subsequent create_node or insert_with_proof call. Also fix valori_e2e_tracer.py to clean up test_e2e_db before each run so replayed WAL events from a prior run don't cause ID collisions.
1 parent 68d666b commit db6b571

2 files changed

Lines changed: 22 additions & 11 deletions

File tree

crates/valori-ffi/src/lib.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,8 @@ impl ValoricoreEngine {
9393
}
9494
let fxp_vec = FxpVector { data: fxp_data };
9595

96-
let rid = engine.state.next_record_id();
97-
9896
if let Some(ref mut committer) = engine.event_committer {
97+
let rid = committer.live_state().next_record_id();
9998
let event = KernelEvent::InsertRecord { id: rid, vector: fxp_vec, metadata: None, tag };
10099
// C-1: commit_event() already applies the event to live_state internally
101100
// (shadow-apply → persist → live-apply). Do NOT call apply_committed_event again.
@@ -159,9 +158,12 @@ impl ValoricoreEngine {
159158
let k = NodeKind::from_u8(kind)
160159
.ok_or_else(|| PyValueError::new_err(format!("invalid NodeKind: {}", kind)))?;
161160

162-
let next_id = engine.state.next_node_id();
163-
164161
if let Some(ref mut committer) = engine.event_committer {
162+
// Must read next_node_id from the committer's live_state, not engine.state.
163+
// engine.state is never mutated when a committer is present — only
164+
// committer.live_state is. Using engine.state gives a stale (always-0)
165+
// ID after the first node, causing ShadowApply(InvalidOperation).
166+
let next_id = committer.live_state().next_node_id();
165167
let event = KernelEvent::CreateNode { id: next_id, kind: k, record: rid };
166168
// C-1: commit_event applies internally; do NOT call apply_committed_event.
167169
committer.commit_event(event).map_err(|e| {
@@ -275,10 +277,10 @@ impl ValoricoreEngine {
275277
let proof_bytes = generate_proof_bytes(&fixed_values);
276278
let proof_hex = hex::encode(&proof_bytes);
277279

278-
let rid = engine.state.next_record_id();
279280
let tag = tags[i];
280281

281282
if let Some(ref mut committer) = engine.event_committer {
283+
let rid = committer.live_state().next_record_id();
282284
let event = KernelEvent::InsertRecord {
283285
id: rid,
284286
vector: fxp_vec,
@@ -432,9 +434,8 @@ impl ValoricoreEngine {
432434
}
433435
}
434436

435-
let rid = engine.state.next_record_id();
436-
437437
if let Some(ref mut committer) = engine.event_committer {
438+
let rid = committer.live_state().next_record_id();
438439
let event = KernelEvent::InsertRecord {
439440
id: rid,
440441
vector: fxp_vec,

scripts/valori_e2e_tracer.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import os
2-
import time
2+
import shutil
33
import math
44
# pyrefly: ignore [missing-import]
55
from valoricore import MemoryClient
66

7+
DB_PATH = "./test_e2e_db"
8+
79
def mock_embed(text: str):
810
"""
911
Deterministic mock embedding function returning exactly 384 floats.
1012
Used to bypass heavy ML model downloads in CI/CD environments.
1113
"""
1214
base = sum(ord(c) for c in text)
13-
# Generate 384 deterministic floats between -1.0 and 1.0
1415
return [math.sin(base + i) for i in range(384)]
1516

1617
def print_header(title):
@@ -20,10 +21,15 @@ def print_header(title):
2021

2122
def main():
2223
print_header("VALORI KERNEL: END-TO-END WORKFLOW TRACER")
23-
24+
25+
# Always start with a clean slate so replayed WAL events don't collide with
26+
# the IDs this run expects.
27+
if os.path.exists(DB_PATH):
28+
shutil.rmtree(DB_PATH)
29+
2430
# 1. Initialize MemoryClient
2531
print("\n[STEP 1] Initialization")
26-
client = MemoryClient(path="./test_e2e_db", dim=384)
32+
client = MemoryClient(path=DB_PATH, dim=384)
2733
print(f"✅ Client initialized.")
2834
print(f"🔐 Initial State Hash: {client.get_state_hash()}")
2935

@@ -76,5 +82,9 @@ def main():
7682
print("🎉 END TO END WORKFLOW COMPLETE")
7783
print("="*60 + "\n")
7884

85+
# Clean up test database
86+
if os.path.exists(DB_PATH):
87+
shutil.rmtree(DB_PATH)
88+
7989
if __name__ == "__main__":
8090
main()

0 commit comments

Comments
 (0)