Remote-Local Protocol¶
For AttachmentFunctionPool calls, the Viper RPC layer implements a
pattern that is unusual but central to the design: business logic
executes on the server, but state mutations happen on the client.
The roles invert during execution.
When a stateful pool method runs server-side and needs to read or
mutate the client’s AttachmentMutating, the server sends a callback
RPC back to the client. The client reads (or mutates) its
client-local AttachmentMutating, responds, and the server
continues.
CLIENT SERVER
│ │
│──► RPCPacketCallAttachmentFunction ─────────►│
│ (pool: PlayerModel, func: create) │
│ │
│ ┌─────┴─────┐
│ │ Execute │
│ │ business │
│ │ logic │
│ └─────┬─────┘
│ │
│◄── RPCPacketCallAttachmentGettingGet ◄───────│ ← ROLE INVERSION
│ (server asks client to read) │ Server CALLS
│ │
│ [Read from LOCAL AttachmentMutating] │
│──► RPCPacketReturnValue ────────────────────►│ ← Client RESPONDS
│ │
│◄── RPCPacketCallAttachmentMutatingUpdate ◄───│ ← ROLE INVERSION
│ (server asks client to mutate) │
│ │
│ [Mutate CLIENT-LOCAL AttachmentMutating] │
│──► RPCPacketReturnVoid ─────────────────────►│
│ │
│ [Function complete]
│ │
│◄── RPCPacketReturnValue (final result) ◄─────│
│ │
On the server, the mutating parameter passed to the pool function
is an AttachmentMutatingRemote — a proxy that forwards every
operation to the client. The bridge code that implements the pool
method does not know it is talking to a remote state:
// Server-side bridge — the same code runs locally and remotely.
// When called via RPC, `mutating` is AttachmentMutatingRemote,
// which proxies every Set/Get/Update back to the client.
PlayerKey create(std::shared_ptr<Viper::AttachmentMutating> const & mutating,
std::string const & nickname, Demo::Level level) {
auto const key = PlayerKey::create();
auto const property = PlayerProperty{nickname, level};
Attachments::Player_Property::set(mutating, key, property); // proxied!
return key;
}
The Viper RPC layer defines fifteen callback packet types covering
every read and mutation operation an AttachmentMutating can
perform — three reads (Keys, Has, Get) and twelve mutations
(Set, Diff, Update on documents; UnionInSet, SubtractInSet
on sets; map and ordered-array mutations).
Safety by isolation¶
The Remote-Local pattern looks dangerous at first read — the server
sends commands that mutate client state. The design is inherently
safe at the state-ownership level because the server has no handle on
the client’s authoritative state. Mutations land in an AttachmentMutating context owned by
the client, isolated from the model the client treats as ground
truth. The client alone decides whether — and when — to integrate
that context back into its authoritative state.
The protocol’s safety property is at the state-ownership level : the server cannot reach into the client’s model. What happens downstream of the context (held in memory, integrated into the authoritative state, eventually persisted, discarded) is the client’s prerogative and is orthogonal to the protocol.
CLIENT SERVER
│ │
│ ┌───────────────────────────────────┐ │
│ │ AttachmentMutating (context) │ │
│ │ CLIENT-LOCAL, server-isolated │ │
│ │ │ │
│ │ All mutations land here │◄───────────────│ Server sends mutations
│ └───────────────┬───────────────────┘ │
│ │ │
│ │ Integration into authoritative │
│ │ state is the CLIENT's decision. │
│ │ Persistence, if any, is downstream │
│ │ of integration — never driven │
│ │ by the server. │
│ ▼ │
│ ┌───────────────────────────────────┐ │
│ │ Client-authoritative state │ │
│ └───────────────────────────────────┘ │
Failure mode |
Effect on the client’s authoritative state |
|---|---|
Network timeout |
Mutation context abandoned, never integrated |
Server exception |
Mutation context abandoned, never integrated |
Server sends invalid data |
Client may discard the context before integration |
Partial mutations |
Context never integrated as a whole |
The structural integrity of the client’s authoritative state is guaranteed at the protocol level. Semantic integrity under multi-author reduction is a separate concern of the commit layer — see Dual-Layer Contract.
Transport security — authentication and encryption against a spoofed or eavesdropping server — is a separate concern again: Viper assumes a trusted network and delegates it to deployment (see Security posture). State isolation bounds what a connected server can do; it is not a substitute for securing the connection.