17 KiB
Yonexus Protocol Specification
Version: draft v0.3 Status: planning
1. Purpose
This document defines the built-in Yonexus communication protocol used between:
Yonexus.ServerYonexus.Client
The protocol covers:
- connection setup
- pairing
- authentication
- heartbeat
- status/lifecycle events
- protocol-level errors
- transport of application rule messages over the same WebSocket channel
Important security rule:
- pairing codes must not be delivered to
Yonexus.Clientover the Yonexus WebSocket channel - pairing codes must be delivered out-of-band to a human administrator via Discord DM
1.1 Canonical Terminology
These names are treated as protocol-level canonical terms:
identifier: unique logical identity of a Yonexus client instance.rule_identifier: exact-match routing key for application messages.builtin: reserved protocol namespace used only for Yonexus control frames.pairingCode: short-lived out-of-band code generated by the server for human-mediated pairing.secret: server-issued shared secret used in reconnect authentication proof construction.publicKey/privateKey: client signing keypair.nextAction: the server's directed next step inhello_ack.
The protocol and implementation repos should prefer these exact names over synonyms.
2. Transport
Transport is WebSocket.
Yonexus.Serverlistens as WebSocket serverYonexus.Clientconnects as WebSocket client- protocol frames are UTF-8 text in v1
- binary frames are not required in v1
Client connects to configured mainHost, which in v1 should be a full WebSocket URL:
ws://host:port/pathwss://host:port/path
Recommended canonical config:
- require/prefer a full WebSocket URL in v1 rather than raw
host:port
3. Message Categories
3.1 Builtin Protocol Messages
Builtin messages always use:
builtin::${json_payload}
builtin is reserved and must not be registered by user code.
3.2 Application Rule Messages
Application messages use:
${rule_identifier}::${message_content}
When Yonexus.Server receives a rule message from a client, it must rewrite it before dispatch to:
${rule_identifier}::${sender_identifier}::${message_content}
4. Builtin Envelope
Builtin wire format:
builtin::{JSON}
Canonical envelope:
interface BuiltinEnvelope {
type: string;
requestId?: string;
timestamp?: number; // UTC unix seconds
payload?: Record<string, unknown>;
}
Rules:
timestampuses UTC unix secondsrequestIdis used for correlation where neededpayloadcontent depends ontype
5. Builtin Message Types
5.1 Session Setup
hello
Sent by Yonexus.Client immediately after WebSocket connection opens.
Purpose:
- declare identifier
- advertise current auth material state
- announce protocol version
Example:
builtin::{
"type":"hello",
"requestId":"req_001",
"timestamp":1711886400,
"payload":{
"identifier":"client-a",
"hasSecret":true,
"hasKeyPair":true,
"publicKey":"<optional-public-key>",
"protocolVersion":"1"
}
}
hello_ack
Sent by Yonexus.Server in response to hello.
Possible nextAction values:
pair_requiredauth_requiredrejectedwaiting_pair_confirm
Example:
builtin::{
"type":"hello_ack",
"requestId":"req_001",
"timestamp":1711886401,
"payload":{
"identifier":"client-a",
"nextAction":"pair_required"
}
}
5.2 Pairing Flow
5.2.1 Pairing Design Rule
Yonexus.Server must never send the actual pairingCode to Yonexus.Client through the Yonexus WebSocket channel.
The pairing code must be delivered to the configured human administrator using:
notifyBotTokenadminUserId
Specifically:
Yonexus.Serversends a Discord DM to the configured admin user- the DM contains the client identifier and pairing code
- the human relays the code to the client side by some trusted out-of-band path
5.2.2 Pairing Request Creation
When pairing is required, Yonexus.Server generates:
pairingCodeexpiresAtttlSeconds
The admin DM must include at minimum:
identifierpairingCodeexpiresAtor TTL
Example DM body:
Yonexus pairing request
identifier: client-a
pairingCode: ABCD-1234-XYZ
expiresAt: 1711886702
pair_request
Sent by Yonexus.Server to Yonexus.Client after pairing starts.
Purpose:
- indicate that pairing has started
- indicate whether admin notification succeeded
- provide expiry metadata without revealing the code
Example:
builtin::{
"type":"pair_request",
"requestId":"req_002",
"timestamp":1711886402,
"payload":{
"identifier":"client-a",
"expiresAt":1711886702,
"ttlSeconds":300,
"adminNotification":"sent",
"codeDelivery":"out_of_band"
}
}
Allowed adminNotification values:
sentfailed
If notification failed, pairing must not proceed until retried successfully.
pair_confirm
Sent by Yonexus.Client to confirm pairing.
Purpose:
- submit the pairing code obtained out-of-band
Example:
builtin::{
"type":"pair_confirm",
"requestId":"req_002",
"timestamp":1711886410,
"payload":{
"identifier":"client-a",
"pairingCode":"ABCD-1234-XYZ"
}
}
pair_success
Sent by Yonexus.Server after successful pairing.
Purpose:
- return generated secret
- confirm trusted pairing state
Example:
builtin::{
"type":"pair_success",
"requestId":"req_002",
"timestamp":1711886411,
"payload":{
"identifier":"client-a",
"secret":"<random-secret>",
"pairedAt":1711886411
}
}
pair_failed
Sent by Yonexus.Server when pairing fails.
Typical reasons:
expiredinvalid_codeidentifier_not_allowedadmin_notification_failedinternal_error
Example:
builtin::{
"type":"pair_failed",
"requestId":"req_002",
"timestamp":1711886710,
"payload":{
"identifier":"client-a",
"reason":"expired"
}
}
5.3 Authentication Flow
After pairing, reconnect authentication uses:
- stored
secret - 24-character random nonce
- current UTC unix timestamp
- client private key
5.3.1 Proof Construction
Logical proof content:
secret + nonce + timestamp
Implementation recommendation:
- use canonical serialized object bytes for signing
Recommended logical form:
{
"secret":"...",
"nonce":"...",
"timestamp":1711886500
}
5.3.2 Signature Primitive
Recommended primitive:
- digital signature using client private key
- verification using stored client public key on server
auth_request
Sent by Yonexus.Client after pairing or on reconnect.
Example:
builtin::{
"type":"auth_request",
"requestId":"req_003",
"timestamp":1711886500,
"payload":{
"identifier":"client-a",
"nonce":"RANDOM24CHARACTERSTRINGX",
"proofTimestamp":1711886500,
"signature":"<base64-signature>",
"publicKey":"<optional-public-key-if-rotating>"
}
}
Server validation:
- identifier is allowlisted
- identifier exists in registry
- client is in paired state
- public key matches expected key if provided
- signature verifies successfully
- proof contains correct secret
abs(now - proofTimestamp) < 10- nonce has not appeared in recent nonce window
- handshake attempts in last 10 seconds do not exceed 10
auth_success
Sent by Yonexus.Server on success.
Example:
builtin::{
"type":"auth_success",
"requestId":"req_003",
"timestamp":1711886501,
"payload":{
"identifier":"client-a",
"authenticatedAt":1711886501,
"status":"online"
}
}
auth_failed
Sent by Yonexus.Server on auth failure.
Allowed reasons include:
unknown_identifiernot_pairedinvalid_signatureinvalid_secretstale_timestampfuture_timestampnonce_collisionrate_limitedre_pair_required
re_pair_required
Sent by Yonexus.Server when trust state must be reset.
Example:
builtin::{
"type":"re_pair_required",
"requestId":"req_004",
"timestamp":1711886510,
"payload":{
"identifier":"client-a",
"reason":"nonce_collision"
}
}
5.4 Heartbeat
heartbeat
Sent by Yonexus.Client every 5 minutes after authentication.
Example:
builtin::{
"type":"heartbeat",
"timestamp":1711886800,
"payload":{
"identifier":"client-a",
"status":"alive"
}
}
heartbeat_ack
Optional response by Yonexus.Server.
v1 policy:
heartbeat_ackmay be enabled by the server but clients must not require it for healthy operation
Example:
builtin::{
"type":"heartbeat_ack",
"timestamp":1711886801,
"payload":{
"identifier":"client-a",
"status":"online"
}
}
5.5 Status / Lifecycle Notifications
status_update
Sent by Yonexus.Server when client state changes.
Example:
builtin::{
"type":"status_update",
"timestamp":1711887220,
"payload":{
"identifier":"client-a",
"status":"unstable",
"reason":"heartbeat_timeout_7m"
}
}
disconnect_notice
Sent by Yonexus.Server before deliberate close.
Example:
builtin::{
"type":"disconnect_notice",
"timestamp":1711887460,
"payload":{
"identifier":"client-a",
"reason":"heartbeat_timeout_11m"
}
}
5.6 Errors
error
Generic protocol-level error.
Recommended builtin error codes:
MALFORMED_MESSAGEUNSUPPORTED_PROTOCOL_VERSIONIDENTIFIER_NOT_ALLOWEDPAIRING_REQUIREDPAIRING_EXPIREDADMIN_NOTIFICATION_FAILEDAUTH_FAILEDNONCE_COLLISIONRATE_LIMITEDRE_PAIR_REQUIREDCLIENT_OFFLINEINTERNAL_ERROR
6. State Machines
6.1 Client State Machine
Suggested Yonexus.Client states:
idleconnectingconnectedpairing_requiredpairing_pendingpairedauthenticatingauthenticatedreconnectingerror
Typical transitions:
idle
-> connecting
-> connected
-> (pairing_required | authenticating)
pairing_required
-> pairing_pending
-> paired
-> authenticating
-> authenticated
authenticated
-> reconnecting
-> connecting
On re_pair_required:
authenticated | authenticating -> pairing_required
6.2 Server-Side Client State
Per client trust state:
unpairedpendingpairedrevoked
Per client liveness state:
onlineunstableoffline
7. Security Windows and Replay Protection
7.1 Nonce Requirements
Nonce rules:
- exactly 24 random characters
- fresh per auth attempt
- must not repeat within recent security window
7.2 Recent Nonce Window
Server stores for each client:
- the last 10 nonces seen within the recent validity window
If a nonce collides:
- authentication fails
- server marks condition unsafe
- client must re-pair
7.3 Handshake Attempt Window
Server stores recent handshake attempt timestamps.
If more than 10 handshake attempts occur within 10 seconds:
- authentication fails
- server marks condition unsafe
- client must re-pair
7.4 Time Drift Validation
Server validates:
abs(current_utc_unix_time - proofTimestamp) < 10
If validation fails:
- auth fails
- no session is established
8. Rule Message Dispatch
All non-builtin messages use:
${rule_identifier}::${message_content}
Client to server example:
chat_sync::{"conversationId":"abc","body":"hello"}
Server rewrites before matching:
chat_sync::client-a::{"conversationId":"abc","body":"hello"}
Dispatch algorithm:
- parse first delimiter section as
rule_identifier - if
rule_identifier === builtin, route to builtin protocol handler - otherwise iterate registered rules in registration order
- invoke the first exact match
- ignore/log if no match is found
v1 policy:
- rule matching is exact string match only; prefix, wildcard, and regex routing are out of scope
Processor input:
- on client:
${rule_identifier}::${message_content} - on server for client-originated messages:
${rule_identifier}::${sender_identifier}::${message_content}
9. Connection Rules
Server should reject connection attempts when:
- identifier is absent
- identifier is not in configured allowlist
- protocol version is unsupported
- hello/auth payload is malformed
Recommended v1 policy:
- only one active authenticated connection per client identifier
- terminate old connection and accept new one after successful auth
10. Persistence Semantics
10.1 Yonexus.Server Persists
At minimum:
- identifier
- public key
- secret
- trust state
- pairing code + expiry if pending
- pairing notification metadata
- last known liveness status
- metadata timestamps
May persist or reset on restart:
- recent nonces
- recent handshake attempts
Recommended v1:
- clear rolling security windows on restart
- keep long-lived trust records
10.2 Yonexus.Client Persists
At minimum:
- identifier
- private key
- secret
- optional last successful pair/auth metadata
11. Versioning
Protocol version is advertised during hello.
Initial version:
1
12. Canonical JSON Shapes
interface HelloPayload {
identifier: string;
hasSecret: boolean;
hasKeyPair: boolean;
publicKey?: string;
protocolVersion: string;
}
interface PairRequestPayload {
identifier: string;
expiresAt: number;
ttlSeconds: number;
adminNotification: "sent" | "failed";
codeDelivery: "out_of_band";
}
interface PairConfirmPayload {
identifier: string;
pairingCode: string;
}
interface PairSuccessPayload {
identifier: string;
secret: string;
pairedAt: number;
}
interface AuthRequestPayload {
identifier: string;
nonce: string;
proofTimestamp: number;
signature: string;
publicKey?: string;
}
interface HeartbeatPayload {
identifier: string;
status: "alive";
}
13. Example Flows
13.1 First-Time Pairing Flow
Client connects WS
Client -> builtin::hello
Server sends Discord DM to configured admin with identifier + pairingCode
Server -> builtin::hello_ack(nextAction=pair_required)
Server -> builtin::pair_request(expiresAt, adminNotification=sent, codeDelivery=out_of_band)
Human reads DM and relays pairingCode to client side
Client -> builtin::pair_confirm(pairingCode)
Server -> builtin::pair_success(secret)
Client stores secret
Client -> builtin::auth_request(signature over secret+nonce+timestamp)
Server -> builtin::auth_success
Client enters authenticated state
13.2 Normal Reconnect Flow
Client connects WS
Client -> builtin::hello(hasSecret=true)
Server -> builtin::hello_ack(nextAction=auth_required)
Client -> builtin::auth_request(...)
Server -> builtin::auth_success
Client begins heartbeat schedule
13.3 Unsafe Replay / Collision Flow
Client -> builtin::auth_request(nonce collision)
Server detects unsafe condition
Server -> builtin::re_pair_required(reason=nonce_collision)
Server invalidates existing secret/session trust
Server sends fresh Discord DM pairing notification on next pairing start
Client returns to pairing_required state
13.4 Heartbeat Timeout Flow
Client authenticated
No heartbeat for 7 min -> server marks unstable
No heartbeat for 11 min -> server marks offline
Server -> builtin::disconnect_notice
Server closes WS connection
14. Implementation Notes
14.1 Parsing
Because the wire format is string-based with :: delimiters:
- only the first delimiter split should determine the
rule_identifier - for
builtin, the remainder is parsed as JSON once - message content itself may contain
::, so avoid naive full split logic
14.2 Discord DM Notification
When pairing starts on Yonexus.Server:
- use configured
notifyBotToken - send DM to
adminUserId - include only required pairing data
- if DM send fails, surface pairing notification failure
Sensitive values that must never be logged in plaintext:
secret- private key
- raw proof material
15. Open Clarifications
- Exact signing algorithm: Ed25519 is a strong default candidate
- Secret length and generation requirements
- Pairing code format and length
- Is human code relay enough for v1, or should admin approve/deny controls be added later?
- Should
heartbeat_ackbe mandatory or optional? - Should client reconnect use exponential backoff?
- Should duplicate active connections replace old sessions or be rejected in stricter modes?
16. Reserved Builtin Types
Reserved builtin type values:
hellohello_ackpair_requestpair_confirmpair_successpair_failedauth_requestauth_successauth_failedre_pair_requiredheartbeatheartbeat_ackstatus_updatedisconnect_noticeerror
These names are reserved by Yonexus and must not be repurposed by user rules.