CADS-Tunnel docs
Explanation

How the edge decides whether to admit a channel join

Agent-Fabric channels covers how a broker-mediated connection gets established — rendezvous, the fallback ladder, ciphertext-only routing. This page covers what happens the instant before that: how the edge’s broker decides whether to admit a presenting (channel, holder) pair at all, source-grounded in crates/edge/src/channel_authorize.rs.

The broker holds no membership state of its own

Every single channel join — not just the first one, every one — makes the edge call POST {control-plane}/internal/channel/authorize with the channel id and the presenting holder’s public key, over the shared edge↔CP admin token. The control plane’s durable channel_members table is the only source of truth; the edge never caches “who’s allowed in” as its own registry. The response is a real, resolved verdict, not a guess:

Two different kinds of “no” — and why they used to be the same

Until a fix landed on main (2026-07-31, tracked as scimbe/CADS-Tunnel#231), every non-success outcome from that CP round-trip — a clean 404 (genuinely not a member), a clean 401 (edge↔CP admin-token mismatch), a connection timeout, or the CP simply being mid-restart — collapsed to the exact same refusal. That’s a real problem for a coordination-only architecture like this one: the control plane restarting for a few seconds (routine during active development, and not something a channel member should ever notice) made every presenting grant get refused plane-wide, for every holder, indistinguishable from every membership having been revoked simultaneously — even though membership rows are durable and the CP hadn’t touched them.

Live-reproduced before the fix, not theoretical: a completely fresh ct-agent channel --serve process — never run before, so it can't have "gotten stuck" — failed its very first admission attempt with edge broker refused the channel join the moment the CP blipped. That proved the refusal was happening at this authorize lookup, not anywhere in client-side state.

The fix draws a real distinction the code now enforces:

That 30-second window is a deliberate trade: long enough to bridge a routine restart, short enough that a member revoked mid-outage can’t ride the stale cache for long. It’s also why the underlying HTTP client for this specific call deliberately does not reuse pooled connections — a half-dead pooled connection surviving a CP restart is exactly the ambiguous “not really resolved” state the fix exists to eliminate; a fresh connection per authorize call fails fast and predictably instead.

What this means if your own channel join gets refused

If you see edge broker refused the channel join (or, server-side in the edge’s own log, channel-join NO [not-member] channel=... holder=...: unknown channel or holder not a member), the two now-distinguished causes read very differently:

Found an error, or something that didn't work as documented? Open an issue →