feat(session-mechanism/graph): C 方案——条件分支、子图、人审、取消、分池、自动派活、进度

This commit is contained in:
admin committed 2026-10-19 21:37:51 +08:00
1 parent 3b5bcc535a
commit 0c62e1b350
3 files changed
+1016 -39

No files matched your search

+97 -3
View File
@@ -178,6 +178,92 @@
python graph/graphctl.py expand --ws "<ws>" --gid "<gid>" --node P [--force]
## 三之五、条件分支(`when` + `on_dep_fail`)
节点加 `when` 子句就变成有条件的节点:条件全成立才放行,任一被证伪直接 `skipped`。
{"id": "B", "deps": ["A"], "deps_rule": "all_done",
"when": [{"node": "A", "status": "done"}]}
{"id": "C", "deps": ["A"], "deps_rule": "all_done",
"when": [{"node": "A", "status": "blocked"}]}
`A` 成了走 `B`、`A` 败了走 `C`,就是这样写出来的。判「成 / 败」用 `deps_rule: all_done`(`blocked` 也算有定论),用默认的 `all_success` 会先卡在依赖上。
上游跑不成时下游怎么办,由 `on_dep_fail` 决定,三档:
1. `wait`(默认)—— 停在待办里等,与旧行为一致。
2. `skip` —— 连坐跳过,一路往下跳。
3. `block` —— 整枝停住,进 `blocked`(`blocked_by: dep`)。
`skipped` 是终态:不会再被派活、不算进进度分母、下游按 `on_dep_fail` 继续传。
## 三之六、子图
节点内部再拆流程,用 `subgraph` 内联写,展开成 `<父id>/<子id>`:
{"id": "P", "domain": ["docs/"], "subgraph": {"nodes": [
{"id": "s1", "accepts": ["文档写完了"]},
{"id": "s2", "deps": ["s1"], "accepts": ["索引登记了"]}]}}
规则四条:
1. 子节点 id 自动加 `<父>/` 前缀,内部 `deps` 也跟着映射过去,所以子图里怎么写 `deps` 都行。
2. 子节点**默认继承父节点域**(子流程通常写同一块);要分片就用 `subgraph.child_domain`,支持 `{id}`。
3. 父节点展开后进 `gating`,子节点全 `done` ⇒ 父 `done`;子节点全定下来但有不满意的 ⇒ 父 `blocked`(`blocked_by: child_failed`)。
4. `map` 与 `subgraph` 只能选一个,同时写直接报错。
`subgraph` 里还能再套 `subgraph`,前缀会一层层叠上去。
## 三之七、人审节点与取消
人审:节点写 `"approval": true`,回报 `done` 后不进 `done`,而是进 `awaiting` 等人过目:
python graph/graphctl.py approve --ws "<ws>" --gid "<gid>" --node A --by user
python graph/graphctl.py approve --ws "<ws>" --gid "<gid>" --node A --reject --reason "不合格"
通过 ⇒ `done`(记 `approved_at` / `approved_by`);驳回 ⇒ `blocked`(`blocked_by: rejected`)。下游在 `awaiting` 期间一律不派。
取消:`cancelled` 是终态,会连坐子节点:
python graph/graphctl.py cancel --ws "<ws>" --gid "<gid>" --node P --reason "改需求"
python graph/graphctl.py cancel --ws "<ws>" --gid "<gid>" --all
已经 `done` 的节点不被取消(结果不能撤回)。下游按 `on_dep_fail` 处置,配 `skip` 就整枝跳过。
## 三之八、自动派活与排期
`tick` 是「算完就开」:一次调用把超时回收、展开、条件判定推到不动点,再按就绪集直接把节点开成 `running`。
python graph/graphctl.py tick --ws "<ws>" --gid "<gid>" --by tick [--limit N] [--dry-run]
输出 `started`(这次开了谁)、`missed`(算的时候还行、写的时候被别人改了,通常为空)、`ready_now`(下一批)、`slots`(还剩几个名额)。
`plan` 给上层排期用,出的一行已经把四条硬规则写成规范形态:
python graph/graphctl.py plan --ws "<ws>" --gid "<gid>" [--scope "<线名>/"] [--after-sec 240]
1. `cwds` —— 正斜杠绝对路径。
2. `workspace_scope` —— 线名加尾斜杠,默认取工作区目录名。
3. `next_run_at` —— 毫秒 epoch。
4. `scheduled_at` —— ISO 本地时间。
`plan` 只产出行,**不写宿主库** —— 图模式零侵入这条不变,落库由上层一条命令完成。
## 三之九、按域分池与进度
分池:同域并发单独限流,与 `max_parallel` 叠加生效。
python graph/graphctl.py init ... --pool "docs/ui/=1" --pool "server/=2"
{"pools": {"docs/ui/": 1}} # 写进 nodes-json 也可以
判定用的是域前缀重叠:节点域 `docs/ui/a/` 落进 `docs/ui/` 这个池。
进度与关键路径:
python graph/graphctl.py progress --ws "<ws>" --gid "<gid>"
输出 `total` / `by_status` / `percent`(分母排除 `skipped` 与 `cancelled`)、`blocked` / `awaiting`、`critical_path`(**未完成节点之间的最长依赖链**,告诉你哪条链决定工期)。
## 四、与现有机制的关系
| 事项 | 现有机制 | 图模式 |
@@ -195,7 +281,7 @@
python graph/test_graphctl.py -v
全绿应看到 `Ran 92 tests ... OK`,用时 70 秒上下(每条用例起若干次子进程;52 条是原有基线,40 条是 2026-10-19 为三件批量能力补的)。
全绿应看到 `Ran 122 tests ... OK`,用时 100 秒上下(每条用例起若干次子进程;52 条是初版基线,40 条是 2026-10-19 为三件批量能力补的,30 条是同日为条件分支/子图/人审/取消/分池/派活/进度补的)。
测试只往临时目录写:`<GRAPH_TEST_TMP>/graph-test-<pid>/<用例名>`,默认 `GRAPH_TEST_TMP` = `AIProject/ai1net-dsh-server/tmp`。每条用例只**新建**自己的目录、跑完不删,整轮收尾由 `tearDownModule` 清一次;这样一次跑下来新建几十个文件、不触发本机沙箱的「批量删除」守卫(那个守卫一次删满 50 个文件就会拦,曾让 20 条用例报 ERROR,而用例本身零失败)。想换落点就设环境变量 `GRAPH_TEST_TMP`。
@@ -216,7 +302,7 @@
rm -rf "E:/ProgramData/AIProject/ai1net-dsh-server/tmp"/graph-test-*
十二个用例类各管一摊:
十九个用例类各管一摊:
1、`T1_Init` —— 建图:`--kpi` 按条数生成节点(全角分号也认)、`--nodes-json` 带依赖与域、重复 id 报错、自动编号避让、`max_parallel`。
2、`T2_CheckErrors` —— 硬错三条:环(含自环)、依赖指向不存在的节点、状态非法。
@@ -230,14 +316,22 @@
10、`T10_Lease` —— 租约:start 记租约、超时回收(回 pending / block / keep 三种策略)、回收后名额还给别人、节点级 `lease_sec` 覆盖全局、`--lease-sec 0` 关闭、手改的无租约 running 不误杀、回收写事件。
11、`T11_Retry` —— 重试与触发:重排写 `not_before`、等待窗内不派、`fixed`/`linear`/`exponential` 三种间隔、超限进 blocked 且 `retry_exhausted`、默认不自动重试、`all_success`/`all_done`/`one_success` 三种触发、非法 `deps_rule` 双处拦截。
12、`T12_Map` —— 展开:`over` 列表、N=0 立即完成、从 evidence/artifacts 读 N 项、子节点全完成收敛父节点、N=10 但 cap=2 只放 2 个、`child_domain` 模板、默认不继承域、父节点不许 start/report、`expand` 命令与 `--force`、子节点 attempts 独立。
13、`T13_Condition` —— 条件分支:A 成走 B、A 败走 C、跳过向下游级联、非法 `on_dep_fail` 双处拦截。
14、`T14_Subgraph` —— 子图:展开成 `<父>/<子id>`、内部 deps 加前缀、子节点全成父 done、有子节点失败父 blocked、空子图直接完成、与 `map` 互斥。
15、`T15_Approval` —— 人审:`done` 后停在 `awaiting`、下游不放行、通过后放行、驳回进 `blocked`、非 `awaiting` 拒批。
16、`T16_Cancel` —— 取消:连坐子节点、`--all`、不动已 `done`、下游按 `on_dep_fail` 跳过、缺目标报错。
17、`T17_Pools` —— 分池:同域只放 1 个、`--pool` 落进 `meta.pools`、上限 0 被拒。
18、`T18_Dispatch` —— 派活:`tick` 按并发上限开、`--dry-run` 不动状态、等依赖、`--limit`;`plan` 四条硬规则形态。
19、`T19_Progress` —— 进度:`percent` 与 `by_status`、关键路径跳过已完成、全完成 100%。
测试对 `graphctl.py` 的唯一要求是「能当命令行跑」,不 import 它的内部函数 —— 所以改内部实现不会连带改测试。
## 六、还没做的(下一步)
- 自动派活:读 `ready` 后自动建排期(要复用现有排期硬规则:cwds 正斜杠、workspace_scope、next_run_at 毫秒、scheduled_at ISO)。
- 判定节点完成:现在靠回报 `--state done`,还没有「看证据够不够」的自动判定。
- 回退边:`attempts` 与重试已在记,但还没有「判不过回到某个上游节点」的字段(现在的回退只有"自己重试")。
- 展开源变化:`map.from` 只在上游 `done` 之后读一次,上游重跑(回退)不会重新展开。
- 排期落库:`plan` 只出规范行,写进宿主排期表由上层完成。
- 跨图子图:现在子图只能内联,还不能引用另一张图。
这几件都只在本目录内加,不影响现有机制。