User Story
As a developer building the relay's routing logic, I want a typed Go wrapper for the relay↔binary routing envelope ({conn_id, frame}), with marshal/unmarshal helpers and exhaustive tests, so that every future routing-layer ticket can use a single canonical type instead of poking at JSON ad-hoc.
Context
The relay forwards WS frames between phones and a pyrycode binary, prepending/stripping a small routing wrapper that tells the binary which phone connection a frame came from. The wire shape is defined in pyrycode/pyrycode/docs/protocol-mobile.md § Routing envelope:
{
"conn_id": "c-7f3a...",
"frame": { /* the actual envelope */ }
}
conn_id is a relay-assigned, per-phone-connection opaque string. frame is the inner envelope; the relay treats it as opaque bytes and MUST NOT deserialise it.
This ticket establishes the canonical Go vocabulary (type + helpers) the rest of the routing layer will use. Networking, header validation, and the connection registry are separate, later tickets.
Acceptance Criteria
Technical Notes
- Stdlib only (
encoding/json); json.RawMessage is the natural fit for the opaque frame field but the architect owns the final type choice.
- The wrapper is the boundary between "relay thinks about routing" and "relay never thinks about message content." All future routing-layer code should use these helpers, never inline JSON.
Out of Scope
- WebSocket upgrade / frame forwarding.
- Connection-pool data structure mapping server-id ↔ connections.
- Auth header validation.
- The
conn_id generation scheme (relay-assigned, opaque — later ticket).
Size Estimate
XS — single file, pure functions, ~50 LOC + ~50 LOC tests.
Not security-sensitive: pure JSON marshalling, no auth, no networking, no internet exposure.
User Story
As a developer building the relay's routing logic, I want a typed Go wrapper for the relay↔binary routing envelope (
{conn_id, frame}), with marshal/unmarshal helpers and exhaustive tests, so that every future routing-layer ticket can use a single canonical type instead of poking at JSON ad-hoc.Context
The relay forwards WS frames between phones and a pyrycode binary, prepending/stripping a small routing wrapper that tells the binary which phone connection a frame came from. The wire shape is defined in
pyrycode/pyrycode/docs/protocol-mobile.md§ Routing envelope:{ "conn_id": "c-7f3a...", "frame": { /* the actual envelope */ } }conn_idis a relay-assigned, per-phone-connection opaque string.frameis the inner envelope; the relay treats it as opaque bytes and MUST NOT deserialise it.This ticket establishes the canonical Go vocabulary (type + helpers) the rest of the routing layer will use. Networking, header validation, and the connection registry are separate, later tickets.
Acceptance Criteria
internal/relay/, exporting:conn_idstring and aframeraw-JSON payload (kept as opaque bytes — see opacity invariant below).Marshalhelper that takes a connection id and an inner frame[]byteand returns the JSON-encoded envelope. Returns an error on empty connection id and on inner-frame bytes that are not valid JSON.Unmarshalhelper that parses a JSON-encoded envelope into the struct. Returns an error on malformed JSON, missingconn_id, or missingframe.conn_id.frame.make vetclean;make testgreen;make buildstill produces a working binary (no behaviour change tocmd/pyrycode-relay/main.go).Technical Notes
encoding/json);json.RawMessageis the natural fit for the opaque frame field but the architect owns the final type choice.Out of Scope
conn_idgeneration scheme (relay-assigned, opaque — later ticket).Size Estimate
XS — single file, pure functions, ~50 LOC + ~50 LOC tests.
Not
security-sensitive: pure JSON marshalling, no auth, no networking, no internet exposure.