Skip to content

Commit fbe0390

Browse files
committed
docs: detail optional trust mode in architecture document
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I341160d1f8bf1176bef49990c3ee3b306a6a6964
1 parent a66bc01 commit fbe0390

1 file changed

Lines changed: 27 additions & 5 deletions

File tree

docs/architecture.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,18 @@ this prevents project-specific caches from becoming winners for unrelated paths.
127127
Fallback cache traffic bypasses these router features and does not update health
128128
or routing state.
129129

130+
Optional trust enforcement runs at the same acceptance point as filters. After a
131+
candidate narinfo is fetched, ncro verifies the configured upstream signing key
132+
when present and can record the signed narinfo as a local claim. In `signed`
133+
mode, one valid configured signer is enough. In `quorum` mode, ncro only stores
134+
a route after enough signer keys agree on the same `StorePath`, `NarHash`,
135+
`NarSize`, and `References`. Rejected candidates are handled like filter
136+
rejections: the route is not cached and ncro keeps trying remaining candidates.
137+
When the policy is not satisfied and `trust.fail_closed = false`, the content is
138+
served anyway but the bypass is logged and counted (`ncro_trust_bypass_total`).
139+
The signature covers only the signed fingerprint, not the streamed NAR bytes;
140+
the full model and that boundary are documented in [trust.md](trust.md).
141+
130142
> [!NOTE]
131143
> Persistence is intentionally narrow. SQLite stores two kinds of data so a
132144
> restart does not force ncro to relearn everything from scratch.
@@ -135,13 +147,23 @@ First type of stored data is **route entries**, a mapping from narinfo hash to
135147
the winning upstream URL, stored with a creation timestamp and TTL. When the
136148
cache exceeds `max_entries`, the least recently used entry is evicted first.
137149
**Health snapshots** on another hand are per-upstream EMA latency estimates and
138-
failure counts, refreshed by the background probe loop. Fallback-cache responses
139-
are (intentionally) not stored as route entries, so normal upstreams become
140-
active again as soon as they recover.
150+
failure counts, refreshed by the background probe loop. **Trust claims** (when
151+
trust is enabled) are a third kind of stored data: durable records of which
152+
signer vouched for which content. Fallback-cache responses are (intentionally)
153+
not stored as route entries, so normal upstreams become active again as soon as
154+
they recover.
155+
156+
Negative lookups are cached in two layers: a short-lived in-memory LRU
157+
(`in_memory_negative_ttl`) absorbs rapid-fire duplicate misses, while a
158+
longer-lived SQLite entry (`negative_ttl`) lets a known miss survive a restart.
159+
The in-memory layer is the only routing state that does not live in SQLite.
141160

142161
Discovery and mesh are optional extensions. Discovery can add peers from the
143-
local network, while mesh gossip shares recent route decisions across trusted
144-
nodes using signed UDP packets. Consider:
162+
local network, while mesh gossip shares recent route decisions (and, when
163+
`mesh.gossip_trust_claims` is enabled, re-verified trust claims) across trusted
164+
nodes using signed UDP packets. Relayed claims are re-verified against their
165+
original Nix signer key before being trusted, so a peer is a witness, not a
166+
signing authority. Consider:
145167

146168
```mermaid
147169
flowchart LR

0 commit comments

Comments
 (0)