867 lines
17 KiB
Markdown
867 lines
17 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
builtin::${json_payload}
|
|
```
|
|
|
|
`builtin` is reserved and must not be registered by user code.
|
|
|
|
## 3.2 Application Rule Messages
|
|
|
|
Application messages use:
|
|
|
|
```text
|
|
${rule_identifier}::${message_content}
|
|
```
|
|
|
|
When `Yonexus.Server` receives a rule message from a client, it must rewrite it before dispatch to:
|
|
|
|
```text
|
|
${rule_identifier}::${sender_identifier}::${message_content}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Builtin Envelope
|
|
|
|
Builtin wire format:
|
|
|
|
```text
|
|
builtin::{JSON}
|
|
```
|
|
|
|
Canonical envelope:
|
|
|
|
```ts
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
secret + nonce + timestamp
|
|
```
|
|
|
|
Implementation recommendation:
|
|
- use canonical serialized object bytes for signing
|
|
|
|
Recommended logical form:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
idle
|
|
-> connecting
|
|
-> connected
|
|
-> (pairing_required | authenticating)
|
|
|
|
pairing_required
|
|
-> pairing_pending
|
|
-> paired
|
|
-> authenticating
|
|
-> authenticated
|
|
|
|
authenticated
|
|
-> reconnecting
|
|
-> connecting
|
|
```
|
|
|
|
On `re_pair_required`:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
${rule_identifier}::${message_content}
|
|
```
|
|
|
|
Client to server example:
|
|
|
|
```text
|
|
chat_sync::{"conversationId":"abc","body":"hello"}
|
|
```
|
|
|
|
Server rewrites before matching:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
1
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Canonical JSON Shapes
|
|
|
|
```ts
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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.
|