Files
unshell/PROTOCOL.md
T

635 lines
22 KiB
Markdown
Raw Normal View History

2026-04-23 15:36:27 -06:00
# UnShell Protocol Specification
2026-04-23 15:36:27 -06:00
**Version:** 0.6.0
**Status:** Draft
**Last updated:** 2026-04-23
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
## 1. Introduction
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
**Non-Normative**
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
The UnShell protocol is a tree-addressed packet protocol for remote procedure calls, response hooks, and bidirectional streams across a hierarchy of connected endpoints.
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
The protocol is intended to be small, extensible, and canonical.
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
Small means the core protocol stays narrow enough for constrained implementations. Extensible means new behavior is introduced through leaves, procedures, and payload schemas instead of frequent protocol redesign. Canonical means there should be one clearly defined way to express each core protocol behavior.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
This document combines exact protocol definition with rationale. Rationale blocks explain why a rule exists, but do not define interoperability requirements.
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
> **Rationale:** This document uses a formal specification layout: descriptive sections first, exact protocol definition later, and rationale kept adjacent to the rules it explains.
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
## 2. Document Conventions
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
**Normative**
2026-04-23 15:36:27 -06:00
The key words `MUST`, `MUST NOT`, `REQUIRED`, `SHALL`, `SHALL NOT`, `SHOULD`, `SHOULD NOT`, `RECOMMENDED`, `MAY`, and `OPTIONAL` in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119.txt) when, and only when, they appear in all capitals.
2026-04-23 15:36:27 -06:00
Unless a section is explicitly marked otherwise, sections labeled `Normative` define protocol requirements and sections labeled `Non-Normative` provide description, rationale, deployment guidance, or open design commentary.
2026-04-23 15:36:27 -06:00
All `Rationale` blocks in this document are non-normative.
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
## 3. Purpose and Scope
2026-04-23 15:36:27 -06:00
**Non-Normative**
2026-04-23 15:36:27 -06:00
The purpose of this specification is to define the set of protocol components required to assemble complete UnShell protocol packets and to provide a framework through which the protocol can be extended through leaves and procedure contracts.
2026-04-23 15:36:27 -06:00
To achieve this purpose, the scope of this specification includes:
2026-04-23 15:36:27 -06:00
- endpoint addressing by path
- packet framing
- packet structure
- local authority rules for downwards procedure calls
- path-based routing behavior
- upwards and downwards packet semantics
- hook behavior
- stream behavior
- the required introspection procedure
- extension through leaves, procedures, and payload schemas
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
The UnShell protocol assumes that a connection already exists, that the local implementation has decided whether a peer should be admitted into routing, and that any required authentication or authorization has already been handled by the surrounding system.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
The following items are beyond the scope of this specification:
2026-04-23 15:36:27 -06:00
- authentication
- authorization
- connection establishment
- admission protocol
- transport selection
- transport-specific serialization formats
- encryption
- obfuscation
- router management interfaces
- deployment-specific orchestration behavior
- sensing, analytics, and decision-making systems above the protocol layer
2026-04-23 15:36:27 -06:00
Every implementation is expected to maintain its own live connection set and its own ground truth about which peers are connected, admitted, and routable.
2026-04-23 15:36:27 -06:00
> **Rationale:** Authentication and handshakes were intentionally removed from the core scope. They are too deployment-specific to define canonically without bloating the protocol.
2026-04-23 15:36:27 -06:00
## 4. Protocol Overview
2026-04-23 15:36:27 -06:00
**Non-Normative**
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
Endpoints are addressed by path.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
Leaves are hosted by endpoints.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
A superior endpoint issues a downwards `Call` toward a subordinate endpoint or one of its leaves.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
If the caller wants output, it declares a hook inside the call. The recipient returns one or more `Data` packets upwards toward the hook host. If that hook is stream-oriented, the same `Data` packet type is also used for subsequent bidirectional stream traffic.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
The protocol therefore has two core packet roles:
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
- `Call` for downwards invocation
- `Data` for returned data and stream traffic
2026-04-23 15:36:27 -06:00
This document uses the following notation for readability:
2026-04-23 15:36:27 -06:00
- `/a/b/c` for endpoint paths
- `/a/b/c { leaf: tty0 }` for a leaf on an endpoint
- `/a/b/c { hook: 7 }` for a hook hosted by an endpoint
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
These notations are descriptive only. Leaves and hooks are not encoded as path segments.
2026-04-23 15:36:27 -06:00
## 5. Terms and Definitions
2026-04-23 15:36:27 -06:00
**Normative**
2026-04-23 15:36:27 -06:00
| Term | Definition |
|---|---|
| Tree | The set of connected endpoints arranged by path. |
| Endpoint | A participant in the protocol that can send, receive, host leaves, and route packets. |
| Path | An ordered sequence of segments identifying an endpoint, serialized as `Vec<String>`. |
| Upwards | In the direction of rising authority, closer to the root node. |
| Downwards | In the direction of falling authority, farther from the root node. |
| Leaf | A named service or object hosted by an endpoint. |
| Call | A downwards packet that invokes a procedure on an endpoint or leaf. |
| Procedure | An application-defined operation identified by `procedure_id`. |
| Hook | A response channel declared inside a `Call`. |
| Stream | A bidirectional exchange of `Data` packets associated with a hook and a local `stream_id`. |
| Authority | The endpoint that directly maintains a child connection at a local routing boundary. |
| Subordinate | The lower of two endpoints in a described authority relationship. |
| Registered | Local connection state in which a peer participates in routing. |
| Unregistered | Local connection state in which a peer is connected but not routable. |
2026-04-23 15:36:27 -06:00
## 6. Naming and Structural Conventions
2026-04-23 15:36:27 -06:00
**Normative**
2026-04-23 15:36:27 -06:00
Paths are serialized as `Vec<String>`.
2026-04-23 15:36:27 -06:00
Leaf identity is carried in `dst_leaf`.
2026-04-23 15:36:27 -06:00
Hook identity is carried in `hook_id`.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
Stream identity is carried in `stream_id`.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
No path prefixes are reserved by this protocol.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
`procedure_id` is the canonical identifier for a procedure contract. A procedure contract includes the source library or namespace, the specific procedure identity, and the expected input and output schema pair.
2026-04-23 15:36:27 -06:00
The same `procedure_id` is used on both `Call` and `Data` packets.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
> **Rationale:** `procedure_id` is intentionally stricter than a method name or content type. It identifies a full callable contract, not just a label.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
## 7. Endpoint Model
**Normative**
### 7.1 Local Authority
Each endpoint enforces authority only at the connections it directly maintains.
At a local routing boundary:
- a `Call` packet MUST be accepted only if it arrives from the direct parent connection permitted to issue downwards calls into the destination subtree represented by that boundary
- a `Call` packet that violates that rule MUST be dropped silently
- a `Data` packet MAY arrive from either direction if it belongs to a valid hook or stream flow and routes correctly by path
This protocol does not define a protocol-level authority error packet.
### 7.2 Local Connection States
Each implementation MUST maintain at least the following local states:
| State | Meaning |
|---|---|
| `Unregistered` | The connection exists locally but is not part of routing state. |
| `Registered` | The connection is admitted into local routing state and may send, receive, or forward protocol traffic. |
While a connection is `Unregistered`, an implementation:
- MUST NOT forward protocol packets through it
- MUST NOT trust its path claims for routing
- MUST NOT allocate hook or stream state on its behalf
Transition into `Registered` is implementation-defined and out of scope for this document.
Transition out of `Registered` MUST invalidate all local routing entries, hook state, and stream state associated with that connection.
> **Rationale:** The protocol no longer defines a handshake, but it still needs a hard boundary between connected peers and admitted peers.
## 8. Packet Framing
**Normative**
Each protocol packet consists of two length-prefixed byte sections:
1. header bytes
2. payload bytes
Both lengths MUST be encoded as big-endian `u32`.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
The header MUST be serialized before the payload.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
Routing decisions MUST be made from header fields only.
Routers MUST NOT inspect payload structure in order to route a packet.
## 9. Packet Types
**Normative**
This protocol defines exactly two packet types.
| Packet Type | Value | Meaning |
|---|---|---|
| `Call` | `0x01` | Downwards procedure invocation. |
| `Data` | `0x02` | Hook output, event output, or stream traffic. |
Example in the current Rust implementation:
2026-04-22 10:03:24 -06:00
2026-04-22 21:39:08 -06:00
```rust
#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum PacketType {
2026-04-23 15:36:27 -06:00
Call = 0x01,
Data = 0x02,
}
```
2026-04-23 15:36:27 -06:00
`Call` is used for downwards invocation.
2026-04-23 15:36:27 -06:00
`Data` is used for hook output, event output, and stream traffic.
2026-04-23 15:36:27 -06:00
> **Rationale:** This is the canonical simplification of the earlier model. Separate response and stream-close packet types were removed.
2026-04-23 15:36:27 -06:00
## 10. Packet Header
**Normative**
2026-04-23 15:36:27 -06:00
| Field | Meaning |
|---|---|
| `packet_type` | Selects packet semantics. |
| `src_path` | Path of the sending endpoint. |
| `dst_path` | Path of the destination endpoint. |
| `dst_leaf` | Target leaf for a `Call`, if any. |
| `hook_id` | Hook identifier local to the endpoint hosting the hook. |
| `stream_id` | Stream identifier local to the endpoint receiving the stream traffic. |
Header rules:
- `src_path` and `dst_path` MUST be present on all packets
- `dst_leaf` MUST be `None` on `Data`
- `stream_id` MUST NOT appear on `Call` unless the call declares a stream-oriented hook
- `hook_id` MUST appear on `Data` when the packet belongs to a hook or hook-backed stream
- `stream_id` MUST appear on `Data` when the packet belongs to a stream
2026-04-23 15:36:27 -06:00
A packet whose header violates these rules MUST be discarded.
2026-04-23 15:36:27 -06:00
Example in the current Rust implementation:
```rust
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
2026-04-23 15:36:27 -06:00
pub struct PacketHeader {
pub packet_type: PacketType,
pub src_path: Vec<String>,
pub dst_path: Vec<String>,
pub dst_leaf: Option<String>,
pub hook_id: Option<u64>,
pub stream_id: Option<u32>,
2026-04-22 10:03:24 -06:00
}
2026-04-23 15:36:27 -06:00
```
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
## 11. Routing Rules
2026-04-23 15:36:27 -06:00
**Normative**
2026-04-23 15:36:27 -06:00
### 11.1 Path Routing
2026-04-23 15:36:27 -06:00
All protocol routing is path-based.
2026-04-23 15:36:27 -06:00
When forwarding a packet, an implementation MUST:
1. compare `dst_path` against its locally registered child paths
2. choose the longest matching prefix
3. forward the packet toward that child if such a child exists
4. otherwise, deliver the packet locally if `dst_path` identifies the local endpoint
5. otherwise, drop the packet silently
The protocol defines no mandatory error packet for unresolved destinations.
### 11.2 Call Enforcement
2026-04-23 15:36:27 -06:00
When forwarding or receiving a `Call`, an endpoint MUST verify the local parent-child relationship at the boundary where the packet arrives.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
If the sender on that connection is not the direct parent permitted to issue downwards calls into the relevant subtree, the endpoint MUST drop the packet silently.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
### 11.3 Data Routing
2026-04-23 15:36:27 -06:00
`Data` packets are routed by `dst_path` using the same path-routing rules as `Call` packets.
2026-04-23 15:36:27 -06:00
The sender of a `Data` packet MUST set `dst_path` to the path of the stream peer or the hook host.
2026-04-23 15:36:27 -06:00
### 11.4 Stream Fastpath
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
An implementation MAY maintain an internal fastpath keyed by `(local_connection, stream_id)` for performance.
2026-04-23 15:36:27 -06:00
Such an optimization MUST remain behaviorally equivalent to path-based routing.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
The protocol itself does not route by `stream_id` alone.
2026-04-23 15:36:27 -06:00
> **Rationale:** `stream_id` is intentionally not treated as a globally routable identifier.
## 12. Call Definition
**Normative**
| Field | Meaning |
|---|---|
| `procedure_id` | Identifier of the invoked procedure contract. |
| `data` | Application-defined procedure input payload. |
| `response_hook` | Optional hook declaration for returned data. |
Rules:
- the receiver MUST interpret `procedure_id` as the identifier of the procedure being invoked
- the protocol does not define argument encoding beyond raw bytes in `data`
- a `Call` that expects a result MUST include `response_hook`
- if `response_hook` is absent, the receiver MAY execute the procedure but MUST NOT fabricate an implicit response path
Example in the current Rust implementation:
```rust
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
2026-04-23 15:36:27 -06:00
pub struct CallMessage {
pub procedure_id: String,
pub data: Vec<u8>,
2026-04-22 21:39:08 -06:00
pub response_hook: Option<HookTarget>,
}
2026-04-23 15:36:27 -06:00
```
2026-04-23 15:36:27 -06:00
### 12.1 Required Introspection Procedure
2026-04-23 15:36:27 -06:00
The empty string `""` is reserved as the required introspection procedure.
2026-04-23 15:36:27 -06:00
Every endpoint MUST implement `procedure_id == ""`.
Behavior:
- when `dst_leaf` is `None`, the call requests endpoint introspection
- when `dst_leaf` is set, the call requests introspection for that specific leaf
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
The result MUST be returned through the declared response hook.
2026-04-23 15:36:27 -06:00
### 12.2 Failure Behavior
2026-04-23 15:36:27 -06:00
If the destination endpoint does not exist, the packet is dropped during routing.
2026-04-23 15:36:27 -06:00
If the destination endpoint exists but `dst_leaf` names no local leaf, the endpoint MUST discard the `Call` silently.
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
If `procedure_id` is unknown or unsupported, the endpoint SHOULD report failure through the declared hook using an application-defined error payload. If no hook exists, the endpoint MUST discard the call silently.
## 13. Hook Definition
**Normative**
Hooks are declared only inside `CallMessage.response_hook`.
There is no standalone hook-open packet.
| Field | Meaning |
|---|---|
| `hook_id` | Identifier local to the endpoint that hosts the hook and expects responses. |
| `return_path` | Endpoint path to which returned `Data` packets are sent. |
| `response_type` | Advisory indication of whether the expected response is event-like or stream-like. |
Rules:
- `hook_id` MUST be unique within the receiving endpoint's active hook set
- `return_path` MUST name the endpoint hosting the hook
- `response_type` is advisory and does not itself terminate or prolong hook lifetime
Example in the current Rust implementation:
2026-04-22 21:39:08 -06:00
```rust
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
2026-04-23 06:36:39 -06:00
pub struct HookTarget {
pub hook_id: u64,
2026-04-23 15:36:27 -06:00
pub return_path: Vec<String>,
2026-04-23 06:36:39 -06:00
pub response_type: HookResponseType,
}
2026-04-23 06:36:39 -06:00
#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum HookResponseType {
2026-04-23 15:36:27 -06:00
Event = 0,
2026-04-23 06:36:39 -06:00
Stream = 1,
}
```
2026-04-23 15:36:27 -06:00
## 14. Data Definition
**Normative**
| Field | Meaning |
|---|---|
| `procedure_id` | Identifier of the procedure contract to which this returned payload belongs. |
| `data` | Application-defined output payload. |
| `end` | Sender indicates completion of its participation in the hook or stream. |
| `cancel` | Sender requests termination of the associated stream processing. |
Rules:
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
- the receiver MUST interpret `procedure_id` as the contract identifier for the returned payload
- the router MUST NOT inspect or validate `procedure_id`
- the receiver MAY validate that the returned `procedure_id` matches the hook or stream context it established
Example in the current Rust implementation:
2026-04-22 21:39:08 -06:00
```rust
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
2026-04-23 15:36:27 -06:00
pub struct DataMessage {
pub procedure_id: String,
2026-04-22 21:39:08 -06:00
pub data: Vec<u8>,
2026-04-23 15:36:27 -06:00
pub end: bool,
pub cancel: bool,
2026-04-22 21:39:08 -06:00
}
```
2026-04-23 15:36:27 -06:00
### 14.1 Event Data
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
For non-stream responses:
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
- `hook_id` MUST be present
- `stream_id` MUST be absent
- `end` SHOULD be `true` on the final packet for that hook
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
An event-style hook MAY still emit multiple `Data` packets if the application requires chunking or phased output.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
### 14.2 Stream Establishment
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
A stream exists only as part of a hook whose `response_type` is `Stream`.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
There is no standalone stream-open packet.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
The first `Data` packet for a stream MUST:
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
- carry the hook's `hook_id`
- carry a `stream_id`
- set `dst_path` to the hook host's `return_path`
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
Once established, either side MAY continue exchanging `Data` packets carrying that `stream_id` and the appropriate peer `dst_path`.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
`stream_id` is local to the endpoint that receives and demultiplexes that stream.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
An endpoint MUST NOT reuse an active `stream_id` within its local stream table.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
### 14.3 Stream Data
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
For stream-associated traffic:
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
- `stream_id` MUST be present
- `hook_id` SHOULD be present on every packet and MUST be present on the first packet
- `dst_path` MUST identify the peer endpoint for that stream packet
2026-04-23 15:36:27 -06:00
### 14.4 End and Cancel
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
Rules:
2026-04-23 15:36:27 -06:00
- a sender MAY set `end = true` without `cancel = true`
- a sender MAY set `cancel = true` without `end = true`
- a sender MAY set both when it intends immediate termination
- a receiver of `cancel = true` SHOULD stop local processing for that stream as soon as practical
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
There is no separate stream-close or hook-close packet.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
### 14.5 Unknown Stream IDs
2026-04-23 15:36:27 -06:00
If an endpoint receives `Data` with an unknown or expired `stream_id`, it MUST discard the packet.
2026-04-23 15:36:27 -06:00
The protocol does not define a mandatory error response for this case.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
## 15. Introspection Payloads
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
**Normative**
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
The required introspection procedure `""` MUST return one of the following payloads through the declared hook.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
### 15.1 Endpoint Introspection
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
Returned when `procedure_id == ""` and `dst_leaf == None`.
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
| Field | Meaning |
|---|---|
| `leaves` | List of introspection summaries for the endpoint's hosted leaves. |
2026-04-22 10:03:24 -06:00
2026-04-23 15:36:27 -06:00
Each `LeafIntrospectionSummary` contains:
2026-04-23 15:36:27 -06:00
| Field | Meaning |
|---|---|
| `leaf_name` | The leaf's local name. |
| `description` | Optional human-readable description. |
| `procedures` | Introspection records for the leaf's supported procedures. |
| `state_procedure_id` | Procedure contract identifier associated with the serialized `state` payload. |
| `state` | Serialized current-state payload. |
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
Example in the current Rust implementation:
2026-04-23 15:36:27 -06:00
```rust
2026-04-22 21:39:08 -06:00
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
2026-04-23 15:36:27 -06:00
pub struct EndpointIntrospection {
pub leaves: Vec<LeafIntrospectionSummary>,
2026-04-22 21:39:08 -06:00
}
2026-04-22 21:39:08 -06:00
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
2026-04-23 15:36:27 -06:00
pub struct LeafIntrospectionSummary {
pub leaf_name: String,
2026-04-22 21:39:08 -06:00
pub description: Option<String>,
2026-04-23 15:36:27 -06:00
pub procedures: Vec<ProcedureIntrospection>,
pub state_procedure_id: String,
pub state: Vec<u8>,
2026-04-22 21:39:08 -06:00
}
```
2026-04-23 15:36:27 -06:00
### 15.2 Leaf Introspection
2026-04-23 15:36:27 -06:00
Returned when `procedure_id == ""` and `dst_leaf` names a specific leaf.
2026-04-23 15:36:27 -06:00
| Field | Meaning |
|---|---|
| `leaf_name` | The leaf's local name. |
| `description` | Optional human-readable description. |
| `procedures` | Introspection records for the leaf's supported procedures. |
| `state_procedure_id` | Procedure contract identifier associated with the serialized `state` payload. |
| `state` | Serialized current-state payload. |
2026-04-23 15:36:27 -06:00
Each `ProcedureIntrospection` contains:
2026-04-23 15:36:27 -06:00
| Field | Meaning |
|---|---|
| `name` | Procedure name within the leaf. |
| `description` | Optional human-readable description. |
| `params` | Parameter definitions accepted by the procedure. |
| `response_type` | Advisory indication of whether the procedure normally responds as an event or stream. |
2026-04-23 15:36:27 -06:00
Each `ProcedureParameter` contains:
2026-04-23 15:36:27 -06:00
| Field | Meaning |
|---|---|
| `name` | Parameter name. |
| `value_type` | Application-defined parameter type name. |
2026-04-23 15:36:27 -06:00
Example in the current Rust implementation:
2026-04-23 15:36:27 -06:00
```rust
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct LeafIntrospection {
pub leaf_name: String,
pub description: Option<String>,
pub procedures: Vec<ProcedureIntrospection>,
pub state_procedure_id: String,
pub state: Vec<u8>,
}
2026-04-23 15:36:27 -06:00
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct ProcedureIntrospection {
pub name: String,
pub description: Option<String>,
pub params: Vec<ProcedureParameter>,
pub response_type: HookResponseType,
}
2026-04-23 15:36:27 -06:00
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct ProcedureParameter {
pub name: String,
pub value_type: String,
}
2026-04-22 21:39:08 -06:00
```
2026-04-23 15:36:27 -06:00
Rules:
2026-04-23 15:36:27 -06:00
- `state_procedure_id` MUST identify the procedure contract associated with the serialized `state` payload
- `params` MUST describe the accepted parameter names and parameter types for that procedure
- introspection SHOULD describe current state, but does not establish a cache coherence guarantee
2026-04-23 15:36:27 -06:00
## 16. Protocol Description
2026-04-23 15:36:27 -06:00
**Non-Normative**
2026-04-23 15:36:27 -06:00
The UnShell protocol has a deliberately narrow center:
2026-04-23 15:36:27 -06:00
- addressing by path
- one downwards packet type
- one returned-data packet type
- hooks for correlation
- streams as an extension of hook-backed data flow
2026-04-23 15:36:27 -06:00
This is meant to make the protocol easier to reason about and easier to implement in small agents.
2026-04-23 15:36:27 -06:00
`procedure_id` is the main semantic anchor. In this design, the caller and callee are expected to share knowledge of what a procedure contract means. The protocol does not carry a global registry.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
## 17. Security Considerations
2026-04-23 15:36:27 -06:00
**Non-Normative**
2026-04-23 15:36:27 -06:00
Although security is not defined by the protocol itself, implementations should treat the `Unregistered` state as a strict quarantine boundary.
2026-04-23 15:36:27 -06:00
Recommended behavior:
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
- authenticate or otherwise validate a peer before moving it to `Registered`
- rate-limit or expire idle unregistered peers
- avoid disclosing topology before admission
- avoid detailed admission failure reasons
- invalidate hooks and streams on disconnect unless a higher-layer session mechanism exists
2026-04-22 21:39:08 -06:00
2026-04-23 15:36:27 -06:00
## 18. Serialization and Implementation Notes
2026-04-23 15:36:27 -06:00
**Non-Normative**
2026-04-23 15:36:27 -06:00
This document uses Rust-like `rkyv` struct notation to describe fields because it matches the current implementation language. The notation is explanatory. The protocol semantics are language-agnostic.
2026-04-23 15:36:27 -06:00
Recommended implementation limits:
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
| Item | Recommended limit |
|---|---|
| header length | 64 KiB |
| payload length | 64 MiB |
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
## 19. Known Hard Problems
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
**Non-Normative**
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
### 19.1 Loop Prevention Outside Strict Trees
2026-04-23 15:36:27 -06:00
The protocol does not carry a hop count, route vector, or loop-detection token.
2026-04-23 15:36:27 -06:00
That keeps packets small, but it means loop prevention must be handled by topology discipline or implementation policy.
2026-04-23 15:36:27 -06:00
### 19.2 Canonical Connection Management
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
The document defines `Registered` and `Unregistered` states but intentionally does not define how a peer moves between them.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
That preserves flexibility, but it means interoperable admission behavior requires a higher-layer convention.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
### 19.3 Shared Meaning of `procedure_id`
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
`procedure_id` is only useful if both sides share its meaning.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
The protocol intentionally does not define a global registry or schema negotiation mechanism. That keeps the core minimal, but it pushes interoperability for procedure contracts into shared libraries, operator knowledge, or higher-layer conventions.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
### 19.4 Stream Resumption Across Disconnects
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
Hook and stream state are tied to local connection state.
2026-04-23 06:36:39 -06:00
2026-04-23 15:36:27 -06:00
When a connection disappears, the associated hook and stream context disappears with it. Any resumable behavior therefore requires a higher-layer session mechanism.