diff --git a/docs/adr/ADR-038-npx-ruvector-rvlite-witness-integration.md b/docs/adr/ADR-038-npx-ruvector-rvlite-witness-integration.md new file mode 100644 index 000000000..93832bac8 --- /dev/null +++ b/docs/adr/ADR-038-npx-ruvector-rvlite-witness-integration.md @@ -0,0 +1,205 @@ +# ADR-038: npx ruvector & rvlite Witness Verification Integration + +| Field | Value | +|-------|-------| +| **Status** | Proposed | +| **Date** | 2026-02-16 | +| **Deciders** | RuVector core team | +| **Supersedes** | -- | +| **Related** | ADR-029 (RVF canonical format), ADR-032 (RVF WASM integration), ADR-037 (Publishable RVF acceptance test) | + +## Context + +ADR-037 introduced the publishable RVF acceptance test, which produces two artifacts: + +1. **JSON manifest** -- human-readable scorecards, ablation assertions, and SHAKE-256 witness chain +2. **`.rvf` binary** -- native WITNESS_SEG (0x0A) + META_SEG (0x07), verifiable by `rvf_crypto::verify_witness_chain()` + +ADR-032 added `rvf_witness_verify` and `rvf_witness_count` exports to `rvf-wasm`, enabling browser-side verification. + +However, neither the `npx ruvector` CLI nor the `rvlite` browser runtime currently exposes witness chain verification to end users. The Rust `rvf-cli` has `rvf verify-witness` (17 subcommands), but the Node.js wrapper in `npm/packages/ruvector/bin/cli.js` does not surface it. Similarly, `rvlite` lists `@ruvector/rvf-wasm` as an optional peer dependency but does not call the witness verification exports. + +This means an external developer who receives a `.rvf` acceptance test artifact currently needs the Rust toolchain to verify it. The goal is zero-friction verification via `npx` or a browser import. + +## Decision + +### 1. `npx ruvector rvf verify-witness ` + +Add a `rvf verify-witness` subcommand to the ruvector Node.js CLI (`npm/packages/ruvector/bin/cli.js`): + +``` +npx ruvector rvf verify-witness acceptance_manifest.rvf +``` + +**Implementation path** (ordered by preference): + +| Backend | Mechanism | Latency | Availability | +|---------|-----------|---------|--------------| +| Native N-API | `@ruvector/rvf-node` binding to `rvf_crypto::verify_witness_chain()` | <1ms | When native binary is installed | +| WASM | `@ruvector/rvf-wasm` `rvf_witness_verify()` export | ~5ms | Always (WASM is universal) | + +The CLI auto-detects the best available backend (same pattern as the existing `VectorDB` platform detection). It loads the `.rvf` file, locates the first WITNESS_SEG, extracts the payload, and calls the verification function. + +**Output format:** + +``` +Verifying witness chain: acceptance_manifest.rvf + Segment type: WITNESS_SEG (0x0A) + Entry count: 147 entries (73 bytes each) + Chain status: INTACT -- all hashes verified (SHAKE-256) + VERIFICATION: PASSED +``` + +**Error cases:** + +``` + Chain status: BROKEN at entry 42 -- prev_hash mismatch + VERIFICATION: FAILED (exit code 1) +``` + +### 2. `npx ruvector rvf inspect ` + +Extend the existing `rvf inspect` to parse and display acceptance test metadata from the META_SEG: + +``` +npx ruvector rvf inspect acceptance_manifest.rvf + +Segments: + [0] WITNESS_SEG 0x0A 10,731 bytes (147 entries) + [1] META_SEG 0x07 2,048 bytes (JSON metadata) + +Acceptance Test Metadata: + Format: rvf-acceptance-test v2 + Chain root hash: 7a3f...b2c1 + All passed: true + Scorecards: 3 modes (A/B/C) +``` + +### 3. `rvlite` browser SDK -- `verifyWitnessChain()` + +Add a `verifyWitnessChain()` function to the rvlite SDK (`npm/packages/rvlite/src/index.ts`): + +```typescript +import { verifyWitnessChain } from 'rvlite'; + +// Load .rvf file (e.g., from fetch or File API) +const rvfBytes = await fetch('acceptance_manifest.rvf').then(r => r.arrayBuffer()); +const result = verifyWitnessChain(new Uint8Array(rvfBytes)); + +console.log(result.valid); // true +console.log(result.entryCount); // 147 +console.log(result.error); // null or error description +``` + +**Implementation:** + +```typescript +export interface WitnessVerifyResult { + valid: boolean; + entryCount: number; + error: string | null; +} + +export function verifyWitnessChain(rvfBytes: Uint8Array): WitnessVerifyResult { + // 1. Parse segment header to find WITNESS_SEG + // 2. Extract payload bytes + // 3. Allocate WASM memory, copy payload + // 4. Call rvf_witness_verify(ptr, len) + // 5. Interpret result (positive = count, negative = error code) + // 6. Free WASM memory +} +``` + +This function: +- Requires `@ruvector/rvf-wasm` (already an optional peer dep in rvlite) +- Throws a clear error if the WASM module is not available +- Handles WASM memory allocation/deallocation internally +- Returns a typed result object, not a raw integer + +### 4. `rvlite` CLI -- `rvlite verify-witness ` + +Register a `verify-witness` command in `cli-rvf.ts` alongside the existing `rvf-migrate` and `rvf-rebuild` commands: + +```bash +npx rvlite verify-witness acceptance_manifest.rvf +``` + +This uses the same WASM backend as the SDK function above. + +### 5. MCP tool -- `rvf_verify_witness` + +Add to the ruvector MCP server (`npm/packages/ruvector/bin/mcp-server.js`) so Claude Code can verify acceptance test artifacts directly: + +```json +{ + "name": "rvf_verify_witness", + "description": "Verify SHAKE-256 witness chain in an .rvf file", + "input_schema": { + "type": "object", + "properties": { + "path": { "type": "string", "description": "Path to .rvf file" } + }, + "required": ["path"] + } +} +``` + +## Integration Surface + +``` + ┌────────────────────────┐ + │ acceptance-rvf (Rust) │ + │ generate + verify │ + └──────────┬─────────────┘ + │ produces + ┌──────────▼─────────────┐ + │ acceptance_manifest.rvf │ + │ WITNESS_SEG + META_SEG │ + └──────────┬─────────────┘ + ┌────────────────┼────────────────┐ + │ │ │ + ┌─────────▼──────┐ ┌──────▼───────┐ ┌──────▼──────────┐ + │ npx ruvector │ │ npx rvlite │ │ Browser (rvlite │ + │ rvf │ │ verify- │ │ SDK) │ + │ verify-witness │ │ witness │ │ verifyWitness │ + └────────┬───────┘ └──────┬───────┘ │ Chain() │ + │ │ └──────┬──────────┘ + ┌────────▼────────────────▼─────────────────▼──────────┐ + │ @ruvector/rvf-wasm │ + │ rvf_witness_verify(chain_ptr, chain_len) -> i32 │ + │ rvf_witness_count(chain_len) -> i32 │ + └──────────────────────────────────────────────────────┘ +``` + +## Implementation Order + +| Phase | Work | Package | Complexity | +|-------|------|---------|------------| +| **1** | `verifyWitnessChain()` SDK function | `rvlite` | Low -- WASM call + segment parsing | +| **2** | `verify-witness` CLI command | `rvlite` | Low -- wraps SDK function | +| **3** | `rvf verify-witness` CLI subcommand | `ruvector` | Medium -- N-API fallback + WASM detection | +| **4** | `rvf inspect` metadata display | `ruvector` | Low -- parse META_SEG JSON | +| **5** | `rvf_verify_witness` MCP tool | `ruvector` | Low -- wraps CLI logic | + +Each phase is independently shippable. Phase 1+2 enable browser verification. Phase 3-5 enable CLI and agent verification. + +## Consequences + +### Positive + +- External developers verify `.rvf` acceptance tests with `npx ruvector rvf verify-witness` -- zero Rust toolchain required +- Browser-based verification via `rvlite` SDK requires only `npm install rvlite @ruvector/rvf-wasm` +- Claude Code agents can verify witness chains via MCP tool without file manipulation +- Consistent verification path: Rust CLI, Node.js CLI, browser SDK, and WASM microkernel all use the same `rvf_witness_verify` implementation +- Auto-detection prefers native N-API when available for sub-millisecond verification + +### Negative + +- WASM module adds ~46 KB to rvlite when `@ruvector/rvf-wasm` is installed +- Segment header parsing must be duplicated in TypeScript (WASM only verifies the chain payload, not the segment framing) +- N-API binding for `verify_witness_chain` does not exist yet in `rvf-node` -- Phase 3 requires adding it + +### Neutral + +- The JSON manifest verification (`verify --input manifest.json`) remains available via the Rust binary for users who prefer JSON over binary `.rvf` +- `@ruvector/rvf-wasm` remains an optional peer dependency -- rvlite works without it but witness verification is unavailable