Updates
This commit is contained in:
+104
-131
@@ -1,158 +1,131 @@
|
||||
# Protocol
|
||||
|
||||
## Constants
|
||||
Wire contract for PearData P2P RPC. Source of truth: `shared/protocol.js`, `shared/schema.js`, `shared/metrics.js`.
|
||||
|
||||
## Identity
|
||||
|
||||
| Constant | Value |
|
||||
|----------|--------|
|
||||
| `PROTOCOL` | `peardata/rpc` |
|
||||
| `PROTOCOL_VERSION` | `1` |
|
||||
| `APP_NAME` | `peardata` |
|
||||
| `APP_VERSION` | `0.1.0` (keep in sync with package where useful) |
|
||||
| Encoding | compact-encoding JSON (`shared/encodings.js`) |
|
||||
| Schema | lightweight validators (`shared/schema.js`) `SCHEMA_VERSION=1` |
|
||||
|----------|-------|
|
||||
| Protocol id | `peardata/rpc` |
|
||||
| Protocol version | `1` |
|
||||
| Invite prefix | `pd1.` |
|
||||
| Default role | `viewer` |
|
||||
|
||||
Bump `PROTOCOL_VERSION` on breaking request/response shapes. Additive methods may land without a bump if clients ignore unknown methods.
|
||||
|
||||
After `npm run rename`, `PROTOCOL` becomes `<slug>/rpc` and invite prefix is regenerated.
|
||||
|
||||
## Transport
|
||||
|
||||
1. Client opens HyperDHT secret stream to server public key (Noise, mutual key auth).
|
||||
2. `ProtomuxRPC` is attached with `protocol: PROTOCOL` and shared encodings.
|
||||
3. Client calls `handshake` before other RPCs (connection helper does this automatically).
|
||||
4. Server may `push` events on named channels.
|
||||
Bump `PROTOCOL_VERSION` on breaking argument/result shapes.
|
||||
|
||||
## Roles
|
||||
|
||||
| Role | Intent |
|
||||
|------|--------|
|
||||
| `viewer` | Read-only |
|
||||
| `operator` | Mutate domain data |
|
||||
| `admin` | Invite mint, clear, revoke, config |
|
||||
| Role | Rank | Typical access |
|
||||
|------|------|----------------|
|
||||
| `viewer` | 1 | Read metrics, subscribe, list alerts |
|
||||
| `operator` | 2 | Ack/silence alerts, run jobs, set alert config |
|
||||
| `admin` | 3 | Mint invites, revoke peers, export snapshot |
|
||||
|
||||
Hierarchy: `admin > operator > viewer` (`roleAllows`).
|
||||
|
||||
Unknown methods default to **admin** required in `assertAllowed` if missing from `MethodRoles` — always register new methods.
|
||||
Auth modes at handshake: public key (viewer), capability token / `pd1.` invite, admin seed proof, allowlist.
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Min role | Request args | Response (summary) |
|
||||
|--------|----------|--------------|--------------------|
|
||||
| `handshake` | viewer | `clientName`, `clientVersion`, optional `capability`, `adminProof` | role, auth, versions, features |
|
||||
| `ping` | viewer | `{}` | `{ ok, pong, peerId }` |
|
||||
| `getServerInfo` | viewer | `{}` | app, versions, host, peer count |
|
||||
| `getAuthStatus` | viewer | `{}` | peerId, role, authMode, displayName |
|
||||
| `listMessages` | viewer | `{ limit? }` | `{ messages: [...] }` |
|
||||
| `getPresence` | viewer | `{}` | `{ peers: [...] }` |
|
||||
| `postMessage` | operator | `{ text }` (1–2000 chars) | created message |
|
||||
| `setDisplayName` | viewer | `{ name }` (1–40 chars) | updated label |
|
||||
| `clearMessages` | admin | `{}` | success + system push |
|
||||
| `mintInvite` | admin | `{ role?, ttlMs?, peerId?, alias? }` | `invite` (`pd1.…`), jti, exp |
|
||||
| `listPeers` | admin | `{}` | live + policy peers |
|
||||
| `revokePeer` | admin | `{ peerId }` (64 hex) | success; target dropped |
|
||||
### Session
|
||||
|
||||
### Handshake request
|
||||
| Method | Role | Notes |
|
||||
|--------|------|-------|
|
||||
| `handshake` | viewer | Negotiate version + elevate role |
|
||||
| `ping` | viewer | Hot |
|
||||
| `getServerInfo` | viewer | Agent metadata |
|
||||
| `getAuthStatus` | viewer | Peer role / auth mode |
|
||||
| `setDisplayName` | viewer | Label for audit / UI |
|
||||
|
||||
```json
|
||||
{
|
||||
"clientName": "peardata",
|
||||
"clientVersion": "0.1.0",
|
||||
"capability": "<optional HMAC token>",
|
||||
"adminProof": { "nonce": "<hex>", "mac": "<hex>" }
|
||||
}
|
||||
```
|
||||
### Node
|
||||
|
||||
### Handshake response
|
||||
| Method | Role | Result |
|
||||
|--------|------|--------|
|
||||
| `getNodeInfo` | viewer | Hostname, CPUs, charts, sample interval |
|
||||
| `getHealth` | viewer | `{ status, score, checks }` |
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"protocol": "peardata/rpc",
|
||||
"protocolVersion": 1,
|
||||
"schemaVersion": 1,
|
||||
"role": "operator",
|
||||
"peerId": "<64 hex>",
|
||||
"serverTime": 0,
|
||||
"auth": { "mode": "capability", "role": "operator" },
|
||||
"features": { "hmacAuth": true, "invites": true, "room": true }
|
||||
}
|
||||
```
|
||||
### Metrics discovery & query
|
||||
|
||||
### Auth modes (`auth.mode` / `session.authMode`)
|
||||
| Method | Role | Args | Result |
|
||||
|--------|------|------|--------|
|
||||
| `listContexts` | viewer | — | Context catalog |
|
||||
| `getContext` | viewer | `{ id }` | Charts in context |
|
||||
| `listCharts` | viewer | — | Netdata-ish chart map |
|
||||
| `getChart` | viewer | `{ id }` | Chart summary |
|
||||
| `queryData` | viewer | `{ chart, after, before, points, group, tier }` | Time series |
|
||||
| `getAllMetrics` | viewer | `{ format: json\|prometheus\|shell }` | Latest export |
|
||||
|
||||
| Mode | How obtained |
|
||||
|------|----------------|
|
||||
| `viewer` | Default after connect with no grant |
|
||||
| `capability` | Valid capability / invite |
|
||||
| `seed` | Valid admin proof from `SERVER_SEED` |
|
||||
| `allowlist` / registered | Elevated via policy / admin keys (implementation in ACL + policy) |
|
||||
`after` / `before`: absolute unix seconds, or relative (negative = relative to `before`/`now`), Netdata-style.
|
||||
|
||||
Exact labels depend on handshake path; UI shows `authMode` from `getAuthStatus`.
|
||||
### Live subscriptions
|
||||
|
||||
## Pushes (server → client events)
|
||||
| Method | Role | Args |
|
||||
|--------|------|------|
|
||||
| `subscribeMetrics` | viewer | `{ charts: string[]\|['*'], intervalMs }` |
|
||||
| `unsubscribeMetrics` | viewer | — |
|
||||
| `subscribeAnomalies` | viewer | — |
|
||||
| `unsubscribeAnomalies` | viewer | — |
|
||||
|
||||
| Push | Payload |
|
||||
### Anomalies & alerts
|
||||
|
||||
| Method | Role | Notes |
|
||||
|--------|------|-------|
|
||||
| `listAnomalies` | viewer | Recent events |
|
||||
| `listAlerts` / `getAlert` | viewer | State + config |
|
||||
| `setAlertConfig` | operator | Upsert threshold |
|
||||
| `ackAlert` | operator | Clear until next breach |
|
||||
| `silenceAlert` | operator | Disable temporarily |
|
||||
|
||||
### Jobs
|
||||
|
||||
| Method | Role | Known jobs |
|
||||
|--------|------|------------|
|
||||
| `listJobs` | viewer | — |
|
||||
| `runJob` | operator | `collectOnce`, `snapshot`, `gcBuffers` |
|
||||
| `cancelJob` | operator | By job id |
|
||||
|
||||
### Admin
|
||||
|
||||
| Method | Role |
|
||||
|--------|------|
|
||||
| `mintInvite` | admin |
|
||||
| `listPeers` | admin |
|
||||
| `revokePeer` | admin |
|
||||
| `exportSnapshot` | admin |
|
||||
|
||||
## Pushes (server → client)
|
||||
|
||||
| Event | Payload |
|
||||
|-------|---------|
|
||||
| `push:metrics` | `{ samples: MetricSample[] }` |
|
||||
| `push:anomaly` | `AnomalyEvent` |
|
||||
| `push:alert` | Alert transition |
|
||||
| `push:health` | `HealthSnapshot` |
|
||||
| `push:job` | `JobRecord` |
|
||||
| `push:system` | Generic notices |
|
||||
|
||||
## Errors
|
||||
|
||||
Handlers throw `Error` with `.code`:
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `push:message` | `{ id, peerId, displayName, text, ts }` |
|
||||
| `push:presence` | `{ peers: [...] }` |
|
||||
| `push:system` | `{ type, ... }` e.g. `{ type: "cleared" }` |
|
||||
|
||||
Registered via `rpc.event` / `session.push`. Client `connection.js` binds all `Pushes` values.
|
||||
|
||||
## Invites
|
||||
|
||||
Envelope: `pd1.` + base64url(JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"v": 1,
|
||||
"publicKeyHex": "<server>",
|
||||
"capability": "<token>",
|
||||
"role": "operator",
|
||||
"jti": "...",
|
||||
"expiresAt": null
|
||||
}
|
||||
```
|
||||
|
||||
Capability token: `base64url(payload).base64url(HMAC-SHA256)`.
|
||||
|
||||
Payload fields (canonical order for MAC): `v`, `role`, `peerId`, `exp`, `jti`, `iat`.
|
||||
|
||||
### Connection input classification
|
||||
|
||||
`classifyConnectionInput(string)` accepts:
|
||||
|
||||
| Input | Kind |
|
||||
|-------|------|
|
||||
| 64 hex chars | `publicKey` |
|
||||
| `pd1.…` | `invite` (extracts key + capability) |
|
||||
| other | error |
|
||||
|
||||
## Error codes
|
||||
|
||||
| Code | When |
|
||||
|------|------|
|
||||
| `RATE_LIMIT_EXCEEDED` | Peer over RPM budget |
|
||||
| `PERMISSION_DENIED` | Role too low for method |
|
||||
| `INVALID_ARGS` | Schema validation failed |
|
||||
| `CONNECTION_TIMEOUT` | Client dial timeout |
|
||||
| `RPC_ERROR` | Generic client-normalized failure |
|
||||
| `UNKNOWN_ERROR` | Unclassified server handler error |
|
||||
|
||||
Clients should read `error.code` when present (`client/errors.js` preserves codes).
|
||||
| `PERMISSION_DENIED` | Role too low |
|
||||
| `RATE_LIMIT_EXCEEDED` | RPM exceeded |
|
||||
| `INVALID_ARGS` | Schema failure |
|
||||
| `NOT_CONNECTED` | Client-side |
|
||||
| `CAPABILITY_*` | Invite/token issues |
|
||||
|
||||
## Versioning policy
|
||||
|
||||
1. Document every method in this file.
|
||||
2. Add `MethodRoles` entry before implementing handlers.
|
||||
3. Add `validateMethodArgs` case for mutating methods.
|
||||
4. Add brittle tests for pure helpers; integration test for critical paths.
|
||||
5. Bump `PROTOCOL_VERSION` when existing response shapes break.
|
||||
6. Bump `SCHEMA_VERSION` when validation semantics change meaningfully.
|
||||
1. Additive methods/fields: no version bump if old clients ignore unknowns.
|
||||
2. Rename/remove/change meaning: bump `PROTOCOL_VERSION`; reject or compat-negotiate in handshake.
|
||||
3. REST API versions (`v1`/`v2`/`v3`) are independent of RPC version but share the store.
|
||||
|
||||
## Related
|
||||
## Example session
|
||||
|
||||
- [ARCHITECTURE.md](./ARCHITECTURE.md)
|
||||
- [SECURITY.md](./SECURITY.md)
|
||||
- [EXTENDING.md](./EXTENDING.md)
|
||||
- [TESTING.md](./TESTING.md)
|
||||
```text
|
||||
client → handshake { clientName, clientVersion, capability? }
|
||||
server → { role, protocolVersion, auth }
|
||||
client → subscribeMetrics { charts: ['*'], intervalMs: 1000 }
|
||||
server → push:metrics { samples: [...] } # ~1 Hz
|
||||
client → queryData { chart: 'system.cpu', after: -300, points: 300 }
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user