nerve-ipc-core 0.1.1

Core IPC layer for the NERVE protocol: authentication, request lifecycle, transport-agnostic dispatch, Unix Domain Socket server, and WebSocket server.
Documentation
# nerve-ipc-core

Core IPC layer for the NERVE protocol.

## What is NERVE?

NERVE is a binary framing protocol for local AI daemon communication. It defines the wire format, message types, codec, and request semantics used between a browser extension, local tools, and an AI daemon.

## What does nerve-ipc-core provide?

`nerve-ipc-core` is the runtime/core layer of NERVE. It provides:

- **Authentication** — per-install token generation and persistence (`~/.anvesha/token`)
- **Transport servers** — Unix Domain Socket (UDS) server for local tool integrations; WebSocket server for browser extension connections
- **Transport-agnostic dispatch** — a single `dispatch_frame()` function shared by both transports, performing no I/O
- **Request lifecycle and cancellation** — per-connection `RequestTable` tracking active and cancelled requests
- **WebSocket handshake authentication** — Origin check and per-install token verification at the HTTP upgrade step

`nerve-ipc-core` has no knowledge of search relevance, AI reasoning, or crawling. That belongs to the layers above.

## Relationship to nerve-ipc

| Crate | Role |
|-------|------|
| [`nerve-ipc`]https://crates.io/crates/nerve-ipc | Wire format, codec, frame types, message schema, protocol constants |
| `nerve-ipc-core` *(this crate)* | Authentication, request lifecycle, dispatch, UDS server, WebSocket server |

`nerve-ipc-core` depends on `nerve-ipc` for all protocol types. It does not redefine any wire-format types.

## Installation

```toml
[dependencies]
nerve-ipc-core = "0.1"
```

To also build the daemon binary:

```toml
[dependencies]
nerve-ipc-core = { version = "0.1", features = ["daemon"] }
```

## Architecture

```
Browser Extension
      │
      │  NERVE frames over WebSocket (ws://127.0.0.1:9001)
      │  Sec-WebSocket-Protocol: anvesha-v1.<token>
      │  Origin: chrome-extension://<extension_id>
      ▼
┌─────────────────────────────────────────┐
│             nerve-ipc-core              │
│                                         │
│  ws_server          server              │
│  (WebSocket)        (UDS)               │
│       │               │                 │
│       └───────┬───────┘                 │
│               ▼                         │
│          dispatch_frame()               │  ← transport-agnostic
│               │                         │  ← Ping handled inline
│               │                         │  ← Cancel marks request
│               ▼                         │  ← SearchQuery → AI boundary
│         RequestTable                    │  ← per-connection, isolated
│                                         │
└─────────────────────────────────────────┘
      │
      │  (future)
      ▼
  AI Daemon
  ├── query understanding
  ├── search client (HTTPS → hosted API)
  ├── context builder
  └── inference / token streaming
```

Each accepted connection runs in its own OS thread with an independent `RequestTable`. A misbehaving client cannot affect other connections.

## API Overview

### `config::Config`

Runtime configuration: bind address, WebSocket port, extension ID for origin checking, token path, UDS socket path.

```rust
use nerve_ipc_core::config::Config;

let config = Config::default(); // 127.0.0.1:9001, ~/.anvesha/token
```

### `auth`

Token generation and persistence. The token is 32 bytes of OS randomness stored as 64 hex characters.

```rust
use nerve_ipc_core::auth::{generate_token, load_or_create_token};
use std::path::Path;

let token = generate_token(); // fresh 64-char hex token
let token = load_or_create_token(Path::new("/path/to/token"))?; // load or create
```

### `dispatch::dispatch_frame`

Transport-agnostic frame handler. Returns a `DispatchAction` that the caller translates into I/O.

```rust
use nerve_ipc_core::dispatch::{dispatch_frame, DispatchAction};
use nerve_ipc_core::request_table::RequestTable;
// (frame comes from nerve-ipc's codec::decode)
```

### `request_table::RequestTable`

Per-connection in-flight request tracking with cancellation support.

```rust
use nerve_ipc_core::request_table::RequestTable;
use nerve_protocol::types::RequestId;

let mut table = RequestTable::new();
table.insert(RequestId(1));
table.cancel(RequestId(1));
assert!(table.is_cancelled(RequestId(1)));
table.remove(RequestId(1));
```

### `server::run` / `server::handle_connection`

UDS server: binds to a path, accepts connections, spawns threads.

### `ws_server::run_ws` / `ws_server::run_ws_on_listener`

WebSocket server: binds to `127.0.0.1:<port>`, performs auth handshake, dispatches frames.

## Protocol / Frame Concepts

One WebSocket binary message = one NERVE frame. The 20-byte NERVE frame header contains magic, version, message type, flags, request ID, and payload length. Payload is arbitrary bytes (typically JSON for message types that carry structured data).

Frame types handled by `nerve-ipc-core`:

| Message          | Behaviour                                                                     |
|------------------|-------------------------------------------------------------------------------|
| `Ping`           | Echo reply with `FINAL` flag, same `request_id`                               |
| `SearchQuery`    | Registered in `RequestTable` → `ForwardToAiDaemon` (AI daemon: future)       |
| `Cancel`         | Marks the request cancelled in `RequestTable`                                 |
| `AgentTaskStart` | Registers request                                                             |
| `AgentTaskEvent` | No-op (future: forwarded to AI daemon)                                        |
| `AgentTaskDone`  | Removes request from table                                                    |
| Unknown types    | Ignored safely                                                                |

## Security

`nerve-ipc-core` is only accessible to the browser extension and local tools:

| Control | Detail |
|---------|--------|
| **Bind address** | Always `127.0.0.1`, never `0.0.0.0` |
| **Origin check** | `Origin: chrome-extension://<ANVESHA_EXTENSION_ID>` |
| **Token auth** | Per-install secret at `~/.anvesha/token` (mode `0600`); passed via `Sec-WebSocket-Protocol: anvesha-v1.<token>` — never in the URL |
| **Payload limit** | `payload_length` validated before allocation; oversized frames rejected before any bytes are read |
| **Error responses** | 403 on auth failure — does not reveal which check failed |

## Design Goals

- Protocol correctness first
- Deterministic, non-blocking dispatch
- Connection isolation: one bad client does not crash the daemon
- Real IPC in tests — no mocks, no fake transports
- Clean boundary between transport and AI daemon
- Minimal surface area

## Non-Goals

- AI reasoning, search ranking, or crawler logic
- Async runtime
- TLS (localhost-only)
- Browser-specific code

## Running the Daemon Binary

```sh
cargo run --features daemon
```

On first run:
- Generates a random token and writes it to `~/.anvesha/token` (mode `0600`)
- Starts the WebSocket server on `127.0.0.1:9001`
- Starts the UDS server on `/tmp/nerve.sock`

On subsequent runs the existing token is reloaded and validated.

## Testing

```sh
cargo test --all-features
```

65 tests. No mocks — every test exercises real server code over real sockets.

### WebSocket tests (25 tests)

| File | Coverage |
|------|----------|
| `ws_basic.rs` | Server startup, valid token auth, Origin auth, Ping roundtrip, sequential pings |
| `ws_security.rs` | Wrong token → 403, missing token → 403, wrong origin → 403, missing origin → 403, correct origin wrong token → 403, oversized payload header rejected, bad magic rejected, truncated header rejected, bind addr is 127.0.0.1, oversized WebSocket message rejected |
| `ws_connections.rs` | 2 concurrent clients, 3 concurrent clients, bad frame closes one connection only, Cancel isolation between connections, disconnect/reconnect |
| `ws_protocol.rs` | request_id echo, FINAL/STREAM flags on Ping reply, SearchQuery connection stays open, Cancel connection stays open, AgentTask lifecycle, multiple pings with distinct IDs |

### UDS and unit tests (40 tests)

- Ping round-trip (single and multiple)
- Concurrent connections (2 and 6+ simultaneous clients)
- Connection isolation (malformed client does not affect concurrent good client)
- Cancel semantics (unknown, duplicate, cross-connection)
- SearchQuery dispatch to AI boundary
- Interleaved SearchQuery and Ping on same connection
- Cancel of pending SearchQuery
- Request lifecycle and cleanup
- Malformed frames, partial frames, rapid connect/disconnect
- `RequestTable` unit tests
- `read_frame` unit tests
- `generate_token` unit tests

## Repository Structure

```
nerve-core/
├── src/
│   ├── main.rs           # daemon entry point — starts WS + UDS servers
│   ├── lib.rs
│   ├── config.rs         # Config struct; bind address, ports, token path
│   ├── auth.rs           # per-install token generation and persistence
│   ├── server.rs         # UDS accept loop, thread-per-connection, read_frame
│   ├── ws_server.rs      # WebSocket accept loop + auth handshake
│   ├── dispatch.rs       # transport-agnostic dispatch, AI daemon boundary
│   └── request_table.rs  # per-connection request lifecycle and cancellation
│
└── tests/
    ├── helpers/mod.rs    # shared WebSocket test helpers
    ├── ws_basic.rs       # WebSocket: startup, auth, ping
    ├── ws_security.rs    # WebSocket: auth rejection, malformed frames
    ├── ws_connections.rs # WebSocket: concurrent clients, isolation
    ├── ws_protocol.rs    # WebSocket: NERVE semantics
    ├── ping.rs / ping_roundtrip.rs
    ├── cancel.rs / cancel_edge_cases.rs / cancel_marks_requests.rs
    ├── concurrent_connections.rs
    ├── lifecycle.rs
    ├── search_roundtrip.rs / search_streaming.rs / search_cancel_mid_stream.rs
    ├── search_worker_routing.rs
    ├── agent_task_lifecycle.rs
    ├── error_handling.rs
    ├── request_table.rs
    └── socket_read_frame.rs
```

## Dependencies

```toml
[dependencies]
nerve-ipc-core = "0.1"

# nerve-ipc provides wire format, codec, and protocol types
# (pulled in transitively via nerve-ipc-core)
```

Direct dependencies of `nerve-ipc-core`:

| Dependency | Purpose |
|------------|---------|
| `nerve-ipc` | Wire format, codec, frame types, protocol constants |
| `tracing` | Structured logging facade |
| `tungstenite` | WebSocket implementation (blocking, thread-per-connection) |
| `getrandom` | Cryptographically secure token generation |
| `tracing-subscriber` | Log subscriber — daemon binary only (`features = ["daemon"]`) |

## Status

**v0.1.0 — WebSocket transport complete.**

- ✅ Multiple simultaneous connections (UDS + WebSocket)
- ✅ Connection isolation (per-connection `RequestTable`)
- ✅ Request lifecycle and cancellation
- ✅ Clean AI daemon boundary (`ForwardToAiDaemon`)
- ✅ Transport-agnostic dispatch (`dispatch_frame` shared by UDS and WebSocket)
- ✅ WebSocket server on `127.0.0.1:9001`
- ✅ Origin + token authentication at the WebSocket handshake
- ✅ Per-install token at `~/.anvesha/token` (mode `0600`, `getrandom`)
- ✅ Oversized payload rejected before allocation
- ⏳ AI daemon integration
- ⏳ Browser extension

## License

Licensed under either of:

- Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE)
- MIT License ([LICENSE-MIT]LICENSE-MIT)

at your option.

Copyright 2026 Shreyas BK