From c5330bb9f97cf77d625b561f62cff3500ea47fbd Mon Sep 17 00:00:00 2001 From: root Date: Wed, 1 Apr 2026 01:08:12 +0000 Subject: [PATCH] add project plan --- PLAN.md | 416 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..38d0643 --- /dev/null +++ b/PLAN.md @@ -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": "", + "pairingCode": "" + } +} +``` + +### 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": "", + "nonce": "<24-char-nonce>", + "proofTimestamp": 1711886500, + "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 +``` + +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": "", + "hasSecret": true, + "hasKeyPair": true, + "publicKey": "", + "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?