From 5a5008080a4c35b94642b9166d6d99b7673e6719 Mon Sep 17 00:00:00 2001 From: hdbg Date: Thu, 19 Mar 2026 00:29:06 +0100 Subject: [PATCH] docs: document explicit AuthResult enums and request multiplexing --- IMPLEMENTATION.md | 26 +++++++++++ server/crates/arbiter-proto/src/transport.rs | 46 +++++++++++++++----- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index f71de5a..4718707 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -6,6 +6,20 @@ This document covers concrete technology choices and dependencies. For the archi ## Client Connection Flow +### Authentication Result Semantics + +Authentication no longer uses an implicit success-only response shape. Both `client` and `user-agent` return explicit auth status enums over the wire. + +- **Client:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `APPROVAL_DENIED`, `NO_USER_AGENTS_ONLINE`, or `INTERNAL` +- **User-agent:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `BOOTSTRAP_REQUIRED`, `TOKEN_INVALID`, or `INTERNAL` + +This makes transport-level failures and actor/domain-level auth failures distinct: + +- **Transport/protocol failures** are surfaced as stream/status errors +- **Authentication failures** are surfaced as successful protocol responses carrying an explicit auth status + +Clients are expected to handle these status codes directly and present the concrete failure reason to the user. + ### New Client Approval When a client whose public key is not yet in the database connects, all connected user agents are asked to approve the connection. The first agent to respond determines the outcome; remaining requests are cancelled via a watch channel. @@ -68,9 +82,21 @@ The `program_client.nonce` column stores the **next usable nonce** — i.e. it i ## Communication - **Protocol:** gRPC with Protocol Buffers +- **Request/response matching:** multiplexed over a single bidirectional stream using per-connection request IDs - **Server identity distribution:** `ServerInfo` protobuf struct containing the TLS public key fingerprint - **Future consideration:** grpc-web lacks bidirectional stream support, so a browser-based wallet may require protojson over WebSocket +### Request Multiplexing + +Both `client` and `user-agent` connections support multiple in-flight requests over one gRPC bidi stream. + +- Every request carries a monotonically increasing request ID +- Every normal response echoes the request ID it corresponds to +- Out-of-band server messages omit the response ID entirely +- The server rejects already-seen request IDs at the transport adapter boundary before business logic sees the message + +This keeps request correlation entirely in transport/client connection code while leaving actor and domain handlers unaware of request IDs. + --- ## EVM Policy Engine diff --git a/server/crates/arbiter-proto/src/transport.rs b/server/crates/arbiter-proto/src/transport.rs index b31aa61..25259c3 100644 --- a/server/crates/arbiter-proto/src/transport.rs +++ b/server/crates/arbiter-proto/src/transport.rs @@ -1,29 +1,49 @@ //! Transport-facing abstractions shared by protocol/session code. //! -//! This module defines a small duplex interface, [`Bi`], that actors and other +//! This module defines a small set of transport traits that actors and other //! protocol code can depend on without knowing anything about the concrete //! transport underneath. //! -//! [`Bi`] is intentionally minimal and transport-agnostic: -//! - [`Bi::recv`] yields inbound messages -//! - [`Bi::send`] accepts outbound messages +//! The abstraction is split into: +//! - [`Sender`] for outbound delivery +//! - [`Receiver`] for inbound delivery +//! - [`Bi`] as the combined duplex form (`Sender + Receiver`) +//! +//! This split lets code depend only on the half it actually needs. For +//! example, some actor/session code only sends out-of-band messages, while +//! auth/state-machine code may need full duplex access. +//! +//! [`Bi`] remains intentionally minimal and transport-agnostic: +//! - [`Receiver::recv`] yields inbound messages +//! - [`Sender::send`] accepts outbound messages //! //! Transport-specific adapters, including protobuf or gRPC bridges, live in the //! crates that own those boundaries rather than in `arbiter-proto`. //! +//! [`Bi`] deliberately does not model request/response correlation. Some +//! transports may carry multiplexed request/response traffic, some may emit +//! out-of-band messages, and some may be one-message-at-a-time state machines. +//! Correlation concerns such as request IDs, pending response maps, and +//! out-of-band routing belong in the adapter or connection layer built on top +//! of [`Bi`], not in this abstraction itself. +//! //! # Generic Ordering Rule //! //! This module consistently uses `Inbound` first and `Outbound` second in //! generic parameter lists. //! -//! For [`Bi`], that means `Bi`: +//! For [`Receiver`], [`Sender`], and [`Bi`], this means: +//! - `Receiver` +//! - `Sender` +//! - `Bi` +//! +//! Concretely, for [`Bi`]: //! - `recv() -> Option` //! - `send(Outbound)` //! -//! [`expect_message`] is a small helper for request/response style flows: it -//! reads one inbound message from a transport and extracts a typed value from -//! it, failing if the channel closes or the message shape is not what the -//! caller expected. +//! [`expect_message`] is a small helper for linear protocol steps: it reads one +//! inbound message from a transport and extracts a typed value from it, failing +//! if the channel closes or the message shape is not what the caller expected. //! //! [`DummyTransport`] is a no-op implementation useful for tests and local //! actor execution where no real stream exists. @@ -75,9 +95,15 @@ pub trait Receiver: Send + Sync { /// Minimal bidirectional transport abstraction used by protocol code. /// -/// `Bi` models a duplex channel with: +/// `Bi` is the combined duplex form of [`Sender`] and +/// [`Receiver`]. +/// +/// It models a channel with: /// - inbound items of type `Inbound` read via [`Bi::recv`] /// - outbound items of type `Outbound` written via [`Bi::send`] +/// +/// It does not imply request/response sequencing, one-at-a-time exchange, or +/// any built-in correlation mechanism between inbound and outbound items. pub trait Bi: Sender + Receiver + Send + Sync {} pub trait SplittableBi: Bi {