dsh-vibe-math 2.3.1 → 2.3.2

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 (38) hide show
  1. package/AUDIT-CHECKLIST.md +45 -0
  2. package/README.md +16 -6
  3. package/RELEASE-NOTES-2.3.2.md +145 -0
  4. package/audit-formal-sensitivity.mjs +11 -2
  5. package/audit-prompt-invariants.mjs +414 -0
  6. package/audit-spec-traceability.mjs +173 -0
  7. package/docs/formal-verification.md +33 -10
  8. package/docs/generate_framework_diagram_v5.mjs +2 -1
  9. package/docs/test-timing.md +32 -10
  10. package/formal-verify-v2.test.mjs +242 -2
  11. package/formal-verify-v3.test.mjs +176 -4
  12. package/formal-verify-v4.test.mjs +184 -5
  13. package/formal-verify-v5.test.mjs +91 -4
  14. package/installer.js +3 -1
  15. package/package.json +5 -2
  16. package/prompt-corpus-persona/persona-corpus.json +2 -2
  17. package/prompt-corpus-persona/persona-corpus.md +6 -2
  18. package/prompt-corpus-v2/formal-verify-v2.json +134 -44
  19. package/prompt-corpus-v2/formal-verify-v2.md +1033 -44
  20. package/prompt-corpus-v3/formal-verify-v3.json +200 -128
  21. package/prompt-corpus-v3/formal-verify-v3.md +948 -243
  22. package/prompt-corpus-v4/formal-verify-v4.json +8 -3
  23. package/prompt-corpus-v4/formal-verify-v4.md +38 -10
  24. package/prompt-corpus-v5/prompt-corpus-v5.json +175 -246
  25. package/prompt-corpus-v5/prompt-corpus-v5.md +341 -781
  26. package/prompt-v5-integrity.test.mjs +136 -22
  27. package/run-tests.mjs +30 -11
  28. package/vibe-math-v2/vibe-math-v2.js +149 -35
  29. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +53 -5
  30. package/vibe-math-v3/vibe-math-v3.js +88 -23
  31. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +7 -6
  32. package/vibe-math-v4/vibe-math-v4.js +103 -24
  33. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +34 -11
  34. package/vibe-math-v5/agent.cordis.yml +6 -2
  35. package/vibe-math-v5/vibe-math-v5.js +56 -10
  36. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +40 -13
  37. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +16 -2
  38. package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5.svg +6 -5
@@ -121,7 +121,7 @@ VibeMath/Projects/<project>/
121
121
  | **会议** | `vibe_v4_meeting(agenda)` → 向全体发会议 prompt → 收齐发言 → 写 `Shared/meetings/<id>.md` → 广播结论 | 会议用于分工/方向/任务分配/提出验证/表决"是否已解决" |
122
122
  | **任务板** | 常驻在会议/留言提议任务 → 框架记 `Shared/taskboard.md`;认领后被唤醒 | 框架只搬运,不决定谁做什么 |
123
123
  | **产物沉淀** | `vibe_v4_publish_progress` / `record_proposition` / `record_method` / `record_subproblem` | **必填**:价值程度 / 动机用途计划 / 自身概率估计(框架校验,缺则提示) |
124
- | **共识验证** | `vibe_v4_propose_verify(targetId)` → 常驻们同意后开辩论 | 独立初评→公开辩论→**全票真/假才入 Verified/**;否则留库附概率 |
124
+ | **共识验证** | 回执字段 `propose_verify: "<id>"`(常规/心跳轮与会议发言都会读取;v4 **没有** `vibe_v4_propose_verify` 这个工具)→ 框架按 FIFO 排队 → 全体独立初评→公开辩论 | 独立初评→公开辩论→**全票真/假才入 Verified/**;否则留库附概率 |
125
125
  | **Lean 形式化验证** | `vibe_v4_lean_run` / `vibe_v4_lean_archive` / `vibe_v4_lean_lib` + `formalVerify` 三档开关 | 见 §31;`passed` 后表决对象从"推导是否正确"变成**忠实性**;`require` 档在收口点加门禁 |
126
126
  | **停止条件** | 会议中全体常驻对"原问题已解决"投票,**全部同意** → 停止唤醒 | 人工 `abort` 始终可用 |
127
127
  | **上下文/compact** | 监测每常驻上下文占比 ≥ 阈值 → 触发 DSH `/compact` | `compactThreshold` 默认 66,可调 |
@@ -793,12 +793,17 @@ else await closeVerify(vs,allTrue)
793
793
  | `LEAN_NOT_FOUND` | `resolveExecutable(leanCommand)` 失败(工具链未安装 → 优雅降级) |
794
794
  | `LEAN_SPAWN_FAILED` | `spawn` 本身抛错 |
795
795
  | `LEAN_RUN_FAILED` | `await handle.done` 抛错 |
796
- | `LEAN_TIMEOUT` | 非零退出且耗时达到 `graceMs`(超时上限) |
796
+ | `LEAN_TIMEOUT` | 到 `graceMs` 还没跑完:框架自己起一个 `cap` 计时器,到点**主动调用 `handle.terminate()`** 并返回合成结果(契约 §7;不再只依赖宿主的 `graceMs`)。宿主先于本计时器杀进程时,用"非零退出且耗时达到 `cap`"兜底 |
797
797
  | `LEAN_FAILED` | 其它非零退出(编译器输出原样返回 ~4KB 尾巴) |
798
798
  | `V4_INVALID_ARGUMENT` | 缺 `file`/`kind`/`name`/`target`/`note`;非 `.lean`;越界路径;未知 `kind` |
799
799
  | `V4_NOT_FOUND` | `.lean` 文件不存在(越界被拒 ≠ 不存在:两者码不同) |
800
800
  | `V4_WRITE_FAILED` | 目标路径写不进去 |
801
801
 
802
+ **超时是主动的**:`leanRunFile` 用 `Promise.race([handle.done, ctx.timeout(cap)])`,计时器一响就
803
+ `terminate()`;`done` 先赢则清掉该计时器(插件 runtime 没有全局 `setTimeout`,只能用 `timer` 服务)。
804
+ **唯一可删文件的路径**(§4.1 撤回归档证明):删除 → **用 `fs` 复核文件真的没了** → 还在就**覆盖为
805
+ 撤回说明**(`-- 已撤回(…)`),并把实际走的那条路(`deleted`/`overwritten`/`failed`)写进活动日志。
806
+
802
807
  **绝不抛异常到调度循环**:`leanRunFile` 把每一种失败都变成可读结果;`onResidentEnd` 里的
803
808
  `applyFormalReply` 整体包在 try/catch 内(回执处理失败只记 `activityLog`,不影响本轮结算)。
804
809
 
@@ -879,22 +884,40 @@ Lean 这一批改动同时做了一次**静态提示词面**审计(`audit-pers
879
884
  - **表决者**:不得投 1 或 0;给一个严格介于 0 与 1 之间的值(记为弃权)并在 `reason` 里写清偏差,
880
885
  同时用回执 `formal:{decision:'defect', note:'<具体偏差>'}` 记录(`note` 必填,缺则 `V4_INVALID_ARGUMENT`)。
881
886
  - **框架**:把该对象**降级为 `attempted`**(无论此前是 `passed` 还是 `blocked`)、清空 `proof`、
882
- 撤回归档证明 `Verified/Lean/<id>.lean`(`subprocess` 可删则删,否则用"已撤回"说明覆盖)、
883
- `note` 记入记录与 `Formal/TODO.md`、活动日志公告。
887
+ 撤回归档证明 `Verified/Lean/<id>.lean`(**删除 → 用 `fs` 复核真的没了 → 还在就覆盖为"已撤回"说明**,
888
+ 见 §31.14)、`note` 记入记录与 `Formal/TODO.md`、活动日志公告。
884
889
  - **`require` 档**:降级后 `formalGateOk` 为假 → 本次裁定**不定论**(`deferForFormal`),
885
- 对象进「形式化待办」,修正形式化并重新跑通后再投票。
890
+ 对象进「形式化待办」,修正形式化并重新跑通后再投票。**`encourage` 档不得声称这一点**(没有门禁,
891
+ 框架撤不回表决):注入文本与活动日志都按档位分岔,说明"本档没有门禁:请务必给弃权值"。
886
892
  - **只有独立于这份 Lean 代码也能确定命题为假**时才投 0,并在 `reason` 里写清独立理由。
887
893
 
888
894
  同批补齐的提示词硬要求(契约 §6):注入文本里的工具名**一律全称**(`vibe_v4_lean_*`;缩写
889
- `lean_archive` 不是注册名);归档可复用定义/引理**前先跑通**(`run=true` 或先 `vibe_v4_lean_run`),
890
- 跑不通不许进 `Formal/Lib`/`Proved`;**工具链缺失**(`LEAN_NOT_FOUND`)时把代码归档并在 `note`
891
- 写明"宿主无 Lean 工具链"——这算显式阻塞原因,门禁据此放行,不会因为装不了 Lean 而卡死。
895
+ `lean_archive` 不是注册名,连活动日志行也不例外);归档可复用定义/引理**前先跑通**(`run=true` 或先 `vibe_v4_lean_run`),
896
+ 跑不通不许进 `Formal/Lib`/`Proved`;**工具链不可用**时把代码归档并在 `note`
897
+ 写明"宿主无 Lean 工具链"——**两个错误码都写出来**(`LEAN_NOT_FOUND`:没有 Lean 可执行文件;
898
+ `NO_SUBPROCESS`:宿主根本不提供 `subprocess` 服务),两者都算显式阻塞原因,门禁据此放行,
899
+ 不会因为装不了 Lean 而卡死。
892
900
 
893
- **测试**:`formal-verify-v4.test.mjs` 增至 **226 条断言**(含 `defect` 端到端:降级 + 撤回归档证明 +
901
+ **测试**:`formal-verify-v4.test.mjs` 增至 **262 条断言**(含 `defect` 端到端:降级 + 撤回归档证明 +
894
902
  进待办 + `require` 档不定论 + 缺 `note` 被拒;注入文本不得出现缩写工具名、不得出现"偏离 → 0"),
895
903
  并新增随包发布的人工复核语料 `prompt-corpus-v4/formal-verify-v4.{json,md}`
896
- (覆盖 off 无 Lean 文字、encourage、**require**、passed 忠实性分支、平时工作轮、回执契约;
897
- 工作区路径归一化为 `<WS>`、VibeMath 根为 `<VIBEMATH>`,可 diff、无本机路径)。
904
+ (覆盖 off 无 Lean 文字、encourage、**require**、passed 忠实性分支(require/encourage 两种措辞各一份)、
905
+ 平时工作轮、回执契约;工作区路径归一化为 `<WS>`、VibeMath 根为 `<VIBEMATH>`,可 diff、无本机路径)。
906
+
907
+ ### 31.14 深审计轮修复(本轮)
908
+
909
+ 逐条对着共同契约复审后修掉的问题(每条都有断言,见 §31.10 的测试表):
910
+
911
+ | 问题 | 症状 | 修复 |
912
+ |---|---|---|
913
+ | **`lean_run` 把 `passed` 降级** | `formalSetRun` 硬编码 `status:'attempted'`(注释却写着"`passed`/`blocked` 不被红运行降级",v2/v5 也是保留语义):运行中任何一次 `vibe_v4_lean_run {target}` 都会把已通过的记录打回 `attempted` 而 `proof` 指针留着——下一轮表决提示词因此丢掉**忠实性分支**,`require` 档还会对一份已跑绿的归档证明**重新关门禁** | `status: prev.status==='passed'?'passed':(prev.status==='blocked'?'blocked':'attempted')`;断言"红重跑不降级" |
914
+ | **`off` 模式仍写 `State/formal.json`** | `saveAll()` 无条件落这份状态文件:一个从未用过该功能的 run 也会留下 Lean 状态文件(v3 的守卫是 `formalOn()||有记录`) | 仅当 `formalOn()||formalPersisted||有记录/待办` 才写;`formalPersisted` 保证 `start()` 的清零**真的落盘**(否则 resume 会恢复陈旧 `passed`),纯 `off` 会话不产生该文件 |
915
+ | **超时不主动终止** | 只靠宿主的 `graceMs`,从不调 `handle.terminate()`(契约 §7 明写要求),宿主的宽限失效时 Lean 进程会残留 | `Promise.race([handle.done, ctx.timeout(cap)])`,到点 `terminate()` 并返回 `LEAN_TIMEOUT`;`done` 先赢则清掉计时器(插件 runtime 无全局 `setTimeout`,只能用 `timer` 服务) |
916
+ | **撤回归档证明只是"尽力删除"** | 删除失败(无 `subprocess`/shell 假成功/权限)时,被撤回的证明仍停在 `Verified/Lean/<id>.lean`——所有人找"这条结论的证明"的地方;记录却已降级 | 删除 → **用 `fs` 复核文件真的没了** → 还在就**覆盖为 `-- 已撤回(…)` 说明**;返回并播报实际走的那条路(`deleted`/`overwritten`/`failed`) |
917
+ | **`encourage` 档承诺"不定论"** | 忠实性分支与活动日志都无条件写"本次裁定**不定论**",但只有 `require` 有门禁——这是在承诺框架没有的行为(契约 §4.1-3/§6.1;v5 已分岔) | 按档位分岔:`require` 保留"不定论";`encourage` 改为"本档没有门禁:请务必给弃权值,以保证本轮无法得出一致结论" |
918
+ | **注入文本只写 `LEAN_NOT_FOUND`** | `NO_SUBPROCESS`(宿主没有 `subprocess` 服务)这条出路没写进提示词,代理会反复重试而不是记阻塞(契约 §6 硬要求 4;v3/v5 都写了两个码) | 同一句话里补上两个码,并断言该句在插件里只出现一份(不按档位复制) |
919
+ | **活动日志里用缩写工具名** | `'… lean_run …'`(不是注册名;host 会把活动日志念给团队) | 改为 `vibe_v4_lean_run`;注入文本扫描面扩到 `State/session.json` 的 `activityLog` |
920
+ | **`实现方案.md` 写了不存在的工具** | §5 表格里的 `vibe_v4_propose_verify(targetId)` 从未注册(验证对象靠回执字段 `propose_verify`),照着文档找工具会扑空 | 表格改回真实机制(回执字段 → FIFO 排队) |
898
921
 
899
922
 
900
923
 
@@ -109,7 +109,9 @@
109
109
  reusable definitions VibeMath/Formal/Lib/; proved lemmas VibeMath/Formal/Proved/.
110
110
  - The toolchain knobs leanCommand / leanArgs / leanTimeoutMs are tunable as well
111
111
  (e.g. leanCommand='lake' with leanArgs=['env','lean']); a missing Lean binary is
112
- reported as LEAN_NOT_FOUND and still lets the code be written and archived.
112
+ reported as LEAN_NOT_FOUND, a host with no subprocess service as NO_SUBPROCESS
113
+ (on a timeout the process is terminated first), and in every one of those cases the
114
+ code can still be written down and archived.
113
115
  - vibe_v5_status / vibe_v5_report show the mode, per-object formal status and the
114
116
  formalization TODO. The framework never installs Lean and never judges fidelity.
115
117
 
@@ -201,7 +203,9 @@
201
203
  reusable definitions VibeMath/Formal/Lib/; proved lemmas VibeMath/Formal/Proved/.
202
204
  - The toolchain knobs leanCommand / leanArgs / leanTimeoutMs are tunable as well
203
205
  (e.g. leanCommand='lake' with leanArgs=['env','lean']); a missing Lean binary is
204
- reported as LEAN_NOT_FOUND and still lets the code be written and archived.
206
+ reported as LEAN_NOT_FOUND, a host with no subprocess service as NO_SUBPROCESS
207
+ (on a timeout the process is terminated first), and in every one of those cases the
208
+ code can still be written down and archived.
205
209
  - vibe_v5_status / vibe_v5_report show the mode, per-object formal status and the
206
210
  formalization TODO. The framework never installs Lean and never judges fidelity.
207
211
 
@@ -1714,10 +1714,39 @@ export function apply(ctx) {
1714
1714
  return { ok: false, code: 'LEAN_SPAWN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
1715
1715
  }
1716
1716
  let outcome
1717
+ // docs/formal-verification.md §7: a TIMEOUT must actually TERMINATE the process —
1718
+ // `graceMs` is only a request to the host, so relying on it alone could leave a runaway
1719
+ // Lean (or a host that ignores graceMs) alive while we report LEAN_TIMEOUT. Race `done`
1720
+ // against a `cap` timer that calls `handle.terminate()`, exactly like v2/v3/v4. The
1721
+ // timer disposer runs in BOTH outcomes, so a settled run never leaks a timer.
1722
+ let timerDisposer = null
1723
+ let timedOut = false
1724
+ // A `done` that settles AFTER the timeout won the race must not surface as an unhandled
1725
+ // rejection; the guarded `ran` promise is what the race uses, so the chain is attached
1726
+ // once and never re-created after it may already have rejected.
1727
+ const ran = Promise.resolve(handle.done).then(
1728
+ (v) => ({ settled: true, value: v }),
1729
+ (e) => ({ settled: false, error: e }))
1717
1730
  try {
1718
- outcome = await handle.done
1731
+ const r = await Promise.race([
1732
+ ran,
1733
+ new Promise((resolve) => {
1734
+ timerDisposer = ctx.timeout(() => {
1735
+ timedOut = true
1736
+ try { if (typeof handle.terminate === 'function') handle.terminate() } catch (e) { /* the race result is the report */ }
1737
+ resolve({ settled: true, value: { exitCode: null, signal: 'SIGTERM' } })
1738
+ }, cap)
1739
+ }),
1740
+ ])
1741
+ if (r.settled) outcome = r.value
1742
+ else throw r.error
1719
1743
  } catch (e) {
1744
+ if (timerDisposer) { try { timerDisposer() } catch (e2) { /* already settled */ } }
1720
1745
  return { ok: false, code: 'LEAN_RUN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
1746
+ } finally {
1747
+ // `done`/error wins -> the timer must not fire later. When the TIMEOUT won, the timer
1748
+ // has already fired and disposing it is a no-op, so this is safe in either order.
1749
+ if (timerDisposer) { try { timerDisposer() } catch (e) { /* already settled */ } }
1721
1750
  }
1722
1751
  let out = '', err = ''
1723
1752
  try { if (handle.collected && handle.collected.stdout) out = handle.collected.stdout.readFrom(0).text } catch (e) { /* best effort */ }
@@ -1729,8 +1758,8 @@ export function apply(ctx) {
1729
1758
  ok, exitCode, signal: (outcome && outcome.signal) || null, ms,
1730
1759
  command: argv.join(' '), file: rel,
1731
1760
  stdout: tail(out, 4000), stderr: tail(err, 4000),
1732
- timedOut: ms >= cap,
1733
- code: ok ? undefined : (ms >= cap ? 'LEAN_TIMEOUT' : 'LEAN_FAILED'),
1761
+ timedOut: timedOut || ms >= cap,
1762
+ code: ok ? undefined : ((timedOut || ms >= cap) ? 'LEAN_TIMEOUT' : 'LEAN_FAILED'),
1734
1763
  }
1735
1764
  }
1736
1765
  async function formalSetRun(target, run) {
@@ -1787,8 +1816,13 @@ export function apply(ctx) {
1787
1816
  await putFormal(t, formalOf(t), list)
1788
1817
  await writeFormalTodo()
1789
1818
  await writeFormalIndex()
1819
+ // `removeArchivedProof` can fail on a host whose shell cannot delete AND whose
1820
+ // overwrite also fails. The record is downgraded either way, so say it out loud:
1821
+ // otherwise the stale file stays at the exact path everyone looks for proofs, and
1822
+ // nothing in any prompt or index would disclose that the withdrawal was incomplete.
1823
+ const stillThere = removed ? [] : ['|⚠ 归档证明 ', proofRel, ' 未能撤回(宿主删除与覆盖均失败);记录已降级,请不要把它当作该对象的证明。']
1790
1824
  await saveChatLine('【形式化】' + (memberId || '成员') + ' 认定 ' + t + ' 的形式化**不忠实**:' + why
1791
- + ' —— 已撤回「已通过」状态并从 Verified/Lean/ 删除归档证明;请修正形式化、重新跑通后再投票。')
1825
+ + ' —— 已撤回「已通过」状态' + (removed ? '并从 Verified/Lean/ 删除归档证明' : '') + ';请修正形式化、重新跑通后再投票。' + stillThere.join(''))
1792
1826
  return { ok: true, target: t, status: 'attempted', removed }
1793
1827
  }
1794
1828
  function formalPromptBlock(target) {
@@ -1811,9 +1845,12 @@ export function apply(ctx) {
1811
1845
  L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
1812
1846
  // Only `require` actually GATES the conclusion; in `encourage` the framework still
1813
1847
  // withdraws the proof (and records the defect) but must not promise a hold it cannot
1814
- // enforce — the voter's own abstention is what keeps the ballot from concluding.
1848
+ // enforce. Say the same thing v2/v3/v4 say instead of silently omitting it: the
1849
+ // voter must know that the ABSTENTION is what keeps this round from concluding.
1815
1850
  L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办)'
1816
- + (mode === 'require' ? ',本次裁定**不定论**;' : ';'))
1851
+ + (mode === 'require'
1852
+ ? ',本次裁定**不定论**;'
1853
+ : '。本档没有门禁:请务必给一个严格介于 0 与 1 之间的弃权值,以保证本轮无法得出一致结论;'))
1817
1854
  L.push(' 修正形式化并重新跑通后再投票。')
1818
1855
  L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 reason 里写清独立理由。')
1819
1856
  } else if (rec.status === 'blocked') {
@@ -1834,7 +1871,7 @@ export function apply(ctx) {
1834
1871
  L.push(" · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision='blocked' 时必须写明 note)。")
1835
1872
  }
1836
1873
  L.push(' · 归档可复用定义/引理前先跑通(vibe_v5_lean_archive run=true 或先 vibe_v5_lean_run);跑不通不要入库。')
1837
- L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
1874
+ L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)或根本没有 subprocess 服务(NO_SUBPROCESS)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这两种都算显式阻塞原因,定论门禁可以据此放行。')
1838
1875
  }
1839
1876
  return L.join('\n')
1840
1877
  }
@@ -1983,7 +2020,12 @@ export function apply(ctx) {
1983
2020
  const rec = Object.assign({}, prev, {
1984
2021
  status: passed ? 'passed' : 'attempted',
1985
2022
  file: workRel,
1986
- proof: passed ? 'Verified/Lean/' + target + '.lean' : (prev.proof || ''),
2023
+ // A FAILED re-archive must also drop the pointer to the previous proof: the code
2024
+ // that proof referred to has just been overwritten by `body` (the file that failed),
2025
+ // so keeping it would advertise `Verified/Lean/<id>.lean` as this object's proof
2026
+ // while the object's own last run is a failure — a self-contradicting record, and
2027
+ // a pointer that the reviewer's `formalPromptBlock(rec.proof)` would print.
2028
+ proof: passed ? 'Verified/Lean/' + target + '.lean' : '',
1987
2029
  decision: 'used',
1988
2030
  note: String(args.note || prev.note || ''),
1989
2031
  run: { at: now(), ok: !!run.ok, exitCode: run.exitCode === undefined ? null : run.exitCode, ms: run.ms || 0, stdoutTail: tail(run.stdout, 800), stderrTail: tail(run.stderr, 800) },
@@ -3455,7 +3497,11 @@ export function apply(ctx) {
3455
3497
  // (4b) Lean formalization signal — the mandatory difficulty judgement. This is the
3456
3498
  // path that matters in practice: a member that never calls a Lean tool still has to
3457
3499
  // say "used / blocked", and `require` mode refuses to conclude without it.
3458
- if (p.formal && typeof p.formal === 'object') {
3500
+ //
3501
+ // `formalOn()` gates it: off mode is a TRUE no-op, and the field is not even offered in the
3502
+ // reply contract there, so a stray / stale / hallucinated reply must not create Lean state.
3503
+ // (The TOOLS stay usable in off mode on purpose — a tool call is deliberate.)
3504
+ if (formalOn() && p.formal && typeof p.formal === 'object') {
3459
3505
  const f = p.formal
3460
3506
  const target = idSafe(String(f.target || ''))
3461
3507
  if (target) {
@@ -4212,7 +4258,7 @@ export function apply(ctx) {
4212
4258
  // dynamic registration would depend on a runtime knob and break the effect discipline),
4213
4259
  // while the MODE only decides whether the framework TELLS members about them. In 'off'
4214
4260
  // mode they still work if a human or agent calls them deliberately.
4215
- registerTool('vibe_v5_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the workspace and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: S, target: S, timeout_ms: I }, ['file']), (s, a, x) => s.leanRunTool(s.memberIdOfAgent(x), a))
4261
+ registerTool('vibe_v5_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the workspace and report the result. Never throws: a host with no subprocess service returns NO_SUBPROCESS and a missing toolchain returns LEAN_NOT_FOUND (in both cases the code can still be written down with vibe_v5_lean_archive), a timeout terminates the process and returns LEAN_TIMEOUT, and a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: S, target: S, timeout_ms: I }, ['file']), (s, a, x) => s.leanRunTool(s.memberIdOfAgent(x), a))
4216
4262
  registerTool('vibe_v5_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: S, target: S, content: S, from: S, note: S, run: B }, ['kind']), (s, a, x) => s.leanArchive(s.memberIdOfAgent(x), a))
4217
4263
  registerTool('vibe_v5_lean_lib', '(member) List (and by default rebuild) the Lean reuse library: your institute\'s Formal/Index.md, plus the global cross-project Formal/Lib and Formal/Proved indexes. Look here BEFORE writing a new definition so you reuse instead of redefining.', objParams({ refresh: B }), async (s, a) => {
4218
4264
  const r = a && a.refresh === false ? { lib: null, proved: null, objects: Object.keys(s.formalRecords()).length } : await s.rebuildLeanLibIndexes()
@@ -1092,6 +1092,11 @@ Progress/,不要从头再来",**不得**复用 `【入职首轮】` 与"你
1092
1092
 
1093
1093
  - `off`(默认)——提示词里不出现任何 Lean 内容,验证流程与门禁完全不变(`off` 是**真正的无操作**,
1094
1094
  这是测试第 1 条断言);三个 Lean 工具仍然注册(注册是静态的),只是框架不告诉成员它们存在。
1095
+ `off` 约束的是**框架主动做的事**:它不注入 Lean 文本、不设门禁、不改验证流程,回执里的 `formal`
1096
+ 通道在 `off` 档**完全不解析**(一个残留/幻觉出来的 `formal` 字段不会产生任何形式化状态)。
1097
+ 工具本身在任何档位下都照常执行并照实记录——**"主动调用是一个明确的动作"**,不会被静默忽略;
1098
+ 要"连工具也不留痕迹",请用 `leanCommand` 指向一个不存在的可执行文件(`LEAN_NOT_FOUND`),
1099
+ 而不是指望 `off` 去拦截人的显式调用。
1095
1100
  - `encourage`——验证提示词给出"按实现难度决定是否形式化"的要求与工具/路径;
1096
1101
  常规轮与心跳轮给出"顺手形式化可复用对象"的要求;**不设门禁**。
1097
1102
  - `require`——同样的提示词(语气为必须),**加门禁**:真/假结论必须
@@ -1122,24 +1127,39 @@ Progress/,不要从头再来",**不得**复用 `【入职首轮】` 与"你
1122
1127
  `formal:{decision:'defect', note:'<具体偏差>'}` 记录;
1123
1128
  - `recordFidelityDefect` 把该对象**降级为 `attempted`**、清空 `proof`、撤回
1124
1129
  `Verified/Lean/<id>.lean`(`subprocess` 可删则删,否则用"已撤回"说明覆盖,见
1125
- `removeArchivedProof`)、写入 `Formal/TODO.md` 并群聊公告;
1130
+ `removeArchivedProof`)、写入 `Formal/TODO.md` 并群聊公告;**撤回失败**(宿主既不能删也不能
1131
+ 覆盖)时必须在群聊公告里明说"归档证明未能撤回",不能让一个已被判定不忠实的形式化继续躺在
1132
+ `Verified/Lean/` 里而无人知晓;
1126
1133
  - `require` 档下这次裁定**不定论**,对象进「形式化待办」,修正形式化并重新跑通后再投票;
1134
+ `encourage` 档**没有门禁**,因此注入文本必须明说"本档没有门禁:请务必给一个严格介于 0 与 1
1135
+ 之间的弃权值,以保证本轮无法得出一致结论"——不能只把"框架会搁置本次裁定"这句删掉了事;
1136
+ - 重新 `lean_archive kind='proof'` 但**运行未通过**时,`proof` 指针必须一并清空:工作文件已经被
1137
+ 这次失败的代码覆盖,旧指针指向的文件不再是该对象的证明(否则 `Formal/Index.md` 会同时报告
1138
+ 「运行 fail」和一条归档证明路径);
1127
1139
  - 只有表决者**独立于这份 Lean 代码**也能确定命题为假时才投 0。
1128
1140
 
1129
1141
  **提示词硬要求(契约 §6,四套一致)**:注入文本里的工具名**一律全称**
1130
1142
  (`vibe_v5_lean_*`;缩写 `lean_archive` 不是注册名,代理照抄就调用了一个不存在的工具);
1131
1143
  归档可复用定义/引理**前先跑通**(`run=true` 或先 `lean_run`),跑不通不许入库;
1132
- **工具链缺失**(`LEAN_NOT_FOUND`)时把代码归档并在 `note` 写明"宿主无 Lean 工具链"——
1133
- 这算显式阻塞原因,门禁可以据此放行,代理不会因为装不了 Lean 而卡死。
1134
-
1135
- **测试**:`formal-verify-v5.test.mjs`(117 断言,含 `defect` 的端到端路径:降级 + 撤回归档证明 +
1136
- 进待办 + `require` 档不定论;以及"注入文本不得出现缩写工具名"),fake-Lean 通过 `subprocess`
1137
- 服务注入,因此不需要真的装 Lean;`audit-v5-sensitivity.mjs` 里 9 个专打形式化的探针;
1138
- `prompt-v5-integrity.test.mjs`(588 断言)把 Lean 相关提示词原文写进语料
1139
- (`lean-work` / `lean-verify` / `lean-fidelity` / **`lean-require`** / **`lean-after-defect`**,
1140
- 共 70 条提示词),供人工复核"忠实性审查"那段与缺陷撤回后的状态;语料把工作区路径
1141
- **大小写无关**地归一化为 `<WS>`(首版因 Windows 路径大小写差异漏掉了 VibeMath 根的绝对路径,
1142
- 语料因此不确定且泄露本机路径——已修)。
1144
+ **没有工具链**(`LEAN_NOT_FOUND`)或**宿主没有 subprocess 服务**(`NO_SUBPROCESS`)时把代码
1145
+ 归档并在 `note` 写明"宿主无 Lean 工具链"——两种都要在注入文本里点名(只认前者的代理会把后者
1146
+ 当成未知故障反复重试),这算显式阻塞原因,门禁可以据此放行,代理不会因为装不了 Lean 而卡死。
1147
+
1148
+ **运行(契约 §7)**:`lean_run` 用 `handle.done` 与 `cap` 毫秒计时器 **race**,超时一侧先调用
1149
+ `handle.terminate()`(存在才调)再返回 `LEAN_TIMEOUT`——只给 `graceMs` 是"请求"而非保证,
1150
+ runaway 的编译器会在框架已经报告超时之后继续存活;`done` 先到则释放计时器,不泄漏。
1151
+
1152
+ **测试**:`formal-verify-v5.test.mjs`(138 断言,含 `defect` 的端到端路径:降级 + 撤回归档证明 +
1153
+ 进待办 + `require` 档不定论;"注入文本不得出现缩写工具名";"超时路径真的调用 `terminate()`";
1154
+ "宿主没有 `subprocess` 服务时返回 `NO_SUBPROCESS` 且代码仍能写下来归档";`encourage` 档的
1155
+ 弃权措辞),fake-Lean 通过 `subprocess` 服务注入,因此不需要真的装 Lean;
1156
+ `audit-formal-sensitivity.mjs --only=v5` 里 13 个专打形式化的探针;
1157
+ `prompt-v5-integrity.test.mjs`(595 断言)把 Lean 相关提示词原文写进语料
1158
+ (`lean-work` / `lean-verify` / `lean-fidelity` / **`lean-require`** / **`lean-after-defect`** /
1159
+ **`lean-tool-hint`**,共 71 条提示词),供人工复核"忠实性审查"那段、缺陷撤回后的状态、
1160
+ 以及三个 Lean 工具自己的 `hint` / 失败消息;语料把工作区路径**大小写无关**地归一化为 `<WS>`、
1161
+ 把 VibeMath 根归一化为 **`<VIBEMATH>`**(与 v2/v3/v4 的语料一致,可并排 diff;首版因 Windows
1162
+ 路径大小写差异漏掉了 VibeMath 根的绝对路径,语料因此不确定且泄露本机路径——已修)。
1143
1163
 
1144
1164
 
1145
1165
  ---
@@ -1184,7 +1204,7 @@ Progress/,不要从头再来",**不得**复用 `【入职首轮】` 与"你
1184
1204
 
1185
1205
  | 工具 | 参数 | 说明 |
1186
1206
  |---|---|---|
1187
- | `vibe_v5_lean_run` | `{ file, target?, timeout_ms? }` | 在 `subprocess` 服务上执行 Lean,返回 `{ok, exitCode, ms, command, stdout, stderr}`。绝不抛异常:缺工具链 → `LEAN_NOT_FOUND`,超时 → `LEAN_TIMEOUT`,路径越界 → `V5_INVALID_ARGUMENT`。给了 `target` 就把这次运行记到该对象上 |
1207
+ | `vibe_v5_lean_run` | `{ file, target?, timeout_ms? }` | 在 `subprocess` 服务上执行 Lean,返回 `{ok, exitCode, ms, command, stdout, stderr}`。绝不抛异常:宿主没有 `subprocess` 服务 → `NO_SUBPROCESS`,缺工具链 → `LEAN_NOT_FOUND`,**超时 → 先 `handle.terminate()` 再返回 `LEAN_TIMEOUT`**,文件不存在 → `V5_NOT_FOUND`,路径越界或非 `.lean` → `V5_INVALID_ARGUMENT`。给了 `target` 就把这次运行记到该对象上 |
1188
1208
  | `vibe_v5_lean_archive` | `{ kind, name?, target?, content?, from?, note?, run? }` | `kind='def'/'lemma'` → 归档到**全局** `VibeMath/Formal/{Lib,Proved}/`;`kind='proof'` → 写 `Formal/<target>.lean`,运行通过则同时写 **`Verified/Lean/<target>.lean`** 并把对象标为 `passed`;`kind='blocked'` → 记录显式难度判断/阻塞原因(`note` 必填) |
1189
1209
  | `vibe_v5_lean_lib` | `{ refresh? }` | **重建并返回**三处索引(本所 `Formal/Index.md`、全局 `Lib/Index.md`、`Proved/Index.md`)与逐对象形式化状态;写新定义前先查重、直接复用 |
1190
1210
 
@@ -1213,6 +1233,13 @@ Progress/,不要从头再来",**不得**复用 `【入职首轮】` 与"你
1213
1233
 
1214
1234
  Lean 工具另外会返回**非错误码**的失败原因(都属于"可读结果"而非异常):`NO_SUBPROCESS`(宿主没有 subprocess 服务)、`LEAN_NOT_FOUND`(解析不到 `leanCommand`)、`LEAN_SPAWN_FAILED`、`LEAN_RUN_FAILED`、`LEAN_FAILED`(编译器非零退出)、`LEAN_TIMEOUT`。
1215
1235
 
1236
+ > ⚠ **尚未实现的已登记错误码**(本轮审计发现,予以显式登记而非默默留在文档里):
1237
+ > `V5_LEAD_REQUIRED`、`V5_NOT_MEMBER`、`V5_MEMBER_NAME_TAKEN`、`V5_INVALID_MEMBER_NAME`、
1238
+ > `V5_MAILBOX_FULL`、`V5_MESSAGE_TOO_LARGE`、`V5_TASK_LIMIT` —— 这些名字在代码里**一次都没有出现**。
1239
+ > 原因:v5 的成员 id 由框架分配(成员不命名自己),邮箱不做容量上限,任务不做数量上限,
1240
+ > 职权不足一律用 `V5_NOT_ACADEMICIAN` / `V5_MEMBER_NOT_FOUND`。**新增调用点时必须二选一**:
1241
+ > 要么真的用上该错误码,要么把它从本节删掉(不要留一个"文档里有、代码里没有"的契约)。
1242
+
1216
1243
  ### 15.4 每轮回执契约(`replySpec`)
1217
1244
 
1218
1245
  每轮提示词末尾都附一份 JSON 契约。**凡框架实际处理的字段,必须在此出现**,否则该通道对成员
@@ -36,7 +36,7 @@ flowchart TB
36
36
  end
37
37
 
38
38
  subgraph L3["💾 状态与产物"]
39
- PROJ["会话日志的 host-only 投影单元<br/>键 vibeMathV5<br/>11 类事件 · 纯折叠 applyV5Event"]
39
+ PROJ["会话日志的 host-only 投影单元<br/>键 vibeMathV5<br/>12 类事件 · 纯折叠 applyV5Event"]
40
40
  FS["人可读产物<br/>Members/&lt;id&gt;/* · Shared/* · Verified/ · Problems/"]
41
41
  end
42
42
 
@@ -176,6 +176,8 @@ stateDiagram-v2
176
176
  attempted --> passed : lean_archive kind='proof' 且该文件运行 exit 0
177
177
  attempted --> blocked : lean_archive kind='blocked'(note 必填)
178
178
  none --> blocked : 同上
179
+ passed --> attempted : 回执 formal.decision='defect'(忠实性缺陷)<br/>清空 proof · 撤回 Verified/Lean/&lt;id&gt;.lean · 写 Formal/TODO.md · 公告
180
+ blocked --> attempted : 同上(缺陷优先于阻塞判断)
179
181
  passed --> [*] : 归档到 Verified/Lean/&lt;id&gt;.lean
180
182
  blocked --> [*] : 记入 Formal/Index.md 的难度判断列
181
183
 
@@ -184,6 +186,12 @@ stateDiagram-v2
184
186
  「你不需要重新检查推导,
185
187
  任务是忠实性审查」
186
188
  end note
189
+ note left of attempted
190
+ 忠实性缺陷不是"命题为假"(契约 §4.1):
191
+ 表决者不得投 0,只给 0 与 1 之间的弃权值;
192
+ require 档本轮因此不定论(formal-required),
193
+ encourage 档没有门禁,靠该弃权值阻止一致结论。
194
+ end note
187
195
  ```
188
196
 
189
197
  ```mermaid
@@ -201,9 +209,14 @@ flowchart TB
201
209
  style OK2 fill:#eef7f1,stroke:#1f7a52
202
210
  ```
203
211
 
204
- **三档**:`off`(默认,**真正的无操作**)|`encourage`(按实现难度自行决定,不设门禁)|
212
+ **三档**:`off`(默认,**真正的无操作**:不注入任何 Lean 文本、不设门禁、不改验证流程)|
213
+ `encourage`(按实现难度自行决定,不设门禁)|
205
214
  `require`(真/假结论必须先有 Lean 通过或**显式、可审计**的阻塞记录)。
206
215
 
216
+ > `off` 的"无操作"约束的是**框架主动做的事**(提示词、门禁、验证流程、回执里的 formal 通道)。
217
+ > 三个 Lean 工具仍然无条件注册、任何档位下都可被代理或人**主动**调用,并且会照实执行与记录
218
+ > ——"调用是一个明确的动作,不会被静默忽略"。
219
+
207
220
  **"根据实现难度决定不做"是被允许的,但必须说出来**:`blocked` 记录要求 `note` 非空 ——
208
221
  决定权在代理,静默跳过不行。
209
222
 
@@ -419,6 +432,7 @@ flowchart LR
419
432
  | Lean 通过后,审查对象切换为**忠实性**("你不需要重新检查推导") | `formal-verify-v5.test.mjs` |
420
433
  | `require` 模式:无形式化记录的真/假结论被记为未定论 + 形式化待办,不写 `Verified/` | `formal-verify-v5.test.mjs` |
421
434
  | 「因难度决定不做形式化」必须显式记录原因(`note` 非空) | `formal-verify-v5.test.mjs` |
435
+ | 忠实性缺陷**不得**记成"命题为假":撤回 `passed`、删除 `Verified/Lean/<id>.lean`、写待办,`require` 档不定论 | `formal-verify-v5.test.mjs` |
422
436
  | 命题的证明归档到 `Verified/Lean/<id>.lean`;可复用定义归档到**跨项目** `Formal/Lib/` | `formal-verify-v5.test.mjs` |
423
437
  | Lean 路径守卫按词法归一 `..`(字符串前缀检查会被穿越绕过) | `formal-verify-v5.test.mjs` |
424
438
  | 解雇是真实的(释放子会话、收回任务) | `selfdrive-v5.mjs` |
@@ -103,11 +103,12 @@
103
103
  <text x="1430.3333333333333" y="743.3" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">院士/研究员可雇自己的临时工</text>
104
104
  <text x="1430.3333333333333" y="759.8" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">雇主或院士可真实解雇(释放子会话)</text>
105
105
  <text x="1430.3333333333333" y="776.3" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">代号永不复用 · 按人/全所双配额</text>
106
- <rect x="248" y="796" width="1410" height="76" rx="10" fill="#ffffff" stroke="#b3202c" stroke-width="1.6"/>
107
- <text x="953" y="813.8" font-size="12.5" font-weight="700" fill="#10202e" text-anchor="middle">Lean 形式化验证(可调参数 formalVerify = off / encourage / require)</text>
108
- <text x="953" y="830.3" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">encourage:验证时按实现难度决定是否形式化;require:真/假结论必须先有「Lean 通过」或显式阻塞记录</text>
109
- <text x="953" y="846.8" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">★ 一旦 Lean 通过,审查对象就变了:不再是「推导对不对」,而是「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」</text>
110
- <text x="953" y="863.3" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">归档:证明 → Verified/Lean/&lt;对象&gt;.lean|可复用定义 → VibeMath/Formal/Lib/|已证引理 → VibeMath/Formal/Proved/</text>
106
+ <rect x="248" y="796" width="1410" height="90" rx="10" fill="#ffffff" stroke="#b3202c" stroke-width="1.6"/>
107
+ <text x="953" y="812.5" font-size="12.5" font-weight="700" fill="#10202e" text-anchor="middle">Lean 形式化验证(可调参数 formalVerify = off / encourage / require)</text>
108
+ <text x="953" y="829.0" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">encourage:验证时按实现难度决定是否形式化;require:真/假结论必须先有「Lean 通过」或显式阻塞记录</text>
109
+ <text x="953" y="845.5" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">★ 一旦 Lean 通过,审查对象就变了:不再是「推导对不对」,而是「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」</text>
110
+ <text x="953" y="862.0" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">★ 忠实性缺陷(decision=defect)≠ 命题为假:撤回「已通过」+ 撤回归档证明 + 进「形式化待办」,绝不记成 0/假</text>
111
+ <text x="953" y="878.5" font-size="10.5" font-weight="400" fill="#2c4557" text-anchor="middle">归档:证明 → Verified/Lean/&lt;对象&gt;.lean|可复用定义 → VibeMath/Formal/Lib/|已证引理 → VibeMath/Formal/Proved/</text>
111
112
  <rect x="248" y="880" width="1410" height="38" rx="10" fill="#ffffff" stroke="#0b6fb8" stroke-width="1.6"/>
112
113
  <text x="953" y="903.1" font-size="11.5" font-weight="700" fill="#10202e" text-anchor="middle">调度器优先级:进行中的会议或验证(二者互斥,永不同时)→ 队列中的验证 → 暂存会议 → 已认领/被分派的在办任务(按 activityTimeoutMs 节流)→ 加急邮件 → 群聊摘要 → 停滞自动开会 → 兜底心跳</text>
113
114
  <rect x="40" y="964" width="790" height="196" rx="14" fill="#eef7f1" stroke="#1f7a52" stroke-width="2"/>