# 子集群状态与自动调度

本地模式和中心模式使用相同工具。前者把集群、任务和历史保存在本机；后者保存在中心。中心分派任务，指定 companion 执行器负责管理成员，探测本身在成员的原生节点运行。

| 工具 | 用途 |
| --- | --- |
| `ssh_cluster_manage` | 创建、更新、停用和删除集群 |
| `ssh_cluster_policy` | 管理候选路由、质量门槛与自动操作授权 |
| `ssh_cluster_inspect` | 查询集群、策略、任务、事件和历史 |
| `ssh_cluster_task` | 发起一次采集/选路测量、取消排队任务、核对未知写入的回执 |

## 配置周期探测

1. 用 `ssh_node_manage` 安装、启动并检查成员节点。记录返回的 `deviceId`；服务器名称不能代替设备 ID。
2. 执行器使用运行 companion 的主 `@local` 设备 ID。远端成员可以只安装 `sshmcp-node`；节点不接收中心 API Key 或其他机器的 SSH 私钥。执行器须通过已配置 SSH 管理通路访问成员；其他 companion 的 `@local` 只能由其本人管理。
3. 调用 `ssh_cluster_manage(action="save", expectedRevision=0, spec=...)`。例如，将下列设备占位符替换为刚查询的真实 ID：

```json
{
  "id": "office",
  "members": [{
    "device": "MEMBER_DEVICE_ID",
    "probes": [{
      "id": "ssh",
      "target": {"kind": "tcp", "address": "127.0.0.1:22"},
      "intervalMs": 10000,
      "timeoutMs": 2000,
      "window": 30
    }]
  }],
  "executors": ["COMPANION_PRIMARY_DEVICE_ID"],
  "intervalMs": 10000,
  "staleAfterMs": 60000
}
```

`address` 在成员节点解释。原生探测周期由每个 probe 的 `intervalMs` 控制，集群的同名字段控制执行器采集周期。上例分别为 10 秒；任务较慢时不叠加重复执行。

已有 P2P link 可使用 `{"kind":"p2p","link":"已有 link ID"}`；经该 link 探测对端已授权 TCP 服务可使用 `{"kind":"forward","link":"已有 link ID","target":"127.0.0.1:22"}`。不配置任意网段扫描；没有可用 link 或权限时会记录失败。

`parent` 引用现有集群，最多八层。一个设备只能有一个直接归属，父视图汇总后代。`gateways` 可声明本集群或后代中的网关候选。停用一个集群只退役它直接拥有的探测；子集群独立启停。

配置保存后，查询 `ssh_cluster_inspect(section="clusters", id="office")`，检查 `appliedRevision`、执行器心跳和成员 runtime。保存成功不等于成员已经加载。

## 授权自动选路

先使用普通路由工具配置并验证候选路径。在来源 companion 上应用一条显式 `sourceDevice` 路由，作为策略起点。策略必须有 2–8 个不同候选，全部使用同一来源主设备和同一 SSH 服务器；目标服务器须是 `targetCluster` 成员登记的管理名称或别名。

调用 `ssh_cluster_policy(action="save", expectedRevision=0, spec=...)`。`spec` 包含：

- `id`、`sourceDevice`、`sourceCluster`、`targetCluster`。
- `candidates`：每项有唯一 `id` 和完整 `route`，保留已检查路由的 `server`、`via`、`sourceDevice`、`description`、`ingress`、`resources`、`enabled`；`sourceInstance` 只能省略或为 null。其中一个候选必须与当前路由完全一致。
- `mandatoryVia`：每个候选均须保留的完整必经跳板顺序。不会因为延迟更低而跳过这些跳板。
- `autoSwitch`：默认 false。打开后允许达到门槛时修改路由；已有节点授权不随自动切换扩张。
- `autoRepair`：默认 false。打开后，仅在所有候选都出现新鲜失败时尝试启动当前路径已登记、被策略引用的受管资源。若需要修改节点配置或权限，留下错误，由显式 route plan 处理。

默认每 30 秒测量候选，单候选超时 5 秒、需要 3 次独立确认、操作冷却 60 秒。当前路径正常时，替代路径的近期中位延迟须至少改善 20%；当前路径连续失败 2 次时，可以按同样确认门槛切到可用候选。默认每小时最多尝试修复 3 次。参数的单位、范围与后果都在工具 schema 中。

每次测量由 `sourceDevice` 的实际 companion 新建 SSH 连接并完成原有身份认证。中心到成员的管理连接耗时不会成为来源设备的候选延迟。可选 `gatewayDevice` 须在来源或目标集群声明，并且有新鲜、未达到容量门槛的原生负载数据。负载指连接/流配额使用率，不是 CPU 占用率。

手动改路、受管资源变更或撤权会阻止旧策略继续覆盖。检查原因后，以当前 `expectedRevision` 保存完整策略，才能明确接纳新的状态。

## 读取结果

`ssh_cluster_inspect` 的 `section` 可选 `clusters`、`policies`、`jobs`、`events`、`history`。后面三类默认返回最近 30 条，可通过 `limit` 调整，最多 256 条。权威存储会按容量淘汰旧历史和已结束任务；它不是无限日志归档。

| 状态/数值 | 含义 |
| --- | --- |
| `healthy` | 节点 runtime 和配置匹配，已配置探测有新鲜成功样本 |
| `degraded` | 有新鲜的探测失败或超时 |
| `observation_failed` | 执行器本轮无法检查成员；详细错误保留，不直接推断物理设备已关机 |
| `configuration_unloaded` | 检查到配置，但目标 runtime 尚未加载对应版本 |
| `unknown` / `stale` | 尚无足够样本或观测已过期；不会继续作为健康候选 |
| `rttUs`、P50/P95、抖动 | 微秒；1000 微秒 = 1 毫秒。TCP 建连、P2P 应用 Ping、转发建连和 SSH 认证耗时分别标注 |
| `failureRatePercent` | 窗口内应用探测的失败/超时比例，不是物理网络丢包率 |
| `atMs`、`receivedAtMs` | 毫秒时间戳，分别保留测量/接收时间；成员时钟明显偏差会使状态过期 |

原生窗口最多 60 个样本，每节点最多 16 个探测、2 个同时进行的探测。修改目标或相关 P2P 配置会重建窗口，旧任务的迟到结果不能混入新窗口。

## 后台运行与退役

启用的任务使其指定 companion 在 MCP 前台退出后继续运行。要跨系统重启启动 companion，仍须用 `ssh_node_manage(server="@local", action="startup", startup="login" 或 "boot")` 配置系统监督；作用域与所需权限由工具返回。

停止 daemon 或失去中心连接后不启动新的写入。任务带 180 秒租约、执行者和尝试 ID；运行中每 20 秒续租。纯观测租约到期后可重新排队。执行中的路由修改结果不明时记为 `unknown`，只核对已保存的 route plan 回执；不会重放命令或盲目再次 apply。未开始且已经过期的计划可以确认为 `not_started`，其他无法核实的结果保留待处理。

退役顺序是停用引用策略，等待在途任务结束或核对未知回执，停用集群并等待 `appliedRevision == revision`，再解除策略、子集群和设备引用后删除。只清除 `cluster:<id>` 拥有的探测，保留节点其他配置和服务。

持续目录同步、外部通知、通用远程 shell 任务不由这组工具提供。
