dsh-vibe-math 2.5.0 → 2.7.0

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 (59) hide show
  1. package/README.en.md +175 -369
  2. package/README.md +175 -370
  3. package/cordis.patch.yml +78 -22
  4. package/docs/AUDIT-CHECKLIST.md +3 -3
  5. package/docs/final-paper.md +172 -0
  6. package/docs/formal-verification.md +184 -29
  7. package/docs/math-computation.md +121 -0
  8. package/docs/release-notes/RELEASE-NOTES-2.6.0.md +268 -0
  9. package/docs/release-notes/RELEASE-NOTES-2.7.0.en.md +151 -0
  10. package/docs/release-notes/RELEASE-NOTES-2.7.0.md +115 -0
  11. package/docs/test-timing.md +25 -9
  12. package/installer.js +7 -4
  13. package/package.json +21 -3
  14. package/prompt-corpus-persona/persona-corpus.json +16 -16
  15. package/prompt-corpus-persona/persona-corpus.md +164 -52
  16. package/prompt-corpus-v2/formal-verify-v2.json +128 -92
  17. package/prompt-corpus-v2/formal-verify-v2.md +1574 -108
  18. package/prompt-corpus-v3/formal-verify-v3.json +137 -53
  19. package/prompt-corpus-v3/formal-verify-v3.md +1406 -100
  20. package/prompt-corpus-v4/formal-verify-v4.json +14 -14
  21. package/prompt-corpus-v4/formal-verify-v4.md +247 -14
  22. package/prompt-corpus-v5/prompt-corpus-v5.json +62 -62
  23. package/prompt-corpus-v5/prompt-corpus-v5.md +570 -3
  24. package/tests/audit-math-computation-contract.mjs +191 -0
  25. package/tests/audit-math-computation-parity.mjs +169 -0
  26. package/tests/audit-math-computation-sensitivity.mjs +231 -0
  27. package/tests/audit-persona-sensitivity.mjs +64 -11
  28. package/tests/audit-persona-surface.test.mjs +53 -16
  29. package/tests/audit-v5-integrity.mjs +14 -0
  30. package/tests/e2e-v5-round2.test.mjs +923 -3
  31. package/tests/formal-verify-v2.test.mjs +295 -6
  32. package/tests/formal-verify-v3.test.mjs +275 -4
  33. package/tests/formal-verify-v4.test.mjs +295 -6
  34. package/tests/formal-verify-v5.test.mjs +9 -1
  35. package/tests/helpers/math-computation-fake-seam.mjs +135 -0
  36. package/tests/math-computation-shared.test.mjs +504 -0
  37. package/tests/prompt-v5-integrity.test.mjs +14 -8
  38. package/tests/selfdrive-v5.mjs +55 -4
  39. package/vibe-math-v2/agent.cordis.yml +36 -14
  40. package/vibe-math-v2/math-computation.js +936 -0
  41. package/vibe-math-v2/math-engines.js +156 -0
  42. package/vibe-math-v2/vibe-math-v2.js +1653 -48
  43. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
  44. package/vibe-math-v3/agent.cordis.yml +30 -12
  45. package/vibe-math-v3/math-computation.js +936 -0
  46. package/vibe-math-v3/math-engines.js +156 -0
  47. package/vibe-math-v3/vibe-math-v3.js +1623 -53
  48. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
  49. package/vibe-math-v4/agent.cordis.yml +44 -8
  50. package/vibe-math-v4/math-computation.js +936 -0
  51. package/vibe-math-v4/math-engines.js +156 -0
  52. package/vibe-math-v4/vibe-math-v4.js +1592 -37
  53. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
  54. package/vibe-math-v5/agent.cordis.yml +46 -10
  55. package/vibe-math-v5/math-computation.js +936 -0
  56. package/vibe-math-v5/math-engines.js +156 -0
  57. package/vibe-math-v5/vibe-math-v5.js +2233 -42
  58. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +153 -4
  59. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +251 -0
@@ -28,14 +28,22 @@
28
28
 
29
29
  | 参数 | 取值 | 默认 | 含义 |
30
30
  |---|---|---|---|
31
- | `formalVerify` | `'off'` \| `'encourage'` \| `'require'` | `'off'` | 三档开关,见 §2 |
31
+ | `formalVerify` | `'off'` \| `'encourage'` \| `'require'` | `'off'` | 三档开关,见 §2(**只**表示"验证时的要求强度") |
32
32
  | `leanCommand` | 字符串 | `'lean'` | 要执行的 Lean 可执行文件(例:`'lake'`) |
33
33
  | `leanArgs` | 字符串数组 | `[]` | 插在文件名之前的附加参数(例:`['env','lean']` 配合 `leanCommand='lake'`) |
34
- | `leanTimeoutMs` | 正整数 | `120000` | 单次 Lean 运行的超时上限 |
35
-
36
- - 非法值一律**回退到默认**(`formalVerify` 非三档之一 → `'off'`;`leanTimeoutMs` 非正 → 默认)。
37
- - 参数必须出现在该架构既有的参数体系里:`set_params` / `vibe_v4_set` / `vibe_v5_set`、
38
- 参数 schema(`*_setup` / `*_template`)、`status`/`report` 的可读参数表。
34
+ | `leanTimeoutMs` | 正整数 | `120000` | 单次 Lean 运行的超时上限(异步作业同样用它作单次预算) |
35
+ | `leanAsync` | 布尔 | `true` | `true` = 编译走**后台队列**(`lean_run` / `lean_archive{run:true}` 入队后立即返回 `{async:{jobId,state:'queued'}}`);`false` = **完全同步 await**(旧路径逐字保留)。只有 `settled(ok)` 才可能置 `passed`,见 §7.3 |
36
+ | `leanJobsMaxParallel` | 正整数 | `1` | 后台编译并发上限(默认 1 = 串行;调大即并行,v2/v3 把合法范围夹在 1–8) |
37
+ | `leanInitiative` | `'off'` \| `'normal'` \| `'eager'` | `'normal'` | **日常流程中的形式化主动性**:`off` 不主动(只在验证提示词里按 `formalVerify` 的要求做)/ `normal` 顺手把有价值且可能复用的东西形式化 / `eager` 更主动。**与 `formalVerify` 是两件事**:它不改变定论门禁 |
38
+ | `leanSearchPaths` | 字符串数组 | `[]` | 额外编译搜索根:先注入它们、再注入自动的 `<VibeMath 根>`(去重、保持用户给的顺序);`leanArgs` 里已显式给 `--search-path`/`-R`/`--root` 时**不注入任何东西**,见 §7.6 |
39
+
40
+ - 非法值一律**回退到默认**(`formalVerify` 非三档之一 → `'off'`;`leanInitiative` 非三档之一 → `'normal'`;
41
+ `leanTimeoutMs` 非正 → 默认;`leanJobsMaxParallel` < 1 → 1;`leanSearchPaths` 非数组 → `[]` 并去空串/去重)。
42
+ - **`leanAsync` 必须显式布尔归一化**:字符串 `'false'` 不得被当作真值放行(它是"关掉后台队列"的开关,
43
+ 错误归一化会让用户以为已同步执行、实际仍在排队)。
44
+ - 参数必须出现在该架构既有的参数体系里:v2/v3 的 `vibe_math_set_params`、v4 的 `vibe_v4_set`、v5 的 `vibe_v5_set`,
45
+ 以及各自的控制面(v2/v3:`vibe_math_setup` / `vibe_math_template` / `vibe_math_status`;v4:`vibe_v4_configure` / `vibe_v4_status`;
46
+ v5:`vibe_v5_configure` / `vibe_v5_status` —— **v4/v5 没有 `*_setup`/`*_template`**),外加 `status`/`report` 的可读参数表。
39
47
  - **模式是动态的**,可以在运行中切换:所有与模式相关的提示词文本都必须在**构造提示词的那一刻**
40
48
  由 `params.formalVerify` 现算,**不得**写进"入职时冻结"的人格/章程快照
41
49
  (否则切换模式后成员读到的仍是旧指令)。
@@ -46,15 +54,19 @@
46
54
 
47
55
  ### `off`(默认)—— 不额外进行任何要求
48
56
 
49
- - **成员提示词**(按模式动态注入的那部分)里**不出现**任何 Lean 相关内容;验证流程、门禁、归档全部不变,
57
+ - **成员提示词**里**不出现**任何 Lean 相关内容(**默认 `leanInitiative:'normal'` 时**);验证流程、门禁、归档全部不变,
50
58
  也不写入任何形式化状态。这是真正的"无操作",四套都有断言与探针守着。
59
+ - **两个轴必须分清(v5 统一口径)**:`formalVerify` 管**验证阶段**的要求强度,`leanInitiative` 管**日常日线**;
60
+ `formalVerify:'off'` 只关掉前者——如果用户显式设置 `leanInitiative:'eager'`,日线**仍然注入**("定论不额外要求,
61
+ 但日常鼓励形式化"是合法组合,见 §6.2 与 `tests/formal-verify-vN` 的 `off+eager` 探针)。`leanInitiative:'off'`
62
+ 则连日线都不注入:两种轴都关掉时,提示词里才真的一个字都不出现。
51
63
  - **回执通道在 `off` 档必须失效**:`formal` 字段本来就不在 `off` 档的回执契约里,所以一个残留/幻觉/被
52
64
  引用的 `formal` 回执**不得**创建形式化记录(否则 `off` 就不是无操作了)。四个架构都必须在这个入口
53
65
  上加 `formalOn()` 守卫。
54
- - 三个 Lean 工具**仍然注册且可用**(注册是静态的,与既有 `ctx.effect` 纪律一致);`off` 只是不主动
66
+ - 五个 Lean 工具(run / archive / lib / read / job)**仍然注册且可用**(注册是静态的,与既有 `ctx.effect` 纪律一致);`off` 只是不主动
55
67
  向成员宣讲它们。代理/人主动调用时照常工作——**工具调用是刻意行为,回执字段不是**,这条区别就是
56
68
  上一条守卫的理由。
57
- - **主代理的 persona 是静态的,不受档位影响**:它**必须始终**文档化这三个工具与四个参数
69
+ - **主代理的 persona 是静态的,不受档位影响**:它**必须始终**文档化这五个工具与八个参数
58
70
  (否则用户在 `off` 档根本发现不了这个开关,也就无法打开它)。"成员提示词里没有 Lean 文字"与
59
71
  "persona 里写着这个能力"**不矛盾**,两者都由测试守着(前者见各 `formal-verify-vN` 的 `off` 小节,
60
72
  后者见 `audit-persona-surface.test.mjs`)。
@@ -100,19 +112,24 @@
100
112
  <VibeMath 根>/
101
113
  ├─ Formal/ # 全局可复用 Lean 库(跨项目)
102
114
  │ ├─ Lib/<name>.lean # 可复用定义/对象/假设(def / structure / notation)
103
- │ ├─ Lib/Index.md # 名称 → 文件 → 类别 → 摘要
115
+ │ ├─ Lib/Index.md # 名称 → 文件 → 类别 → 依赖(import)→ 摘要
104
116
  │ ├─ Proved/<name>.lean # 已成立的 Lean 命题/引理(机器已核对)
105
117
  │ └─ Proved/Index.md # 名称 → 文件 → 陈述 → 依赖
106
118
  └─ Projects/<项目>/ # (v5 为 Projects/<项目>/Institutes/<所>/)
107
119
  ├─ Formal/
108
120
  │ ├─ <对象id>.lean # 该对象的形式化工作文件
109
121
  │ ├─ Index.md # 对象 → 状态 → 文件 → 归档证明 → 运行结果 → 难度判断
122
+ │ ├─ Jobs/<jobId>.json # 后台编译作业的镜像(崩溃恢复 + lean_lib.jobs 都读它)
110
123
  │ └─ TODO.md # require 模式下的「形式化待办」
111
124
  └─ Verified/
112
125
  ├─ <原有定论卡片>
113
126
  └─ Lean/<对象id>.lean # ★ 归档证明:该定论对象对应的形式化代码
114
127
  ```
115
128
 
129
+ - **`Formal/Jobs/<jobId>.json` 是异步队列的**唯一持久镜像**:内存队列是权威,但这个文件让崩溃/重启后
130
+ 的恢复、`<prefix>lean_lib` 的 `jobs` 字段与 `<prefix>lean_job` 三处看到同一份事实。作业终态为
131
+ `settled` / `failed` / `timeout` / `interrupted` 四者之一。
132
+
116
133
  - **归档证明放在 `Verified/Lean/<id>.lean`**:它和定论卡片同处 `Verified/`,
117
134
  一眼可见"这条结论的形式化证明在哪"。
118
135
  - **可复用的东西放全局 `Formal/Lib` 与 `Formal/Proved`**:跨项目复用是这套设计的核心收益。
@@ -185,7 +202,7 @@
185
202
 
186
203
  ---
187
204
 
188
- ## 5. 工具(每个架构三个,前缀各自不同)
205
+ ## 5. 工具(每个架构五个,前缀各自不同)
189
206
 
190
207
  前缀:v2/v3 → `vibe_math_`;v4 → `vibe_v4_`;v5 → `vibe_v5_`。
191
208
 
@@ -197,9 +214,11 @@
197
214
  | `target` | string | | 关联对象 id(给了就更新该对象的运行记录) |
198
215
  | `timeout_ms` | integer | | 覆盖 `leanTimeoutMs` |
199
216
 
200
- 返回:`{ok, exitCode, signal, ms, command, stdout, stderr, file}`;
201
- 找不到工具链返回 `{ok:false, code:'LEAN_NOT_FOUND', error}`;超时返回 `{ok:false, code:'LEAN_TIMEOUT'}`。
202
- **绝不抛异常到调度循环**——任何失败都要变成可读结果并记录。
217
+ - `leanAsync=false`(同步路径)返回:`{ok, exitCode, signal, ms, command, stdout, stderr, file}`;
218
+ 找不到工具链返回 `{ok:false, code:'LEAN_NOT_FOUND', error}`;超时返回 `{ok:false, code:'LEAN_TIMEOUT'}`。
219
+ - `leanAsync=true`(默认)**不编译**:入队后立即返回 `{ok:true, file, async:{jobId,state:'queued'}, hint}`,
220
+ 结果在下一轮提示的【形式化结果】行公告,或用 §5.5 的 `lean_job` / `lean_lib` 的 `jobs` 字段查询。
221
+ - **绝不抛异常到调度循环**——任何失败都要变成可读结果并记录。
203
222
 
204
223
  ### 5.2 `<prefix>lean_archive`
205
224
 
@@ -208,21 +227,57 @@
208
227
  | `kind` | 必填 | 行为 |
209
228
  |---|---|---|
210
229
  | `'def'` / `'lemma'` | `name`, `content` 或 `from` | 写入全局 `Formal/Lib/<name>.lean`(def)或 `Formal/Proved/<name>.lean`(lemma),重建对应 `Index.md`;可选 `run:true` 先跑一次再归档 |
211
- | `'proof'` | `target`, `content` 或 `from` | 写入 `Formal/<target>.lean`;若该文件最近一次运行 `ok`,同时写 `Verified/Lean/<target>.lean` 并把对象标为 `passed` |
230
+ | `'proof'` | `target`, `content` 或 `from` | 写入 `Formal/<target>.lean`;**只有 `settled(ok)`**(§7.3)才同时写 `Verified/Lean/<target>.lean` 并把对象标为 `passed`,否则记 `attempted` |
212
231
  | `'blocked'` | `target`, `note` | 记录显式难度判断/阻塞原因(`note` 空 → 拒绝),对象标为 `blocked` |
213
232
 
233
+ - **内容哈希去重**:同 `content`(规范化后哈希相同)且**构建上下文一致**的 `def`/`lemma`/`proof` 不再重写、
234
+ 不再重编译,直接返回 `{ok:true, deduped:true, …}`(对象已是 `passed` 时也返回 `passed`)。去重的判据与
235
+ §7.3 的 `settled(ok)` 同源;内容变了就正常走新作业。
236
+ - `leanAsync=true` 时 `run:true`(或 `kind='lemma'` 的隐式编译)同样**入队即返回**;文件已写、编译未完成,
237
+ 所以返回里明确要求"落地为通过之前不得把它当作可复用定义"。
238
+
214
239
  ### 5.3 `<prefix>lean_lib`
215
240
 
216
241
  无必填参数。**扫描并重建**三处索引(项目 `Formal/Index.md`、全局 `Lib/Index.md`、`Proved/Index.md`),
217
242
  返回可复用库清单(供代理写新定义前先查重、直接复用)。`refresh:false` 时只读不重建。
243
+ `leanAsync=true` 时额外返回 `async` 与 `jobs`(每个作业的 state/rel/target/attempts/路径),
244
+ 以及 `paths.searchPath`(框架注入的模块根)。
245
+
246
+ ### 5.4 `<prefix>lean_read`
247
+
248
+ 只读地取回**一个已归档** Lean 文件的原文(供"逐字复用"):
249
+
250
+ | 参数 | 类型 | 必填 | 说明 |
251
+ |---|---|---|---|
252
+ | `name` | string | ✅ | 归档文件名(可省略 `.lean`),经该架构的 id 安全化 |
253
+ | `kind` | `'auto'` \| `'lib'` \| `'proved'` | | 默认 `auto`(先 Lib 再 Proved) |
254
+
255
+ 返回 `{ok, name, file, kind, sha256, bytes, text, truncated}`;**只允许** `<VibeMath 根>/Formal/{Lib,Proved}`
256
+ 之内的文件(越界拒绝),正文上限 64KB(超出则 `truncated:true`)。这是"先查库再写新定义"的闭环:
257
+ `lean_lib` 告诉你有什么,`lean_read` 给你原文,然后 `import` 或逐字复制。
258
+
259
+ ### 5.5 `<prefix>lean_job`
260
+
261
+ 只读地查看后台编译作业:
262
+
263
+ | 参数 | 类型 | 必填 | 说明 |
264
+ |---|---|---|---|
265
+ | `jobId` | string | | 省略 = 本会话作业清单;给了 = 该作业的 state/exitCode/回执路径/归档路径 |
266
+ | `waitMs` | integer | | >0 时最多等这么多毫秒(轮询队列,**不阻塞心跳**),超时返回当前 state |
267
+
268
+ `leanAsync=false`(同步档)时它仍然可用(此时清单里只会有同步运行留下的记录),返回体里带
269
+ `async:false` 与 `maxParallel`,便于成员判断当前模式。**只有 `state='settled'` 且 `exitCode=0`
270
+ (且内容哈希/构建上下文一致)才算通过**;`queued`/`running` 一律还不算,`failed`/`timeout`/`interrupted`
271
+ 都不算。
218
272
 
219
273
  ---
220
274
 
221
275
  ## 6. 提示词注入(在构造提示词时现算)
222
276
 
223
277
  > **硬要求(四套一致,逐字级别的约束)**
224
- > 1. **工具名一律写全称**(`<prefix>lean_run` / `<prefix>lean_archive` / `<prefix>lean_lib`)。
225
- > 注入文本里**不得**出现 `lean_run` / `lean_archive` / `lean_lib` 这类缩写——那不是注册名,
278
+ > 1. **工具名一律写全称**(`<prefix>lean_run` / `<prefix>lean_archive` / `<prefix>lean_lib` /
279
+ > `<prefix>lean_read` / `<prefix>lean_job`)。
280
+ > 注入文本里**不得**出现 `lean_run` / `lean_archive` / `lean_lib` / `lean_read` / `lean_job` 这类缩写——那不是注册名,
226
281
  > 代理照抄会调用一个不存在的工具(工具自己返回的 `hint` 字段同样算注入文本)。
227
282
  > 2. **回执字段名必须与该架构真实契约一致**:v2/v3 的评审值字段是 `Result`,v4/v5 是 `verdict`。
228
283
  > 写错字段名 = 那一票被静默丢弃。
@@ -287,6 +342,15 @@
287
342
  归档前先跑通(<prefix>lean_run 或 lean_archive run=true):跑不通的定义不要进可复用库。
288
343
  ```
289
344
 
345
+ - **这段文字的档位由 `leanInitiative` 决定,不是 `formalVerify`**:`off` = 完全不出现(连日线都不注入);
346
+ `normal` = 上面的"顺手"档;`eager` = 追加"日常就主动把有价值的小引理/命题/定义形式化"。
347
+ `formalVerify` 只影响**验证提示词**的要求强度;两者可自由组合(例如 `formalVerify=off` +
348
+ `leanInitiative=eager` 表示"定论不额外要求,但日常鼓励形式化")——**注意:`eager` 在 `formalVerify=off`
349
+ 下也必须注入日线**,`off` 不许把主动性这条独立轴一起吞掉(v5 统一口径,四套都有 `off+eager` 探针)。
350
+ - 异步档(`leanAsync=true`)下这段还必须写清:归档返回的是**排队中的作业**,
351
+ **未落地为 `settled(ok)` 之前不得声称已通过、也不得转忠实性审查**;查进度用
352
+ `<prefix>lean_lib` 的 `jobs` 或 `<prefix>lean_job`,复用先 `<prefix>lean_read` 取原文。
353
+
290
354
  ### 6.3 回执契约
291
355
 
292
356
  非 `off` 模式时,在每轮回执契约里加入(**必须真的被框架解析并落库**——只把字段写进提示词而不实现
@@ -306,12 +370,78 @@
306
370
 
307
371
  ## 7. Run 语义(实现要点)
308
372
 
309
- - 用 `subprocess` 服务:`resolveExecutable(leanCommand)` → `spawn({argv:[exe,...leanArgs,file], cwd, stdio:{stdin:'ignore',stdout:{maxBytes},stderr:{maxBytes}}, graceMs})`
373
+ ### 7.1 调用与超时
374
+
375
+ - **父目录不必预建(round-3 记录)**:宿主的文本写会自动创建缺失的父目录(`dsh-fs-local/lib/index.js` 的 `writeFileAtomic` 先做 `mkdir(dirname(absolutePath), { recursive: true })`),因此 `Formal/Jobs/`、`Computation/<runId>/` 及其 `attempts/<n>/` 都可以"直接写";`ensureDirs()` 里的列举只为空项目可发现性。把"严格 fs 拒绝创建父目录"当作真实宿主行为来构造失败用例是**错误前提**。
376
+ - 用 `subprocess` 服务:`resolveExecutable(leanCommand)` → `spawn({argv, cwd, stdio:{stdin:'ignore',stdout:{maxBytes},stderr:{maxBytes}}, graceMs})`
310
377
  → `await handle.done` → `handle.collected.stdout?.readFrom(0).text`。
311
- - **必须**给 `cwd`(项目根或 `<VibeMath 根>`),并对超时调用 `handle.terminate()`。
378
+ - **必须**给 `cwd`(项目根或 `<VibeMath 根>`),并在超时时调用 `handle.terminate()`——
379
+ **不能只依赖 `graceMs`**:宿主不杀进程时,超时的编译会变成泄漏的孤儿进程。
312
380
  - 输出截断到 ~4KB 再入库(避免把巨大的编译器输出写进状态)。
313
381
  - 宿主没有 `subprocess` 服务 → 返回 `{ok:false, code:'NO_SUBPROCESS'}`,并只记录 `attempted`。
314
382
 
383
+ ### 7.2 同步路径(`leanAsync=false`)
384
+
385
+ 与旧版逐字一致:`await` 编译器返回,直接得到 exitCode/stdout/stderr,然后按 §7.3 判 `settled(ok)`。
386
+ 保留它是为了"我需要一个确定的答案再继续"的场景,以及给探针一个不依赖队列的对照路径。
387
+
388
+ ### 7.3 `settled(ok)`:唯一允许置 `passed` 的判据
389
+
390
+ 一次编译只有在**三件事同时成立**时才算"落地为通过":
391
+
392
+ 1. **进程退出码为 0**(`exitCode === 0`,且不是 `timeout`、不是 `interrupted`);
393
+ 2. **内容哈希未变**:编译期间该 `.lean` 文件没有被改写(同一次归档的内容哈希一致);
394
+ 3. **构建上下文未变**:引擎、`leanArgs`、搜索路径这一组"编译上下文"与入队时一致。
395
+
396
+ 只有 `settled(ok)` 可以:把对象标为 `passed`、写 `Verified/Lean/<id>.lean`、把引理并入可复用库。
397
+ **其它一切结果都停在 `attempted`**:`failed`(退出码非 0)、`timeout`、`interrupted`、
398
+ 以及哈希/上下文不匹配(此时结果**作废**并在 `note` 里写明 "file changed while compiling" /
399
+ "build context changed while compiling")。`require` 档的门禁据此判定——排队中或失败中的作业
400
+ **绝不能**让一条结论变成"已形式化"。
401
+
402
+ ### 7.4 后台队列(`leanAsync=true`,默认)
403
+
404
+ - 入队即返回:成员继续工作;每个作业在 `<项目>/Formal/Jobs/<jobId>.json` 有镜像。
405
+ - 并发上限 = `leanJobsMaxParallel`(默认 1 = 串行,资源占用可预期)。
406
+ - 落地时**一次性公告**给成员(注入到其下一轮提示的【形式化结果】行),并在群聊/活动日志留痕;
407
+ 随后由 §7.3 决定 `passed` 还是 `attempted`。
408
+ - **成员不得抢跑**:提示词明确写"作业落地为通过之前不得声称已通过或转忠实性审查"(§6.2)。
409
+
410
+ ### 7.5 卸载与中断(`dispose` / abort)
411
+
412
+ 插件 fiber 卸载、会话销毁或 abort 时:**终止每一个在跑的编译器进程**(`terminate()`),
413
+ 并把作业标为 `interrupted`、把对象停在 `attempted`——**绝不因为"进程已经不在"而当作通过**。
414
+ `interrupted` 的作业可以重跑(重跑会 `attempts+1`)。
415
+
416
+ ### 7.6 `--search-path` 注入(四套一致)
417
+
418
+ 编译器 argv 固定为:
419
+
420
+ ```
421
+ [exe, ...用户 leanArgs, --search-path <VibeMath 根>, <file>]
422
+ ```
423
+
424
+ - 自动根**只有一个**:`<VibeMath 根>`(`import Formal.Lib.<name>` 的模块根),插在
425
+ **用户参数之后、文件名之前**——`lake env lean` 因此自然变成 `lake env lean --search-path <root> <file>`。
426
+ - `leanSearchPaths` 非空时,**先注入用户给的路径(按给定顺序),再注入自动根**,整体去重。
427
+ - **用户显式给出搜索根就不注入**:`leanArgs` 里已有 `--search-path` / `-R` / `--root` 时完全尊重用户配置。
428
+ - **不使用环境变量**:宿主的 `spawn` 没有 env 槽位,所以"注入"只能是参数注入;
429
+ 也不依赖 `LEAN_PATH` 之类的约定。
430
+
431
+ ### 7.7 崩溃 / 重启恢复
432
+
433
+ 启动或 resume 时扫描 `<项目>/Formal/Jobs/*.json`(**在状态文件装好之后**,否则会写在未加载的
434
+ `formal` 记录上):
435
+
436
+ - `queued` → 重新入队;
437
+ - `running` + 文件哈希仍匹配 → 标 `interrupted`,并**重新入队**(`attempts+1`);
438
+ - `running` + 哈希不匹配 → 只标 `interrupted`(结果不可用,不重跑旧内容);
439
+ - `settled` 且**同一构建上下文** → 只补写那次已成立的结论(`resume` 路径);
440
+ 上下文已变则标 `interrupted` 并记 `build context changed`。
441
+
442
+ **恢复绝不"静默地"把对象验证掉**:任何不确定的情形都落到 `attempted`/`interrupted` + 明确 note,
443
+ 由代理决定是否重跑。
444
+
315
445
  ---
316
446
 
317
447
  ## 8. 门禁(`require`)的实现位置
@@ -350,27 +480,29 @@
350
480
 
351
481
  ### 9.2 `<VibeMath 根>/Formal/Lib/Index.md`
352
482
 
353
- **必需列**:`名称 | 文件 | 类别 | 摘要`。
483
+ **必需列**:`名称 | 文件 | 类别 | 依赖(import) | 摘要`。
354
484
  **可选列**:若该架构为库文件保留运行记录,可增加 `最近运行`(v3 就是这样做的,v2/v4/v5 没有);
355
485
  **没有记录就不许加这一列、更不许写假数据**——"说得出这个文件还编不编得过"是有价值的信息,
356
486
  但编一个不说实话的运行状态比没有更糟。
357
487
 
488
+ **依赖列由框架扫描该 `.lean` 的 `import` 行得到**(四套都写),这样索引一次回答
489
+ "有什么 / 叫什么 / 怎么导入":一个定义 import 了谁,决定了它能不能被逐字复用。
490
+
358
491
  ```markdown
359
492
  # 可复用 Lean 定义库(跨项目)
360
- | 名称 | 文件 | 类别 | 摘要 | 最近运行 | ← 最后一列可选
361
- |---|---|---|---|---|---|
362
- | ZMod5 | Lib/ZMod5.lean | def | 模 5 剩余类与基本引理 | ok |
493
+ | 名称 | 文件 | 类别 | 依赖(import) | 摘要 | 最近运行 | ← 最后一列可选
494
+ |---|---|---|---|---|---|---|
495
+ | ZMod5 | Lib/ZMod5.lean | def | Mathlib.Data.ZMod.Basic | 模 5 剩余类与基本引理 | ok |
363
496
  ```
364
497
 
365
498
  ### 9.3 `<VibeMath 根>/Formal/Proved/Index.md`
366
499
 
367
- **必需列**:`名称 | 文件 | 陈述`。
368
- **可选列**:`依赖`(仅当该架构记录依赖关系时)、`最近运行`(同上)、`类别`(v2/v4/v5 用它标注
369
- `lemma`;v3 不加,因为整张表都是引理)。
500
+ **必需列**:`名称 | 文件 | 陈述 | 依赖`(依赖同样由 `import` 行扫描得到,四套都写)。
501
+ **可选列**:`最近运行`、`类别`(v2/v4/v5 用它标注 `lemma`;v3 不加,因为整张表都是引理)。
370
502
 
371
503
  ```markdown
372
504
  # 已成立的 Lean 命题 / 引理(机器已核对,可跨项目复用)
373
- | 名称 | 文件 | 陈述 | 依赖 | 最近运行 | ← 后两列可选
505
+ | 名称 | 文件 | 陈述 | 依赖 | 最近运行 | ← 最后一列可选
374
506
  |---|---|---|---|---|---|
375
507
  | pell_sq_odd | Proved/pell_sq_odd.lean | … | ZMod5 | ok |
376
508
  ```
@@ -382,7 +514,10 @@
382
514
 
383
515
  ## 10. 测试要求(每个架构都要有)
384
516
 
385
- 1. **`off` 是无操作**:提示词里不出现 Lean 字样;验证流程与门禁行为与改动前一致。
517
+ 1. **`off` 是(验证阶段的)无操作**:`formalVerify:'off'` + 默认 `leanInitiative:'normal'` 时提示词里不出现
518
+ Lean 字样;验证流程与门禁行为与改动前一致。**主动性是独立轴**:显式 `leanInitiative:'eager'` 时,
519
+ `off` 档仍注入日线(且只注入日线——验证提示词里不得出现验证阶段的 Lean 文本),
520
+ `leanInitiative:'off'` 则连日线也不注入。
386
521
  2. **`encourage` 注入**:验证提示词含鼓励段落;平时工作提示词含"顺手形式化"段落;
387
522
  对象 `passed` 后,验证提示词切换为**忠实性审查**措辞。
388
523
  3. **`require` 门禁**:无形式化记录时"真"结论**不写入 Verified/**,而是 `undecided` +
@@ -394,7 +529,7 @@
394
529
  5. **参数**:非法值回退;可在运行中切换(切换后新提示词立刻反映新模式)。
395
530
  6. **灵敏度探针**(新增,放进 `audit-formal-sensitivity.mjs`):每条不变式都要有能让对应套件**变红**的变异,
396
531
  且探针必须真的启动套件、真的被套件读取、变异真的改变行为(见 `AUDIT-CHECKLIST.md` §2)。
397
- 7. **静态提示词面**(放进 `audit-persona-surface.test.mjs`,四个预设一起):三个 Lean 工具名与四个参数名
532
+ 7. **静态提示词面**(放进 `audit-persona-surface.test.mjs`,四个预设一起):五个 Lean 工具名与八个参数名
398
533
  必须出现在**该预设 persona 的 `prefix` 与 `text` 两个块**里,档位名(`'off'`/`'encourage'`/`'require'`)
399
534
  逐字出现,忠实性语义与 `Formal/Lib` / `Formal/Proved` / `Verified/Lean` 路径写清;
400
535
  反向:persona 里的每个 `vibe_*` 名字必须真的注册。`audit-persona-sensitivity.mjs` 用变异副本
@@ -419,6 +554,18 @@
419
554
  11. **提示词硬要求的探针**(放进 `audit-formal-sensitivity.mjs`):把注入文本里的工具名改成缩写
420
555
  (`lean_archive`)、删掉 `require` 档的要求段落、把忠实性分支改回"偏离 → 0"——三者都必须让
421
556
  对应套件**变红**。
557
+ 12. **异步队列**(四套都要):`leanAsync=true` 时 `lean_run` / `lean_archive{run:true}` **立即返回**
558
+ `async.jobId` 且不阻塞成员;作业在 `Formal/Jobs/<jobId>.json` 有镜像;落地后**恰好公告一次**
559
+ (【形式化结果】行);`leanAsync=false` 走同步路径并给出与旧版一致的返回体。
560
+ 13. **`settled(ok)` 判据**:只有 exit 0 + 内容哈希未变 + 构建上下文未变三者同时成立才置 `passed`
561
+ 并写 `Verified/Lean/<id>.lean`;编译期间文件被改写或上下文改变时,结果必须**作废**并停在
562
+ `attempted`(断言状态与 note)。排队中/失败/超时/中断**一律不得**让对象变成已形式化。
563
+ 14. **卸载与崩溃恢复**:dispose/abort 必须 `terminate()` 在跑的编译并标 `interrupted`(绝不 `passed`);
564
+ 恢复扫描 `Formal/Jobs/*.json`——`queued` 重入队、`running` 标记中断(哈希匹配则重入队、`attempts+1`)、
565
+ `settled` 只在**同一构建上下文**下补写结论。
566
+ 15. **去重与搜索路径注入**:同内容 + 同构建上下文再归档 → `deduped:true` 且不重写不重编译;
567
+ 编译器 argv 必须是 `[exe, ...用户 leanArgs, --search-path <VibeMath 根>, <file>]`,
568
+ 用户已显式给出搜索根时**不注入**,`leanSearchPaths` 先于自动根且去重。
422
569
 
423
570
  ---
424
571
 
@@ -431,3 +578,11 @@
431
578
  一档落库语义(§4.1)——**降级 + 待办 + 不定论**,而不是把它记成"命题为假"。
432
579
  - **不把 Lean 通过等同于"命题为真"**:`passed` 只表示"形式化代码通过内核检查",
433
580
  该代码是否忠实于命题仍需 m 票审查。这正是 §0 表格里"审查对象变化"的含义。
581
+ - **不注入环境变量**:宿主的 `spawn` 没有 env 槽位,"搜索路径"只能靠 argv 注入(§7.6)。
582
+ 因此也不依赖 `LEAN_PATH` 之类的约定——换宿主时行为不会因为环境差异而变。
583
+ - **异步队列不是持久任务系统**:队列是内存态 + `Formal/Jobs/*.json` 镜像,没有后台守护进程;
584
+ 进程消失时进行中的编译就是 `interrupted`,由恢复逻辑重入队或标记(§7.7)。
585
+ - **不做数学引擎的 argv 模板**:本版本**没有** Maple / MATLAB / Wolfram 的命令行模板,
586
+ 也没有 `mathEngineOverride` 参数(四套插件与本文件都不包含它们)。若将来加入这类外部引擎,
587
+ 其 argv 是**版本相关**的(同一命令在不同版本上参数不同),必须逐个标注 `VERIFY` 并保留一个显式的
588
+ 覆盖入口(如 `mathEngineOverride`)作为逃生门——**不得**把一个未经验证的模板写成"可用"。
@@ -0,0 +1,121 @@
1
+ # 数学计算工具(`math_computation`)—— 契约与用法
2
+
3
+ > 四个预设(v2/v3/v4/v5)共用同一个工具。规格与冻结件在开发检出里:`_oneoff/math-computation-spec-v3.md`(rev-3)、`_oneoff/mc-P1-ready/INTERFACE-FREEZE.md`、`mc-P1-ready/{engines.md,tool-schema.json,receipt.md,prompts.md,guards.md}`。
4
+ > 本文件只讲**已实现的行为**与**明确不能保证的东西**,不含性能或效果承诺。
5
+
6
+ ## 1. 它是什么
7
+
8
+ `math_computation` 让成员先在**运行时探测**本机可用的计算引擎与包,再以统一接口执行计算,并把每次执行落成**可复核回执**:脚本原件、完整输出、实际 argv、解释器/引擎版本、退出码与耗时。它比"随手用宿主 shell 跑一条命令"多出来的只有三件事——**探测**、**统一接口**、**归档**。
9
+
10
+ **P1 引擎集合**:`python`、`r`、`octave`、`julia`(自由软件,全功能)+ `matlab`、`maple`、`wolfram`(商业:只探测与许可检查,**永不安装**,有证即可用)+ `cli`(通用逃生口,**默认开启**)。SageMath 属 P2,P1 不注册。
11
+
12
+ ## 2. 参数(六个,名字已冻结)
13
+
14
+ | 参数 | 取值 | 默认 | 说明 |
15
+ |---|---|---|---|
16
+ | `mathComputation` | `off` \| `auto` \| `on` | `auto` | `off` 是**真 no-op**:不探测、不执行、不在提示词里出现;`auto` 探测到引擎才工作 |
17
+ | `mathMode` | `typed` \| `typed+shell` | `typed+shell` | **只是提示词策略**(见 §6):允许在工具不可用时用宿主 shell 兜底,但结论必须标注"未经工具归档";`typed` 不提 shell,并禁用 `engine:'cli'` |
18
+ | `mathEngines` | 引擎名数组 | `[python, r, octave, julia, matlab, maple, wolfram, cli]` | 允许的引擎**及其探测顺序**;去掉 `cli` 即关闭逃生口 |
19
+ | `mathTimeoutMs` | 正整数(≥1000) | `60000` | 单次执行上限;超时后**主动终止**进程 |
20
+ | `mathPackages` | 字符串数组 | `[]` | 默认要求存在的包/工具箱(缺包只报告,不安装) |
21
+ | `mathInstallScope` | `user` \| `system` | `user` | **作用域只对当次调用生效,永不记忆**;多数包管理器没有 system 模板,此时 system 请求会被拒绝 |
22
+
23
+ 非法值一律回落默认(枚举非白名单 → 默认;`mathTimeoutMs` 非正 → 默认并抬到 ≥1000;`mathEngines` 过滤未知项、去重、空则回落默认)。
24
+
25
+ ## 3. 工具接口
26
+
27
+ ```
28
+ math_computation {
29
+ op: 'probe' | 'run' | 'receipt' | 'install',
30
+ engine?, mode?: 'code'|'file'|'expr', code?|file?|expr?,
31
+ packages?, timeoutMs?, captureFiles?, record?,
32
+ cli?: { command, argv?, stdin? }, // engine='cli' 必填
33
+ scope?: 'user'|'system', dryRun?, confirm? // op='install'
34
+ }
35
+ ```
36
+
37
+ - **`probe`**:只探测引擎/版本/许可与(可选的)包,**不执行用户代码**。没有可用引擎时返回 `MATH_ENGINE_NOT_FOUND` + 逐 OS 的用户安装指引。
38
+ - **`run`**:`mode:'code'` 由工具把源码落成 `script.<ext>` 再执行;`mode:'file'` 只接受**项目内**路径(复制进回执目录后执行);`mode:'expr'` 走该引擎的 eval 模板(表达式只占**一个** argv 元素)。`engine:'cli'` 时 argv = `cli.command` + `cli.argv`,`mode:'expr'` 被拒绝。
39
+ - **`receipt`**:对既有 `Computation/<id>/` **幂等**重渲染 `receipt.md`、补写缺失产物,**不重跑**。
40
+ - **`install`**:两步。先 `dryRun`(默认)返回计划与 `planToken`;执行必须把该 token 原样回传 `confirm`,token 不符即拒绝。**商业引擎一律拒绝**。
41
+
42
+ 失败码(11 个,全部 `MATH_` 前缀):`MATH_NOT_AVAILABLE`、`MATH_ENGINE_NOT_FOUND`、`MATH_ENGINE_LICENSE_REQUIRED`、`MATH_ENGINE_UNUSABLE`、`MATH_MISSING_PACKAGES`、`MATH_TIMEOUT`、`MATH_NONZERO_EXIT`、`MATH_ENGINE_BAD_ARGV`、`MATH_REFUSED`、`MATH_INVALID_ARGUMENT`、`MATH_NO_SUBPROCESS`。每个"环境缺失"类失败都带 `next`(自装指引 / 代装计划 / 厂商链接 / 启用建议)。
43
+
44
+ **"没有 subprocess 服务"与"没有引擎"是两件事**(避免误导用户去装 python):
45
+ - 宿主若能声明自己没有 subprocess 服务(预设可选提供 `hasSubprocess:()=>false`),`run` 与 `probe` 一律返回 `MATH_NO_SUBPROCESS` + `next.kind:'note'`;
46
+ - 宿主不提供该声明时:命令**解析不到** ⇒ `MATH_ENGINE_NOT_FOUND`(附逐 OS 安装指引);能解析但 `spawn` 拿不到句柄(返回 `null`)⇒ `MATH_NO_SUBPROCESS`。
47
+ - **机读词汇表(穷举;权威副本在 `_oneoff/mc-P1-ready/tool-schema.json#refusalVocabulary`,由 parity 守卫盯着,改名即红)**:
48
+ - `code`(11 个):`MATH_NOT_AVAILABLE` `MATH_ENGINE_NOT_FOUND` `MATH_ENGINE_LICENSE_REQUIRED` `MATH_ENGINE_UNUSABLE` `MATH_MISSING_PACKAGES` `MATH_TIMEOUT` `MATH_NONZERO_EXIT` `MATH_ENGINE_BAD_ARGV` `MATH_REFUSED` `MATH_INVALID_ARGUMENT` `MATH_NO_SUBPROCESS`;
49
+ - `next.kind`(7 个):`user-install`(用户自装指引)、`agent-install`(代装计划/计划就绪)、`vendor`(商业引擎厂商指引)、`enable`(`mathComputation:'off'` 的启用建议)、`engine-override`(bad-argv ⇒ 用 `mathEngineOverride` 覆盖模板)、`reason`(机读拒绝原因)、`note`(补充说明,如无 subprocess);
50
+ - `next.reason`(12 个,仅当 `next.kind==='reason'`):`policy`、`engine-not-allowed`、`missing-cli-command`、`expr-not-supported`、`mode-not-supported`、`path-outside-project`、`file-not-found`、`system-scope-unsupported`、`plan-token-mismatch`、`archive-missing`、`receipt-unparsable`、`no-subprocess`。
51
+ 调用方**不必解析文案**:按 `code` + `next.kind`(必要时 `next.reason`)分支即可;新增或改名必须同时更新这里与 `tool-schema.json`,否则守卫变红。
52
+
53
+ ## 4. 回执:可复核的支撑材料
54
+
55
+ ```
56
+ <项目或研究所根>/Computation/<runId>/
57
+ script.<ext> # 唯一权威源码(先落盘再执行)
58
+ stdout.txt / stderr.txt # 完整输出(返回体只给 ≤64KB 截断版)
59
+ receipt.json / receipt.md # 机器可读 + 人读
60
+ captured/… # captureFiles 的项目内副本
61
+ ```
62
+
63
+ - `runId = <preset>-<projectSlug>-<sha256(engine|mode|规范化的脚本|排序后的包需求)[0:12]>`——**不含墙钟**:同引擎、同脚本、同包要求在同一项目下命中同一目录。
64
+ - `receipt.json` 记录:引擎与版本、脚本路径与哈希、**实际 argv**、cwd、退出码/超时/耗时、截断标记、产物哈希、`network:"not-enforced-by-plugin"`。
65
+ - **复跑**:`math_computation { op:'run', engine:'<e>', mode:'file', file:'Computation/<id>/script.<ext>' }`,或直接照 `receipt.json` 的 `argv` + `cwd` 重放。
66
+ - 报告里"我算过"要给 `Computation/<id>/receipt.json`;**回执是经验证据,不是证明**——对象是否已验证仍由各预设既有的验证/共识路径决定。
67
+
68
+ ### 4.1 归档 → 编辑 → 重跑(可发现性与完整性)
69
+
70
+ 回执与返回体都带 **`scriptPath`**(归档脚本周相对路径)与 **`scriptHash`**(脚本字节的 sha256),所以"这次跑的到底是哪份代码"是可查的:
71
+
72
+ 1. `mode:'code'` 把源码落成 `Computation/<id>/script.<ext>`;agent **可以用普通文件工具打开并编辑它**。
73
+ 2. 编辑后用 `mode:'file'` 指向该脚本重跑,会写出**一份新回执**(新 attempt、新 `scriptHash`)。
74
+ 3. **旧回执对修改后的代码无效**。`mode:'file'` 的回执按**路径**归属同一个 archive id(不是按内容),所以"同一文件编辑后重跑"落在同一 id 的**新 attempt**,并显式给出:
75
+ - `scriptChanged:true`:该文件当前哈希与**上一份回执**记录的 `scriptHash` 不一致(旧回执不再代表当前代码);
76
+ - `scriptChangedDuringRun:true`:文件在**本次运行期间**被改动(典型是另一个成员同时在编辑)——运行前后各取一次哈希才可能发现;
77
+ - 两者都同时进入 `warnings[]`(`SCRIPT_CHANGED_SINCE_LAST_RECEIPT` / `SCRIPT_CHANGED_DURING_RUN`)与 `receipt.md`,**绝不静默**。
78
+ 4. `op:'receipt'` 会重新核对归档脚本的当前哈希与回执记录,返回 `scriptChanged` / `currentScriptHash`;它**只读**返回既有数据,仅补写**缺失**的 `receipt.md`,从不改写已有回执文件。**归档目录不存在**时返回 `MATH_REFUSED` + `next:{kind:'reason', reason:'archive-missing'}`(显式拒绝,不是"参数错误");`receipt.json` 存在但**无法解析**时仍是 `MATH_INVALID_ARGUMENT`(`reason:'receipt-unparsable'`,那确实是坏掉的产物)。
79
+ - **哈希的能力边界(按设计,不是缺陷)**:`scriptHash`/`scriptChanged` 是**内容**哈希——"内容相同但换了 inode/mtime(删了重建、复制覆盖)"与"改了又改回原样"**无法区分**,因此也不会被告警;需要区分"文件被动过"请用宿主的版本守卫(`ctx.fs` 的 `expected.version`)。**每会话模块实例的生命周期**跟随该会话对象(v4 原有生命周期,非本轮引入)。
80
+ - **手改状态文件里的未知键不会被静默丢弃(round-4)**:v5 从研究所状态文件恢复参数时,白名单之外的键会被忽略,但**会记入 `vibe_v5_status` 的 `diagnostics`**(`{kind:'state-dropped-keys', where, keys}`)并打印警告;同一归一化器也服务 `vibe_v5_set` / `vibe_v5_configure`,所以未知键在任何入口都不会无声消失。
81
+ 5. **归档是追加式的**:同一 id 的**首次**运行落在 `Computation/<id>/`,之后每次运行落在 `Computation/<id>/attempts/<n>/`(n 从 2 开始,取第一个空位);已有文件**永不覆盖**。**并发**下同一 id 的两个运行由模块按 id 串行分配,因此一定分别拿到 attempt 1 / attempt 2,不会共用目录(跨进程并发见 §6 的限制说明)。
82
+ 6. **失败关闭(fail-closed)**:源文件在运行**期间消失** ⇒ `scriptChangedDuringRun:true`;`op:'receipt'` 读不到归档脚本(被删/移走)⇒ `scriptChanged:true` + `currentScriptHash:null`。任何"读不到/对不上"都按"旧回执不可用"处理,绝不静默当真。
83
+ 7. **保留上限**:每个 id 最多 `MATH_ARCHIVE_MAX_ATTEMPTS_PER_RUN`=20 个 attempt、每个项目最多 `MATH_ARCHIVE_MAX_RUNS`=200 个 run 目录(后者仅在宿主提供可选 `listDir` 时检查)。**超过只告警**(`ARCHIVE_RETENTION_EXCEEDED`),**永不自动删除**。
84
+
85
+ **验证者(或复核代理)要查的六件事**:① 打开 `receipt.json` 确认 `scriptPath`;② 重新计算该脚本 sha256,与 `scriptHash` **逐字相等**;③ 确认 `scriptChanged` 与 `scriptChangedDuringRun` 均为 `false`(否则该回执不足以支撑结论);④ 确认 `sourceFile`(若有)正是被验证的源码,且 `attempt`/`attemptDir` 是当前代码对应的最新 attempt;⑤ 按 `argv` + `cwd` 重放,结果一致;⑥ 一旦改代码,**必须重跑并引用新回执**,报告里的旧回执撤下。
86
+
87
+ ## 5. 安装:两条路径
88
+
89
+ 1. **用户自己装**:工具在缺引擎时返回逐 OS 命令(Windows/macOS/Linux)与官方链接;商业引擎只给厂商安装包与激活说明。
90
+ 2. **代理代装**:`plan → confirm`(token 绑定该计划,内容变了就失效);默认 **user 作用域**(`pip --user`、用户 depot、`R_LIBS_USER`、Octave 用户包目录…);**system 作用域必须每次显式指定且不被记住**;每次执行写审计 `Computation/installs/<planToken>.json`(计划、作用域、管理器、命令、退出码、before/after、卸载命令模板)。
91
+
92
+ **诚实的回滚说明**:包安装**没有通用回滚**。工具保证的是——记下确切命令与 before/after 版本(卸载**可验证**)、默认 user 作用域限制影响面、提供各管理器的卸载模板;**不做**自动回滚。
93
+
94
+ ## 6. 插件**能**与**不能**强制的东西
95
+
96
+ **能强制**:入参 schema(闭合;未知键拒绝)、**词法级**项目内路径守卫(见下)、cwd、超时并终止进程、输出上限、回执落盘、插件自身的写域(只写 `Computation/`)、安装两步与作用域默认、同一 archive id 的**串行分配**(并发运行不会共用 attempt 目录)。
97
+
98
+ **不能强制**(如实声明,勿当成承诺):
99
+ - **路径守卫是字符串级的**:`projectRel` 只做词法规范化(拒绝绝对路径、盘符、`..` 越界),**不做 realpath、不解析符号链接/junction/硬链接**。项目内一个指向外部的链接可以绕过该守卫拿到 `mode:'file'` 的读取与执行;真正的容器化由宿主 `ctx.fs` 后端负责(`resolve`/`contains`),插件不重复实现。审计 A 的用例把这一**限制**钉成了断言(`tests/math-computation-shared.test.mjs` §16c)。
100
+ - 脚本内部的网络访问与文件访问——宿主 `subprocess.spawn` **没有 policy 槽**(`env` 层只做凭据/`DSH_*` 清洗与显式合并,不能限制网络或写盘;参见 `dsh-subprocess-local` 的 `runner-launch-*.js` 与 `vibe-math-v5.js` 中 audit M8 注释),任何被执行的计算进程都在宿主 fs 策略之外;
101
+ - `mathMode` 只是提示词策略:**模型仍可能直接调用宿主 shell**,插件无法阻止,也无法为 shell 路径生成回执。因此默认策略要求:走 shell 得出的结论必须标注"**未经工具归档(shell 路径)**",**只有工具路径的计算才算可复核支撑材料**;
102
+ - 商业引擎的 CLI 模板随版本变化(Maple 尤甚):描述符带 `VERIFY` 标记,**每次运行都回显实际 argv**;若引擎以用法/选项错退出,返回 `MATH_ENGINE_BAD_ARGV` + `next.kind='engine-override'`(指向 `mathEngineOverride` 覆盖模板),而不是裸 `MATH_NONZERO_EXIT`。有许可的机器应验证这三个模板。
103
+ - 真强制(禁网/限权)需要宿主 sandbox/policy 支持——**列为待上游需求**,不在本轮范围。
104
+
105
+ **验证边界(诚实声明)**:引擎执行本身、各引擎的真实 argv/版本/许可路径与安装器命令**只通过假 subprocess seam 验证**(`tests/helpers/math-computation-fake-seam.mjs`),**没有**在真实 python/R/octave/julia/matlab/maple/wolfram 上跑过;真机验证需要一台装有这些引擎的机器(安装计划、模板与许可探测的**静态**面已在 `guards.md` 与 parity 守卫里冻结)。
106
+
107
+ **并发下的完整性**:同一 archive id 的分配与"占用"(receipt.json 落盘)在**一个插件实例内**是串行的,因此两个并发同 id 运行会分别拿到 attempt 1 / attempt 2,绝不会共用目录。**跨进程**并发仍依赖宿主的独占创建原语(当前 fs 接口没有暴露,列为已知限制)。
108
+
109
+ **父目录由宿主创建(round-3 记录,勿再当缺陷)**:宿主的文本写会**自动创建缺失的父目录**——`dsh-fs-local/lib/index.js` 的 `writeFileAtomic` 在落盘前执行 `mkdir(dirname(absolutePath), { recursive: true })`。因此 `Computation/<runId>/`、`Computation/<runId>/attempts/<n>/` 以及异步作业镜像 `Formal/Jobs/` **不需要预先存在**;`ensureDirs()` 仍然列出它们只是为了空项目里的可发现性(v2/v3/v4/v5 现已都含 `Formal/Jobs` 与 `Computation`)。真机上"严格 fs 拒绝创建父目录"的实验与真实宿主行为矛盾,不能作为失败证据。
110
+
111
+ ## 7. `cli` 逃生口为什么默认开启
112
+
113
+ 模型本来就有宿主 shell 可用(默认 `mathMode='typed+shell'` 就允许把它当兜底),所以 `cli` **不扩大爆炸半径**;区别在于 `cli` 仍在工具内:照样有 cwd、超时、输出上限、路径守卫与回执(`engine` 记为 `cli:<command>`)。要收紧:把 `mathMode` 设为 `typed`,或从 `mathEngines` 去掉 `'cli'`——两种情况下 `engine:'cli'` 都返回 `MATH_REFUSED`。
114
+
115
+ ## 8. 与形式化验证(Lean)的关系
116
+
117
+ `math_computation` 产出**经验证据**(回执可复核);形式化证明仍是**可选**的 Lean 路径(`formalVerify: off|encourage|require`,默认 `off`)。计算回执**不能**顶替 Lean 的 `passed`/`blocked`,也不能把对象标为"已验证"。
118
+
119
+ ## 9. 参数配置在哪改
120
+
121
+ `vibe_math_set_params`(v2/v3)、`vibe_v4_set`(v4)、`vibe_v5_set`(v5)以及各自的控制面:v2/v3 是 `vibe_math_setup`(交互式 schema)/`vibe_math_status`/`vibe_math_template`/`vibe_math_save_settings`,v4 是 `vibe_v4_configure`/`vibe_v4_status`(**没有** `template`/`setup`),v5 是 `vibe_v5_configure`/`vibe_v5_status`(同样没有 `template`/`setup`);六个参数名在四套、README 双语参数表与本文中拼写一致(由 `tests/audit-math-computation-parity.mjs` 与 `audit-math-computation-contract.mjs` 盯着)。