# SubstrateCoordinator Design

**ADRs**: ADR-003 (system architecture), ADR-029 (coordinator layer)
**Last reviewed**: 2026-06-06

## Overview

The coordinator owns all cross-backend operations inside `kkernel`. Pack crates do not depend on
it — they receive a single-backend `KhiveRuntime`. The coordinator routes across backends above
the pack layer.

## Architecture

```text
kkernel::coordinator
  mod.rs  — SubstrateCoordinator + BackendRegistry + LocatorCache
```

Sub-modules (`edges`, `traversal`, `curation`, `health`) are reserved per ADR-029 for D5/D6
work that is not yet implemented.

## Implementation Phases

### D1 — BackendRegistry (shipped)

`BackendRegistry` stores backends in a `BTreeMap<String, BackendEntry>` for deterministic
iteration order. The first registered backend is the primary.

### D2 — LocatorCache (shipped)

`LocatorCache` maps substrate UUIDs to the backend that owns them. Entries expire after 5 minutes
(configurable via `with_locator_ttl`). Eviction is lazy on read. `purge_expired` is available for
maintenance tasks.

`locate(id, namespace)` checks the cache first; on a miss it concurrently probes all backends and
populates the cache on first hit.

### D3 — Fan-out search (shipped)

`fan_out_search(query, namespace, limit)` broadcasts `hybrid_search` to all registered backends in
parallel. Results are merged with Reciprocal Rank Fusion (unweighted, k=60). Per-backend errors are
captured in `BackendSearchResult::error` — a single failing backend does NOT abort the fan-out.

When `is_single_backend()` is true the fan-out degenerates to a single backend call.

### D4 — Cross-backend traversal (deferred)

BFS across backend boundaries following `contains`/`extends`/`depends_on` edges. The coordinator
intercepts `traverse()` results, checks each node's backend via `locate()`, and recursively fans
out to the owning backend. Entry point: `cross_backend_traverse(roots, max_depth, relations, ns)`.

### D5 — WAL cascade on hard-delete (deferred)

When a node is hard-deleted, cascade the delete to all incident cross-backend edges using a WAL
journal. On delete, look up WAL entries for the UUID and issue compensating `delete_edge` calls to
each referenced backend. Entry point: `cascade_delete(id, namespace)`.

### D6 — Backend health map (deferred)

Coordinator maintains a health score per backend derived from consecutive error counts and last
successful call timestamp. `fan_out_search` skips unhealthy backends (score below threshold).
Requires a background health-check loop and a `BackendHealthMap`. Entry point: `health_map()`.

## Single-backend behaviour

When only one backend is registered, every D1–D6 mechanism degenerates to its trivial identity:
no fan-out, no cross-backend routing, no health map misses. Multi-backend complexity is opt-in
via `khive.toml` (ADR-028).

## Invariants

- `BackendRegistry` is append-only after boot; no backend is removed at runtime.
- The primary backend is always the first registered.
- `LocatorCache` entries are immutable once inserted (backend affinity is stable per entity).
- `fan_out_search` never panics on per-backend errors; errors are captured in the result.
