add project plan
This commit is contained in:
416
PLAN.md
Normal file
416
PLAN.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# Yonexus.Client — Project Plan
|
||||
|
||||
## 1. Goal
|
||||
|
||||
`Yonexus.Client` is the OpenClaw plugin that acts as a client in a Yonexus network.
|
||||
|
||||
It is responsible for:
|
||||
- connecting to `Yonexus.Server`
|
||||
- managing local keypair and secret
|
||||
- completing the out-of-band pairing confirmation
|
||||
- authenticating on each reconnect
|
||||
- sending periodic heartbeats
|
||||
- exposing a TypeScript API for client-side plugins and integrations
|
||||
- sending and receiving application messages through the server
|
||||
|
||||
---
|
||||
|
||||
## 2. Configuration
|
||||
|
||||
```ts
|
||||
interface YonexusClientConfig {
|
||||
mainHost: string;
|
||||
identifier: string;
|
||||
notifyBotToken: string;
|
||||
adminUserId: string;
|
||||
}
|
||||
```
|
||||
|
||||
Field semantics:
|
||||
- `mainHost`: WebSocket endpoint of `Yonexus.Server` (`ws://host:port/path` or `wss://`)
|
||||
- `identifier`: unique identity of this client within the Yonexus network
|
||||
- `notifyBotToken`: Discord bot token (kept for potential future client-side notification needs)
|
||||
- `adminUserId`: Discord user id of the human administrator (reference/shared with Yonexus system)
|
||||
|
||||
Validation:
|
||||
- missing required fields must fail plugin initialization
|
||||
- `mainHost` must be a valid WebSocket URL
|
||||
- `identifier` must be non-empty
|
||||
|
||||
---
|
||||
|
||||
## 3. Runtime Lifecycle
|
||||
|
||||
### 3.1 Startup
|
||||
|
||||
On OpenClaw gateway startup:
|
||||
1. load and validate config
|
||||
2. load local identity, keypair, and secret from persistent storage
|
||||
3. if no keypair exists, generate one
|
||||
4. connect to `mainHost`
|
||||
5. start pairing or authentication flow depending on local state
|
||||
6. after authentication, start heartbeat schedule
|
||||
7. handle incoming messages and rule dispatch
|
||||
|
||||
### 3.2 Reconnect
|
||||
|
||||
On disconnect:
|
||||
1. stop heartbeat
|
||||
2. enter `reconnecting` state
|
||||
3. attempt reconnect with backoff
|
||||
4. after reconnect, perform authentication or pairing again
|
||||
|
||||
---
|
||||
|
||||
## 4. Local Identity and Storage
|
||||
|
||||
### 4.1 Keypair
|
||||
|
||||
`Yonexus.Client` must generate a cryptographic keypair on first run.
|
||||
|
||||
Recommended algorithm: Ed25519
|
||||
- private key: never transmitted to server
|
||||
- public key: transmitted to server during pairing and optionally during auth
|
||||
|
||||
### 4.2 Local Persistence Model
|
||||
|
||||
```ts
|
||||
interface LocalClientState {
|
||||
identifier: string;
|
||||
privateKey: string; // encoded private key, stored locally
|
||||
publicKey: string; // encoded public key
|
||||
secret?: string; // shared secret issued by server after pairing
|
||||
pairingStatus: "unpaired" | "pending" | "paired" | "revoked";
|
||||
pairedAt?: number;
|
||||
lastConnectedAt?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Security Notes
|
||||
|
||||
- private key must never leave the local instance
|
||||
- storage format should make encryption-at-rest a future option
|
||||
- initial implementation may use plaintext local file if documented as a limitation
|
||||
|
||||
---
|
||||
|
||||
## 5. Pairing Flow
|
||||
|
||||
### 5.1 Entry Condition
|
||||
|
||||
Pairing starts when:
|
||||
- the server responds to `hello` with `hello_ack(nextAction: "pair_required")`
|
||||
- OR the server sends `pair_request` builtin message
|
||||
|
||||
### 5.2 Step A — Receive Notification
|
||||
|
||||
Client receives `pair_request` from server via WebSocket.
|
||||
|
||||
This message contains:
|
||||
- `expiresAt`
|
||||
- `ttlSeconds`
|
||||
- `adminNotification`
|
||||
- `codeDelivery: "out_of_band"`
|
||||
|
||||
### 5.3 Step B — Human-Mediated Code Acquisition
|
||||
|
||||
The pairing code is delivered to a human administrator via Discord DM from the server.
|
||||
|
||||
The client operator must obtain this code from the human through some out-of-band trusted path.
|
||||
|
||||
This is intentionally not automated — a human must relay the code.
|
||||
|
||||
### 5.4 Step C — Submit Confirmation
|
||||
|
||||
Client sends `pair_confirm` builtin message to server:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "pair_confirm",
|
||||
"payload": {
|
||||
"identifier": "<this-client-identifier>",
|
||||
"pairingCode": "<human-provided-code>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.5 Step D — Receive Secret
|
||||
|
||||
On success, server sends `pair_success` containing:
|
||||
- `identifier`
|
||||
- `secret`
|
||||
- `pairedAt`
|
||||
|
||||
Client must:
|
||||
- store `secret` locally
|
||||
- update `pairingStatus` to `paired`
|
||||
- store `pairedAt`
|
||||
|
||||
### 5.6 Failure Handling
|
||||
|
||||
If server responds with `pair_failed`:
|
||||
- log the reason
|
||||
- return to `pairing_required` state
|
||||
- wait for human to provide a new code and retry
|
||||
|
||||
---
|
||||
|
||||
## 6. Authentication
|
||||
|
||||
### 6.1 Entry Condition
|
||||
|
||||
Authentication starts when:
|
||||
- client connects and already has a stored `secret`
|
||||
- server responds to `hello` with `hello_ack(nextAction: "auth_required")`
|
||||
|
||||
### 6.2 Proof Construction
|
||||
|
||||
Client constructs a proof payload from:
|
||||
- `secret`: stored shared secret
|
||||
- `nonce`: exactly 24 random characters, generated fresh each attempt
|
||||
- `timestamp`: current UTC unix seconds
|
||||
|
||||
Logical concatenation:
|
||||
|
||||
```text
|
||||
secret + nonce + timestamp
|
||||
```
|
||||
|
||||
Implementation recommendation:
|
||||
- use a canonical serialized object, then sign its canonical bytes
|
||||
- avoid naive string concatenation in code
|
||||
|
||||
Example canonical JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"secret": "...",
|
||||
"nonce": "RANDOM24CHARACTERSTRINGX",
|
||||
"timestamp": 1711886500
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Signing
|
||||
|
||||
Client signs the canonical proof bytes using its private key.
|
||||
|
||||
### 6.4 Sending Auth Request
|
||||
|
||||
Client sends `auth_request` builtin message:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "auth_request",
|
||||
"payload": {
|
||||
"identifier": "<this-client-identifier>",
|
||||
"nonce": "<24-char-nonce>",
|
||||
"proofTimestamp": 1711886500,
|
||||
"signature": "<base64-signature>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.5 Response Handling
|
||||
|
||||
On `auth_success`:
|
||||
- record `lastAuthenticatedAt`
|
||||
- transition to `authenticated` state
|
||||
- start heartbeat loop
|
||||
|
||||
On `auth_failed`:
|
||||
- log the reason
|
||||
- handle according to reason:
|
||||
- `stale_timestamp` / `future_timestamp`: retry with fresh timestamp
|
||||
- `nonce_collision`: retry with fresh nonce
|
||||
- others: escalate to re-pairing if needed
|
||||
|
||||
On `re_pair_required`:
|
||||
- clear stored `secret` and public key trust
|
||||
- transition to `pairing_required`
|
||||
- begin pairing flow again
|
||||
|
||||
---
|
||||
|
||||
## 7. Heartbeat
|
||||
|
||||
### 7.1 Sending
|
||||
|
||||
After successful authentication, client starts a heartbeat loop.
|
||||
|
||||
Interval: every 5 minutes (300 seconds)
|
||||
|
||||
Message: `heartbeat` builtin message with `status: "alive"`.
|
||||
|
||||
### 7.2 Handling heartbeat_ack
|
||||
|
||||
If server responds with `heartbeat_ack`, client may:
|
||||
- update local connection metadata
|
||||
- optionally log
|
||||
|
||||
`heartbeat_ack` is optional on the server side; client should not fail if it is absent.
|
||||
|
||||
### 7.3 On Disconnect
|
||||
|
||||
When WebSocket connection is lost:
|
||||
- stop heartbeat loop immediately
|
||||
- enter `reconnecting` state
|
||||
- attempt reconnect with backoff
|
||||
|
||||
---
|
||||
|
||||
## 8. Messaging and Rule Dispatch
|
||||
|
||||
### 8.1 Outbound: sendMessageToServer
|
||||
|
||||
```ts
|
||||
async function sendMessageToServer(message: string): Promise<void>
|
||||
```
|
||||
|
||||
Constraints:
|
||||
- client must be authenticated / connected
|
||||
- message must already conform to `${rule_identifier}::${message_content}`
|
||||
|
||||
### 8.2 Inbound: Rule Dispatch
|
||||
|
||||
Client receives messages from server formatted as:
|
||||
|
||||
```
|
||||
${rule_identifier}::${message_content}
|
||||
```
|
||||
|
||||
Client maintains its own rule registry (separate from server's).
|
||||
|
||||
Dispatch algorithm:
|
||||
1. parse first `::` segment as `rule_identifier`
|
||||
2. if `rule_identifier === builtin`, route to builtin protocol handler
|
||||
3. iterate registered rules in registration order
|
||||
4. invoke first exact match
|
||||
5. if no match, ignore or log as unhandled
|
||||
|
||||
### 8.3 registerRule API
|
||||
|
||||
```ts
|
||||
function registerRule(rule: string, processor: (message: string) => unknown): void
|
||||
```
|
||||
|
||||
Validation:
|
||||
- must reject `builtin`
|
||||
- must reject duplicate rule unless explicit override mode is added later
|
||||
|
||||
---
|
||||
|
||||
## 9. WebSocket Client
|
||||
|
||||
### 9.1 Connection
|
||||
|
||||
Client connects to `mainHost` on startup and on each reconnect.
|
||||
|
||||
### 9.2 First Message: hello
|
||||
|
||||
Immediately after connection opens, client sends `hello`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"payload": {
|
||||
"identifier": "<this-client-identifier>",
|
||||
"hasSecret": true,
|
||||
"hasKeyPair": true,
|
||||
"publicKey": "<current-public-key>",
|
||||
"protocolVersion": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
- `hasSecret`: whether a secret is stored locally
|
||||
- `hasKeyPair`: always `true` after first key generation
|
||||
- `publicKey`: current public key (required after keypair generation)
|
||||
|
||||
### 9.3 Reconnect Backoff Strategy
|
||||
|
||||
Recommended initial strategy:
|
||||
- first retry: 1 second
|
||||
- subsequent retries: multiply by 2, cap at 60 seconds
|
||||
- jitter: add random 0–1s to avoid thundering herd
|
||||
|
||||
---
|
||||
|
||||
## 10. Error Handling
|
||||
|
||||
Structured errors required for at minimum:
|
||||
- `INVALID_CONFIG` — missing required config fields
|
||||
- `CONNECTION_FAILED` — cannot connect to `mainHost`
|
||||
- `PAIRING_FAILED` — server rejected pairing
|
||||
- `AUTH_FAILED` — server rejected auth proof
|
||||
- `RE_PAIR_REQUIRED` — server demands re-pairing
|
||||
- `RULE_ALREADY_REGISTERED` — duplicate rule registration
|
||||
- `RESERVED_RULE` — attempted to register `builtin`
|
||||
- `MALFORMED_MESSAGE` — malformed builtin/application message received
|
||||
- `NOT_AUTHENTICATED` — attempted to send before auth
|
||||
|
||||
---
|
||||
|
||||
## 11. Implementation Phases
|
||||
|
||||
### Phase 0 — Skeleton
|
||||
- plugin manifest and entry point
|
||||
- config loading and validation
|
||||
- basic OpenClaw hook registration
|
||||
- minimal logging/error scaffolding
|
||||
|
||||
### Phase 1 — WebSocket Client
|
||||
- WebSocket client connection
|
||||
- hello message
|
||||
- reconnect with backoff
|
||||
- connection close lifecycle
|
||||
|
||||
### Phase 2 — Local Identity
|
||||
- keypair generation (Ed25519)
|
||||
- local state persistence
|
||||
- state load on startup
|
||||
|
||||
### Phase 3 — Pairing
|
||||
- handle `pair_request`
|
||||
- accept human-provided pairing code
|
||||
- submit `pair_confirm`
|
||||
- store `secret` on `pair_success`
|
||||
- handle `pair_failed`
|
||||
|
||||
### Phase 4 — Authentication
|
||||
- proof construction
|
||||
- signing
|
||||
- send `auth_request`
|
||||
- handle `auth_success`
|
||||
- handle `auth_failed`
|
||||
- handle `re_pair_required`
|
||||
|
||||
### Phase 5 — Heartbeat
|
||||
- heartbeat loop (5 min interval)
|
||||
- heartbeat message construction
|
||||
- handle `heartbeat_ack`
|
||||
- stop loop on disconnect
|
||||
|
||||
### Phase 6 — Rule Dispatch and APIs
|
||||
- rule registry
|
||||
- inbound message dispatch
|
||||
- `registerRule` API
|
||||
- `sendMessageToServer` API
|
||||
|
||||
### Phase 7 — Hardening
|
||||
- structured error definitions
|
||||
- redacted logging for sensitive values
|
||||
- integration test coverage
|
||||
- failure-path coverage
|
||||
|
||||
---
|
||||
|
||||
## 12. Open Questions for Yonexus.Client
|
||||
|
||||
These should be resolved before or during implementation:
|
||||
|
||||
1. What cryptographic library will be used for Ed25519 keypair and signing?
|
||||
2. Should `notifyBotToken` and `adminUserId` be retained in client config, or are they purely server-side concerns?
|
||||
3. Should reconnect backoff be configurable or fixed?
|
||||
4. Should the client maintain a local log of recent sent/received messages for debugging?
|
||||
5. Should `sendMessageToServer` throw if called before authentication, or queue the message?
|
||||
Reference in New Issue
Block a user