Skip to content

Commit 5a7cdfd

Browse files
committed
docs: add node-protocol and exec-capability design notes
Specify the node-protocol wire contract for peer interoperability (identity, handshake, capability negotiation, signed tokens, frame categories, versioning) so an independent implementation can be a first-class mesh peer. Add a design note for an exec capability (terminals and processes) that reuses the existing identity, grant-token, transport, and audit spine and places exec in the dangerous-verb tier. Both are design-only; the oplog and exec frames are draft co-design items.
1 parent c466765 commit 5a7cdfd

2 files changed

Lines changed: 226 additions & 0 deletions

File tree

docs/exec-capability.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Exec capability — terminals and processes
2+
3+
> Status: **design, not implemented.** This note proposes a second capability
4+
> domain for Cascade — brokered remote compute — alongside the existing storage
5+
> (file) domain. It reuses Cascade's identity, capability-token, transport, and
6+
> audit spine; the new surface is a node-side exec subsystem and a set of
7+
> grant-gated verbs.
8+
9+
## What and why
10+
11+
Cascade already crossed the line from pure file sync to remote node
12+
administration at v10: the management plane can restart the daemon, push config,
13+
and add backends on another node over the authenticated peer connection. The
14+
exec capability extends that remote-administration surface to **spawning and
15+
controlling terminals and processes**: an authorised peer can open a PTY or run
16+
a process on a node and stream its output back, gated by the same token model and
17+
written to the same audit log.
18+
19+
This makes a Cascade node independently more useful — a grant-gated remote shell
20+
across your own devices is valuable on its own — and gives any peer
21+
implementation (see [`node-protocol.md`](node-protocol.md)) a uniform,
22+
mesh-native way to broker compute without inventing a parallel auth model.
23+
24+
## Placement
25+
26+
A new workspace crate, `crates/exec`, owns the node-side compute:
27+
28+
- **PTYs** via [`portable-pty`](https://crates.io/crates/portable-pty) (the
29+
wezterm crate) — a native PTY without a Node/node-gyp toolchain, fitting the
30+
all-Rust workspace.
31+
- **Processes** via `tokio::process` for headless child processes (no TTY).
32+
33+
The engine drives it through the management dispatch; `crates/exec` never reaches
34+
the network or the grant store directly — it is a capability provider the
35+
authorised management path calls into, mirroring how backends are self-contained
36+
behind the `Backend` trait.
37+
38+
## Control plane — new management verbs
39+
40+
Extend the `ManageRequest` / `ManageResponse` command set and the grant verb
41+
vocabulary:
42+
43+
- `pty.spawn` (shell, cwd, env, size) → session id
44+
- `pty.write` (session, bytes)
45+
- `pty.resize` (session, cols, rows)
46+
- `pty.kill` (session, signal)
47+
- `proc.spawn` (argv, cwd, env) → session id
48+
- `proc.signal` (session, signal)
49+
- `proc.kill` (session)
50+
51+
These dispatch into `crates/exec` through the existing per-command authorisation
52+
and append-only audit, exactly as the storage and lifecycle verbs do.
53+
54+
## Data plane — streams over the transport
55+
56+
Control travels as management frames; **live `stdin` / `stdout` / `stderr`
57+
travel as stream channels over the authenticated peer connection (and the relay
58+
for WAN)** — never through the content-addressed block store. A running stream is
59+
ephemeral and mutable; the block store is for immutable, addressable content, and
60+
forcing live output through it would be a category error. Streams carry
61+
backpressure so a slow consumer throttles the producer rather than unbounded
62+
buffering on the node.
63+
64+
## Authorisation — exec is a dangerous verb
65+
66+
This is the part that must be rigorous, because it changes Cascade's blast radius
67+
from "wrong file synced" to **remote code execution gated by a grant**. Exec
68+
verbs join the dangerous tier alongside backend, lifecycle, and grant
69+
administration, with the same discipline Cascade already applies there:
70+
71+
- **Never satisfied by a node-wide grant.** Exec authority is granted to an
72+
explicit scope, deliberately, never blanket.
73+
- **Bounded delegation.** A delegated exec token can only narrow authority and
74+
its expiry is clamped to its parent's, like every other token.
75+
- **Audited.** Every spawn, signal, and kill is written to the append-only audit
76+
log with the bearer, scope, and command.
77+
- **Revocable at the next check.** A revoked token fails the verifier on the next
78+
command, cutting access promptly.
79+
80+
The exec authorisation path should be treated as the most rigorously designed and
81+
tested code in this feature — a flaw there is the difference between a capability
82+
and an exploit.
83+
84+
## Representation and discovery
85+
86+
Running sessions are tracked in node state (a new `exec_sessions` table, or an
87+
extension of the management state), so a node can enumerate its live sessions for
88+
an authorised peer. The node advertises the `exec` capability in the protocol
89+
handshake ([`node-protocol.md`](node-protocol.md)) so peers know it can broker
90+
compute; a node that omits it simply offers files and management as before.
91+
92+
A later, optional refinement: surface live sessions through a synthetic
93+
`/proc`-style view in the existing presenters, so a terminal or process appears
94+
as a node in the VFS. That is sugar over the mechanism above, not a prerequisite.
95+
96+
## Status
97+
98+
Design only. Nothing here is built. The control verbs and stream framing are the
99+
`exec` half of the draft node protocol and are co-designed with the first peer
100+
implementation before either side writes code.

docs/node-protocol.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Cascade node protocol
2+
3+
> Status: **design**. This document specifies the wire contract by which nodes
4+
> interoperate in a Cascade mesh, so that an independent implementation — in any
5+
> language — can be a first-class peer, not a client of a Cascade binary. The
6+
> protocol is the contract; no single codebase is. Parts marked **draft
7+
> (co-design)** are not yet implemented and are being designed jointly with the
8+
> first external implementation.
9+
10+
## Why a specified protocol
11+
12+
Cascade's mesh is valuable beyond the Cascade binary: its device identity,
13+
authenticated transport, capability-token authorisation, content exchange, and
14+
(forthcoming) exec and op-log frames are exactly what another tool would
15+
otherwise reimplement to mesh with it. Rather than have such tools depend on the
16+
Cascade binary at runtime, they should be able to implement this protocol and
17+
join as peers.
18+
19+
The hazard of two implementations is drift: an under-specified protocol degrades
20+
"interoperable" to "interoperable in theory" the first time one side relies on
21+
behaviour the other never promised. So three rules are load-bearing:
22+
23+
1. **The wire is specified here, not by reference to the Rust code.** Behaviour
24+
that matters for interop is written down; the implementation conforms to the
25+
document, not the reverse.
26+
2. **Versioned, with a capability-negotiation handshake.** Nodes advertise a
27+
protocol version and the capability domains they support, and degrade
28+
gracefully — heterogeneous peers (one with capabilities the other lacks) are
29+
the normal case, not an error.
30+
3. **Conformance vectors are the forcing function.** A shared, language-neutral
31+
set of test vectors — handshake transcripts, token-verification cases, frame
32+
encode/decode fixtures — is executed by every implementation's CI. Two
33+
implementations that both pass the vectors stay compatible; documentation
34+
alone does not guarantee that.
35+
36+
## Identity
37+
38+
A node's identity is its **device ID**: the base32 encoding of the SHA-256 of its
39+
self-generated TLS certificate. All peer connections are TLS-encrypted and
40+
authenticated by device ID — a peer is exactly the holder of the private key for
41+
the certificate whose hash is its ID. This is the existing Cascade scheme; an
42+
interoperating implementation adopts it verbatim so identities are mutually
43+
verifiable.
44+
45+
## Transport and handshake
46+
47+
Peer connections are mutually-authenticated TLS. Reachability is governed by a
48+
single `DiscoveryReach` posture (`lan-only` / `private` / `public`); WAN peers
49+
traverse NAT via the opaque byte-pipe relay (HMAC-gated, payload-blind) and the
50+
rendezvous-by-presence path under the `public` posture. On connect, peers
51+
exchange a **handshake** carrying:
52+
53+
- the protocol **version**,
54+
- the set of **capability domains** the node supports (see below),
55+
- identity proof (implicit in the TLS layer).
56+
57+
A peer must not send frames for a capability domain the other did not advertise,
58+
and must reject (or quarantine) frames it does not understand rather than guess.
59+
60+
## Capability domains
61+
62+
A node advertises which of these it implements; the mesh is heterogeneous by
63+
design.
64+
65+
| Domain | Frames | Status |
66+
| --------------- | --------------------------------------- | ----------------- |
67+
| `content` | block exchange (BEP-derived) | implemented |
68+
| `management` | `ManageRequest` / `ManageResponse` | implemented |
69+
| `exec` | process/PTY control + streams | draft (co-design) |
70+
| `oplog` | per-peer append-only log sync | draft (co-design) |
71+
72+
A node that implements only `content` + `management` is a normal Cascade file
73+
node; one that adds `exec` can broker terminals and processes; one that adds
74+
`oplog` participates in a replicated operation log. None is required of all
75+
peers.
76+
77+
## Authorisation
78+
79+
Authority is a **capability grant** — a verb over a scope — held on a node and
80+
carried, between nodes, as a **signed capability token**. Tokens are signed by
81+
the issuing node's device key; a bearer presents one and the verifier checks
82+
signature, expiry, and revocation before authorising the carried grant through
83+
the same path an on-node grant takes. Delegation forms **bounded chains**: each
84+
hop can only narrow authority, never widen it, and a token's expiry is clamped to
85+
its parent's. Every authorised command is written to an append-only audit log.
86+
87+
The dangerous verb classes — backend administration, node lifecycle, grant
88+
administration, and (when implemented) **exec** — are never satisfied by a
89+
node-wide grant; they require an explicit scope and a deliberate grant. See
90+
[`exec-capability.md`](exec-capability.md) for why exec sits in this tier.
91+
92+
## Frame categories
93+
94+
- **`content` — block exchange.** Content-addressed, immutable blocks. The
95+
substrate for file bytes; adaptive block sizes; last-write-wins per block for
96+
P2P-only folders. (Implemented.)
97+
- **`management` — control.** `ManageRequest` / `ManageResponse`: a verb command
98+
set (status, pin, cache, config, policy, backend, lifecycle, grant
99+
administration) dispatched into the same handlers the local CLI drives, gated
100+
by per-command authorisation and audited. (Implemented.)
101+
- **`oplog` — per-peer log sync (draft).** An operation log replicated as
102+
content: each peer's log is a **single-writer, append-only file**, so two
103+
peers' logs can never block-conflict, and distributing them is replication —
104+
consumers merge all peers' logs by a deterministic reduce. The unit is a
105+
per-peer log file, never one shared log file (whose per-block LWW would corrupt
106+
concurrent appends). The op shape, signing, and the reduce contract are the
107+
co-design items.
108+
- **`exec` — process/PTY control and streams (draft).** Control verbs travel as
109+
`management` frames; live stdin/stdout/stderr travel as **stream channels over
110+
the transport**, never through the content-addressed block store (a live stream
111+
is not immutable content). See [`exec-capability.md`](exec-capability.md).
112+
113+
## Versioning and compatibility
114+
115+
Every connection negotiates a protocol version and capability set at handshake.
116+
A version bump that changes a frame's meaning is gated by the conformance
117+
vectors: an implementation claiming a version must pass that version's vectors.
118+
Unknown capabilities and unknown frame types are ignored or quarantined per
119+
posture, never assumed.
120+
121+
## Status
122+
123+
Implemented today: identity, TLS transport, discovery/relay, capability tokens
124+
and delegation, `content`, and `management`. The `exec` and `oplog` domains are
125+
draft and co-designed with the first external peer implementation; this document
126+
is the place their wire shapes land before either side builds them.

0 commit comments

Comments
 (0)