dig-rpc-protocol 0.3.1

Canonical DIG-node JSON-RPC protocol: request/response types, the method enum + tier classification, the error-code taxonomy, and an OpenRPC 1.2.6 document generator. The single source of truth both DIG node implementations depend on. Pure types — no I/O, no async, no server logic. (Formerly dig-rpc-types.)
Documentation
# dig-rpc-protocol

The **canonical DIG-node JSON-RPC protocol** (formerly `dig-rpc-types`) — the single source of truth both
DIG node implementations (the digstore `dig-node` crate and the standalone
`dig-node` binary) depend on instead of hand-rolling `json!({…})` shapes and
ad-hoc error codes.

Pure types: **no I/O, no async, no server logic, no crypto.** The
[`dig-rpc`](https://github.com/DIG-Network/dig-rpc) server crate and the node
implementations build on these types.

## What's here

| Module | Contents |
|---|---|
| `envelope` | JSON-RPC 2.0 request/response, `RequestId`, `Version` |
| `error` | the canonical `ErrorCode` taxonomy, the `RpcError` envelope (`{code, message, data:{code, origin}}`), and the one constructor helper |
| `method` | the `Method` enum — stable wire names, per-method `Tier`, the mTLS peer allowlist |
| `tier` | the three access tiers (`PublicRead` / `Peer` / `Control`) |
| `types` | every method's params + results, field-for-field with the canonical node |
| `frames` | the shape-dispatched DHT + PEX peer wires |
| `openrpc` | the OpenRPC 1.2.6 document generator (the single discovery source) |

The full contract is in [`SPEC.md`](./SPEC.md).

## Full Protocol Interface (at-a-glance LLM reference)

### All Methods (34 total)

| Method Name | Wire Name | Tier | Peer-Reachable | Summary |
|---|---|---|---|---|
| `GetContent` | `dig.getContent` | PublicRead || Read a verified window of a resource's ciphertext |
| `GetCapsule` | `dig.getCapsule` | PublicRead || Fetch the whole .dig module for (store, root) |
| `GetModule` | `dig.getModule` | PublicRead || Alias of dig.getCapsule |
| `GetManifest` | `dig.getManifest` | PublicRead || Fetch the public discovery manifest resource |
| `GetMetadata` | `dig.getMetadata` | PublicRead || Fetch the plaintext metadata manifest |
| `ListCapsules` | `dig.listCapsules` | PublicRead || List a store's confirmed capsules |
| `GetProof` | `dig.getProof` | PublicRead || Get the real inclusion proof + execution-proof status |
| `GetProofStatus` | `dig.getProofStatus` | PublicRead || Poll a real execution-proof job by id |
| `GetAnchoredRoot` | `dig.getAnchoredRoot` | PublicRead || Resolve a store's chain-anchored tip root |
| `GetCollection` | `dig.getCollection` | PublicRead || Get collection-level facts for NFT launcher ids |
| `ListCollectionItems` | `dig.listCollectionItems` | PublicRead || List resolved NFT collection items (paginated) |
| `Health` | `dig.health` | PublicRead || Liveness and capability summary |
| `Methods` | `dig.methods` | PublicRead || List the method names this node implements |
| `GetNetworkInfo` | `dig.getNetworkInfo` | Peer || This node's peer-network posture |
| `GetPeers` | `dig.getPeers` | Peer || The peers this node currently knows |
| `Announce` | `dig.announce` | Peer || Accept a peer announcement (peer_id + addresses) |
| `GetAvailability` | `dig.getAvailability` | Peer || Batch-check whether this node holds items |
| `ListInventory` | `dig.listInventory` | Peer || Enumerate the stores / roots this node serves |
| `FetchRange` | `dig.fetchRange` | Peer || Fetch a single verified range frame of a resource |
| `Stage` | `dig.stage` | Control || Compile a local folder into a capsule .dig in-process |
| `CacheGetConfig` | `cache.getConfig` | Control || Get the local-cache configuration |
| `CacheSetCapBytes` | `cache.setCapBytes` | Control || Set the local-cache size cap |
| `CacheClear` | `cache.clear` | Control || Clear the local cache |
| `CacheListCached` | `cache.listCached` | Control || List the durable cached modules |
| `CacheRemoveCached` | `cache.removeCached` | Control || Remove one cached capsule |
| `CacheFetchAndCache` | `cache.fetchAndCache` | Control || Fetch and cache one capsule |
| `CacheStats` | `cache.stats` | Control || Cache telemetry: cap + usage, entry count + bytes, eviction + hit/miss |
| `ControlPeerStatus` | `control.peerStatus` | Control || Snapshot the node's peer network |
| `ControlSubscribe` | `control.subscribe` | Control || Subscribe the node to a store (persisted watch + gap-fill) |
| `ControlUnsubscribe` | `control.unsubscribe` | Control || Unsubscribe the node from a store |
| `ControlListSubscriptions` | `control.listSubscriptions` | Control || List the node's persisted store subscriptions |
| `ControlPeersConnect` | `control.peers.connect` | Control || Dial a peer into the connected pool |
| `ControlPeersDisconnect` | `control.peers.disconnect` | Control || Drop a pooled peer by peer_id |
| `RpcDiscover` | `rpc.discover` | Control || Return the OpenRPC self-describe document |

**Access Tiers:** `PublicRead` = anonymous content reads (HTTPS / mTLS). `Peer` = mTLS peer surface (node↔node). `Control` = loopback / in-process only.

**Peer-Reachable Methods (10 total):** `dig.getContent`, `dig.getNetworkInfo`, `dig.getPeers`, `dig.announce`, `dig.getAvailability`, `dig.listInventory`, `dig.fetchRange`, `dig.getAnchoredRoot`, `dig.getCollection`, `dig.listCollectionItems`. All `Control` and non-peer `PublicRead` methods are never peer-reachable.

### Error Codes (22 total)

| Code | Machine Code | Origin | Meaning |
|---|---|---|---|
| `-32700` | `PARSE_ERROR` | Node | Invalid JSON received |
| `-32600` | `INVALID_REQUEST` | Node | JSON is not a valid Request object |
| `-32601` | `METHOD_NOT_FOUND` | Node | Method does not exist / is not available |
| `-32602` | `INVALID_PARAMS` | Node | Invalid or missing method parameters |
| `-32603` | `INTERNAL_ERROR` | Node | Node failed to satisfy a well-formed call |
| `-32000` | `SERVER_ERROR` | Node | Generic server error (config/IO/chain failure) |
| `-32004` | `RESOURCE_UNAVAILABLE` | Node | Resource not available at requested root (genuine miss) |
| `-32005` | `ROOT_NOT_ANCHORED` | Node | Requested/served root is not chain-anchored (fail-closed) |
| `-32006` | `PEER_UNREACHABLE` | Peer | No NAT-traversal strategy reached the peer |
| `-32007` | `RANGE_NOT_SATISFIABLE` | Node | Byte range lies outside the resource |
| `-32008` | `CONTENT_REDIRECT` | Node | Content held elsewhere — `data.redirect` names holders |
| `-32010` | `UPSTREAM_ERROR` | Upstream | Upstream/proxy fetch failed |
| `-32011` | `STAGE_INVALID_INPUT` | Node | `dig.stage`: dir unreadable / walk budget exceeded |
| `-32012` | `STAGE_NO_FILES` | Node | `dig.stage`: no files to compile |
| `-32013` | `STAGE_OVER_CAP` | Node | `dig.stage`: input exceeds store cap |
| `-32014` | `STAGE_COMPILE_FAILED` | Node | `dig.stage`: compile / IO failure |
| `-32020` | `ONION_CIRCUIT_UNAVAILABLE` | Onion | Private read circuit could not be built/kept |
| `-32021` | `PRIVACY_REQUIRES_LOCAL_NODE` | Onion | Privacy mode requires local originator |
| `-32022` | `ONION_HOPS_OUT_OF_RANGE` | Onion | Hop count outside `[2, 5]` |
| `-32030` | `UNAUTHORIZED` | Control | Control-plane call not authorized (loopback gate failed) |
| `-32031` | `NOT_SUPPORTED` | Control | Control-plane method recognized but not supported |
| `-32032` | `CONTROL_ERROR` | Control | Control-plane runtime error |

**Error Origins:** `Node` = node's own read/serve path. `Peer` = peer-network layer. `Upstream` = upstream fetch (proxy/whole-store sync). `Onion` = private-retrieval layer. `Control` = loopback control plane.

### Request/Response Envelope

**JSON-RPC 2.0 strict:**

```
Request: {"jsonrpc":"2.0","id":<num|str>,"method":"<wire_name>","params":<object|null>}
Response: {"jsonrpc":"2.0","id":<num|str>,"result":<any>} | {"jsonrpc":"2.0","id":<num|str>,"error":{"code":<int>,"message":"<str>","data":{"code":"<UPPER_SNAKE>","origin":"<str>",...}}}
```

**Error Envelope Structure:**
```rust
{
  code: i32,                    // numeric wire code (e.g., -32004)
  message: String,              // human-readable summary
  data: {
    code: String,              // stable machine code (e.g., "RESOURCE_UNAVAILABLE")
    origin: String,            // subsystem (node/peer/upstream/onion/control)
    redirect?: RedirectInfo,   // present only on CONTENT_REDIRECT
    <extra fields>             // method-specific fields flattened
  }
}
```

### Interface Version

**`INTERFACE_VERSION`** is not yet versioned in the wire (future work). The protocol evolves via additive `Method` variants and `ErrorCode` assignments; both are `#[non_exhaustive]` so a client match-arm default (`_ => …`) handles future extensions.

## Error taxonomy at a glance

Standard JSON-RPC codes plus the DIG protocol codes. The onion codes
`-32020/-32021/-32022` are the published normative contract and keep their
numbers; the control-plane codes are `-32030/-32031/-32032`.

```rust
use dig_rpc_protocol::{RpcError, ErrorCode, ErrorOrigin};

let e = RpcError::of(ErrorCode::ResourceUnavailable, "not at this root");
assert_eq!(e.code, ErrorCode::ResourceUnavailable);       // -32004
assert_eq!(e.data.code, "RESOURCE_UNAVAILABLE");          // stable branch key
assert_eq!(e.data.origin, ErrorOrigin::Node);
```

## Method catalogue + discovery

```rust
use dig_rpc_protocol::{Method, Tier, openrpc_document};

assert_eq!(Method::GetContent.name(), "dig.getContent");
assert_eq!(Method::GetContent.tier(), Tier::PublicRead);
assert!(Method::GetContent.is_peer_reachable());
assert!(!Method::CacheClear.is_peer_reachable());          // control tier

let doc = openrpc_document(env!("CARGO_PKG_VERSION"));      // OpenRPC 1.2.6
```

## Features

| Flag | Default | Effect |
|---|---|---|
| `schema-export` | off | derive `schemars::JsonSchema` on every wire type (Go/TS codegen) |

## Testing

```
cargo test --all-features        # unit + conformance vectors
cargo llvm-cov --all-features    # coverage (CI-gated ≥80%)
```

Licensed under Apache-2.0 OR MIT.