define split yonexus server/client architecture

This commit is contained in:
nav
2026-03-31 23:14:05 +00:00
parent 83e02829e7
commit 1d270110b0
3 changed files with 812 additions and 605 deletions

268
FEAT.md Normal file
View File

@@ -0,0 +1,268 @@
# Yonexus — Feature Checklist
## Project Direction
Yonexus is a **two-plugin** cross-instance communication system for OpenClaw:
- `Yonexus.Server`
- `Yonexus.Client`
This repository now targets the split-plugin architecture only.
---
## 1. Yonexus.Server Features
### 1.1 Server Runtime
- WebSocket server startup on OpenClaw gateway boot
- Configurable bind host / bind port
- Optional public WebSocket URL metadata
- Connection accept / close lifecycle handling
- One active authenticated connection per client identifier
### 1.2 Client Registry
- In-memory active client session registry
- Persistent client trust registry keyed by `identifier`
- Store client public key
- Store shared secret
- Store pairing state and expiry
- Store pairing notification metadata
- Store heartbeat timestamps
- Store recent security windows (nonce / handshake attempts)
- Store liveness state (`online | unstable | offline`)
### 1.3 Allowlist and Validation
- `followerIdentifiers` allowlist enforcement
- Reject unknown client identifiers
- Reject malformed builtin payloads
- Reject unsupported protocol versions
### 1.4 Pairing Flow
- Generate pairing code
- Generate pairing expiry / TTL
- Start pending pairing session
- Never send pairing code over Yonexus WebSocket
- Send pairing code to human admin via Discord DM using `notifyBotToken`
- Include target client `identifier` in pairing DM
- Accept client-submitted pairing code via builtin protocol
- Fail pairing on invalid code / expired code / notification failure
- Issue shared secret after successful pairing
- Persist paired trust material
### 1.5 Authentication
- Verify signed proof from client
- Validate stored secret
- Validate nonce format and uniqueness
- Validate timestamp drift `< 10s`
- Track recent handshake attempts
- Enforce `>10 attempts / 10s` unsafe threshold
- Trigger re-pair on unsafe condition
- Rotate or invalidate trust state when required
### 1.6 Heartbeat and Status
- Receive heartbeat from authenticated clients
- Update `lastHeartbeatAt`
- Mark client `unstable` after 7 minutes without heartbeat
- Mark client `offline` after 11 minutes without heartbeat
- Close socket when client becomes offline
- Optional heartbeat acknowledgement
- Periodic server-side status sweep timer
### 1.7 Messaging and Dispatch
- `sendMessageToClient(identifier, message)` API
- Rewrite inbound client messages to `${rule_identifier}::${sender_identifier}::${message_content}`
- Builtin message routing
- Rule registry for application messages
- First-match rule dispatch
- Reject reserved rule `builtin`
- Reject duplicate rule registration by default
### 1.8 Operations and Safety
- Structured errors for pairing/auth/transport failures
- Redacted logging for sensitive values
- Restart-safe persistent storage for trust state
- Clear or safely rebuild rolling security windows on restart
---
## 2. Yonexus.Client Features
### 2.1 Client Runtime
- WebSocket client startup on OpenClaw gateway boot
- Connect to configured `mainHost`
- Disconnect / reconnect lifecycle handling
- Retry/backoff reconnect strategy
### 2.2 Local Identity and Trust Material
- Persist local `identifier`
- Generate public/private keypair on first run
- Persist private key locally
- Persist server-issued secret locally
- Load existing trust material on restart
### 2.3 Pairing Flow
- Send `hello` after connect
- Enter pairing mode when server requires pairing
- Receive pairing metadata without receiving code itself
- Accept human-provided pairing code on client side
- Send pairing confirmation to server
- Store secret after `pair_success`
### 2.4 Authentication
- Build proof from `secret + nonce + timestamp`
- Prefer canonical serialized payload for signing
- Sign proof with local private key
- Send builtin `auth_request`
- Handle `auth_success`
- Handle `auth_failed`
- Handle `re_pair_required`
### 2.5 Heartbeat
- Start heartbeat loop after authentication
- Send heartbeat every 5 minutes
- Stop heartbeat when disconnected / unauthenticated
- Handle optional heartbeat acknowledgement
### 2.6 Messaging and Dispatch
- `sendMessageToServer(message)` API
- Builtin message routing
- Rule registry for application messages
- First-match rule dispatch
- Reject reserved rule `builtin`
- Reject duplicate rule registration by default
---
## 3. Shared Protocol Features
### 3.1 Builtin Wire Format
- `builtin::{json}` message format
- Standard builtin envelope with `type`, `requestId`, `timestamp`, `payload`
- UTC unix seconds as protocol timestamp unit
### 3.2 Builtin Types
- `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`
### 3.3 Security Constraints
- Pairing code must be delivered out-of-band only
- Pairing code must not travel over Yonexus WebSocket
- Nonce length fixed at 24 random characters
- Nonce replay detection window
- Timestamp freshness validation
- Rate-limit / unsafe reconnect detection
### 3.4 Rule Message Format
- Application messages use `${rule_identifier}::${message_content}`
- Server rewrites inbound client messages before dispatch
- Rule matching is exact-match in v1
---
## 4. Configuration Features
### 4.1 Yonexus.Server Config
- `followerIdentifiers: string[]`
- `notifyBotToken: string`
- `adminUserId: string`
- `listenHost?: string`
- `listenPort: number`
- `publicWsUrl?: string`
### 4.2 Yonexus.Client Config
- `mainHost: string`
- `identifier: string`
- `notifyBotToken: string`
- `adminUserId: string`
### 4.3 Validation
- Fail startup on missing required fields
- Fail startup on invalid config shape
- Validate required split-plugin semantics per side
---
## 5. Docs and Deliverables
### Required Planning / Spec Docs
- `PLAN.md`
- `PROTOCOL.md`
- `FEAT.md`
### Next Implementation Deliverables
- server plugin manifest
- client plugin manifest
- README for dual-plugin architecture
- implementation task breakdown
- protocol test cases
- pairing/auth failure-path test matrix
---
## 6. Suggested Delivery Order
### Phase 0 — Planning
- [x] Rewrite project direction
- [x] Define split-plugin model
- [x] Write protocol draft
- [x] Write feature checklist
### Phase 1 — Skeleton
- [ ] Create `Yonexus.Server` plugin scaffold
- [ ] Create `Yonexus.Client` plugin scaffold
- [ ] Add config schema / manifests
- [ ] Add minimal startup hooks
### Phase 2 — Transport
- [ ] Implement WebSocket server
- [ ] Implement WebSocket client
- [ ] Implement hello / hello_ack flow
- [ ] Implement reconnect baseline
### Phase 3 — Pairing and Auth
- [ ] Implement keypair generation
- [ ] Implement pairing creation
- [ ] Implement Discord DM notification
- [ ] Implement pairing confirmation
- [ ] Implement secret issuance
- [ ] Implement signed auth proof validation
- [ ] Implement nonce and rate-limit protection
### Phase 4 — Heartbeat and Messaging
- [ ] Implement heartbeat loop
- [ ] Implement server status sweep
- [ ] Implement `sendMessageToServer`
- [ ] Implement `sendMessageToClient`
- [ ] Implement rule registry and dispatch
### Phase 5 — Hardening
- [ ] Add persistence
- [ ] Add restart recovery behavior
- [ ] Add structured errors
- [ ] Add logging/redaction
- [ ] Add integration tests
- [ ] Add operator docs
---
## 7. Non-Goals
Not in initial scope unless explicitly added later:
- direct client-to-client sockets
- multi-server topology
- distributed consensus
- queueing guarantees for offline clients
- management UI
- advanced pattern matching for rules

608
PLAN.md
View File

@@ -2,133 +2,183 @@
## 1. Goal
Yonexus is an OpenClaw plugin for **cross-instance communication** between multiple OpenClaw deployments.
Yonexus is a cross-instance communication system for OpenClaw, implemented as **two separate plugins**:
- `Yonexus.Server`
- `Yonexus.Client`
Together they provide:
- communication between multiple OpenClaw instances
- a central WebSocket hub model
- client pairing and authentication
- heartbeat-based client liveness tracking
- rule-based message dispatch
- out-of-band pairing notification to a human administrator via Discord DM
- TypeScript interfaces for higher-level plugin/runtime integrations
This project is no longer a role-switched single plugin. It is now explicitly split into two installable plugins with distinct responsibilities.
---
## 2. Plugin Split
## 2.1 Yonexus.Server
`Yonexus.Server` is installed only on the main OpenClaw instance.
Responsibilities:
- start and maintain the WebSocket server
- accept incoming client connections
- maintain the client registry
- handle pairing flow
- verify authentication proofs
- track heartbeat and connection state
- route or relay messages to connected clients
- rewrite inbound client messages before rule dispatch
- send Discord DM pairing notifications to the human administrator
## 2.2 Yonexus.Client
`Yonexus.Client` is installed on follower OpenClaw instances.
Responsibilities:
- connect to the configured Yonexus server
- generate and persist local keypair on first use
- persist local client identity and secret
- perform pairing confirmation
- perform authenticated reconnect
- send periodic heartbeats
- expose client-side messaging and rule registration APIs
---
## 3. Deployment Model
A Yonexus network contains:
- exactly one instance with role `main`
- one or more instances with role `follower`
- exactly one OpenClaw instance running `Yonexus.Server`
- one or more OpenClaw instances running `Yonexus.Client`
The plugin provides:
- a WebSocket-based communication layer between OpenClaw instances
- pairing and identity verification for followers
- persistent follower registry and trust state on the main node
- heartbeat-based follower status tracking
- a rule-based message dispatch mechanism
- TypeScript function interfaces for other plugin/runtime code
This project is **not** an organization/identity management plugin anymore. All prior goals are discarded.
Topology rules:
- `Yonexus.Server` must be reachable via fixed IP/domain or otherwise stable addressable endpoint
- `Yonexus.Client` instances do not need stable public IP/domain
- all `Yonexus.Client` instances connect outbound to the `Yonexus.Server` WebSocket endpoint
- no direct client-to-client communication is required in v1
- inter-client communication, if needed, is relayed by `Yonexus.Server`
---
## 2. High-Level Architecture
## 4. Configuration Model
### 2.1 Roles
Each OpenClaw instance running Yonexus must be configured with a `role`:
- `main`
- `follower`
Role semantics:
- `main` is the hub/server for all Yonexus communication
- `follower` connects outbound to the `main` instance
### 2.2 Network Topology
- The `main` instance must expose a fixed reachable IP/domain and run a WebSocket service.
- `follower` instances do not need fixed IP/domain.
- All `follower` instances connect to the `main` WebSocket endpoint.
- No direct follower-to-follower communication is required in v1.
- Messages between followers, if needed, are relayed by `main`.
### 2.3 Runtime Lifecycle
- On OpenClaw gateway startup:
- if role is `main`, Yonexus starts a WebSocket server through a hook
- if role is `follower`, Yonexus starts a WebSocket client and attempts to connect to `mainHost`
---
## 3. Configuration Model
## 3.1 Common Config
## 4.1 Yonexus.Server Config
```ts
role: "main" | "follower"
followerIdentifiers: string[]
notifyBotToken: string
adminUserId: string
listenHost?: string
listenPort: number
publicWsUrl?: string
```
## 3.2 Follower Config
Semantics:
- `followerIdentifiers`: allowlist of client identifiers permitted to pair/connect
- `notifyBotToken`: Discord bot token used to send pairing notifications
- `adminUserId`: Discord user id of the human administrator who receives pairing codes by DM
- `listenHost`: local bind host for WebSocket server
- `listenPort`: local bind port for WebSocket server
- `publicWsUrl`: optional canonical external URL advertised/documented for clients
Required when `role === "follower"`:
## 4.2 Yonexus.Client Config
```ts
mainHost: string
identifier: string
notifyBotToken: string
adminUserId: string
```
Semantics:
- `mainHost`: WebSocket endpoint of the main instance (`ip:port` or full URL)
- `identifier`: unique follower identity inside the Yonexus network
- `mainHost`: WebSocket endpoint of `Yonexus.Server`
- `identifier`: unique identity of this client inside the Yonexus network
- `notifyBotToken`: kept aligned with shared config expectations if future client-side notification behaviors are needed
- `adminUserId`: human administrator identity reference shared with the Yonexus system
## 3.3 Main Config
## 4.3 Validation Rules
Required when `role === "main"`:
```ts
followerIdentifiers: string[]
```
Semantics:
- `followerIdentifiers`: allowlist of follower identifiers that are permitted to pair/connect
## 3.4 Validation Rules
### Main
- must have `role = main`
### Yonexus.Server
- must provide `followerIdentifiers`
- must expose a stable/reachable IP/domain outside the plugin itself
- must provide `notifyBotToken`
- must provide `adminUserId`
- must provide `listenPort`
- must be deployed on a reachable/stable endpoint
### Follower
- must have `role = follower`
### Yonexus.Client
- must provide `mainHost`
- must provide `identifier`
- must provide `notifyBotToken`
- must provide `adminUserId`
### Shared
- invalid or missing role-specific fields must fail plugin initialization
- unknown follower identifiers must be rejected by `main`
- invalid or missing required fields must fail plugin initialization
- unknown client identifiers must be rejected by `Yonexus.Server`
---
## 4. Main Responsibilities
## 5. Runtime Lifecycle
The `main` instance must maintain a registry keyed by follower `identifier`.
## 5.1 Yonexus.Server Startup
Each follower record contains at minimum:
On OpenClaw gateway startup:
- initialize persistent client registry
- start WebSocket server
- register builtin protocol handlers
- register application rule registry
- start heartbeat/status sweep timer
## 5.2 Yonexus.Client Startup
On OpenClaw gateway startup:
- load local persisted identity, private key, and secret state
- generate keypair if absent
- connect to `mainHost`
- perform pairing or authentication flow depending on local state
- start heartbeat schedule after successful authentication
- attempt reconnect when disconnected
---
## 6. Server Registry and Persistence
`Yonexus.Server` must maintain a registry keyed by client `identifier`.
Each client record contains at minimum:
- `identifier`
- `publicKey`
- `secret`
- pairing state
- pairing expiration data
- pairing notification metadata
- connection status
- security counters/window data
- heartbeat timestamps
- last known connection/session metadata
- last known session metadata
The registry must use:
- in-memory runtime state for active operations
- persistent on-disk storage for restart survival
- in-memory runtime state for active connections and recent security windows
- persistent on-disk storage for durable trust state
### 4.1 Persistent Main Registry Model
Proposed shape:
### 6.1 Proposed Server Record Shape
```ts
interface FollowerRecord {
interface ClientRecord {
identifier: string;
publicKey?: string;
secret?: string;
pairingStatus: "unpaired" | "pending" | "paired" | "revoked";
pairingCode?: string;
pairingExpiresAt?: number;
pairingNotifiedAt?: number;
pairingNotifyStatus?: "pending" | "sent" | "failed";
status: "online" | "offline" | "unstable";
lastHeartbeatAt?: number;
lastAuthenticatedAt?: number;
@@ -142,87 +192,101 @@ interface FollowerRecord {
}
```
Notes:
- `recentNonces` stores only the recent nonce window needed for replay detection
- `recentHandshakeAttempts` stores timestamps for rate-limiting / unsafe reconnect detection
- actual field names can change during implementation, but these semantics must remain
---
## 5. Pairing and Authentication Flow
## 7. Pairing and Authentication
## 5.1 First Connection: Key Generation
## 7.1 First Connection and Key Generation
When a follower connects to main for the first time:
- the follower generates a public/private key pair locally
- the private key remains only on the follower
- the public key is sent to `main` during handshake
When a client connects to the server for the first time:
- `Yonexus.Client` generates a public/private key pair locally
- the private key remains only on the client instance
- the public key is sent to `Yonexus.Server` during handshake
If `main` sees that:
- the follower identifier is allowed, and
- no valid `secret` is currently associated with that identifier
If the server sees that:
- the client identifier is allowed, and
- there is no valid `secret` currently associated with that identifier
then `main` must enter pairing flow.
then the server must enter pairing flow.
## 5.2 Pairing Flow
## 7.2 Pairing Flow
### Step A: Pairing Request
`main` responds with a pairing request containing:
### Step A: Pairing Request Creation
`Yonexus.Server` generates:
- a random pairing string
- an expiration time
The pairing string must **not** be sent to the client over WebSocket.
Instead, `Yonexus.Server` uses `notifyBotToken` to send a Discord DM to `adminUserId` containing:
- the client `identifier`
- the generated `pairingCode`
- the expiration time
### Step B: Pairing Confirmation
If the follower sends that random pairing string back to `main` before expiration:
The client must provide the pairing code back to the server before expiration.
How the client operator obtains the pairing code is intentionally out-of-band from the Yonexus WebSocket channel. The server only trusts that the code came through some human-mediated path.
If the client sends the correct pairing code before expiration:
- pairing succeeds
### Step C: Secret Issuance
After successful pairing:
- `main` generates a random `secret`
- `main` returns that `secret` to the follower
- `main` stores follower `publicKey` + `secret`
- `follower` stores private key + secret locally
- `Yonexus.Server` generates a random `secret`
- `Yonexus.Server` returns that `secret` to the client
- `Yonexus.Server` stores client `publicKey` + `secret`
- `Yonexus.Client` stores private key + secret locally
If Discord DM delivery fails:
- pairing must not proceed
- server should mark the pairing attempt as failed or pending-error
- client must not receive a usable pairing code through the protocol channel
If pairing expires before confirmation:
- pairing fails
- follower must restart the pairing process
- the client must restart the pairing process
## 5.3 Reconnection Authentication Flow
## 7.3 Reconnect Authentication Flow
After pairing is complete, future follower authentication must use:
After pairing is complete, future client authentication must use:
- the stored `secret`
- a 24-character random nonce
- current UTC Unix timestamp
The follower builds a plaintext proof payload from:
The client builds a proof payload from:
- `secret`
- `nonce`
- `timestamp`
Concatenation order:
Logical concatenation order:
```text
secret + nonce + timestamp
```
The follower encrypts/signs this payload using its private key and sends it to `main`.
Implementation recommendation:
- use a canonical serialized object and sign its bytes rather than naive string concatenation in code
`main` verifies:
1. the follower identifier is known and paired
2. the public key matches stored state
3. decrypted/verified payload contains the correct `secret`
4. timestamp difference from current UTC time is less than 10 seconds
The client signs the proof using its private key and sends it to the server.
The server verifies:
1. identifier is known and paired
2. public key matches stored state
3. proof contains the correct `secret`
4. timestamp difference from current time is less than 10 seconds
5. nonce does not collide with the recent nonce window
6. handshake attempts in the last 10 seconds do not exceed 10
If all checks pass:
- authentication succeeds
- follower is considered authenticated for the connection/session
- the client is considered authenticated for the session
If any check fails:
- authentication fails
- main may downgrade/revoke trust state
- server may downgrade or revoke trust state
## 5.4 Unsafe Condition Handling
## 7.4 Unsafe Condition Handling
The connection is considered unsafe and must return to pairing flow if either is true:
- more than 10 handshake attempts occur within 10 seconds
@@ -230,55 +294,55 @@ The connection is considered unsafe and must return to pairing flow if either is
When unsafe:
- existing trust state must no longer be accepted for authentication
- the follower must re-pair
- main should clear or rotate the stored `secret`
- main should reset security windows as part of re-pairing
- the client must re-pair
- server should clear or rotate the stored `secret`
- server should reset security windows as part of re-pairing
---
## 6. Heartbeat and Follower Status
## 8. Heartbeat and Client Status
The main instance must track each followers liveness state:
The server must track each clients liveness state:
- `online`
- `unstable`
- `offline`
## 6.1 Heartbeat Rules
## 8.1 Heartbeat Rules
Each follower must send a heartbeat to main every 5 minutes.
Each client must send a heartbeat to the server every 5 minutes.
## 6.2 Status Transitions
## 8.2 Status Transitions
### online
A follower is `online` when:
A client is `online` when:
- it has an active authenticated WebSocket connection, and
- main has received a recent heartbeat
- the server has received a recent heartbeat
### unstable
A follower becomes `unstable` when:
A client becomes `unstable` when:
- no heartbeat has been received for 7 minutes
### offline
A follower becomes `offline` when:
A client becomes `offline` when:
- no heartbeat has been received for 11 minutes
When follower becomes `offline`:
- main must close/terminate the WebSocket connection for that follower
When a client becomes `offline`:
- the server must close/terminate the WebSocket connection for that client
## 6.3 Status Evaluation Strategy
## 8.3 Status Evaluation Strategy
Main should run a periodic status sweep timer to evaluate heartbeat freshness.
The server should run a periodic status sweep timer.
Recommended initial interval:
Recommended interval:
- every 30 to 60 seconds
---
## 7. Messaging Model
## 9. Messaging Model
Yonexus provides rule-based message dispatch over WebSocket.
## 7.1 Base Message Format
## 9.1 Base Message Format
All application messages must use the format:
@@ -286,21 +350,17 @@ All application messages must use the format:
${rule_identifier}::${message_content}
```
Constraints:
- `rule_identifier` is a string token
- `message_content` is the remainder payload as string
## 9.2 Server-Side Rewriting
## 7.2 Main-Side Rewriting
When `main` receives a message from a follower, before rule matching it must rewrite the message into:
When `Yonexus.Server` receives a message from a client, before rule matching it must rewrite the message into:
```text
${rule_identifier}::${sender_identifier}::${message_content}
```
This ensures rule processors on `main` can identify which follower sent the message.
This ensures server-side processors can identify which client sent the message.
## 7.3 Builtin Rule Namespace
## 9.3 Builtin Rule Namespace
The reserved rule identifier is:
@@ -318,250 +378,202 @@ User code must not be allowed to register handlers for `builtin`.
---
## 8. Rule Registration and Dispatch
## 10. TypeScript API Surface
## 8.1 Public API
## 10.1 Yonexus.Client API
```ts
registerRule(rule: string, processor: (message: string) => unknown): void
```
## 8.2 Rule Format
`rule` must use the format:
```text
${rule_identifier}
```
Validation rules:
- must be non-empty
- must not contain the message delimiter sequence in invalid ways
- must not equal `builtin`
## 8.3 Dispatch Rules
When Yonexus receives a message over WebSocket:
- it iterates registered rules in registration order
- it finds the first matching rule
- it invokes the corresponding processor
- only the first match is used
Clarification for implementation:
- matching should initially be exact match on `rule_identifier`
- if pattern-based matching is desired later, that must be explicitly added in a future phase
If no rule matches:
- the message is ignored or logged as unhandled, depending on runtime policy
---
## 9. TypeScript API Surface
## 9.1 sendMessageToMain
```ts
sendMessageToMain(message: string): Promise<void>
sendMessageToServer(message: string): Promise<void>
```
Rules:
- allowed only on `follower`
- calling from `main` must throw an error
- sends message to connected `main`
- sends message to connected `Yonexus.Server`
- message must already conform to `${rule_identifier}::${message_content}`
## 9.2 sendMessageToFollower
```ts
sendMessageToFollower(identifier: string, message: string): Promise<void>
```
Rules:
- allowed only on `main`
- calling from `follower` must throw an error
- target follower must be known and currently connected/authenticated
- message must already conform to `${rule_identifier}::${message_content}`
## 9.3 registerRule
```ts
registerRule(rule: string, processor: (message: string) => unknown): void
```
Rules:
- rejects `builtin`
- rejects duplicate rule registration unless an explicit override mode is added later
- processors are invoked with the final received string after any main-side rewrite
- rejects duplicate rule registration unless explicit override support is added later
## 10.2 Yonexus.Server API
```ts
sendMessageToClient(identifier: string, message: string): Promise<void>
```
Rules:
- target client must be known and currently connected/authenticated
- message must already conform to `${rule_identifier}::${message_content}`
```ts
registerRule(rule: string, processor: (message: string) => unknown): void
```
Rules:
- rejects `builtin`
- rejects duplicate rule registration unless explicit override support is added later
- processors are invoked with the final received string after any server-side rewrite
---
## 10. Hooks and Runtime Integration
## 11. Hooks and Integration
## 10.1 Main Hook
## 11.1 Yonexus.Server Hooking
The plugin must register a hook so that when OpenClaw gateway starts:
- Yonexus initializes internal state
- Yonexus starts a WebSocket server
- Yonexus begins follower status sweep tasks
`Yonexus.Server` must register hooks so that when OpenClaw gateway starts:
- the WebSocket server is started
- the server registry is initialized
- builtin protocol handling is enabled
- heartbeat sweep begins
## 10.2 Follower Runtime Behavior
## 11.2 Yonexus.Client Behavior
On startup, follower should:
- load local identity/secret/private key state
- connect to `mainHost`
- perform pairing or authentication flow
- start periodic heartbeats when authenticated
- attempt reconnect when disconnected
`Yonexus.Client` must:
- connect outbound to `mainHost`
- manage local trust material
- handle pairing/authentication transitions
- emit heartbeats after authentication
- reconnect after disconnect with retry/backoff behavior
## 10.3 Persistence Requirements
---
### Main persists:
- follower registry
- public keys
- secrets
- pairing state
- security/rate-limit windows if needed across restart, or resets them safely
## 12. Storage Strategy
### Follower persists:
## 12.1 Yonexus.Server Storage
Server persists at minimum:
- identifier
- private key
- current secret
- minimal pairing/auth state needed for reconnect
- public key
- secret
- trust state
- pairing code + expiry if pairing is pending
- pairing notification metadata
- last known status
- metadata timestamps
---
May persist or reset on restart:
- recent nonces
- recent handshake attempts
## 11. Storage Strategy
Recommended v1:
- clear rolling security windows on restart
- keep long-lived trust records
## 11.1 Main Storage
## 12.2 Yonexus.Client Storage
Main needs a local data file for follower registry persistence.
Suggested persisted sections:
- trusted followers
- pairing pending records
- last known status metadata
- security-related rolling records when persistence is desirable
## 11.2 Follower Storage
Follower needs a local secure data file for:
Client persists at minimum:
- identifier
- private key
- secret
- identifier
- optional last successful connection metadata
- optional last successful pair/auth metadata
## 11.3 Security Notes
- private key must never be sent to main
Security notes:
- private key must never be sent to the server
- secret must be treated as sensitive material
- storage format should support future encryption-at-rest, but plaintext local file may be acceptable in initial implementation if clearly documented as a limitation
- encryption-at-rest can be a future enhancement, but any plaintext local storage must be documented as a limitation if used initially
---
## 12. Error Handling
## 13. Error Handling
The plugin should define structured errors for at least:
Structured errors should exist for at least:
- invalid configuration
- invalid role usage
- unauthorized identifier
- pairing required
- pairing expired
- handshake verification failed
- pairing notification failure
- handshake verification failure
- replay/nonce collision detected
- rate limit / unsafe handshake detected
- follower not connected
- unsafe handshake rate detected
- target client not connected
- duplicate rule registration
- reserved rule registration
- malformed message
---
## 13. Initial Implementation Phases
## 14. Initial Implementation Phases
## Phase 0 — Protocol and Skeleton
- finalize config schema
- define persisted data models
- define protocol message types for builtin traffic
- define hook startup behavior
- finalize split-plugin configuration schema
- define persistent data models
- define builtin protocol messages
- define startup hooks for both plugins
- define rule registry behavior
- define Discord DM notification flow
## Phase 1 — Main/Follower Transport MVP
- main WebSocket server startup
- follower WebSocket client startup
## Phase 1 — Transport MVP
- Yonexus.Server WebSocket server startup
- Yonexus.Client WebSocket client startup
- reconnect logic
- basic builtin protocol channel
- persistent registry scaffolding
- builtin protocol channel
- persistent registry/state scaffolding
## Phase 2 — Pairing and Authentication
- follower keypair generation
- pairing request/confirmation flow
- client keypair generation
- pairing request creation
- Discord DM notification to admin user
- pairing confirmation flow
- secret issuance and persistence
- signed/encrypted handshake proof verification
- signed proof verification
- nonce/replay protection
- unsafe-condition reset to pairing
## Phase 3 — Heartbeat and Status Tracking
- follower heartbeat sender
- main heartbeat receiver
- client heartbeat sender
- server heartbeat receiver
- periodic sweep
- status transitions: online / unstable / offline
- forced disconnect on offline
## Phase 4 — Public APIs and Message Dispatch
- `sendMessageToMain`
- `sendMessageToFollower`
## Phase 4 — Public APIs and Dispatch
- `sendMessageToServer`
- `sendMessageToClient`
- `registerRule`
- first-match dispatch
- main-side sender rewrite behavior
- server-side sender rewrite behavior
## Phase 5 — Hardening and Docs
- integration tests
- failure-path coverage
- restart recovery checks
- protocol docs
- operator setup docs for main/follower deployment
- operator setup docs for server/client deployment
---
## 14. Non-Goals for Initial Version
## 15. Non-Goals for Initial Version
Not required in the first version unless explicitly added later:
- direct follower-to-follower sockets
- multi-main clustering
- direct client-to-client sockets
- multi-server clustering
- distributed consensus
- message ordering guarantees across reconnects
- end-to-end application payload encryption beyond the handshake/authentication requirements
- UI management panel
- end-to-end payload encryption beyond the pairing/authentication requirements
- management UI
---
## 15. Open Questions To Confirm Later
## 16. Open Questions To Confirm Later
These should be resolved before implementation starts:
1. Is the handshake primitive meant to be:
- asymmetric encryption with private/public key, or
- digital signature with verification by public key?
Recommended: **signature**, not “private-key encryption” wording.
2. Should `mainHost` accept only full WebSocket URLs (`ws://` / `wss://`) or also raw `ip:port` strings?
3. Should pairing require explicit operator approval on main, or is allowlist membership enough for automatic pairing?
4. On unsafe condition, should the old public key be retained or must the follower generate a brand-new keypair?
5. Should offline followers be allowed queued outbound messages from main, or should send fail immediately?
6. Are rule identifiers exact strings only, or should regex/prefix matching exist in future?
1. Exact signing algorithm:
- Ed25519 is a strong default candidate
2. Should `mainHost` accept only full WebSocket URLs or also raw `ip:port` strings?
3. Is human code relay sufficient for v1 pairing, or should admin approve/deny controls be added later?
4. On unsafe condition, should the old public key be retained or should the client generate a new keypair?
5. Should offline clients support queued outbound messages from server, or should sends fail immediately?
6. Are rule identifiers exact strings only, or should regex/prefix matching exist later?
---
## 16. Immediate Next Deliverables
## 17. Immediate Next Deliverables
After this plan, the next files to create should be:
- `FEAT.md` — feature checklist derived from this plan
- `README.md` — concise operator/developer overview
- `plugin.json` — plugin config schema and entry declaration
- protocol notes for builtin messages
- implementation task breakdown
- `README.md` — concise system overview for both plugins
- `plugin.server.json` or equivalent server plugin manifest
- `plugin.client.json` or equivalent client plugin manifest
- implementation task breakdown

File diff suppressed because it is too large Load Diff