适用版本:S0–S7 + 分布式 D1–D6 全量实现。设计背景见
redesign-and-roadmap.md(单机路线)与distributed-plane-and-dashboard.md(分布式管理层);YAML 语法见yaml-workflow-schema.md,脚本 API 见quickjs-api.md。
$ cargo build --release -p sohara-cli # 单机二进制(target/release/sohara)
$ cargo build --release -p sohara-agent # 节点代理
$ cargo build --release -p sohara-plane # 控制面(Gateway + Manager + 中继)
沙箱环境需先
export CARGO_HOME=$PWD/.cargo-home。 本机若配置了 HTTP 代理,本地 HTTP 访问已在各客户端内置no_proxy,无需处理。
$ sohara init demo && cd demo
$ sohara run flow.yaml
Flow 'basic' finished: processed=2, filtered=1, errors=0, waiting=0, duplicates=0
| 命令 | 说明 |
|---|---|
sohara init [dir] |
生成 flow.yaml + data/input.csv 骨架 |
sohara run <flow.yaml> [--resume] [--verbose] [--history PATH] |
一次性运行;--resume 复用存储的 run_id(幂等续跑);--verbose 打印步骤统计表;每次运行写入 .sohara/history.jsonl(失败运行记 status=error) |
sohara serve <flow.yaml> [--admin ADDR] [--admin-token T] [--resume] [--relay URL] [--relay-token T] |
常驻运行触发器(http/cron/queue)直至 Ctrl+C/SIGTERM;停机时优雅排水并写一条历史 |
sohara approve <flow.yaml> [--step ID] |
放行 approve 步骤停放的记录 |
sohara history [--limit N] [--history PATH] |
查看运行历史 |
serve 模式加 --admin 后:
GET /admin/health、/admin/metrics、/admin/status(流程/触发器/步骤统计/paused)、/admin/approvals、/admin/errors、/admin/historyPOST /admin/pause、POST /admin/resume——协作式暂停:暂停后已拉取的记录被握住不处理,背压向上游传播GET /admin/ui:浏览器打开内嵌 Dashboard(概览卡片、步骤表、触发器、审批队列、错误流、历史;3 秒轮询)--admin-token 后所有 /admin/* 需要 Authorization: Bearer <token>$ sohara serve examples/serve.yaml --admin 127.0.0.1:9528 --admin-token sekret
$ curl -H "Authorization: Bearer sekret" http://127.0.0.1:9528/admin/status
checkpoint: { store: state/orders.json, every: 500 } # 状态存储 + 每 500 条检查点
steps:
- { id: gate, kind: control, type: approve, config: { title: "大额审批", owners: [alice] } }
run --resume:复用存储 run_id,delivered 幂等键去重(内容哈希或 metadata.idempotency_key)。approve 无 store 时降级放行;有 store 时停放,sohara approve 放行后从停放点续跑。examples/)source/sink: file(csv/json/jsonl)、source/sink: db(SQLite,{path, query} / {path, table})、source/sink: http({url, method?, headers?, poll_interval?})、source/transform/sink: script(QuickJS,{script | inline, entry?})。imports: [parts/common.yaml] + 模板 templates: + 步骤 use: <模板名>(嵌套 config 深合并)。examples/README.md。外部请求 ──▶ sohara-plane(Gateway /gw + Manager /ui + 中继 /relay + 对账)
▲ 心跳/命令(agent 拨出)
sohara-agent(每机一个)── 进程级管理 ──▶ sohara serve 实例(--admin --relay)
$ sohara-plane --addr 127.0.0.1:9600 --state plane-state.json [--token plane-tok]
设置 --token 后,/api/*、/agent/*、/relay/*、/ui 全部需要 Bearer 认证;/gw/* 是免 token 的外部统一入口。
# agent.yaml
node: { id: n1 }
plane: { url: "http://127.0.0.1:9600", token: plane-tok } # 与 --token 一致
heartbeat_ms: 1000
instances: [] # 可留空:实例由 plane 声明下发(推荐)
$ sohara-agent agent.yaml
agent 会:每 1s 本地健康检查实例 /admin/health、每 5s 心跳上报;崩溃/健康失败按 policy 指数退避重启(封顶 60s,超预算标记 failed);plane 不可达时保持现状运行(keep-running)。
$ curl -H "Authorization: Bearer plane-tok" -X POST http://127.0.0.1:9600/api/instances \
-H "Content-Type: application/json" -d '{
"id": "orders-1",
"node": "n1",
"flow_id": "orders", # 路由分组(gateway 路由按它匹配)
"desired": "running", # running | paused | stopped
"spec": {
"id": "orders-1",
"flow": "/srv/flows/orders.yaml", # 节点上的 flow 文件路径
"bin": "sohara",
"admin": "127.0.0.1:9528", # 实例管理端口(心跳上报用)
"admin_token": "instance-tok",
"trigger": "127.0.0.1:9527", # http 触发器地址(gateway 转发目标)
"relay": "http://127.0.0.1:9600", # 事件总线桥接(跨机通信)
"health_enabled": true,
"policy": { "restart": true, "max_restarts": 5, "backoff_ms": 2000, "health_failures": 3 }
}
}'
声明式生命周期:改 desired 即收敛——PUT /api/instances/:id/desired {"desired":"stopped"} 停机、再改回 running 拉起;DELETE /api/instances/:id 撤销声明。spec 更新会自动替换进程(重启走 --resume 复用 run_id)。
其余端点:GET /api/nodes、GET /api/instances(desired+actual 合并视图)、GET /api/instances/:id/status(直查实例实时状态,透传 admin token)、GET/POST /api/routes、DELETE /api/routes/:id、GET /api/events(集群事件历史)、GET/POST /api/flows。
$ curl -H "Authorization: Bearer plane-tok" -X POST http://127.0.0.1:9600/api/routes \
-H "Content-Type: application/json" -d '{
"id": "r1", "path": "/webhook/orders", "flow_id": "orders",
"mode": "proxy", "strategy": "round_robin", "sticky_key": "X-Order-Id"
}'
$ curl -X POST http://127.0.0.1:9600/gw/webhook/orders # 转发到候选实例触发器
mode: proxy(默认):反向代理到实例 http 触发器,同步请求-响应;候选 = actual=running 且有 trigger 地址的实例;失败重试下一个候选(共 2 个),全挂 503。mode: bus:显式声明,发布即返回 202——请求体进入 topic 的中继邮箱,由订阅该 topic 的实例 queue 触发器竞争消费(异步任务)。strategy: round_robin | hash;hash 对 sticky_key 头做确定性排序(弱 sticky:实例增减允许漂移,正确性靠业务幂等键)。tags/least-loaded 延后。实例只需在声明里带 relay 地址(启动时等价于 sohara serve --relay <plane>):
sink: { type: queue, config: { topic: orders.events } } 自动本地扇出 + 转发 plane;triggers: [{ id: bus, type: queue, topic: orders.events }] 原样消费(plane 每 500ms 推送注入本地总线)。投递语义:尽力投递,每主题有界 1000 条(超限丢最旧),每批 100 条按游标增量。plane 按稳定订阅者 id(实例 admin 地址)保存游标下限——实例重启不重放已确认消息;plane 重启会丢内存游标(重放邮箱尾部),消费端应以业务幂等键消重;持久化/至少一次待 D5b(NATS/JetStream,可选后期项)。
浏览器打开 http://127.0.0.1:9600/ui(需 Bearer token):
/admin/status 实时数据。三个信任域、三向 token(均为可选项,建议生产全开):
| 通道 | token | 校验方 |
|---|---|---|
| plane ↔ agent(心跳/命令) | plane --token / agent.yaml plane.token |
plane |
| plane ↔ 实例 admin(状态直查) | 声明中的 spec.admin_token |
实例 |
| 实例 ↔ plane relay | spec.relay_token / serve --relay-token |
plane(与 plane token 同中间件) |
单机 admin UI 默认仅显式 --admin 开启,建议绑定 loopback/内网。mTLS、控制面 HA、Gateway 前置 LB 为延后增强(单点已接受,见设计文档 §9)。
.github/workflows/ci.yml 运行全量验证——cargo fmt --check、cargo clippy --all-targets -D warnings、cargo build --workspace --locked、cargo test --workspace --locked、文件/函数长度门禁。vXX.YY.ZZ 或 vXX.YY.ZZ-AAA 格式的 tag(如 v0.2.0、v0.2.0-alpha)触发 .github/workflows/release.yml——按 matrix 构建四个平台并发布到 GitHub Release(自动生成 release notes):| 平台 | 产物 |
|---|---|
| windows x64 | sohara-win-x64.zip |
| linux x64 | sohara-linux-x64.tar.gz |
| linux arm64 | sohara-linux-arm64.tar.gz |
| macOS arm64 | sohara-apple-darwin-arm64.tar.gz |
每个产物包含 sohara、sohara-agent、sohara-plane 三个二进制。
$ git tag v0.2.0-alpha && git push origin v0.2.0-alpha
注:
vXX.YY.ZZ无后缀同样触发发布;其他格式(如v1.2.3-alpha.1带点的后缀)不会触发发布,但仍会运行 ci.yml 的全量验证。
HTTP_PROXY 环境变量——agent/plane/relay 客户端已内置 no_proxy,但外部 curl 请加 --noproxy '*'。--admin 与触发器端口勿相同)或 bin 路径错误;看 agent 日志的 [id] spawn failed 与 restart budget spent。log sink。曾有一次「写后立即读为空」的 tokio 缓冲写竞态,已在 FileSink::write 内显式 flush 修复,完整排查报告见 reports/tokio-write-visibility-race.md。relay 地址、queue 触发器 topic 与发布 topic 一致;plane 日志可见 /relay/publish 202;用 /api/events 与实例 /admin/errors 排查。path 为前缀(/gw 后拼接);mode: bus 的路由缺 topic 返回 400。policy.health_failures 默认 3,本地探测 1s 一次,约 3s 判定 unhealthy 并按 backoff_ms 退避重启。