Metadata-Version: 2.4
Name: adp-operator
Version: 1.1.3
Summary: ADP operator management and C2C runtime tools
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: docker
Requires-Dist: fastapi
Requires-Dist: pandas
Requires-Dist: pyarrow
Requires-Dist: pyarmor
Requires-Dist: psutil
Requires-Dist: python-dotenv
Requires-Dist: python-multipart
Requires-Dist: requests
Requires-Dist: uvicorn
Requires-Dist: colorlog
Requires-Dist: watchdog
Requires-Dist: websockets
Dynamic: description
Dynamic: description-content-type
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# ADP Operator

## C2C 连接首包

C2C TCP 连接建立后，双方必须先交换固定长度的二进制首包。首包校验通过后，连接才允许继续处理 JSON 业务指令。

首包固定为 512 bit，结构如下：

| bit 偏移 | bit 长度 | 字段 | 规则 |
| ---: | ---: | --- | --- |
| 0 | 24 | 固定标识 | 三个 ASCII 字符 `ADP` |
| 24 | 16 | 协议版本 | 16-bit 无符号整数，使用大端序，当前版本为 `7` |
| 40 | 256 | 实例编号 | 当前节点的 `instance_id`，固定32字节 |
| 296 | 8 | 功能控制区 | bit 0–2为功能开关，bit 3–7为保留位 |
| 304 | 208 | 全零保留区 | 必须全部为 `0` |

字符串字段统一使用 UTF-8 编码。字段长度不足时在尾部使用 `0x00` 填充，超过字段长度时按字段长度截断。

### 功能控制区

功能控制区固定为8 bit，每个功能占用一个 bit：

| bit | 功能 | `0` | `1` | 当前默认值 |
| ---: | --- | --- | --- | --- |
| 0 | UDP 主动发现 | 关闭 | 开启 | `1` |
| 1 | 额外 channel 通道 | 不支持 | 支持 | `1` |
| 2 | WebSocket 业务端口 | 关闭 | 开启 | `1` |
| 3–7 | 保留位 | 必须为零 | 不允许 | `0` |

当前默认控制区的二进制内容为：

```text
00000111
```

### 握手流程

1. TCP 客户端连接 C2C 服务端。
2. 客户端发送自己的 512 bit 二进制首包。
3. 服务端读取并校验完整首包。
4. 服务端校验通过后，向客户端返回自己的 512 bit 二进制首包。
5. 客户端校验服务端首包。
6. 双方校验均通过后，才开始发送 JSON 业务指令。

服务端会拒绝以下连接：

- 首包长度不足 512 bit；
- 固定标识不是 `ADP`；
- 目标节点协议版本低于发起连接节点的协议版本；
- `instance_id` 为空；
- 字符串字段的补零格式错误；
- 功能控制区保留位非零；
- 208 bit 全零保留区存在非零数据。

该首包适用于 C2C 直连、反向连接、多跳转发、额外 channel 通道、外部 `C2CSession` 和本地服务探测。未实现该二进制首包的旧节点无法与新协议节点建立连接，因此升级时需要同时更新同一网络中的全部 C2C 节点。

协议版本字段固定为16 bit，使用大端序表示无符号整数，取值范围为 `0`–`65535`。客户端收到服务端首包后比较版本：目标节点版本大于或等于本机版本时允许连接，目标节点版本低于本机版本时拒绝连接。例如本机版本为 `4`、目标版本为 `5` 时允许连接；本机版本为 `5`、目标版本为 `4` 时拒绝连接。

## C2C JSON 指令模型

本节只描述 JSON 控制指令，不包含文件、HTTP Body 或 Operator Channel 接管连接后的二进制数据流。C2C 指令需要同时按照寻址方式、响应模型和传播方式理解。

### 寻址方式

#### 当前节点处理

没有目标节点、也不需要向网络扩散的指令由当前入口节点直接处理，例如 `identify`、`discover`、`shutdown` 和 `close_session`。`discover` 返回当前节点已经掌握的注册表，但不会主动广播一次发现请求。

#### 自动路由的有指向指令

有指向指令使用 `identifier` 指定最终目标的 `node_id` 或 `instance_id`：

```json
{
  "type": "info",
  "identifier": "AdpCpuDispatch"
}
```

目标是当前节点时直接处理；目标不是当前节点时，C2C 优先使用已有路由，没有路由时通过 `route_probe` 寻找一条可达路径，然后转发原始指令。`run`、`call`、`info`、`kill`、`sync_files`、`hidden`、`visible` 和 `channel` 都可以使用这种模式。

#### 指定路径的有指向指令

`route` 不是新的业务指令，而是有指向指令的路由模式：

```json
{
  "type": "call",
  "identifier": "TargetNode",
  "route": ["NextNode", "TargetNode"]
}
```

每一跳按照 `route` 指定的下一节点转发，最终由 `identifier` 对应的节点执行。已经获得明确路径时使用该模式可以避免重复探测。

#### 无指向指令

无指向指令不指定唯一执行节点，但其停止条件和结果处理方式不同：

| 模式 | 指令 | 行为 |
| --- | --- | --- |
| 首个确认 | `probe` | 节点分别判断是否确认，取最先到达的有效 `probe_ack`，其余响应丢弃 |
| 首条路径 | `route_probe` | 在网络中寻找指定身份，返回第一条有效路径 |
| 全量聚合 | `scan` | 在响应窗口内收集所有扫描节点的结果，依次合并并去重 |
| 注册表扇出聚合 | 无 `identifier` 的 `kill` | 向当前已知节点发送终止请求，合并终止数量和错误 |
| 全域广播 | `event` | 所有可达节点处理并继续传播，每一跳只返回传输确认，不收集业务结果 |

`scan` 和 `kill` 都会合并多个结果，但实现不同：`scan` 是具有 TTL 和请求去重的网络传播波；`kill` 根据当前注册表向已知节点扇出有指向请求。

### 响应模型

| 响应模型 | 指令示例 | 响应方式 |
| --- | --- | --- |
| 单目标单响应 | `info`、`identify`、`discover`、`shutdown`、`hidden`、`visible` | 返回一个 ACK 后结束 |
| 单目标多阶段响应 | `run`、`call` | 依次返回接收、日志、结果和结束事件，或错误事件 |
| 首个有效响应 | `probe`、`route_probe` | 任意分支首先确认后结束等待 |
| 多响应合并 | `scan`、全网 `kill` | 收集多个节点的结果后合并 |
| 广播与逐跳确认 | `event` | 每一跳返回 `event_ack`，不返回业务处理结果 |

`run` 的响应可能包含 `run_ack`、`run_start`、`run_running`、`log`、`run_result` 和 `run_end`；`call` 的响应可能包含 `call_ack`、`log`、`call_result` 和 `call_end`。因此它们虽然只执行一个目标，却不是普通的一问一答。

### 全域传播机制

C2C 当前存在三类全域传播机制：

1. `route_probe`：寻找指定身份和第一条可达路径，不执行目标业务。
2. `probe`：携带业务条件寻找任意一个确认节点，第一条确认生效。
3. `event`：向所有可达节点广播业务事件，不等待业务结果。

`scan` 是独立的聚合扫描波：它传播到整个可达网络，但需要在窗口内收集并合并所有有效响应，不采用“首个响应即结束”的规则。

### EventBus 业务事件

EventBus 不维护业务事件类型枚举。节点可以发布任意具有非空 `type` 的事件：

```python
self.c2c.client.event_bus.publish({
    "type": "operators_reloaded",
    "status": "completed",
})
```

C2C 使用固定的顶层 `event` 指令封装业务事件：

```json
{
  "type": "event",
  "event_id": "全局事件编号",
  "node_id": "事件来源节点",
  "sender_node_id": "当前转发节点",
  "created_at": 1793600000.0,
  "expires_at": 1793600060.0,
  "event": {
    "type": "operators_reloaded",
    "status": "completed"
  }
}
```

顶层 `type` 属于 C2C 协议，`event.type` 属于节点业务。即使业务事件名为 `run` 或 `call`，它仍位于 `event` 对象内部，不会被解释成同名控制指令。

EventBus 负责生成 `event_id`、补充来源身份、设置 TTL、逐跳确认、根据 `event_id` 防环和去重，并在直连、反向连接和多跳网络中继续传播。监听器只能观察和处理事件，不能通过返回值阻止传播；单个监听器异常也不会中断网络扩散。

### 固定指令清单

TCP C2C 当前支持27种顶层 JSON 指令：

| 类别 | 指令 |
| --- | --- |
| 路由与组网 | `route_probe`、`probe`、`discover`、`scan`、`get_path`、`connect`、`handshake`、`join`、`takeover` |
| 节点身份与生命周期 | `identify`、`info`、`visible`、`hidden`、`shutdown`、`kill` |
| 算子执行与事件 | `run`、`call`、`event` |
| 文件与会话控制 | `receive_file`、`file_manifest`、`sync_files`、`close_session` |
| 流和自定义通道控制 | `file_stream`、`http_stream`、`channel`、`data`、`reverse_stream_attach` |

这里的 `file_stream`、`http_stream`、`channel` 和 `reverse_stream_attach` 只描述建立或切换数据通道的 JSON 控制阶段，不包含其后的二进制流。JSON `handshake` 是节点业务握手指令，不是连接建立时交换的512 bit 二进制首包。

WebSocket 支持 `scan`、`info`、`visible`、`hidden` 和独有的 `subscribe`，因此 TCP 与 WebSocket 合并后共有28种对外唯一指令。

UDP 使用 `announce`、`heartbeat`、`leave` 和 `renamed` 进行节点发现和状态通知。反向连接内部使用 `reverse_request`、`reverse_response`、`reverse_error` 和 `reverse_stream_open`。它们都不是算子应该直接发送的业务指令。

### 命名空间边界

```text
顶层 type
    C2C 固定 JSON 控制指令

identifier
    最终目标身份

route
    到达最终目标的指定路径

event.type
    节点自定义业务事件

HTTP method + path
    HTTP Gateway 路由

Operator channel
    算子自定义协议
```

当前共有28种对外指令、4种 UDP 发现消息和4种反向连接内部帧，共36种固定协议消息类型；EventBus 业务事件数量不受限制。

## 本机调试网络

设置环境变量后，C2C 节点只加入当前机器上的调试网络：

```bash
export C2C_DEBUG_LOCAL_ONLY=1
```

该模式只改变 UDP 发现：节点公告仅发送到 `127.0.0.1` 的发现端口范围，
同时忽略所有非本机地址发送的 UDP 发现包。因此节点不会自动发现局域网内
其他机器，其他机器也无法通过 UDP 发现该节点。

TCP、WebSocket 和 `--join` 行为保持不变；已经明确知道地址时仍然可以主动连接。

未设置该变量，或变量值不是 `1`、`true`、`yes`、`on` 时，保持原有局域网发现和监听行为。
