Skip to content

Commit 6217850

Browse files
varshith-Gitclaude
andcommitted
fix: developer UX audit — 8 issues resolved
Auth / SDK errors - Add AuthenticationError for 401/403; was misreported as ConnectionError - _raise_for_status() helper replaces bare raise_for_status() everywhere - AsyncRemoteClient._post also intercepts 401/403 before wrapping as ConnectionError - Export AuthenticationError from valoricore.__init__ SDK / cluster parity - Add 8 missing /v1/* routes to cluster_server.rs (memory upsert/search, list nodes, version, timeline, snapshot save/restore/download) - Fix broken SDK: all /v1/ paths now exist on both standalone and cluster Search dimension validation - engine.search_l2 / search_l2_ns reject wrong-dim queries with clear error - Cluster path checks locked_dim() before converting to FxpVector - Integration test added: test_search_dim_validated Audit page JS crash - API route returns [] (not error object) when node unreachable - Client guards with Array.isArray before .map() verify_log() in-process FFI - Extract replay logic from valori-verify/main.rs into lib.rs as pub fn - Expose verify_log_file() PyO3 function in valoricore-ffi - verify_log() tries FFI first; falls back to subprocess with clear message - pip users never need the valori-verify binary on PATH Version unification - pyproject.toml: dynamic = ["version"]; maturin reads from Cargo workspace - Single source of truth: bump root Cargo.toml version, both server and wheel stay in sync automatically UI / DX - npm ci everywhere (CONTRIBUTING, README, dev-setup.sh) for lockfile-exact installs - Add ui/Dockerfile (3-stage: deps/builder/runner with standalone Next.js output) - docker-compose: add valori-ui service - Suppress urllib3 LibreSSL warning on import (cosmetic, not a real error) E2E test + engine bug fixes - e2e-test.yml: maturin develop (no glob), add Cargo cache, split steps - Fix create_edge committer path: edge_id from live_state not engine.state - Fix search committer path: read live_state not engine.state - Both bugs caused MemoryClient graph ops and search to silently fail Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9175eca commit 6217850

27 files changed

Lines changed: 1460 additions & 279 deletions

File tree

.github/workflows/e2e-test.yml

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,31 @@ jobs:
2929
- name: Setup Rust
3030
uses: dtolnay/rust-toolchain@stable
3131

32+
- name: Cache Rust build artifacts
33+
uses: actions/cache@v4
34+
with:
35+
path: |
36+
~/.cargo/registry
37+
~/.cargo/git
38+
target/
39+
key: ${{ runner.os }}-cargo-e2e-${{ hashFiles('Cargo.lock') }}
40+
restore-keys: |
41+
${{ runner.os }}-cargo-e2e-
42+
3243
- name: Setup Python
3344
uses: actions/setup-python@v5
3445
with:
3546
python-version: "3.11"
3647

37-
- name: Build, Install, and Run E2E Test
38-
run: |
39-
python -m venv .venv
40-
source .venv/bin/activate
41-
pip install maturin
42-
cd python
43-
maturin build --release
44-
pip install ../target/wheels/valoricore*.whl --force-reinstall
45-
cd ..
46-
python scripts/valori_e2e_tracer.py
48+
- name: Install maturin
49+
run: pip install maturin
50+
51+
- name: Build and install valoricore wheel
52+
# maturin develop compiles the Rust extension and installs it into the
53+
# active Python environment in one step — no glob-expansion issues from
54+
# matching multiple wheel files, and no path-relativity bugs.
55+
working-directory: python
56+
run: maturin develop --release
57+
58+
- name: Run E2E tracer
59+
run: python scripts/valori_e2e_tracer.py

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ maturin develop # compiles and installs into your active Python env
136136
## 4. Web dashboard (Next.js UI)
137137

138138
```bash
139-
# Node.js 18+ required
139+
# Node.js 20+ required (pinned in ui/package.json engines field)
140140
cd ui
141-
npm install
141+
npm ci # installs exact versions from package-lock.json — do not use npm install
142142
npm run dev # starts at http://localhost:3001
143143
```
144144

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ valori-kernel = { path = "crates/valori-kernel", version = "0.2.1", defaul
4949
valori-wire = { path = "crates/valori-wire", version = "0.2.1" }
5050
valori-consensus = { path = "crates/valori-consensus", version = "0.2.1" }
5151
valori-node = { path = "crates/valori-node", version = "0.2.1" }
52+
valori-verify = { path = "crates/valori-verify", version = "0.2.1" }
5253

5354
# Firmware requires abort on panic (no unwinding support in no_std)
5455
# This also guarantees fail-closed behavior for the Node.

README.md

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ Every byte of state is recovered from the append-only, BLAKE3-chained event log
104104

105105
> **New contributor?** `bash dev-setup.sh` — one script installs Rust, the wasm32 target, Python SDK, and UI deps with OS detection and version gates. See [Build from Source](#build-from-source) and [CONTRIBUTING.md](CONTRIBUTING.md).
106106
107+
**Not writing code?**[Option 2 — Web dashboard](#option-2--web-dashboard-no-code-60-seconds) is the fastest path. Point-and-click project management, no terminal after the first `docker compose up`.
108+
109+
**Writing code?** Pick a client:
110+
107111
**Which client should I use?**
108112

109113
| Client | Install / import | Use when |
@@ -191,7 +195,27 @@ print(db.get_state_hash())
191195

192196
The Rust kernel runs inside your Python process via PyO3 — no server, no Docker, no Rust toolchain needed.
193197

194-
### Option 2 — Docker (~60 seconds, prebuilt image)
198+
### Option 2 — Web dashboard (no code, ~60 seconds)
199+
200+
The fastest path if you're not writing code — a full point-and-click UI over your Valori node.
201+
202+
```bash
203+
docker compose up -d # start the node (port 3000)
204+
cd ui && npm ci npm install && npm run devnpm install && npm run dev npm run dev # start the dashboard (port 3001)
205+
# open http://localhost:3001
206+
```
207+
208+
**What you get:**
209+
- **Project manager home** — create named, isolated workspaces; each project gets its own node, port, and data directory under `~/.valori/projects/<name>/`
210+
- **Persistent state** — opening a project auto-starts its node and restores all data; closing it writes a final snapshot and locks files at rest
211+
- **Live activity** — count-up stats (records, nodes, edges), an activity heatmap, and a timeline of every committed event
212+
- **No URL hardcoding** — the UI proxies to the node through Next.js API routes, so there's nothing to configure
213+
214+
The UI talks to the node server-side (Next.js API routes → node HTTP), so the node port never needs to be exposed to the browser directly. Safe to use behind a firewall.
215+
216+
---
217+
218+
### Option 3 — Docker, raw HTTP / Python SDK (~60 seconds, prebuilt image)
195219

196220
```bash
197221
docker compose up -d
@@ -233,7 +257,7 @@ hits = db.search(vec, k=5, metadata_filter={"year": {"gte": 2020}}) # range filt
233257

234258
Edit `docker-compose.yml` to change `VALORI_DIM` (default: 1536), add auth, or mount S3.
235259

236-
### Option 3 — One-call document ingest (chunk + embed on-node)
260+
### Option 4 — One-call document ingest (chunk + embed on-node)
237261

238262
Add an embedding provider to Docker so clients can POST raw text — no client-side embedding needed:
239263

@@ -260,7 +284,7 @@ print(ans["answer"], "—", ans["citations"][0]["breadcrumb"]) # lands on "…
260284
assert db.tree_verify(built["tree"], ans["receipt"]) # proves it wasn't altered
261285
```
262286

263-
### Option 4 — 3-node cluster
287+
### Option 5 — 3-node cluster
264288

265289
```bash
266290
cargo install --path crates/valori-cli
@@ -269,7 +293,7 @@ valori setup # interactive wizard
269293

270294
[Cluster setup guide](docs/CLUSTER.md) · [Docker Compose](docker-compose.cluster.yml) · [Helm chart](deploy/helm/valori/) · [AWS/Azure Terraform](docs/DEPLOY_AWS.md)
271295

272-
### Option 5 — Agent memory via MCP
296+
### Option 6 — Agent memory via MCP
273297

274298
```bash
275299
VALORI_URL=http://localhost:3000 valori-mcp
@@ -284,17 +308,6 @@ VALORI_URL=http://localhost:3000 valori-mcp
284308

285309
[`crates/valori-mcp/README.md`](crates/valori-mcp/README.md)
286310

287-
### Option 6 — Web dashboard with persistent projects
288-
289-
```bash
290-
cd ui && npm install && npm run dev # http://localhost:3001
291-
```
292-
293-
Each **project** is an isolated, persistent workspace: its own node, port, and
294-
data dir under `~/.valori/projects/<name>/`. The Home screen lists every project
295-
(even when its node is stopped); opening one auto-starts its node and restores
296-
state, and closing it writes a snapshot and locks the files at rest — they can
297-
only be deleted from the UI. → [`docs/phases/phase-6-persistent-projects.md`](docs/phases/phase-6-persistent-projects.md)
298311

299312
---
300313

@@ -327,11 +340,15 @@ Requires Rust 1.80+. For the Python FFI extension: `pip install maturin && matur
327340

328341
## Documentation
329342

343+
**[docs/README.md](docs/README.md)** — start here. Routes you by use case (trying it out / building an app / deploying / verifying / contributing) before listing the full reference index.
344+
345+
Key docs directly:
346+
330347
| Doc | What it covers |
331348
|---|---|
332-
| [docs/getting-started.md](docs/getting-started.md) | Full quickstart for all deployment modes |
333-
| [docs/api-reference.md](docs/api-reference.md) | Complete HTTP API reference |
334-
| [docs/python-reference.md](docs/python-reference.md) | Full Python SDK reference |
349+
| [docs/getting-started.md](docs/getting-started.md) | First insert, search, collections, auth — all deployment modes |
350+
| [docs/api-reference.md](docs/api-reference.md) | Complete HTTP API reference (all `/v1/` endpoints) |
351+
| [docs/python-reference.md](docs/python-reference.md) | Full Python SDK reference — all four clients |
335352
| [docs/CLUSTER.md](docs/CLUSTER.md) | Cluster setup, operations, failover |
336353
| [docs/DR.md](docs/DR.md) | Backup, restore, cross-region DR runbook |
337354
| [docs/CAPACITY.md](docs/CAPACITY.md) | Capacity planning — vectors/GB, WAL growth, S3 cost |

crates/valori-consensus/src/state_machine.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,12 @@ impl ValoriStateMachine {
422422
hash_state_blake3(&self.inner.lock().await.state)
423423
}
424424

425+
/// The dimension the kernel has actually locked to (set on first insert).
426+
/// Returns `None` if no records have been inserted yet.
427+
pub async fn locked_dim(&self) -> Option<usize> {
428+
self.inner.lock().await.state.dim
429+
}
430+
425431
/// Run a read-only closure against the kernel state (Phase 2.5 uses
426432
/// this for serving reads without copying the state out).
427433
pub async fn with_state<T>(&self, f: impl FnOnce(&KernelState) -> T) -> T {

crates/valori-ffi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ crate-type = ["cdylib"]
1111
[dependencies]
1212
valori-kernel = { workspace = true, features = ["std"] }
1313
valori-node = { workspace = true }
14+
valori-verify = { workspace = true }
1415
pyo3 = { version = "0.29.0", features = ["extension-module", "abi3-py39"] }
1516
serde_json = "1.0"
1617
hex = "0.4"

crates/valori-ffi/src/lib.rs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,17 @@ impl ValoricoreEngine {
111111
fn search(&self, vector: Vec<f32>, k: usize, filter_tag: Option<u64>) -> PyResult<Vec<(u32, i64)>> {
112112
let engine = lock_engine!(self);
113113

114-
// H-3: Reject dimension mismatches for search too; the kernel silently truncates
115-
// to min(query.len(), record.len()) which produces wrong distances, not errors.
116-
if let Some(dim) = engine.state.dim {
114+
// When a committer is active, engine.state is never mutated — reads must
115+
// go to live_state. Fall back to engine.state in the no-committer path.
116+
let state_ref: &valori_kernel::state::kernel::KernelState = if let Some(ref c) = engine.event_committer {
117+
c.live_state()
118+
} else {
119+
&engine.state
120+
};
121+
122+
// H-3: Reject dimension mismatches; the kernel silently truncates to
123+
// min(query.len(), record.len()) which produces wrong distances, not errors.
124+
if let Some(dim) = state_ref.dim {
117125
if vector.len() != dim {
118126
return Err(PyValueError::new_err(format!(
119127
"dimension mismatch: engine expects {dim}, got {}", vector.len()
@@ -128,7 +136,7 @@ impl ValoricoreEngine {
128136
let fxp_vec = FxpVector { data: fxp_data };
129137

130138
let mut results = vec![valori_kernel::index::SearchResult::default(); k];
131-
let count = engine.state.search_l2(&fxp_vec, &mut results, filter_tag);
139+
let count = state_ref.search_l2(&fxp_vec, &mut results, filter_tag);
132140

133141
let mut py_results = Vec::with_capacity(count);
134142
for i in 0..count {
@@ -530,11 +538,36 @@ fn verify_embedding(floats: Vec<f32>, claimed_hash: String) -> PyResult<bool> {
530538
Ok(computed_hash == claimed_hash)
531539
}
532540

541+
/// Replay an event log file in-process and return a JSON verification report string.
542+
///
543+
/// This is identical to `valori-verify --report` but runs inside the already-loaded
544+
/// `.so` — no subprocess, no binary on PATH, no Rust toolchain required for pip users.
545+
///
546+
/// Args:
547+
/// log_path: Path to the events.log file.
548+
/// expected_hash: Optional 64-char hex BLAKE3 state hash to compare against.
549+
///
550+
/// Returns:
551+
/// JSON string with the same schema as `valori-verify --report`.
552+
///
553+
/// Raises:
554+
/// RuntimeError: if the file cannot be read or has a corrupt header.
555+
#[pyfunction]
556+
#[pyo3(signature = (log_path, expected_hash = None))]
557+
fn verify_log_file(log_path: String, expected_hash: Option<String>) -> PyResult<String> {
558+
use std::path::Path;
559+
let path = Path::new(&log_path);
560+
let result = valori_verify::verify_log_file(path, expected_hash.as_deref())
561+
.map_err(|e| PyRuntimeError::new_err(e))?;
562+
serde_json::to_string(&result).map_err(|e| PyRuntimeError::new_err(e.to_string()))
563+
}
564+
533565
#[pymodule]
534566
fn valoricore_ffi(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
535567
m.add_class::<ValoricoreEngine>()?;
536568
m.add_function(wrap_pyfunction!(ingest_embedding, m)?)?;
537569
m.add_function(wrap_pyfunction!(generate_proof, m)?)?;
538570
m.add_function(wrap_pyfunction!(verify_embedding, m)?)?;
571+
m.add_function(wrap_pyfunction!(verify_log_file, m)?)?;
539572
Ok(())
540573
}

0 commit comments

Comments
 (0)