define split yonexus server/client architecture
This commit is contained in:
268
FEAT.md
Normal file
268
FEAT.md
Normal 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
|
||||
606
PLAN.md
606
PLAN.md
@@ -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 follower’s liveness state:
|
||||
The server must track each client’s 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
|
||||
- `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
|
||||
539
PROTOCOL.md
539
PROTOCOL.md
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user