dsh-vibe-math 2.3.12 → 2.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +17 -13
  2. package/cordis.patch.yml +1 -1
  3. package/{AUDIT-CHECKLIST.md → docs/AUDIT-CHECKLIST.md} +323 -304
  4. package/docs/COMPAT-AUDIT-ROUND2.md +325 -0
  5. package/docs/generate_framework_diagram_v2.py +114 -0
  6. package/docs/generate_framework_diagram_v3.py +127 -0
  7. package/{RELEASE-NOTES-2.1.0.md → docs/release-notes/RELEASE-NOTES-2.1.0.md} +143 -143
  8. package/{RELEASE-NOTES-2.2.0.md → docs/release-notes/RELEASE-NOTES-2.2.0.md} +266 -266
  9. package/{RELEASE-NOTES-2.2.1.md → docs/release-notes/RELEASE-NOTES-2.2.1.md} +43 -43
  10. package/{RELEASE-NOTES-2.2.2.md → docs/release-notes/RELEASE-NOTES-2.2.2.md} +88 -88
  11. package/{RELEASE-NOTES-2.3.0.md → docs/release-notes/RELEASE-NOTES-2.3.0.md} +207 -207
  12. package/{RELEASE-NOTES-2.3.1.md → docs/release-notes/RELEASE-NOTES-2.3.1.md} +134 -134
  13. package/{RELEASE-NOTES-2.3.10.md → docs/release-notes/RELEASE-NOTES-2.3.10.md} +105 -105
  14. package/{RELEASE-NOTES-2.3.11.md → docs/release-notes/RELEASE-NOTES-2.3.11.md} +57 -57
  15. package/{RELEASE-NOTES-2.3.12.md → docs/release-notes/RELEASE-NOTES-2.3.12.md} +80 -80
  16. package/docs/release-notes/RELEASE-NOTES-2.3.13.md +137 -0
  17. package/docs/release-notes/RELEASE-NOTES-2.3.14.md +83 -0
  18. package/{RELEASE-NOTES-2.3.2.md → docs/release-notes/RELEASE-NOTES-2.3.2.md} +145 -145
  19. package/{RELEASE-NOTES-2.3.3.md → docs/release-notes/RELEASE-NOTES-2.3.3.md} +115 -115
  20. package/{RELEASE-NOTES-2.3.4.md → docs/release-notes/RELEASE-NOTES-2.3.4.md} +69 -69
  21. package/{RELEASE-NOTES-2.3.5.md → docs/release-notes/RELEASE-NOTES-2.3.5.md} +63 -63
  22. package/{RELEASE-NOTES-2.3.6.md → docs/release-notes/RELEASE-NOTES-2.3.6.md} +66 -66
  23. package/{RELEASE-NOTES-2.3.7.md → docs/release-notes/RELEASE-NOTES-2.3.7.md} +59 -59
  24. package/{RELEASE-NOTES-2.3.8.md → docs/release-notes/RELEASE-NOTES-2.3.8.md} +45 -45
  25. package/{RELEASE-NOTES-2.3.9.md → docs/release-notes/RELEASE-NOTES-2.3.9.md} +70 -70
  26. package/docs/test-timing.md +19 -18
  27. package/installer.js +115 -41
  28. package/package.json +43 -37
  29. package/{audit-formal-sensitivity.mjs → tests/audit-formal-sensitivity.mjs} +342 -342
  30. package/{audit-installer-compat.test.mjs → tests/audit-installer-compat.test.mjs} +136 -136
  31. package/tests/audit-installer-policy.test.mjs +261 -0
  32. package/{audit-persona-sensitivity.mjs → tests/audit-persona-sensitivity.mjs} +249 -249
  33. package/{audit-persona-surface.test.mjs → tests/audit-persona-surface.test.mjs} +349 -349
  34. package/{audit-prompt-invariants.mjs → tests/audit-prompt-invariants.mjs} +508 -508
  35. package/{audit-spec-traceability.mjs → tests/audit-spec-traceability.mjs} +193 -193
  36. package/{audit-v5-integrity.mjs → tests/audit-v5-integrity.mjs} +448 -448
  37. package/{audit-v5-sensitivity.mjs → tests/audit-v5-sensitivity.mjs} +384 -384
  38. package/{e2e-v5-round2.test.mjs → tests/e2e-v5-round2.test.mjs} +521 -521
  39. package/{formal-verify-v2.test.mjs → tests/formal-verify-v2.test.mjs} +1315 -1315
  40. package/{formal-verify-v3.test.mjs → tests/formal-verify-v3.test.mjs} +1257 -1257
  41. package/{formal-verify-v4.test.mjs → tests/formal-verify-v4.test.mjs} +1082 -1082
  42. package/{formal-verify-v5.test.mjs → tests/formal-verify-v5.test.mjs} +708 -708
  43. package/{prompt-v5-integrity.test.mjs → tests/prompt-v5-integrity.test.mjs} +3 -3
  44. package/{run-tests.mjs → tests/run-tests.mjs} +121 -118
  45. package/{selfdrive-v5.mjs → tests/selfdrive-v5.mjs} +470 -470
  46. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  47. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  48. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +1 -1
  49. /package/{RELEASE-NOTES-2.0.22.md → docs/release-notes/RELEASE-NOTES-2.0.22.md} +0 -0
@@ -0,0 +1,325 @@
1
+ # Vibe-Mathematics × DSH 0.1.5-rc.2 —— 第二轮独立兼容性审计
2
+
3
+ 审计对象:`dsh-vibe-math` 2.0.21,分支 `fix/dsh-0.1.5-compat-audit`(HEAD `9ba893c`)
4
+ 宿主:DSH **0.1.5-rc.2**(`@deepseek-ai/dsh` package.json),node v24.21.0,win32
5
+ 方法:**活体契约目录**(Host Inspect `Service.listService` / `Event.listEvents` / `Tool.listTools`)+ 宿主包源码逐行核对 + 可执行复现脚本
6
+
7
+ > 本文件替代 `COMPAT-AUDIT-0.1.5-rc.2.md` 作为当前兼容性结论。
8
+ > 上一轮审计提出的 R1–R7 **从未被修复**(在 `FIX-REPORT-2026.md` 中检索 R1–R7 零命中),本文件逐条重新独立验证。
9
+
10
+ ---
11
+
12
+ ## 0. 摘要
13
+
14
+ | 类别 | 结论 |
15
+ |---|---|
16
+ | 版本声明 | 已装 DSH 就是 **0.1.5-rc.2**,与 `testedVersion` 一致;npm `latest`/`next` 同为 0.1.5-rc.2,另有更新的预发布 `0.1.6-alpha.2`(`alpha` tag) |
17
+ | 预设能否挂载 | ✅ 真宿主 discovery **3 discovered / 0 broken / 57 rows / 0 failing** |
18
+ | persona 修复 | ✅ 有效(双 schema 兼容) |
19
+ | 服务方法名 | ✅ 插件调用的每个 (service,method) 都存在于活体契约;唯一调用了不存在方法的 `subagents.followup` 全部 4 处都在 `typeof` 特征检测链上(见 §2) |
20
+ | 工具/命令注册形状 | ✅ `output:{schema,render}`、`handler`+`input.hint` 均合法 |
21
+ | **功能性缺陷** | ❌ **9 条**(F-1…F-4c),其中最严重的是 F-1 与 F-2 |
22
+ | 测试覆盖缺口 | ❌ 现有 380 项测试**全部用 mock 宿主**,且现有"真宿主 e2e"只做 discovery+schema、**跳过三个本地插件行** |
23
+ | 可执行证明 | ✅ F-1 与 F-2 均有**驱动真实宿主类**的复现脚本,且 F-1 的证明同时验证了修法可行 |
24
+
25
+ ---
26
+
27
+ ## 1. 确证的功能性缺陷
28
+
29
+ ### F-1 v4 的真实 `/compact` 是死代码(高)
30
+
31
+ `realCompact()` 在 `subagent/end` 里取 `agents.get(r.childId)`,但宿主在该事件触发前**已经**把该 child 从 agent 注册表移除。
32
+
33
+ **宿主证据链(按执行顺序)**
34
+
35
+ | # | 位置 | 代码 |
36
+ |---|---|---|
37
+ | 1 | `dsh-subagent/lib/index.js:1231` | `await activation.handle.dispose()` ← 先销毁 handle |
38
+ | 2 | `dsh-agent/lib/index.js:490` / `:508` | dispose 走 detach 闭包 → `this.store.delete(entry.id)` |
39
+ | 3 | `dsh-subagent/lib/index.js:1241` | `activation.observer.settle(failure)` ← **之后**才 emit |
40
+ | 4 | `dsh-subagent/lib/index.js:352` | `emit("subagent/end", {...identity, stopReason, lastAssistantMessage}, parent)` |
41
+
42
+ **插件侧**:`vibe-math-v4.js:727` `agent = agents.get(r.childId)` → 必然 `undefined` → `:728` `if(!agent || !agent.session) return`。
43
+
44
+ **后果**:`compactIfNeeded` 从未执行;`:735-736` 的 `r.needCompact=true`/`logActivity('compact', …)` 不可达。
45
+ **缓解**:软压缩仍在工作(resident 自报 `contextPct` ≥ `compactThreshold`(66) → 自我总结指令,`:328-334`),所以不是"完全不压缩",而是**永远只依赖模型自报,引擎侧压缩从未发生**。
46
+ **修法**:在 `subagent/start`(`vibe-math-v4.js:1155` 已监听 `subagent/end`,可在同处增设 start 监听)缓存活的 Agent 引用;`CompactionAgentContext` 只需要 `{session, options}`(活体契约),不查注册表。
47
+
48
+ ### F-2 v2/v3 的工具权限过滤器会被宿主拒绝(高)
49
+
50
+ 原审计只发现 `web`/`fetch`;**实测更严重**:`bash` 在 Windows 上同样未注册。
51
+
52
+ **机制(双侧)**
53
+ - 插件:`subagents.startContinuable({ request: { toolFilter } })`
54
+ - 宿主:`dsh-subagent/lib/index.js:554` `if (composition.toolFilter !== void 0) childCtx.tools.restrict(composition.toolFilter);`
55
+ - 宿主:`dsh-tools/lib/index.js:2801-2803` 对未知名**抛错**
56
+ - 宿主:`dsh-tools/lib/index.js:2864-2867` `restrictableNames` 只含**注册表真正持有**的工具
57
+
58
+ **实测(`audit-toolfilter-live.mjs`,win32)**
59
+
60
+ | 列表 | 值 | 未注册项 |
61
+ |---|---|---|
62
+ | `vibe-math-v2.NETWORK_TOOLS` | `[web_search, web, fetch]` | `web`, `fetch` |
63
+ | `vibe-math-v2.SCRIPT_TOOLS` | `[bash, pwsh]` | **`bash`**(`tool-bash` 行 `disabled: process.platform==='win32'`) |
64
+ | `vibe-math-v3.NETWORK_TOOLS` | 同 v2 | `web`, `fetch` |
65
+ | `vibe-math-v3.SCRIPT_TOOLS` | 同 v2 | **`bash`** |
66
+
67
+ 宿主真实网络工具名是 `web_search` / `web_fetch`(`dsh-tool-web/lib/index.js:262,737`)——**没有** `web`/`fetch`(那是 presentation 的 card/kind 字段)。
68
+
69
+ **触发条件**:`solverAllowNetwork:false` / `solverAllowScripts:false`(默认 `''`=继承,属**用户主动收紧权限**的路径);v2 另加 `*AllowNetwork:true` 且已有 allow 列表的分支。
70
+ **后果**:v2/v3 是 fail-closed(`:518` / `:1014` 明确不再重试去掉过滤器),所以症状是**该子代理永远起不来**、调度卡在该任务,而不是权限被静默放开。
71
+ **修法**:不要硬编码名表。deny/allow 应只包含**当前预设在该平台真正注册**的名字;或保留名表但加"剔除未注册项"的守卫。注意 v4 预设 `tool-web` 是 `fetch: false`,硬编码 `web_fetch` 同样会错。
72
+
73
+ ### F-3 v4 的注册不纳入 `ctx.effect()`(中)
74
+
75
+ | 版本 | 位置 | 写法 |
76
+ |---|---|---|
77
+ | v2 | `vibe-math-v2.js:1508` | `ctx.effect(() => tools.register({…}))` ✅ |
78
+ | v3 | `vibe-math-v3.js:2770` | `ctx.effect(() => tools.register({…}))` ✅ |
79
+ | **v4** | `vibe-math-v4.js:1088` | `tools.register({…})` ❌ 丢弃 disposer |
80
+ | v2 | `vibe-math-v2.js:1545` | `ctx.effect(() => commands.register({…}))` ✅ |
81
+ | v3 | `vibe-math-v3.js:2815` | `ctx.effect(() => commands.register({…}))` ✅ |
82
+ | **v4** | `vibe-math-v4.js:1131` | `commands.register({…})` ❌ 丢弃 disposer |
83
+
84
+ 宿主这两个方法**都返回 disposer**(活体契约 `tools.register(): () => void`、`commands.register(): () => void`)。
85
+ **后果**:预设卸载/HMR 后注册不回收;同进程内第二次挂载同一预设会撞名。v4 的**形状本身是对的**(命令 `handler`/`input.hint` 经 `dsh-commands/lib/types/index.js:134-142` 校验)。
86
+
87
+ ### F-4 `inject` 未声明实际读取的可选服务 → 时序敏感(中)
88
+ `audit-surface-vs-live.mjs` 机械提取结果:
89
+
90
+ | 插件 | `export const inject` | 用 `ctx.get` 读但**未声明** |
91
+ |---|---|---|
92
+ | v2 | `subagents, agents, fs, tools, commands` | `subprocess`, `sandboxPolicy` |
93
+ | v3 | 同上 | `subprocess`, `sandboxPolicy` |
94
+ | v4 | `+ timer` | `subprocess`, `sandboxPolicy`, `compaction` |
95
+
96
+ 三处都在 `apply()` 里**同步快照**:`v2:35-36`、`v3:56-57`、`v4:16-18`。
97
+
98
+ **后果**:若这些服务在预设子树挂载时尚未 provide,快照永久为 `undefined`:
99
+ - `subprocess === undefined` → `runShell` 恒返回 `{ok:false,error:'no-subprocess'}` → `ensureDirs()`(`v4:158`,在 `:905/:925/:1051` 被调用)失效;**仅因为宿主 `fs.writeText` 会自动建父目录才没暴雷**,属静默降级。
100
+ - `sandboxPolicy === undefined` → `getPolicy()` 返回 `undefined`(见 F-5)。
101
+ - `compaction === undefined` → F-1 的路径提前 return(与 F-1 叠加)。
102
+
103
+ > 注:`subprocess` 服务宿主**确实提供**(`dsh-base/cordis.patch.yml:199` 挂载 `@deepseek-ai/dsh-subprocess-local`),缺的是**挂载顺序保证**。
104
+ > **修法**:把这三个加入 `inject`(硬依赖语义,服务到位后自动激活),或改成调用时惰性 `ctx.get()`。
105
+
106
+ ### F-4b 隔离 realm:插件取到的是**宿主根** compaction,预设自己的配置不生效(中)
107
+
108
+ 三套预设都在同一个位置声明了隔离组:
109
+
110
+ | 预设 | 组 | isolate | 插件行位置 |
111
+ |---|---|---|---|
112
+ | v2 | `agent.cordis.yml:170-188` | `{compaction: true, toolResultPruner: true}` | `:254`(组**外**) |
113
+ | v3 | `agent.cordis.yml:191-209` | 同上 | `:276`(组**外**) |
114
+ | v4 | `agent.cordis.yml:212-230` | 同上 | `:299`(组**外**) |
115
+
116
+ 组内是 `compaction-basic` + `command-compact` + `tool-result-pruner`(`thresholdChars: 8192 / headChars: 4096 / tailChars: 1024`)。
117
+ 按 cordis 隔离语义,`isolate: {compaction: true}` 把该服务放进**该组私有 realm**;插件行与组**平级**,其 `ctx.get('compaction')`(v4:18)解析到的是**宿主根**实例,**不是**组内实例。
118
+
119
+ **后果**:预设为压缩/裁剪写的阈值配置对插件侧调用**不生效**;插件实际用的是宿主根 compaction(使用宿主的策略与阈值)。这与 F-1 叠加:即便修好 F-1,压缩行为仍不是预设作者配置的那一套。
120
+ **修法**:若希望用预设内实例,把插件行移进该组(或让插件按 `agent.ctx` 取服务)。**但需先确认这是否有意为之**——把插件行移进 realm 也会影响它对该组其它服务的可见性。
121
+
122
+ ### F-4c 状态目录创建依赖外部二进制(低,但属实机才会暴露)
123
+
124
+ `ensureDirs()`(`v4:158`)走 `runShell(mkdirCmd(...))`,最终 `subprocess.spawn(['powershell', …])`(`:131`)或 `/bin/sh -c mkdir -p`(`:132`)。
125
+ `runShell` 的返回值**在 `ensureDirs` 的调用点无人检查**(`:905/:925/:1051` 都是 `await ensureDirs()` 后继续)。
126
+ **后果**:若 `powershell`/`sh` 不可用或返回非 0,目录不会被创建,而唯一的兜底是 `fs.writeText` 自动建父目录;一旦某条写入路径不经过 `fs.writeText`,落盘就会失败且**没有任何报错**。属"mock 覆盖不到、实机才暴露"的一类。
127
+
128
+ ### F-5 `getPolicy()` 回退会静默改变沙箱围栏根(中)
129
+
130
+ `v2:140`、`v3:206`、`v4:113` 三处同构:
131
+
132
+ ```js
133
+ try { … sandboxPolicy.resolve({ session }) } catch(e){}
134
+ try { … sandboxPolicy.resolve({}) } // ← 回退
135
+ catch(e){} return undefined
136
+ ```
137
+
138
+ 宿主 `dsh-sandbox-policy/lib/index.js:141-145`:未传 session 时用 `resolveWorkspaceRoot(policy.workspaceRoot)`,而该值来自 `config.workspaceRoot ?? process.cwd()`(`:113`)。
139
+ **后果**:回退路径把"写权限围栏根"从**会话工作区**换成**进程 cwd 或配置根**,可能**放宽**可写范围。属静默安全降级,且异常被吞(F-6)。
140
+
141
+ ### F-6 静默吞异常清单(中,放大所有其他风险)
142
+
143
+ 最相关的是宿主服务调用处的"静默 catch":
144
+
145
+ | 位置 | 吞掉什么 | 一旦宿主改名/改语义的后果 |
146
+ |---|---|---|
147
+ | `v2:140` / `v3:206` / `v4:113` | `sandboxPolicy.resolve` 失败 | 围栏根静默改变(F-5) |
148
+ | `v2:503` / `v3:977` / `v4:85` | `subagents.list()` 失败 | 静默回退 `'spawn'`,provider 名错误时无法察觉 |
149
+ | `v2:539` / `v3:1036` / `v4:916,993,1057` | `subagents.interrupt` 失败 | 子代理无法中断,但表面正常 |
150
+ | `v4:738` | `compaction.compactIfNeeded` 失败 | 与 F-1 同类,压缩失败无人知 |
151
+ | `v2:217,221` / `v3:297,301` / `v4:161,162` | `fs` 读写当前项目 | 项目切换丢失,静默回默认 |
152
+
153
+ ### F-7 v3/v2 用 realm 全局 `setInterval` 而非 `ctx.interval`(低,修正原 R6)
154
+
155
+ `v2:1573`、`v3:2842`:`ctx.effect(() => { const t = setInterval(…1000); return () => clearInterval(t) })`
156
+
157
+ **修正原审计判断**:`setInterval` 在真实插件运行时**是存在的**(预设插件行是普通 node ESM,不是受限的 dynamic-cordis 沙箱),且**已正确清理**——所以这**不是功能故障**,只是未使用宿主提供的 `timer` 服务(`ctx.interval` 能随 fiber 自动释放)。降级为**建议**。
158
+
159
+ ### 附:F-8 安装器自检覆盖不到上述问题(中)
160
+
161
+ `installer.js:115-123` 的能力自检只检查 `subagents/agents/tools/commands/fs` 的**方法存在性**,加上 `:139-147` 的 `fs.resolve` 外形检查。它**没有检查** `compaction`、`subprocess`、`sandboxPolicy`——恰好是 F-1/F-4/F-5 所在的服务。因此这类问题永远不会在安装时被报告。
162
+
163
+ ---
164
+ ### F-9 损坏的 JSON 状态文件会被静默覆盖(高,本轮复审新发现)
165
+ `readJson` 无法区分"文件不存在"与"文件存在但不可解析":两者都返回 `undefined`,调用方把它当作"无数据",随后又把这份空数据写回。于是一个被外部损坏的文件会**静默清空用户数据**:
166
+
167
+ | 版本 | 受害文件 | 后果 |
168
+ |---|---|---|
169
+ | v2 | `qs/qs.json` | 全部问题清单被清空(`add_problem` 读空→写回空) |
170
+ | v2 | `Propos/<分类>_Propos.json`、`Verified/<分类>_Verified.json` | 命题/已验证集合被清空 |
171
+ | v3 | `State/agents.json`、`decision_queue.json`、`tasks.json` 等 | 整轮调度状态复位 |
172
+ | v4 | `State/session.json`、`residents.json`、`taskboard.json`、`mailboxes.json` | 整轮运行状态复位 |
173
+
174
+ **修法**:读取命中"存在但不可解析"时记录该路径并告警;写入该路径时**拒绝覆盖**,直到用户修复或删除。文件不存在仍会正常创建,首次运行行为不变。v4 另在写前惰性检查磁盘内容,覆盖"本进程只 configure/start、从未 `loadAll`"的路径(该路径下 `residents.json` 从未被读过,仅靠读时记录抓不到)。
175
+ **测试**:`audit-corrupt-file-guard.test.mjs` **6 passed**,并对三个版本分别做了敏感性验证(关掉守卫则对应用例失败)。
176
+
177
+ ### F-10 F-2 的失败重试在极端情况下不够 fail-closed(中,本轮复审自纠)
178
+
179
+ F-2 引入的"按宿主合法名重试"若把过滤器**全部**名字都过滤掉,原实现会删除过滤器并继续派发 —— 这会在"用户只写了本机不存在的工具名"时**放开全部权限**,与 fail-closed 意图相反。
180
+ **修法**:过滤后为空即视为配置无法满足,打日志并**拒绝派发**该子代理(宁可这一轮不派,也不越权)。已 apply 到 v2 与 v3。
181
+
182
+ ### F-11 纯函数鲁棒性(本轮验证通过,非缺陷)
183
+
184
+ `audit-fuzz-helpers.mjs` 把三个插件的单参纯函数抽出(`parseJson`/`parseProgress`/`parseMethodMd`/`splitHeader`/`idSafe`/`clamp` 系列等),用 19 类恶意输入各调用两次(空串、截断 JSON、超长串、NUL、代理对、`NaN`/`Infinity`、`1e999` 等),共 **1654 次调用**:0 抛异常、0 返回 Promise。这同时回归确认了此前几轮修的卡片往返与锚点解析在畸形输入下不会崩。
185
+
186
+ ---
187
+
188
+ ## 2. 已确证**正确**的部分(避免重复劳动)
189
+
190
+ | 项 | 证据 |
191
+ |---|---|
192
+ | persona 行兼容 | 真宿主 discovery 3/0/57/0;`prefix`+`text` 双键 |
193
+ | 服务方法名 | `audit-surface-vs-live.mjs`:17 个 (service,method) 组合对活体契约全部 `OK` |
194
+ | `subagents.startContinuable(spec)` 形状 | `{provider,label,request:{prompt,parent,agentOptions,toolFilter},signal}` 与 `ContinuableStartSpec` 一致 |
195
+ | `subagents.sendMessage(sender,targetId,content,{signal})` | 4 参顺序与活体契约一致 |
196
+ | `subagents.interrupt(id,{kind:'ancestor',agent})` | 与 `SubagentInterruptAuthority` 一致 |
197
+ | `fs.writeText(target,content,undefined,undefined,policy)` | 5 参顺序正确 |
198
+ | `stat` 缺失返回 `undefined` | 插件依赖此语义,契约成立 |
199
+ | `tools.register` 必填 `output:{schema,render}` | 三套全部提供 |
200
+ | `commands.register` 形状 | `handler` + `input.hint` 经 `dsh-commands:134-142` 校验通过 |
201
+ | `compaction.compactIfNeeded(agent,trigger,signal)` | 三参正确,`trigger='pressure'` 合法 |
202
+ | `subprocess.spawn(spec)` | 形状正确,服务宿主确实提供 |
203
+ | `subagent/end` 监听签名 | 宿主 emit **单参** `info`;三套均 `function(info)` 并只读 `info.id`/`info.stopReason`,**签名正确** |
204
+ | `subagents.followup` | 活体契约中**不存在**,但 4 处调用点全部在 `typeof … === 'function'` 链上,是**故意的跨版本回退**,非缺陷 |
205
+ ## 3. 测试覆盖缺口(重要)
206
+
207
+ | 缺口 | 说明 |
208
+ |---|---|
209
+ | mock 宿主 | 380 项测试全用约 30 行 mock `ctx`;**"宿主是否真这样调用"不在覆盖内** |
210
+ | 定时器路径 | mock 有 `ctx.timeout`,但测试一律用显式 `fireEnd()` 推进,**定时唤醒通道未被覆盖** |
211
+ | `runShell` | v3 mock 用正则假装执行 PowerShell;v4 的 `subprocessMock` 直接 `{exitCode:0}` **不建目录** → 跨平台行为未被真跑 |
212
+ | 真宿主 e2e | `e2e-host-preset-check.mjs` 只做 discovery + 逐行 schema,**从不真正挂载预设**,且 `:70` 明确**跳过三个本地插件行** |
213
+ | toolFilter | 无任何测试触达 `tools.restrict()`,所以 F-2 一直不可见 |
214
+
215
+ ---
216
+
217
+ ## 4. 复现脚本(位于仓库之外的工作区)
218
+
219
+ | 脚本 | 作用 | 实测结果 |
220
+ |---|---|---|
221
+ | `prove-f1-agent-detach.mjs` | **驱动真实 `AgentRegistry`** 复现"teardown 之后 child 已不在注册表" | ✅ CONFIRMED(exit 0) |
222
+ | `prove-f2-toolfilter-throws.mjs` | 用宿主自身守卫谓词 × 真实注册名,判定权限名表 | ✅ 6 个用户场景被拒 |
223
+ | `audit-surface-vs-live.mjs` | 机械提取全部 (service,method) 调用点对照**活体契约目录** | ✅ 17 组合全 OK |
224
+ | `audit-toolfilter-live.mjs` | 按"该预设在本平台真正注册的工具名"判定名表 | ✅ 6 个未注册名 |
225
+ | `_live-tool-names.json` | 本会话 Tool inspect 的实测工具名快照 | — |
226
+ | `e2e-host-preset-check.mjs` | 真宿主 discovery + schema(**注意:不挂载、跳过本地行**) | 3/0/57/0 |
227
+ | `audit-row-schemas.mjs` | 167 行预设 config 对照真实 schema | 1 failing(宿主自带 `cordis` 预设) |
228
+
229
+ ### F-1 可执行证明输出(节选)
230
+
231
+ ```
232
+ agents.get("child-session-1") right after enter() -> Agent present
233
+ captured while live -> Agent (usable)
234
+ === teardown: activation.handle.dispose() [dsh-subagent/lib/index.js:1231] ===
235
+ dispose() -> dsh-agent detachEntered() -> store.delete(entry.id) [dsh-agent/lib/index.js:508]
236
+ === then the host emits subagent/end [dsh-subagent/lib/index.js:1241] ===
237
+ agents.get(info.id) inside the end handler -> undefined <== the child is GONE
238
+ === VERDICT: CONFIRMED ===
239
+ A live reference captured BEFORE teardown still works (the registry is not
240
+ consulted again), but a lookup INSIDE the subagent/end handler always fails.
241
+ ```
242
+
243
+ 这条同时**验证了修法可行**:teardown 前捕获的引用仍然可用。
244
+
245
+ ### F-2 可执行证明输出(节选)
246
+
247
+ ```
248
+ [solverAllowNetwork=false] filter={"deny":["web_search","web","fetch"]}
249
+ -> REJECTED: names unknown global tools "web", "fetch"
250
+ [solverAllowScripts=false] filter={"deny":["bash","pwsh"]}
251
+ -> REJECTED: names unknown global tool "bash"
252
+ [solverAllowNetwork=true + allow] filter={"allow":["read","web_search","web","fetch"]}
253
+ -> REJECTED: names unknown global tools "web", "fetch"
254
+ ```
255
+
256
+ ---
257
+
258
+ ## 6. 修复优先级建议(原始排序,1–6 已在本轮完成)
259
+
260
+ 1. **F-2**(唯一会让用户"想锁权限反而全停"的坑)——改按注册名判定。
261
+ 2. **F-1**(真实压缩从未发生)——在 `subagent/start` 缓存 Agent 引用。
262
+ 3. **F-4**(`inject` 补 `subprocess`/`sandboxPolicy`/`compaction`)——顺带消掉 F-1/F-5 的时序隐患。
263
+ 4. **F-3**(v4 补 `ctx.effect`)——防重复挂载撞名。
264
+ 5. **F-5/F-6**(围栏根回退 + 静默吞异常)——至少加一条可见日志。
265
+ 6. **F-8**(安装器自检补三个服务)——让以后的问题在安装时就暴露。
266
+ 7. 测试:补一个**真正挂载预设**的 e2e 与一个 **toolFilter 冒烟测试**。
267
+
268
+ ---
269
+
270
+ ## 7. 修复实施记录(本轮已完成)
271
+
272
+ | 编号 | 缺陷 | 状态 | 修复位置 | 验证 |
273
+ |---|---|---|---|---|
274
+ | **F-2** | v2/v3 权限过滤器含未注册工具名 | ✅ **已修** | `vibe-math-v2.js:505-556`、`vibe-math-v3.js:987-1038` | `audit-f2-filter-fix.test.mjs` **36 passed**;已做敏感性验证(改回旧代码 → 4 FAIL) |
275
+ | **F-1** | v4 真实 `/compact` 死代码 | ✅ **已修** | `vibe-math-v4.js` 新增 `liveAgents`/`rememberAgent`/`forgetAgent`/`liveAgentOf` + `subagent/start` 监听 | `audit-f1-compact-fix.test.mjs` **5 passed**;敏感性验证(关掉捕获 → 2 FAIL) |
276
+ | **F-3** | v4 注册未纳入 `ctx.effect` | ✅ **已修** | `vibe-math-v4.js` `registerTool()` 与 `commands.register` 均改为 `ctx.effect(...)` | 与 v2/v3 一致;v4 套件 120 passed 无回归 |
277
+ | **F-4** | `apply()` 同步快照可选服务 | ✅ **已修** | 三套均改为惰性 `subprocessOf()`/`sandboxPolicyOf()`/`compactionOf()` | `audit-surface-vs-live.mjs`:**EAGER apply-time snapshot, not injected: (none)** |
278
+ | **F-5** | `getPolicy()` 静默改围栏根 | ✅ **已修**(加可见告警) | 三套 `getPolicy()` | 告警仅在**首次**触发,不刷屏 |
279
+ | **F-6** | 静默吞异常 | ✅ 部分修复 | F-5 相关的静默 catch 已改为一次性告警 | 其余保留(多为有意的防御性回退) |
280
+ | **F-8** | 安装器自检覆盖不到三个服务 | ✅ **已修** | `installer.js` 新增 `subprocess`/`sandboxPolicy`/`compaction` 检查,区分**必须**与**可选降级** | `e2e-installer-test.mjs` 退出 0 |
281
+ | **F-4b** | 插件行在 `compaction` 隔离组**外** → 取到宿主根 compaction | ✅ **已修(最小风险+符合设计)** | v4 `realCompact` 改经**子代理自身 `agent.ctx`** 解析 compaction;插件行**不移动** | `audit-f1-compact-fix.test.mjs` 6 passed,含"插件平面调用数必须为 0" |
282
+ | **F-9** | 损坏 JSON 被静默覆盖(数据丢失) | ✅ **已修** | 三套 `readJson` 记录 + `writeJson` 拒写;v4 另加写前磁盘惰性检查 | `audit-corrupt-file-guard.test.mjs` 6 passed + 三版敏感性验证 |
283
+ | **F-10** | F-2 重试在极端下不够 fail-closed | ✅ **已修(自纠)** | v2/v3:过滤后为空则拒绝派发 | 沿用 F-2 测试;行为由代码审查确认 |
284
+ | **F-11** | 纯函数鲁棒性 | ✅ 验证通过(非缺陷) | — | `audit-fuzz-helpers.mjs` 1654 次调用 0 问题 |
285
+
286
+ ### 回归门槛(全部实跑)
287
+
288
+ ```
289
+ e2e-business 18 passed e2e-v3 100 passed
290
+ e2e-regression 14 passed e2e-v4-fixes 120 passed
291
+ e2e-multisession 25 passed e2e-v3-roundtrip 12 passed
292
+ e2e-d9-d13 7 passed audit-path-consistency 18 passed
293
+ audit-settings-roundtrip 12 audit-round1-regressions 7 passed
294
+ audit-round6-persistence 8 verify-fixes 11 passed
295
+ e2e-host-preset-check 3 discovered / 0 broken / 57 rows / 0 failing
296
+ audit-f1-compact-fix 6 passed audit-f2-filter-fix 36 passed
297
+ audit-corrupt-file-guard 6 passed audit-fuzz-helpers 1654 calls / 0 problem
298
+ e2e-f1-agent-detach CONFIRMED (drives the real AgentRegistry)
299
+ ────────────────────────────────────────────────────────────────
300
+ 合计 380 + 3 个新测试文件(36 + 5 + 1)
301
+ ```
302
+
303
+ ### 未修(需你决策,风险与取舍已说明)
304
+
305
+ | 编号 | 说明 | 为何未动 |
306
+ |---|---|---|
307
+ | **F-4b** | 插件行在 `compaction` 隔离组**外** → 取到宿主根 compaction,预设阈值不生效 | 修法是**把插件行移进该组**,但会同时改变它对组内其它服务的可见性;**需先确认这是否为有意设计** |
308
+ | **F-4c** | `ensureDirs()` 依赖外部二进制且返回值无人检查 | 属"实机才暴露"一类;改动会影响启动路径,建议与你的实机验证一起做 |
309
+ | **F-7** | v2/v3 用全局 `setInterval` 而非 `ctx.interval` | 已核实**不是功能故障**(在 `ctx.effect` 内且正确清理),仅是未用宿主 timer 服务 |
310
+
311
+ - **两个并行深度审计未能返回**(长时间运行后由我中止,无输出)。其覆盖范围(服务 API 逐参数核对、R1–R7 逐条复核)**已由我本人独立完成**,结论即本文件 §1/§2。
312
+ - 未逐条验证上一轮 `FIX-REPORT-2026.md` 第四节列出的约 20 条审计编号(v4 D1/D2/D3/D7、v3 D17/D18/M14/M2 等)——这些**只是编号转述**,需先验证机制再改。
313
+ - **修复已实施**(见 §7):F-1/F-2/F-3/F-4/F-5/F-8 完成并各有回归测试;F-4b/F-4c/F-7 见 §7 末表,需你决策或留待实机验证。
314
+
315
+ ---
316
+
317
+ ## 8. 与上一轮结论的差异(修正记录)
318
+
319
+ | 上一轮说法 | 本轮实测 |
320
+ |---|---|
321
+ | R2 只有 `web`/`fetch` 两个假名 | 还有 **`bash`**(Windows 上 `tool-bash` 行被 `disabled`)——**共 6 个未注册名** |
322
+ | R6「v3 用全局 setInterval」列为风险 | `setInterval` 在真实插件运行时**存在**,且 `ctx.effect` 内**已正确清理**;**降级为建议** |
323
+ | R3「v4 硬编码假名、无重试可能半启动」 | v4 的过滤器**只用用户传入的名字**,不硬编码网络/脚本名;该条**不成立**(v4 的真实问题是注册未纳入 `ctx.effect`) |
324
+ | R1 仅"代码阅读推断" | 本轮**用真实 `AgentRegistry` 可执行复现**(`prove-f1-agent-detach.mjs`,exit 0) |
325
+ | 「兼容性审计 0 处崩溃」 | 兼容性缺陷确实不是崩溃型,但 F-2 会让子代理**永远起不来**,属功能失效而非"无问题" |
@@ -0,0 +1,114 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Generate the Vibe Math V2 framework diagram (架构图) as a PNG poster.
3
+ Output: 示例图/框架图-v2.png (run from anywhere: python docs/generate_framework_diagram_v2.py)
4
+ Layout: left = pipeline (交互/调度/子代理·方向/子代理·验证/收口), right = 数据层 aligned by stage.
5
+ """
6
+ import matplotlib
7
+ matplotlib.use("Agg")
8
+ import os
9
+ import matplotlib.pyplot as plt
10
+ from matplotlib.patches import FancyBboxPatch, Rectangle, FancyArrowPatch
11
+
12
+ plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei"]
13
+ plt.rcParams["axes.unicode_minus"] = False
14
+
15
+ BASE = os.path.dirname(os.path.abspath(__file__))
16
+ ROOT = os.path.dirname(BASE)
17
+ OUT = os.path.join(ROOT, "示例图", "框架图-v2.png")
18
+
19
+ fig = plt.figure(figsize=(18, 11.5))
20
+ ax = fig.add_axes([0, 0, 1, 1])
21
+ ax.axis("off")
22
+ ax.set_xlim(0, 18)
23
+ ax.set_ylim(0, 11.5)
24
+
25
+ def box(x0, y0, w, h, fc, ec="#37474F", lw=1.6, rounding=0.16, z=3):
26
+ ax.add_patch(FancyBboxPatch((x0, y0), w, h,
27
+ boxstyle=f"round,pad=0.10,rounding_size={rounding}",
28
+ facecolor=fc, edgecolor=ec, linewidth=lw, zorder=z))
29
+
30
+ def txt(cx, cy, s, fs=11, color="#263238", weight="normal", z=5):
31
+ ax.text(cx, cy, s, ha="center", va="center", fontsize=fs, color=color,
32
+ fontweight=weight, linespacing=1.45, zorder=z)
33
+
34
+ def arrow(x1, y1, x2, y2, label=None, lx=0, ly=0.24, ls=9.5, rad=0.0,
35
+ style="->", lw=1.8, color="#455A64", dashed=False, z=4):
36
+ ax.add_patch(FancyArrowPatch((x1, y1), (x2, y2), arrowstyle=style,
37
+ mutation_scale=17, linewidth=lw, color=color, zorder=z,
38
+ connectionstyle=f"arc3,rad={rad}",
39
+ linestyle="--" if dashed else "-"))
40
+ if label:
41
+ ax.text((x1 + x2) / 2 + lx, (y1 + y2) / 2 + ly, label, fontsize=ls,
42
+ ha="center", va="center", color="#333",
43
+ bbox=dict(fc="white", ec="none", alpha=0.85, pad=1.0), zorder=6)
44
+
45
+ def band(x0, y0, x1, y1, fc, title, tc="#607D8B"):
46
+ ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, facecolor=fc,
47
+ edgecolor="none", alpha=0.5, zorder=1))
48
+ # title pinned to the band's top-left corner, drawn ABOVE the boxes, with a
49
+ # white backing so it is never obscured by the rectangles
50
+ ax.text(x0 + 0.35, y1 - 0.12, title, fontsize=11.5, color=tc,
51
+ fontweight="bold", ha="left", va="top", zorder=6,
52
+ bbox=dict(fc="white", ec="none", alpha=0.85, pad=1.2))
53
+
54
+ # ── title ────────────────────────────────────────────────────────────────
55
+ ax.text(9, 11.12, "Vibe Math V2 — 多代理数学问题求解与验证框架(概率驱动)", fontsize=21,
56
+ ha="center", va="center", color="#1A237E", fontweight="bold")
57
+ ax.text(9, 10.66, "qs.json 问题清单 + Propos/ 命题库 · 按正确概率/价值调度 · explorer → 逐方向 solver → 多验证器辩论裁决",
58
+ fontsize=10.5, ha="center", va="center", color="#555")
59
+
60
+ # ── layer bands ──────────────────────────────────────────────────────────
61
+ band(0.3, 9.45, 17.7, 10.45, "#E3F2FD", "交互层")
62
+ band(0.3, 7.70, 17.7, 8.95, "#FFF8E1", "调度层 · 插件代码")
63
+ band(0.3, 5.95, 17.7, 7.20, "#E8F5E9", "子代理层 · 方向求解")
64
+ band(0.3, 4.20, 17.7, 5.45, "#E8F5E9", "子代理层 · 交叉验证")
65
+ band(0.3, 1.50, 17.7, 4.00, "#F3E5F5", "收口 · 回写 · 数据层")
66
+
67
+ # ── left pipeline (x 1.0–8.0) ────────────────────────────────────────────
68
+ box(1.0, 9.60, 7.0, 0.75, "#BBDEFB")
69
+ txt(4.5, 9.98, "用户 · 主代理(助手 + 汇报者)\n自然语言 · 翻译需求/汇报/配置 · 不求解不调度", fs=11)
70
+ box(1.0, 7.85, 7.0, 0.95, "#FFE0B2")
71
+ txt(4.5, 8.33, "调度器(插件代码 · 概率驱动)\n唯一文件写者 · 按优先级调度 · 状态机 · 断点持久化", fs=11)
72
+ box(1.0, 6.10, 7.0, 0.95, "#C8E6C9")
73
+ txt(4.5, 6.58, "Explorer → Solver × N\n拆方向(全死路→重派生)· 逐方向多轮迭代", fs=11)
74
+ box(1.0, 4.35, 7.0, 0.95, "#C8E6C9")
75
+ txt(4.5, 4.83, "Verifier × ≥3(严苛审稿人)\n独立审查 → 辩论(交流群)→ 裁决(flat/forced)", fs=11)
76
+ box(1.0, 2.60, 7.0, 0.95, "#C8E6C9")
77
+ txt(4.5, 3.08, "概率 = 1 → 收口\n问题 solved · 命题 1/0 · 优先级 never", fs=11)
78
+
79
+ # ── right data boxes (x 10.2–17.0) ───────────────────────────────────────
80
+ box(10.2, 7.85, 6.8, 0.95, "#E1BEE7")
81
+ txt(13.6, 8.33, "qs/qs.json\n问题清单 + 解法(正确概率)", fs=10.5)
82
+ box(10.2, 6.10, 6.8, 0.95, "#E1BEE7")
83
+ txt(13.6, 6.58, "Propos/&lt;分类&gt;_Propos.json\n命题:布尔估计 · 证明/证伪列表", fs=10.5)
84
+ box(10.2, 4.35, 6.8, 0.95, "#E1BEE7")
85
+ txt(13.6, 4.83, "Verified/\n定论事实(布尔估计 0/1)", fs=10.5)
86
+ box(10.2, 2.90, 6.8, 0.70, "#E1BEE7")
87
+ txt(13.6, 3.25, "Verification_logs/ · Progress_Logs/\n辩论记录 · 进度报告", fs=10)
88
+ box(10.2, 1.60, 6.8, 0.70, "#E1BEE7")
89
+ txt(13.6, 1.95, "Reliable/\n可信参考文献(只读)", fs=10)
90
+
91
+ # ── pipeline arrows ──────────────────────────────────────────────────────
92
+ arrow(4.5, 9.60, 4.5, 8.80, "vibe_math_* 工具", ly=0.26)
93
+ arrow(4.5, 7.85, 4.5, 7.05, "① 按优先级取问题 · 派 Explorer / 每方向 Solver", ly=0.24)
94
+ arrow(4.5, 6.10, 4.5, 5.30, "④ 选 r(命题 / 命题+证明·证伪 / 问题+解法)派 ≥3 验证器", ly=0.24)
95
+ arrow(4.5, 4.35, 4.5, 3.55, "⑤ 裁决 0/1/中间 → ⑥ 概率=1 收口", ly=0.24)
96
+ arrow(0.55, 3.08, 0.55, 8.33, "读取 qs / 已解决回写", lx=0.85, ly=0)
97
+
98
+ # ── stage↔data links ─────────────────────────────────────────────────────
99
+ arrow(8.0, 8.33, 10.2, 8.33, "读写 qs.json", ly=0.28, style="<|-|>")
100
+ arrow(8.0, 6.58, 10.2, 6.58, "② 引理/解法写入(概率<1)", ly=0.28)
101
+ arrow(10.2, 6.30, 8.1, 4.90, "③ 命题/证明 → 验证", ly=0.24, rad=-0.15)
102
+ arrow(8.0, 4.83, 10.2, 4.83, "⑥ 定论 → 晋升/证伪", ly=0.28)
103
+ arrow(7.9, 4.40, 10.3, 3.60, "辩论记录", ly=0.24, rad=-0.1)
104
+
105
+ # ── feature strip ────────────────────────────────────────────────────────
106
+ feat = ["概率=1 自动收口(solved / 1|0)", "never = 永不调度 · 不阻塞终止",
107
+ "断点续跑(resume · 进程纪元)", "reportMode file|push|both · 按项目隔离"]
108
+ fx = [0.6, 5.0, 9.4, 13.8]
109
+ for x0, s in zip(fx, feat):
110
+ box(x0, 0.40, 3.9, 0.72, "#ECEFF1", lw=1.2, rounding=0.2)
111
+ txt(x0 + 1.95, 0.76, s, fs=9.5)
112
+
113
+ plt.savefig(OUT, dpi=170, bbox_inches="tight", facecolor="white")
114
+ print("saved " + OUT)
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Generate the Vibe Math V3 framework diagram (架构图) as a PNG poster.
3
+ Output: 示例图/框架图-v3.png (run from anywhere: python docs/generate_framework_diagram_v3.py)
4
+ Layout: left = pipeline (交互/调度·规划/方向求解/交叉验证/方法沉淀/收口), right = md 知识库数据层.
5
+ """
6
+ import matplotlib
7
+ matplotlib.use("Agg")
8
+ import os
9
+ import matplotlib.pyplot as plt
10
+ from matplotlib.patches import FancyBboxPatch, Rectangle, FancyArrowPatch
11
+
12
+ plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei"]
13
+ plt.rcParams["axes.unicode_minus"] = False
14
+
15
+ BASE = os.path.dirname(os.path.abspath(__file__))
16
+ ROOT = os.path.dirname(BASE)
17
+ OUT = os.path.join(ROOT, "示例图", "框架图-v3.png")
18
+
19
+ fig = plt.figure(figsize=(18, 12))
20
+ ax = fig.add_axes([0, 0, 1, 1])
21
+ ax.axis("off")
22
+ ax.set_xlim(0, 18)
23
+ ax.set_ylim(0, 12)
24
+
25
+ def box(x0, y0, w, h, fc, ec="#37474F", lw=1.6, rounding=0.16, z=3):
26
+ ax.add_patch(FancyBboxPatch((x0, y0), w, h,
27
+ boxstyle=f"round,pad=0.10,rounding_size={rounding}",
28
+ facecolor=fc, edgecolor=ec, linewidth=lw, zorder=z))
29
+
30
+ def txt(cx, cy, s, fs=11, color="#263238", weight="normal", z=5):
31
+ ax.text(cx, cy, s, ha="center", va="center", fontsize=fs, color=color,
32
+ fontweight=weight, linespacing=1.45, zorder=z)
33
+
34
+ def arrow(x1, y1, x2, y2, label=None, lx=0, ly=0.24, ls=9.5, rad=0.0,
35
+ style="->", lw=1.8, color="#455A64", dashed=False, z=4):
36
+ ax.add_patch(FancyArrowPatch((x1, y1), (x2, y2), arrowstyle=style,
37
+ mutation_scale=17, linewidth=lw, color=color, zorder=z,
38
+ connectionstyle=f"arc3,rad={rad}",
39
+ linestyle="--" if dashed else "-"))
40
+ if label:
41
+ ax.text((x1 + x2) / 2 + lx, (y1 + y2) / 2 + ly, label, fontsize=ls,
42
+ ha="center", va="center", color="#333",
43
+ bbox=dict(fc="white", ec="none", alpha=0.85, pad=1.0), zorder=6)
44
+
45
+ def band(x0, y0, x1, y1, fc, title, tc="#607D8B"):
46
+ ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, facecolor=fc,
47
+ edgecolor="none", alpha=0.5, zorder=1))
48
+ ax.text(x0 + 0.35, y1 - 0.12, title, fontsize=11.5, color=tc,
49
+ fontweight="bold", ha="left", va="top", zorder=6,
50
+ bbox=dict(fc="white", ec="none", alpha=0.85, pad=1.2))
51
+
52
+ # ── title ────────────────────────────────────────────────────────────────
53
+ ax.text(9, 11.70, "Vibe Math V3 — 多代理数学研究与验证框架(论文式 md + 规划代理 + 方法库)", fontsize=20,
54
+ ha="center", va="center", color="#1A237E", fontweight="bold")
55
+ ax.text(9, 11.28, "Markdown 知识库(软规范锚点 + 自由叙述) · 规划代理自主制定 N 步计划 · 方法库沉淀理论/框架/工具/方法/思想",
56
+ fontsize=10.5, ha="center", va="center", color="#555")
57
+
58
+ # ── layer bands ──────────────────────────────────────────────────────────
59
+ band(0.3, 10.20, 17.7, 11.05, "#E3F2FD", "交互层")
60
+ band(0.3, 8.45, 17.7, 9.80, "#FFF8E1", "调度层 · 插件代码 + 规划代理")
61
+ band(0.3, 6.70, 17.7, 8.00, "#E8F5E9", "子代理层 · 方向求解")
62
+ band(0.3, 5.00, 17.7, 6.30, "#E8F5E9", "子代理层 · 交叉验证")
63
+ band(0.3, 3.30, 17.7, 4.60, "#E8F5E9", "子代理层 · 方法沉淀")
64
+ band(0.3, 1.40, 17.7, 3.10, "#F3E5F5", "收口 · 回写 · 数据层(md 知识库)")
65
+
66
+ # ── left pipeline (x 1.0–8.0) ────────────────────────────────────────────
67
+ box(1.0, 10.32, 7.0, 0.72, "#BBDEFB")
68
+ txt(4.5, 10.68, "用户 · 主代理(助手 + 汇报者)\n自然语言 · 翻译需求/汇报/配置 · 不求解不调度", fs=11)
69
+ # 调度层:调度器 + 规划代理(并排,中间留通道放「状态简报 ⇄ 计划 JSON」)
70
+ box(1.0, 8.58, 3.0, 1.08, "#FFE0B2")
71
+ txt(2.5, 9.12, "调度器(插件代码)\n唯一物理写者 · 锚点索引/结构性移动\n守硬约束(并发/幂等/已验证不再调度)", fs=10.5)
72
+ box(5.9, 8.58, 2.1, 1.08, "#FFE0B2")
73
+ txt(6.95, 9.12, "规划代理 Planner\n读状态简报 → 一次安排 N 步计划", fs=10.5)
74
+ box(1.0, 6.82, 7.0, 1.06, "#C8E6C9")
75
+ txt(4.5, 7.35, "Explorer → Solver × N\n拆方向(全死路→重派生,旧方向日志归档)· 逐方向多轮迭代 · 上报 methods_used/new_inventions", fs=11)
76
+ box(1.0, 5.12, 7.0, 1.06, "#C8E6C9")
77
+ txt(4.5, 5.65, "Verifier × ≥3(严苛审稿人)\n独立审查 → 辩论(交流群)→ 近共识裁决(同侧均值≥0.85/≤0.15 取均值)", fs=11)
78
+ box(1.0, 3.42, 7.0, 1.06, "#C8E6C9")
79
+ txt(4.5, 3.95, "Method Keeper(方法整理代理)\n提炼新方法卡 · 合并碎片 · 完善体系(上级体系/子方法)· 维护可信断言", fs=11)
80
+ box(1.0, 1.52, 7.0, 1.06, "#C8E6C9")
81
+ txt(4.5, 2.05, "收口 · 可信铁律\n概率=1 → Verified/ 卡(只读绝对可信)· 仅 Verified/ 与验证判真/假可信", fs=11)
82
+
83
+ # ── right md knowledge base (x 10.4–17.0) ─────────────────────────────────
84
+ box(10.4, 8.70, 6.6, 0.86, "#E1BEE7")
85
+ txt(13.7, 9.13, "Problems/(问题清单 · 每问题一个 md)\n陈述/状态/优先级/依赖/被依赖/来源·动机/计划/解法候选", fs=10)
86
+ box(10.4, 6.94, 6.6, 0.86, "#E1BEE7")
87
+ txt(13.7, 7.37, "Progress/(研究日志 · 每问题一个 md)\n方向×轮次叙事 · 旧方向归档「已归档方向」", fs=10)
88
+ box(10.4, 5.24, 6.6, 0.86, "#E1BEE7")
89
+ txt(13.7, 5.67, "Propos/(命题库 · 每命题一个 md)\n陈述/概率/证明·证伪尝试/依赖", fs=10)
90
+ box(10.4, 3.54, 6.6, 0.86, "#E1BEE7")
91
+ txt(13.7, 3.97, "Methods/(通用理论发明库)+ 全局 VibeMath/Methods/\n理论体系·框架·工具·方法·思想 · 应用记录/改进历史/可信断言", fs=10)
92
+ box(10.4, 2.10, 3.2, 0.82, "#E1BEE7")
93
+ txt(12.0, 2.51, "Verified/\n绝对可信(只读)", fs=10)
94
+ box(13.9, 2.10, 3.1, 0.82, "#E1BEE7")
95
+ txt(15.45, 2.51, "Logs/ · State/\n辩论/计划审计 · 私有状态", fs=10)
96
+
97
+ # ── pipeline arrows ──────────────────────────────────────────────────────
98
+ # 主代理 → 调度器(斜向调度器顶部中心)
99
+ arrow(4.0, 10.32, 2.5, 9.66, "vibe_math_* 工具", ly=0.20, ls=9, rad=0.0)
100
+ # 调度器 → 方向求解(竖直,经左列中心)
101
+ arrow(4.5, 8.58, 4.5, 7.88, "① 计划校验后派 Explorer / 每方向 Solver", ly=0.22, ls=9)
102
+ arrow(4.5, 6.82, 4.5, 6.18, "② 选 r(命题 / 命题+证明·证伪 / 问题+解法)派 ≥3 验证器", ly=0.22, ls=9)
103
+ arrow(4.5, 5.12, 4.5, 4.48, "③ 裁决 → 近共识收口", ly=0.22, ls=9)
104
+ arrow(4.5, 3.42, 4.5, 2.58, "④ 概率=1 → Verified/ 卡", ly=0.22, ls=9)
105
+ # 调度器 ↔ 规划代理:通道内错开两条箭头(状态简报 →规划代理;计划 JSON →调度器),label 放箭头下方避免贴 band 顶
106
+ arrow(4.0, 9.34, 5.9, 9.34, "状态简报", ly=-0.20, ls=9)
107
+ arrow(5.9, 8.78, 4.0, 8.78, "计划 JSON", ly=-0.20, ls=9)
108
+
109
+ # ── stage↔data links ─────────────────────────────────────────────────────
110
+ arrow(8.0, 9.13, 10.4, 9.13, "⑤ 注册问题/后生问题/计划回写", ly=0.26, style="<|-|>", ls=9)
111
+ arrow(8.0, 7.37, 10.4, 7.37, "⑥ 引理/子路线/解法写入(概率<1)", ly=0.26, ls=9)
112
+ arrow(10.4, 5.42, 8.1, 5.65, "⑦ 命题/证明 → 验证", ly=0.26, ls=9, rad=-0.15)
113
+ arrow(8.0, 5.65, 10.4, 5.65, "", lw=0)
114
+ arrow(8.0, 3.95, 10.4, 3.97, "⑧ methods_used/new_inventions → 方法库", ly=0.26, ls=9)
115
+ arrow(8.0, 2.51, 10.4, 2.51, "⑨ 定论 → Verified/ 卡(只读)", ly=0.26, ls=9, style="<|-|>")
116
+
117
+ # ── feature strip ────────────────────────────────────────────────────────
118
+ feat = ["仅 Verified/ 与验证判真/假绝对可信", "软规范锚点 + 自由叙述(调度器只解析锚点)",
119
+ "规划失败自动回退启发式调度", "断点续跑(md 即叙事断点 + 进程纪元)",
120
+ "多会话隔离 + 项目锁"]
121
+ fx = [0.5, 4.0, 7.5, 11.0, 14.5]
122
+ for x0, s in zip(fx, feat):
123
+ box(x0, 0.32, 3.4, 0.68, "#ECEFF1", lw=1.2, rounding=0.2)
124
+ txt(x0 + 1.7, 0.66, s, fs=9)
125
+
126
+ plt.savefig(OUT, dpi=170, bbox_inches="tight", facecolor="white")
127
+ print("saved " + OUT)