Files
Yonexus/PROTOCOL.md

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.Server
  • Yonexus.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.Client over 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 in hello_ack.

The protocol and implementation repos should prefer these exact names over synonyms.


2. Transport

Transport is WebSocket.

  • Yonexus.Server listens as WebSocket server
  • Yonexus.Client connects 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/path
  • wss://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:

  • timestamp uses UTC unix seconds
  • requestId is used for correlation where needed
  • payload content depends on type

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_required
  • auth_required
  • rejected
  • waiting_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:

  • notifyBotToken
  • adminUserId

Specifically:

  • Yonexus.Server sends 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:

  • pairingCode
  • expiresAt
  • ttlSeconds

The admin DM must include at minimum:

  • identifier
  • pairingCode
  • expiresAt or 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:

  • sent
  • failed

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:

  • expired
  • invalid_code
  • identifier_not_allowed
  • admin_notification_failed
  • internal_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:

  1. identifier is allowlisted
  2. identifier exists in registry
  3. client is in paired state
  4. public key matches expected key if provided
  5. signature verifies successfully
  6. proof contains correct secret
  7. abs(now - proofTimestamp) < 10
  8. nonce has not appeared in recent nonce window
  9. 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_identifier
  • not_paired
  • invalid_signature
  • invalid_secret
  • stale_timestamp
  • future_timestamp
  • nonce_collision
  • rate_limited
  • re_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_ack may 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_MESSAGE
  • UNSUPPORTED_PROTOCOL_VERSION
  • IDENTIFIER_NOT_ALLOWED
  • PAIRING_REQUIRED
  • PAIRING_EXPIRED
  • ADMIN_NOTIFICATION_FAILED
  • AUTH_FAILED
  • NONCE_COLLISION
  • RATE_LIMITED
  • RE_PAIR_REQUIRED
  • CLIENT_OFFLINE
  • INTERNAL_ERROR

6. State Machines

6.1 Client State Machine

Suggested Yonexus.Client states:

  • idle
  • connecting
  • connected
  • pairing_required
  • pairing_pending
  • paired
  • authenticating
  • authenticated
  • reconnecting
  • error

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:

  • unpaired
  • pending
  • paired
  • revoked

Per client liveness state:

  • online
  • unstable
  • offline

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:

  1. parse first delimiter section as rule_identifier
  2. if rule_identifier === builtin, route to builtin protocol handler
  3. otherwise iterate registered rules in registration order
  4. invoke the first exact match
  5. 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

  1. Exact signing algorithm: Ed25519 is a strong default candidate
  2. Secret length and generation requirements
  3. Pairing code format and length
  4. Is human code relay enough for v1, or should admin approve/deny controls be added later?
  5. Should heartbeat_ack be mandatory or optional?
  6. Should client reconnect use exponential backoff?
  7. Should duplicate active connections replace old sessions or be rejected in stricter modes?

16. Reserved Builtin Types

Reserved builtin type values:

  • hello
  • hello_ack
  • pair_request
  • pair_confirm
  • pair_success
  • pair_failed
  • auth_request
  • auth_success
  • auth_failed
  • re_pair_required
  • heartbeat
  • heartbeat_ack
  • status_update
  • disconnect_notice
  • error

These names are reserved by Yonexus and must not be repurposed by user rules.