guardian-db 0.19.0

High-performance, local-first decentralized database built on Rust and Iroh
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# Guardian Compute

GuardianDB ships **Guardian Compute** — a decentralized edge-computing layer on
top of its local-first, P2P data model. Nodes delegate the execution of
business logic (compiled to WebAssembly) to *other* nodes, and a
capability-aware scheduler routes each task to the peer with the most spare
capacity. Results flow back through ordinary GuardianDB replication.

It turns GuardianDB from "a database that replicates data" into "a database
that also runs work where there is capacity to run it" — without a central
scheduler, over the same Iroh P2P fabric the database already uses.

```
        ┌──────────── Requester ────────────┐        ┌──────────── Executor ────────────┐
        │                                    │        │                                   │
        │ 1. publish task.wasm as a blob     │        │ 0. gossip capability vector       │
        │    (BLAKE3 hash = code identity)   │◄───────│    (cores, RAM, load, models…)    │
        │ 2. scheduler ranks peers by the    │        │                                   │
        │    capability vectors it heard     │───────►│ 4. fetch task.wasm by hash        │
        │ 3. ExecuteRequest over the         │  QUIC  │ 5. run in wasmtime sandbox        │
        │    /guardian-db/compute/1 ALPN     │        │    (fuel, memory cap, timeout)    │
        │ 7. receive output + metrics        │◄───────│ 6. reply with result + metrics    │
        │ 8. store result → replicates       │        │                                   │
        └────────────────────────────────────┘        └───────────────────────────────────┘
```

The whole layer lives in the `guardian-db` crate as the feature-gated `compute`
module (`src/compute/`), plus two small companion crates for authoring tasks
(`guardian-compute-sdk`, `guardian-compute-sdk-macros`).

---

## 1. Why this is a natural fit

Guardian Compute reuses the infrastructure the database already has, rather than
building a parallel stack:

| Need | Reused from GuardianDB / Iroh |
|---|---|
| Secure P2P transport + node identity | `iroh` QUIC + public-key identity |
| Custom protocols | the Iroh `Router`'s ALPN multiplexing (alongside gossip/blobs/docs) |
| Distributing WASM binaries and NN models | `iroh-blobs` — content-addressed by BLAKE3 hash, integrity verified on fetch |
| Broadcasting capability telemetry | `iroh-gossip` |
| Reactive triggers ("when data lands, run X") | the store `EventBus` (the same pattern as `reactive_synchronizer`) |
| Auditable, replicated task ledger | a GuardianDB store |
| Who may participate | the store `AccessController` (permissioned networks) |

What Guardian Compute adds on top: a **WASM sandbox**, **capability telemetry**,
a **scheduler**, a **task ledger**, and **reactive triggers**.

---

## 2. Feature flags

Everything is off by default and adds nothing to a plain build.

| Feature | Enables |
|---|---|
| `compute` | The full runtime, protocol, telemetry, scheduler, ledger, and triggers. Pulls in `wasmtime` (runtime + Cranelift JIT, **no** WASI) and `sysinfo`. |
| `compute-nn` | Implies `compute`. Links the `wasi-nn` API into the sandbox for Edge AI, backed by ONNX Runtime. Heavier build (downloads the ONNX Runtime binaries). |
| `compute-nn-cuda` | Implies `compute-nn`. Loads models with the CUDA execution provider and verifies advertised GPU against real CUDA availability. |

```toml
[dependencies]
guardian-db = { version = "0.18", features = ["compute"] }
# or, for Edge AI:
guardian-db = { version = "0.18", features = ["compute-nn"] }
```

When the `compute` feature is enabled, the executor is registered automatically
during node initialization; you interact with it through accessors on the
Iroh backend (see §9).

---

## 3. Core concepts

- **Task** — a WebAssembly module plus an entrypoint name and opaque input
  bytes. The module is distributed as an iroh blob; the request names it by
  BLAKE3 hash, so it is impossible for an executor to run anything other than
  the code the requester named.
- **Sandbox** — every task runs in a fresh wasmtime `Store` with three hard
  limits: a linear-memory ceiling, a CPU budget (wasmtime *fuel*), and a
  wall-clock deadline (epoch interruption). No WASI is linked by default: a
  task sees nothing but its input and output bytes — no filesystem, no
  network, no clock, no randomness.
- **Task class** — a coarse label (`General`, `Media`, `Analytics`,
  `Inference`) matched against the executor's admission policy.
- **Capability vector** — what a node advertises over gossip: cores,
  architecture, RAM, current load, battery state, free slots, accepted
  classes, and served NN models. Vectors are *hints, not contracts* — the
  executor's local policy always has the final word.
- **Host grants** — opt-in capabilities the executor's owner may extend to
  tasks (logging, reading the local store, NN inference). Off by default,
  which is what keeps runs deterministic.

---

## 4. Execution models

All of these are reached through a `ComputeScheduler` (or, for the direct case,
a `ComputeClient`). The requester's node authenticates over QUIC and doubles as
the blob provider from which the executor fetches the task code.

### 4.1 Direct delegation — "run this on that node"

```rust
let client = backend.compute_client().await?;
let done = client.execute_on(executor_node_id, request, Duration::from_secs(60)).await?;
```

The lowest-level path: you name the executor. The client sends one
`ExecuteRequest`, receives an admission `ExecuteAck` (fast verdict), and — if
accepted — an `ExecuteReply` carrying the output and `ExecMetrics` (fuel spent,
duration, peak memory).

### 4.2 Capability-aware scheduling — the network decides

```rust
let scheduler = backend.compute_scheduler().await?;
let delegated = scheduler.execute(request).await?;   // picks the best node
```

The scheduler ranks the capability vectors it has heard and delegates to the
best node, **failing over** down the ranking when a candidate rejects the task,
is unreachable, or times out. Deterministic task failures (fuel/deadline/memory
limits, a guest trap, a bad module) are final; node-specific failures (blob not
available here, a capability not granted here, an executor hiccup) fail over to
the next candidate.

Scoring favors idle cores first, then free memory, with a battery penalty that
effectively disqualifies laptops on battery, and a reputation discount for nodes
that have produced divergent results (§4.5). A task may pin a **required NN
model**; the scheduler then only considers nodes advertising it (data gravity:
send the task to where the model already is).

### 4.3 Contract-Net auction — trust a fresh bid over stale gossip

```rust
let delegated = scheduler.execute_with_auction(request).await?;
```

For expensive tasks, the scheduler probes the top-ranked candidates for a
*fresh* readiness bid before committing, so a stale gossip vector can't send the
task to a node that is actually busy. Degrades gracefully to the gossip ranking
if nobody answers.

### 4.4 MapReduce fan-out

```rust
let results = scheduler.map(partitions).await;   // one Result per partition, in order
```

Fans partitions out across candidates in parallel, rotating the ranking per task
so the load spreads instead of piling on the top node. The *reduce* step is the
caller's.

### 4.5 Redundant k-of-n execution (untrusted networks)

```rust
let outcome = scheduler.execute_redundant(request, 3).await?;   // majority of 3
```

Runs the same **deterministic** task on up to `k` nodes and returns the majority
result, penalizing the reputation of any node whose output diverges from the
majority. Reputation persists across scheduling rounds, so a known liar is
de-prioritized (never hard-banned). Because it depends on determinism, this mode
and host grants do not mix.

---

## 5. Writing tasks

### 5.1 The SDK (recommended)

The `guardian-compute-sdk` crate hides the raw ABI behind one attribute:

```rust
use guardian_compute_sdk::guardian_task;

#[guardian_task]
fn shout(input: &[u8]) -> Vec<u8> {
    input.to_ascii_uppercase()
}
```

Returning `Result<Vec<u8>, E>` turns an `Err` into a clean trap on the executor:

```rust
use guardian_compute_sdk::{guardian_task, TaskFailure};

#[guardian_task]
fn thumbnail(input: &[u8]) -> Result<Vec<u8>, TaskFailure> {
    let img = image::load_from_memory(input).map_err(|e| TaskFailure::new(e.to_string()))?;
    Ok(img.thumbnail(128, 128).into_bytes())
}
```

Typed I/O over CBOR (enable the SDK's `cbor` feature) — the SDK-level
convention; the wire protocol itself stays opaque bytes:

```rust
#[guardian_task(cbor)]
fn word_count(doc: Document) -> Result<Stats, TaskFailure> { /* In → Out, both serde types */ }
```

Build and publish:

```bash
cargo build -p my-tasks --target wasm32-unknown-unknown --release
# then add the resulting .wasm to the blob store; its BLAKE3 hash is the task id
```

The exported function's name is the `entrypoint` you put in the request.

### 5.2 The ABI (for hand-written modules)

A task module exports:

- `memory` — its linear memory;
- `gdb_alloc: (len: i32) -> i32` — returns an offset where the host writes the
  input bytes;
- the entrypoint `(ptr: i32, len: i32) -> i64` — receives the input location and
  returns the output location packed as `(out_ptr << 32) | out_len`.

Input and output are opaque byte strings; their meaning is a contract between
the module author and the requester.

### 5.3 Host functions (opt-in)

When the executor's owner grants them (`HostGrants`), a task may import, under
the `"gdb"` module:

- `log(ptr, len)` — emit a UTF-8 message into the executor's tracing;
- `store_get(key_ptr, key_len, dest_ptr, dest_cap) -> i32` — read a value from
  the executor's local store.

A module that imports a capability the executor did not grant is refused at
instantiation with `HostCapabilityDenied` — before any code runs. Modules that
import nothing run on any executor. The SDK exposes these behind its `host`
feature as `guardian::log` / `guardian::store_get`.

---

## 6. Edge AI (`compute-nn`)

With the `compute-nn` feature, an executor can serve ONNX models to inference
tasks via the standard `wasi-nn` API. Models are **owner-curated named models**:

```rust
let registry = Arc::new(NnModelRegistry::new());
registry.register_model("whisper-tiny", model_blob_hash);   // name → blob hash
handler.set_nn_models(registry);                            // also enables the Inference class
```

- Models are ordinary iroh blobs — big files addressed by hash, exactly what
  iroh-blobs is for. On the first inference task the executor downloads the
  model from the requester (or any peer that has it) and caches the loaded ONNX
  session; later tasks reuse it.
- The guest reaches models only by `load_by_name`; it cannot load arbitrary
  model bytes. The executor's owner decides what runs.
- The scheduler routes inference tasks by **model affinity**: a task naming a
  `required_model` only goes to nodes advertising it, and an executor asked for
  a model it does not serve rejects cleanly at admission.
- **GPU** (`compute-nn-cuda`): `NnTarget::Gpu` loads models on the CUDA
  execution provider, falling back to CPU safely when CUDA is absent. Advertised
  `Accel::Gpu` is verified against real CUDA availability, not merely declared.

An `Inference`-class task that imports no `wasi-nn` still runs — small models
can run entirely inside pure WASM.

---

## 7. Reactive triggers and the task ledger

Trigger rules run tasks automatically when data lands, using the store event
bus (the same pattern as `reactive_synchronizer`):

```rust
let engine = Arc::new(TriggerEngine::new(TaskLedger::in_memory(), scheduler, TriggerConfig::default()));
engine.on_replicated("/photos", TaskSpec {           // "when a photo replicates…"
    wasm_hash: THUMBNAILER, entrypoint: "thumbnail".into(),
    class: TaskClass::Media, limits: ResourceLimits::default(),
    placement: Placement::BestAvailable, required_model: None,
});
engine.attach_event_bus(store_event_bus);            // bridge EventReplicated → triggers
engine.spawn_requeue_loop(Duration::from_secs(30));  // retry abandoned/failed tasks
```

- **Deduplication**: a replication event fires on *every* replica, so each task's
  ledger key is `blake3(rule id ␟ event id)` — deterministic on every replica.
  Only the node whose atomic claim wins actually dispatches, so the task runs
  once, not once per replica.
- **Lifecycle**: every task is recorded in a `TaskLedger`  `Pending → Running → Done | Failed` — persisted through the `LedgerStore`
  abstraction (`MemoryLedger` for single-node/tests, or your own impl over a
  replicated GuardianDB store for network-wide auditability).
- **Requeue**: a `Running` task whose deadline passes (its dispatcher vanished)
  or a transiently-failed one is re-dispatched, up to a retry budget. The claim
  that transitions `Pending → Running` is atomic (compare-and-swap), so a
  requeue pass never double-dispatches a task another dispatcher already owns.

---

## 8. Security and trust model

- **The sandbox protects the executor.** A task cannot touch the executor's
  filesystem, network, environment, or wall clock, and cannot exceed its
  memory/CPU/time budget. Guest-controlled lengths (output size, host-function
  buffers) are bounds-checked against the guest's own memory *before* the host
  allocates, so a hostile module cannot OOM the executor.
- **Trust in the *result* is the requester's problem.** The sandbox does not
  stop an executor from returning a wrong answer. Two answers to this:
  - **Permissioned networks** (the recommended default): run among nodes you
    trust by identity — your own devices, or an organization's — where the
    `AccessController` governs participation. This is the target for most of the
    layer.
  - **Redundant k-of-n execution** (§4.5) for open networks: compare independent
    results and penalize divergence.
- **Participation is reciprocal, never paid.** By running Guardian Compute you
  agree — as a term of use — that your node may execute tasks for other nodes of
  the same network, in the same measure you may use theirs (the BitTorrent
  "who downloads, seeds" spirit). There is no credit/token economy. Local policy
  stays sovereign: concurrency, accepted classes, host grants, and the
  battery rule (a node on battery advertises no capacity by default) are always
  the owner's to set.
- **Determinism is required only where it matters.** In the 1-requester → 1-
  executor model the result is just another replicated datum; non-determinism is
  fine. Determinism is required only for redundant k-of-n execution, which is
  exactly why the default sandbox withholds the clock and randomness.

---

## 9. Public API map

Everything is under `guardian_db::compute` (the module) and the Iroh backend
accessors.

**On the backend** (available when the `compute` feature is on):

| Accessor | Purpose |
|---|---|
| `compute_handler()` | The executor handler — adjust the admission policy, host grants, NN models |
| `compute_client()` | Requester-side client for direct `execute_on` |
| `compute_scheduler()` | Capability-aware scheduler (`execute`, auction, map, redundant) |
| `compute_capability_gossip()` | The telemetry service feeding the scheduler directory |
| `compute_join_capability_mesh(peers)` | Add peers to the capability gossip mesh |

**Key types** (`guardian_db::compute`):

- Runtime: `WasmRuntime`, `CompiledTask`, `HostGrants`, `HostStoreReader`,
  `ExecMetrics`, `TaskError` (with `is_transient()`); `NnGrant`, `NnTarget`
  (with `compute-nn`).
- Protocol: `ComputeClient`, `ComputeProtocolHandler`, `ExecuteRequest`,
  `ExecutorPolicy`, `RejectReason`, `CompletedTask`, `ComputeCallError`,
  `WasmFetcher`.
- Scheduler: `ComputeScheduler`, `SchedulerConfig`, `ScoreWeights`,
  `CapabilityDirectory`, `ReputationBook`, `Delegated`, `RedundantOutcome`,
  `ScheduleError`.
- Telemetry: `CapabilityGossip`, `TelemetryConfig`, `TelemetrySampler`,
  `CAPABILITY_TOPIC`.
- Ledger & triggers: `TaskLedger`, `LedgerStore`, `MemoryLedger`, `TaskRecord`,
  `TaskState`, `TriggerEngine`, `TriggerRule`, `TriggerConfig`,
  `TaskDispatcher`.
- Vocabulary: `TaskSpec`, `CapabilityVector`, `TaskClass`, `Placement`,
  `CpuArch`, `Accel`, `ResourceLimits`, `NnModelRegistry` (with `compute-nn`).

**Wire identifiers** (versioned; a breaking change bumps the version, never a
silent change):

- Execution ALPN: `/guardian-db/compute/1`.
- Capability gossip topic: `guardian-db/compute/capabilities/2`.

---

## 10. Use cases

- **Edge AI / LLM inference** — a light device offloads inference to the beefy
  node on the same network; the result replicates back.
- **Decentralized media processing** — a new upload triggers thumbnailing,
  image conversion/compression, or metadata extraction on an idle node, so the
  uploader (often a phone) doesn't stall. Image work runs today in pure WASM via
  Rust crates like `image`/`zune`; **heavy video transcoding (ffmpeg-class)
  needs native codecs**, so it requires opt-in *media host functions* — a
  planned capability, not yet implemented. Until then, video transcoding is out
  of reach of the pure sandbox.
- **MapReduce analytics** — slice a heavy query across nodes; each computes its
  partition, the requester reduces.
- **Automation / ETL with failover** — periodic jobs registered in the ledger;
  if the node that usually runs them vanishes, another takes over.

---

## 11. Status

Implemented and tested (unit + end-to-end over real in-process Iroh endpoints):

- **RFC 0002** — Phases 0–5: sandbox, direct delegation, capability-aware
  orchestration (telemetry + scoring + failover + auction), reactive triggers +
  ledger, MapReduce, and k-of-n redundancy with reputation.
- **RFC 0003 Part A** (Edge AI) — NN-1…NN-4: `wasi-nn` grant, blob-backed model
  registry with session caching, model-affinity routing, and GPU with verified
  detection.
- **RFC 0003 Part B** (SDK) — SDK-1…SDK-3: `#[guardian_task]`, typed CBOR I/O,
  and host-function bindings.

Remaining follow-ups (operational, hardware-gated, or scoped for later):
publishing the SDK crates to crates.io, terminal-record retention/pruning in the
ledger, confirming GPU acceleration on real CUDA hardware, and **media host
functions** for native-codec work such as video transcoding (image processing
already runs in pure WASM; ffmpeg-class transcoding does not).