dsh-vibe-math 2.3.0 → 2.3.1

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 (30) hide show
  1. package/AUDIT-CHECKLIST.md +33 -3
  2. package/README.md +21 -0
  3. package/RELEASE-NOTES-2.3.1.md +134 -0
  4. package/audit-formal-sensitivity.mjs +125 -39
  5. package/audit-v5-integrity.mjs +5 -3
  6. package/docs/formal-verification.md +92 -12
  7. package/docs/test-timing.md +79 -0
  8. package/formal-verify-v2.test.mjs +286 -7
  9. package/formal-verify-v3.test.mjs +215 -8
  10. package/formal-verify-v4.test.mjs +282 -3
  11. package/formal-verify-v5.test.mjs +72 -0
  12. package/package.json +9 -2
  13. package/prompt-corpus-v2/formal-verify-v2.json +394 -0
  14. package/prompt-corpus-v2/formal-verify-v2.md +4250 -0
  15. package/prompt-corpus-v3/formal-verify-v3.json +159 -57
  16. package/prompt-corpus-v3/formal-verify-v3.md +1302 -285
  17. package/prompt-corpus-v4/formal-verify-v4.json +84 -0
  18. package/prompt-corpus-v4/formal-verify-v4.md +255 -0
  19. package/prompt-corpus-v5/prompt-corpus-v5.json +54 -16
  20. package/prompt-corpus-v5/prompt-corpus-v5.md +378 -153
  21. package/prompt-v5-integrity.test.mjs +1158 -1085
  22. package/run-tests.mjs +99 -0
  23. package/vibe-math-v2/vibe-math-v2.js +204 -22
  24. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +77 -4
  25. package/vibe-math-v3/vibe-math-v3.js +82 -21
  26. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +17 -0
  27. package/vibe-math-v4/vibe-math-v4.js +114 -22
  28. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +29 -0
  29. package/vibe-math-v5/vibe-math-v5.js +81 -22
  30. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +27 -4
@@ -2588,7 +2588,7 @@ export function apply(ctx) {
2588
2588
  }
2589
2589
  /**
2590
2590
  * 把一次运行记到对象上(契约 §4 状态迁移)。普通运行最多把对象推进到 `attempted`;
2591
- * `passed` 只能由 lean_archive{kinds:'proof'} 且该文件最近一次运行 ok 产生——否则一次
2591
+ * `passed` 只能由 vibe_math_lean_archive{kind:'proof'} 且该文件最近一次运行 ok 产生——否则一次
2592
2592
  * 顺手跑个无关文件就能把对象"洗白"成已形式化。passed/blocked 不会被普通运行降级。
2593
2593
  */
2594
2594
  async function formalSetRun(target, run) {
@@ -2612,28 +2612,34 @@ export function apply(ctx) {
2612
2612
  const L = []
2613
2613
  L.push('【Lean 形式化验证(' + (mode === 'require' ? '强制' : '鼓励') + '模式)】')
2614
2614
  if (rec.status === 'passed') {
2615
- // 整套机制的要害:审查对象**变了**。
2615
+ // 整套机制的要害:审查对象**变了**——不是"推导对不对",而是"这段代码说的是不是这个命题"。
2616
+ // §4.1:忠实性缺陷**不是**"命题为假",所以这一支必须同时给出 defect 出口(不得把偏差记成 0)。
2616
2617
  L.push(' · 该对象已有**通过的 Lean 形式化证明**(' + (rec.proof || rec.file || '') + ',最近一次运行 exit 0)。')
2617
2618
  L.push(' **你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的')
2618
- L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**(有偏差就指出偏差),')
2619
- L.push(' 并据此给出 verdict。')
2619
+ L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。')
2620
+ L.push(' ▸ 一致 → Result = 1。')
2621
+ L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
2622
+ L.push(' ① Result 给一个严格介于 0 与 1 之间的值(记为弃权),并在 Reason 里写清偏差;')
2623
+ L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
2624
+ L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办),本次裁定**不定论**;')
2625
+ L.push(' 修正形式化并重新跑通后再投票。')
2626
+ L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 Reason 里写清独立理由。')
2620
2627
  } else if (rec.status === 'blocked') {
2621
2628
  L.push(' · 该对象已被记录为**形式化阻塞**:' + (rec.note || '未说明') + '。')
2622
2629
  L.push(' 请复核这个判断是否成立;若你认为其实可以形式化,请指出来并动手做。')
2623
2630
  } else {
2624
2631
  L.push(' · 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先写 Lean 代码并执行。')
2625
2632
  L.push(' · 工具:vibe_math_lean_run(执行)· vibe_math_lean_archive(归档)· vibe_math_lean_lib(查已有可复用库)')
2626
- L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' + (vibeRoot() + '/Formal/Lib/').replace(/\\/g, '/'))
2627
- L.push(' 已证引理放 ' + (vibeRoot() + '/Formal/Proved/').replace(/\\/g, '/') + ';写之前先 lean_lib 查重。')
2628
- L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义 / 对象 / 条件 / 假设 / 结论是否与')
2629
- L.push(' 命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
2633
+ L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' + (vibeRoot() + '/Formal/Lib/').replace(/\\/g, '/')
2634
+ + ',已证引理放 ' + (vibeRoot() + '/Formal/Proved/').replace(/\\/g, '/') + ';写之前先 vibe_math_lean_lib 查重。')
2635
+ L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
2630
2636
  if (mode === 'require') {
2631
- L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_math_lean_archive')
2632
- L.push(' kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,')
2633
- L.push(' 会被记为未定论(原因 formal-required)并进入「形式化待办」。')
2637
+ L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_math_lean_archive kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,会被记为未定论(原因 formal-required)并进入「形式化待办」。')
2634
2638
  } else {
2635
- L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断。')
2639
+ L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision=\'blocked\' 时必须写明 note)。')
2636
2640
  }
2641
+ L.push(' · 归档可复用定义/引理前先跑通(vibe_math_lean_archive run=true 或先 vibe_math_lean_run);跑不通不要入库。')
2642
+ L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
2637
2643
  }
2638
2644
  return L.join('\n')
2639
2645
  }
@@ -2646,12 +2652,14 @@ export function apply(ctx) {
2646
2652
  + (formalMode() === 'require'
2647
2653
  ? '本模式下,任何要定论为真/假的对象都必须先有 Lean 通过或显式阻塞记录。'
2648
2654
  : '这会让后续的验证与证明省掉大量重复工作。')
2655
+ // 硬要求 3(契约 §6 顶部):可复用库只收**跑通过**的代码,否则它会被不编译的定义污染。
2656
+ + '归档前先跑通(vibe_math_lean_run 或 run=true);跑不通的定义不要进可复用库。'
2649
2657
  }
2650
2658
  /** 回执契约里的 formal 字段(契约 §6.3):非 off 模式必须出现在回执契约里,否则这条通道不可发现。 */
2651
2659
  function formalJsonField(target) {
2652
2660
  if (!formalOn()) return ''
2653
2661
  const id = String(target == null ? '' : target) || '<对象id>'
2654
- return ',"formal":{"target":"' + id + '","decision":"used|blocked","file":"Formal/' + id + '.lean","note":"难度判断/阻塞原因"}'
2662
+ return ',"formal":{"target":"' + id + '","decision":"used|blocked|defect","file":"Formal/' + id + '.lean","note":"难度判断/阻塞原因/具体偏差"}'
2655
2663
  }
2656
2664
  /**
2657
2665
  * 工作轮(solver / explorer)回执契约里的 formal 字段。这些角色的回执本身就是一段 JSON 模板,
@@ -2661,15 +2669,17 @@ export function apply(ctx) {
2661
2669
  function formalReplyNote() {
2662
2670
  if (!formalOn()) return ''
2663
2671
  return '\n形式化回执(本模式):若你本轮对某个对象做了形式化难度判断,请在回执里加上 '
2664
- + '"formal":{"target":"<对象id>","decision":"used|blocked","file":"Formal/<对象id>.lean","note":"难度判断/阻塞原因"}'
2665
- + '(decision=\'blocked\' 时必须写明 note,否则拒绝记录)。'
2672
+ + '"formal":{"target":"<对象id>","decision":"used|blocked|defect","file":"Formal/<对象id>.lean","note":"难度判断/阻塞原因/具体偏差"}'
2673
+ + '(decision=\'blocked\' 与 decision=\'defect\' 时必须写明 note,否则拒绝记录;'
2674
+ + 'decision=\'defect\' 表示你认定这条已通过的 Lean 形式化**不忠实于命题原文**——'
2675
+ + '那不是"命题为假",框架会撤回其已通过状态并把对象放回形式化待办)。'
2666
2676
  }
2667
2677
  /** 从一条代理回执里取出 formal 判断并落库(顶层 formal 或 meta.formal 都接受)。 */
2668
2678
  async function absorbFormalFromReply(parsed, memberId) {
2669
2679
  if (!formalOn() || !parsed || typeof parsed !== 'object') return
2670
2680
  const f = (parsed.formal && typeof parsed.formal === 'object') ? parsed.formal
2671
2681
  : ((parsed.meta && typeof parsed.meta.formal === 'object') ? parsed.meta.formal : null)
2672
- if (f) await absorbFormalReply(f, memberId)
2682
+ if (f) return await absorbFormalReply(f, memberId)
2673
2683
  }
2674
2684
  /** 对象 id → 关联可验证对象(命题/问题)。用于把表决对象映射到形式化记录。 */
2675
2685
  function verifyTargetId(r) { return String((r && (r.pId || r.qid)) || '') }
@@ -2860,8 +2870,8 @@ export function apply(ctx) {
2860
2870
  if (run.ok) await formalAnnounce('【形式化】' + (memberId || 'scheduler') + ' 运行 Lean 通过:' + run.file + '(' + ((run.ms || 0) / 1000).toFixed(1) + 's)' + (target ? '|对象 ' + target : ''))
2861
2871
  return Object.assign({ ok: !!run.ok }, run, {
2862
2872
  hint: run.ok
2863
- ? '通过。若是某个对象的证明,请用 lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
2864
- : '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 lean_archive kind=\'blocked\' 记录原因。',
2873
+ ? '通过。若是某个对象的证明,请用 vibe_math_lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
2874
+ : '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_math_lean_archive kind=\'blocked\' 记录原因。',
2865
2875
  })
2866
2876
  }
2867
2877
  async function leanArchive(memberId, o) {
@@ -2941,9 +2951,50 @@ export function apply(ctx) {
2941
2951
  }
2942
2952
  return { ok: false, code: 'V3_INVALID_ARGUMENT', message: "kind must be 'def' | 'lemma' | 'proof' | 'blocked'" }
2943
2953
  }
2954
+ /**
2955
+ * §4.1 的落地点:表决者认定这条**已通过的** Lean 形式化不忠实于命题原文。
2956
+ *
2957
+ * 偏差不是"命题为假",而恰好是"这次形式化不合格",因此这里的动作与 blocked 不同:
2958
+ * ① 无论此前是 `passed` 还是 `blocked`,一律**降级**为 `attempted`(都让位于"需重做");
2959
+ * ② 清空 `proof` 并**删除** `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean` 保留,代码不丢);
2960
+ * ③ 把具体偏差写进记录与 `Formal/TODO.md`,并公告。
2961
+ * 之后 `formalGateOk` 为假:require 档本次裁定**不定论**,对象进入「形式化待办」——修正形式化
2962
+ * 并重新跑通后再投票。绝不把这条路径写成 `0`(那会让框架记下"命题为假")。
2963
+ */
2964
+ async function formalRecordDefect(target, note, who) {
2965
+ const id = formalId(target)
2966
+ if (!id) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'defect 记录必须写明 target' }
2967
+ const prev = formalOf(id)
2968
+ const archived = String(prev.proof || '').trim() || ('Verified/Lean/' + id + '.lean')
2969
+ await putFormal(id, Object.assign({}, prev, {
2970
+ status: 'attempted',
2971
+ decision: 'defect',
2972
+ note: note,
2973
+ proof: '',
2974
+ updatedAt: now(),
2975
+ }))
2976
+ // 归档证明必须消失,否则 Verified/Lean/ 里会留下一份"看起来已通过"的不忠实代码。
2977
+ await removeFile(archived)
2978
+ // 待办条目按 id 去重、就地刷新:note 进入 TODO.md 的 why 列(require 档据此搁置定论)。
2979
+ const list = formalTodo()
2980
+ const i = list.findIndex(function (x) { return x && x.id === id })
2981
+ const why = 'formal-defect:' + note
2982
+ if (i === -1) list.push({ id: id, at: now(), why: why, verdict: null, project: currentProject })
2983
+ else list[i] = Object.assign({}, list[i], { why: why, at: now() })
2984
+ await writeFormalTodo()
2985
+ await upsertFormalAnchor(id)
2986
+ await rebuildLeanLibIndexes()
2987
+ await saveAll()
2988
+ await formalAnnounce('【形式化】' + who + ' 认定 ' + id + ' 的 Lean 形式化存在**忠实性缺陷**:' + note
2989
+ + '。这不是"命题为假",而是**形式化不合格**:已撤回其「已通过」状态(降级为 attempted)、'
2990
+ + '删除归档证明 ' + archived + '、写入 Formal/TODO.md;本次裁定**不定论**,修正形式化并重新'
2991
+ + '跑通(vibe_math_lean_archive kind=\'proof\')后再投票。')
2992
+ return { ok: true, kind: 'defect', target: id, status: 'attempted', proof: '', note: note, archived: archived }
2993
+ }
2944
2994
  /**
2945
2995
  * 回执通道(契约 §3/§6.3):代理即使一次 Lean 工具都没调用,也必须能留下"实现难度判断"。
2946
- * decision='blocked' 时 note 必填(否则拒绝记录并公告);'used' 记录草稿文件 → attempted。
2996
+ * decision='blocked' 时 note 必填(否则拒绝记录并公告);'used' 记录草稿文件 → attempted;
2997
+ * decision='defect' 时 note 必填(具体偏差),落库语义见 §4.1 与 `formalRecordDefect`。
2947
2998
  */
2948
2999
  async function absorbFormalReply(f, memberId) {
2949
3000
  if (!formalOn() || !f || typeof f !== 'object') return
@@ -2960,6 +3011,16 @@ export function apply(ctx) {
2960
3011
  await formalAnnounce('【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化阻塞:' + note)
2961
3012
  return
2962
3013
  }
3014
+ if (decision === 'defect') {
3015
+ const note = String(f.note || '').trim()
3016
+ if (!note) {
3017
+ // 与 blocked 同样"显式、可审计":没写出具体偏差就无法复核,整条记录拒绝(返回该架构的错误码)。
3018
+ await formalAnnounce('【形式化】' + who + ' 的 formal.decision=defect 未写明 note,已**拒绝**记录'
3019
+ + '(忠实性缺陷必须写出具体偏差,否则无从复核)。该对象的形式化记录与归档证明**保持不变**。')
3020
+ return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'defect 记录必须写明具体偏差(note)——"形式化与命题不一致"必须显式、可复核' }
3021
+ }
3022
+ return await formalRecordDefect(target, note, who)
3023
+ }
2963
3024
  if (decision === 'used') {
2964
3025
  const prev = formalOf(target)
2965
3026
  const file = String(f.file || ('Formal/' + target + '.lean'))
@@ -2969,7 +3030,7 @@ export function apply(ctx) {
2969
3030
  await formalAnnounce('【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化草稿:' + file)
2970
3031
  return
2971
3032
  }
2972
- await formalAnnounce('【形式化】' + who + ' 的 formal.decision 只能是 \'used\' 或 \'blocked\'(收到 ' + String(f.decision) + '),已忽略。')
3033
+ await formalAnnounce('【形式化】' + who + ' 的 formal.decision 只能是 \'used\'、\'blocked\' 或 \'defect\'(收到 ' + String(f.decision) + '),已忽略。')
2973
3034
  }
2974
3035
 
2975
3036
  // ================= verification (验证器) =================
@@ -3376,7 +3437,7 @@ export function apply(ctx) {
3376
3437
  return { target: k, status: rec.status, file: rec.file, proof: rec.proof, note: rec.note }
3377
3438
  }),
3378
3439
  paths: { project: 'Formal/(相对项目根)', lib: 'VibeMath/Formal/Lib/', proved: 'VibeMath/Formal/Proved/', proofs: 'Verified/Lean/' },
3379
- hint: "复用优先:先在 Lib/ 里找现成定义;新定义用 lean_archive kind='def' 归档,已证引理用 kind='lemma'。",
3440
+ hint: "复用优先:先在 Lib/ 里找现成定义;新定义用 vibe_math_lean_archive kind='def' 归档,已证引理用 kind='lemma';归档前先跑通(run=true 或先 vibe_math_lean_run),跑不通不要入库。",
3380
3441
  verify: (Object.keys(tasks).length ? String(tasks[Object.keys(tasks)[0]].rId || '') : null),
3381
3442
  }
3382
3443
  })
@@ -553,6 +553,23 @@ VibeMath/ # 工作区级
553
553
 
554
554
  **执行与守卫**:`subprocess.resolveExecutable(leanCommand)` → `spawn({argv:[exe, ...leanArgs, abs], cwd:<项目根>, stdio:{stdin:'ignore',stdout/stderr:{maxBytes:64KB}}, graceMs:leanTimeoutMs})` → `await handle.done` → 收集 stdout/stderr(`readFrom(0).text`,截断到 4KB)。路径守卫 = `normalizeAbsPath`(纯词法折叠 `.`/`..`/重复斜杠)+ 边界为 **VibeMath 根**(不是项目根);`lean_run` 的 `file` 先按项目根解析、再按 VibeMath 根解析,两条路径都过同一道守卫。
555
555
 
556
+ ### 11.2 忠实性缺陷(`defect`)、工具名全称与归档纪律
557
+
558
+ **`defect` 是独立的一档,不是"命题为假"**(契约 §4.1)。`passed` 只保证"这段 Lean 代码通过了内核检查",**不保证它说的就是命题想说的**。表决者逐条核对后发现写窄了 / 写宽了 / 换了对象 / 漏了条件时,那是**形式化不合格**:若把它记成 0,框架记下的会是"该命题为假"——用来求真更严格的机制,反而伪造出一个错误的否定结论;若只记成 `blocked`,门禁又会**放行**(`blocked` 本就允许定论),等于带着一个坏形式化去定论。
559
+
560
+ - **回执**:`formal:{target, decision:'defect', note:'<具体偏差>'}`。`note` **必填**,为空则整条记录被**拒绝**(返回 `V3_INVALID_ARGUMENT`),形式化记录与归档证明**保持不变**。顶层 `formal` 与 `meta.formal` 两种写法都接受:验证回执走 `handleVerifier`,solver / explorer / 重派生等工作轮回执走 `absorbFormalFromReply`。
561
+ - **落库**(`formalRecordDefect`,契约 §4.1):无论此前是 `passed` 还是 `blocked`,一律**降级**为 `attempted`(都让位于"形式化不合格,需重做");清空 `proof`;**删除**归档证明 `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean` 保留,代码不丢);`note` 写入形式化记录与 `Formal/TODO.md`(按 id 去重、就地刷新);对象卡锚点刷新为 `已尝试未通过`;公告到活动日志与 `Logs/形式化.md`。
562
+ - **`require` 档**:降级后 `formalGateOk` 为假,本次裁定**不定论**——不改对象的既有权重/概率字段、不写 `Verified/` 卡片,对象进入「形式化待办」;修正形式化并重新跑通(`vibe_math_lean_archive kind='proof'`)后再投票。
563
+ - **唯一可以投 0 的情形**:表决者**独立于这份 Lean 代码**也能确定命题为假,且 `Reason` 写清独立理由。
564
+
565
+ **提示词硬要求**(契约 §6 顶部的五条,v3 全部落地,并由套件对**每条捕获的提示词**做正则扫描,而不是只看一两处):
566
+
567
+ 1. **工具名一律全称**:注入文本(含工具自己返回的 `hint` 字段)里只允许 `vibe_math_lean_run` / `vibe_math_lean_archive` / `vibe_math_lean_lib`。`lean_run` / `lean_archive` / `lean_lib` 这类缩写**不是注册名**,代理照抄会去调用一个不存在的工具;套件用 `/(^|[^a-z_])lean_(run|archive|lib)/` 扫描全部语料,命中即红。
568
+ 2. **回执字段名必须与真实契约一致**:v3 的表决值字段是 **`Result`**(v4/v5 才是 `verdict`)。写错字段名 = 那一票被静默丢弃(退回默认 0.5)。忠实性分支因此写的是 `一致 → Result = 1`;套件断言每条表决提示词含 `Result` 且不含 `verdict`。
569
+ 3. **归档可复用定义/引理前必须先跑通**:`vibe_math_lean_archive` 支持 `run:true`,或先 `vibe_math_lean_run`;**跑不通的代码不得进入 `Formal/Lib` / `Formal/Proved`**,否则"可复用库"会被不编译的定义污染。验证提示词与「顺手形式化」一行都写明了这条纪律(`归档前先跑通(vibe_math_lean_run 或 run=true);跑不通的定义不要进可复用库。`)。
570
+ 4. **工具链缺失时的出路必须写出来**:`LEAN_NOT_FOUND` 时把代码写下来归档,并在回执的 `note` 里写明"宿主无 Lean 工具链"——这算**显式阻塞原因**,`require` 档可以据此放行,代理不会因为装不了 Lean 而卡死。
571
+ 5. **忠实性缺陷不得用 0 表达**:只有独立于 Lean 代码也能确定命题为假时才投 0(§4.1 第 4 条)。套件断言注入文本**不含** `偏离 → 0` 这类"偏差即假"的指令,且含"不要投 0 / 记为形式化不合格 / 走待办"的要求。
572
+
556
573
  ---
557
574
 
558
575
  ## 12. 提示词与知识上下文(关键)
@@ -146,6 +146,17 @@ export function apply(ctx) {
146
146
  if(isWindows()) return 'New-Item -Force -ItemType Directory -Path '+paths.map(psQuote).join(',')+' | Out-Null'
147
147
  return 'mkdir -p '+paths.map(shQuote).join(' ')
148
148
  }
149
+ /**
150
+ * Delete command for the same two interpreters. DSH's `fs` service has no unlink/remove at all
151
+ * (dsh-fs FileSystem: resolve/stat/readText/writeText/editText/listDir), so the ONE place this
152
+ * preset must remove a file — withdrawing a retracted proof on `defect` (spec §4.1) — goes
153
+ * through the platform shell helper the preset already uses for mkdir. `-ErrorAction
154
+ * SilentlyContinue` / `-f` keep it idempotent: deleting an already-absent file is a success.
155
+ */
156
+ function rmCmd(paths){
157
+ if(isWindows()) return 'Remove-Item -Force -ErrorAction SilentlyContinue -LiteralPath '+paths.map(psQuote).join(',')
158
+ return 'rm -f '+paths.map(shQuote).join(' ')
159
+ }
149
160
  async function runShell(script,cwd){
150
161
  const subprocess=subprocessOf(); if(subprocess===undefined) return {ok:false,error:'no-subprocess'}
151
162
  try {
@@ -293,6 +304,9 @@ export function apply(ctx) {
293
304
  const r=formalOf(target)
294
305
  if(r.status==='passed') return 'Lean 通过('+(r.proof||r.file||'')+')'
295
306
  if(r.status==='blocked') return '阻塞('+(r.note||'未说明')+')'
307
+ // A retracted proof must not read as a plain "tried and failed": the card has to say WHY the
308
+ // archived proof disappeared (spec §4.1 — a fidelity defect is not a refutation).
309
+ if(r.status==='attempted'&&r.decision==='defect') return '忠实性缺陷('+(r.note||'未说明')+',待重做)'
296
310
  if(r.status==='attempted') return '已尝试未通过'
297
311
  return '未尝试'
298
312
  }
@@ -405,9 +419,14 @@ export function apply(ctx) {
405
419
  if(rec.status==='passed'){
406
420
  L.push(' · 该对象已有**通过的 Lean 形式化证明**('+(rec.proof||rec.file||'')+',最近运行 exit 0)。')
407
421
  L.push(' **你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的')
408
- L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**(有偏差就指出偏差),')
409
- L.push(' 并据此给出 verdict。')
410
- L.push(' ▸ 因此请把 verdict 用在**忠实性**上:一致 → 1;发现任何偏离 → 0(或按不确定度给中间值并说明)。')
422
+ L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。')
423
+ L.push(' ▸ 一致 → verdict = 1。')
424
+ L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
425
+ L.push(" ① verdict 给一个严格介于 0 与 1 之间的值(记为弃权),并在 reason 里写清偏差;")
426
+ L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
427
+ L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办),本次裁定**不定论**;')
428
+ L.push(' 修正形式化并重新跑通后再投票。')
429
+ L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 reason 里写清独立理由。')
411
430
  } else if(rec.status==='blocked'){
412
431
  L.push(' · 该对象已被记录为**形式化阻塞**:'+(rec.note||'未说明')+'。')
413
432
  L.push(' 请复核这个判断是否成立;若你认为其实可以形式化,请指出来并动手做。')
@@ -415,18 +434,15 @@ export function apply(ctx) {
415
434
  } else {
416
435
  L.push(' · 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先写 Lean 代码并执行。')
417
436
  L.push(' · 工具:vibe_v4_lean_run(执行)· vibe_v4_lean_archive(归档)· vibe_v4_lean_lib(查已有可复用库)')
418
- L.push(' · 工作目录:Formal/(相对项目根 '+frameworkRoot().replace(/\\/g,'/')+'/);')
419
- L.push(' 可复用定义放 '+formalLibRoot().replace(/\\/g,'/')+'/,已证引理放 '+formalProvedRoot().replace(/\\/g,'/')+'/;')
420
- L.push(' 写之前先 vibe_v4_lean_lib 查重。')
421
- L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与')
422
- L.push(' 命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
437
+ L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 '+formalLibRoot().replace(/\\/g,'/')+'/,已证引理放 '+formalProvedRoot().replace(/\\/g,'/')+'/;写之前先 vibe_v4_lean_lib 查重。')
438
+ L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
423
439
  if(formalMode()==='require'){
424
- L.push(' · **本模式要求**:**必须产出 Lean 形式化**,或**必须**给出显式的阻塞原因')
425
- L.push(" (vibe_v4_lean_archive kind='blocked' note=… 或回执 formal.note)。若两者都没有,")
426
- L.push(' 本次裁定不会生效,会被记为未定论(原因 formal-required)并进入「形式化待办」。')
440
+ L.push(" · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_v4_lean_archive kind='blocked' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,会被记为未定论(原因 formal-required)并进入「形式化待办」。")
427
441
  } else {
428
- L.push(' · 若判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断。')
442
+ L.push(" · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision='blocked' 时必须写明 note)。")
429
443
  }
444
+ L.push(' · 归档可复用定义/引理前先跑通(vibe_v4_lean_archive run=true 或先 vibe_v4_lean_run);跑不通不要入库。')
445
+ L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
430
446
  if(rec.status==='attempted'){
431
447
  L.push(' ▸ 该对象已有形式化尝试但尚未通过(最近一次 '+(rec.run?(rec.run.ok?'通过':'未通过'):'无运行记录')+')。')
432
448
  L.push(" 请修复后重跑(vibe_v4_lean_run),跑通后用 kind='proof' 归档。")
@@ -446,10 +462,11 @@ export function apply(ctx) {
446
462
  +(formalMode()==='require'
447
463
  ? '本模式下,任何要定论为真/假的对象都必须先有 Lean 通过或显式阻塞记录。'
448
464
  : '这会让后续的验证与证明省掉大量重复工作。')
465
+ +'归档前先跑通(vibe_v4_lean_run 或 run=true);跑不通的定义不要进可复用库。'
449
466
  }
450
467
  /** The `formal` object every non-off prompt documents in its JSON reply contract. */
451
468
  function formalReplyField(target){
452
- return '{"formal":{"target":"'+String(target||'p-x')+'","decision":"used|blocked","file":"Formal/'+String(target||'p-x')+'.lean","note":"难度判断/阻塞原因"}}'
469
+ return '{"formal":{"target":"'+String(target||'p-x')+'","decision":"used|blocked|defect","file":"Formal/'+String(target||'p-x')+'.lean","note":"难度判断/阻塞原因/具体偏差"}}'
453
470
  }
454
471
 
455
472
  // ---- the three indexes (framework-maintained) ---------------------------
@@ -540,7 +557,7 @@ export function apply(ctx) {
540
557
  return Object.assign({ok:!!run.ok},run,{
541
558
  mode:formalMode(),
542
559
  hint: run.ok
543
- ? "通过。若是某个对象的证明,请用 vibe_v4_lean_archive kind='proof' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind='def'/'lemma' 归档到全局库。"
560
+ ? "通过。若是某个对象的证明,请用 vibe_v4_lean_archive kind='proof' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind='def'/'lemma' 归档到全局库——归档前先跑通(run=true 或先 vibe_v4_lean_run):跑不通的定义不要进可复用库。"
544
561
  : '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_v4_lean_archive kind=\'blocked\' 记录原因。',
545
562
  })
546
563
  }
@@ -623,23 +640,92 @@ export function apply(ctx) {
623
640
  }
624
641
  return {ok:false,code:'V4_INVALID_ARGUMENT',message:"kind must be 'def' | 'lemma' | 'proof' | 'blocked'"}
625
642
  }
643
+ /**
644
+ * Withdraw an archived proof (spec §4.1). Best-effort by contract: the RECORD is the
645
+ * authoritative state, so a host without a subprocess service (or without Lean) still gets a
646
+ * correct `attempted` record — the stale file is then reported in the activity log instead of
647
+ * silently kept. Every path goes through `leanAbsPath` so this can only ever remove a `.lean`
648
+ * file inside the VibeMath root (a hand-edited State/formal.json must not become an
649
+ * arbitrary-file delete).
650
+ */
651
+ async function withdrawProof(rel){
652
+ const raw=String(rel==null?'':rel)
653
+ if(!raw) return {ok:false,skipped:true}
654
+ const abs=leanAbsPath(raw)
655
+ if(abs===null||!/\.lean$/.test(abs)){
656
+ logActivity('formal','拒绝删除越界的归档证明路径:'+raw)
657
+ return {ok:false,skipped:true}
658
+ }
659
+ const r=await runShell(rmCmd([abs]),vibeRoot())
660
+ if(!r||!r.ok) return {ok:false,error:(r&&r.error)||('exit '+String(r&&r.exitCode))}
661
+ return {ok:true,abs}
662
+ }
663
+ /**
664
+ * §4.1 `defect`: a voter checked the Lean code against the proposition and found a FIDELITY
665
+ * defect (written too narrow/wide, wrong object, missing hypothesis). That is a statement about
666
+ * the FORMALIZATION, not about the proposition, so it must never be absorbed as "the
667
+ * proposition is false". The record is therefore ALWAYS downgraded to `attempted` (even from
668
+ * `blocked`), `proof` is cleared, `Verified/Lean/<id>.lean` is withdrawn, the deviation goes
669
+ * into the record + Formal/Index.md + Formal/TODO.md, and the group is told in the activity
670
+ * log. The working file `Formal/<id>.lean` is deliberately KEPT — the code is not lost, only
671
+ * its "passed" claim. In `require` mode the downgrade also makes `formalGateOk` false, so the
672
+ * verdict defers through the existing `deferForFormal` path (no Verified card, TODO entry).
673
+ */
674
+ async function recordFormalDefect(rId,key,note){
675
+ const prev=formalOf(key)
676
+ const proofRel=String(prev.proof||'')||('Verified/Lean/'+key+'.lean')
677
+ const rec=Object.assign({},prev,{status:'attempted',proof:'',decision:'defect',note,updatedAt:now()})
678
+ const why='formal-defect:形式化与命题原文不一致——'+String(note).slice(0,160)
679
+ const todo=formalTodo().slice()
680
+ const i=todo.findIndex(t=>t&&t.id===key)
681
+ // The defect reason must survive the later `deferForFormal`, which keeps an EXISTING entry
682
+ // rather than overwriting it — so the TODO file explains "formalization不合格", not merely
683
+ // "formal-required". An already-deferred object keeps its original 真/假 tally here.
684
+ if(i>=0) todo[i]=Object.assign({},todo[i],{why,at:now()})
685
+ else todo.push({id:key,at:now(),why,verdict:null})
686
+ // Durable write FIRST (record + todo together), then withdraw the file: a crash in between
687
+ // leaves an orphaned file with a truthful record, never a record still claiming `passed`.
688
+ await putFormal(key,rec,todo)
689
+ const del=await withdrawProof(proofRel)
690
+ try { await writeFormalTodo(); await writeFormalIndex() } catch(e){ /* best-effort */ }
691
+ logActivity('formal',(rId||'host')+' 报告 '+key+' 存在**忠实性缺陷**(formal.decision=defect):'+note
692
+ +' ——已撤回「已通过」状态(→ attempted)、'+(del&&del.ok?'删除归档证明 '+proofRel:'删除归档证明失败('+String((del&&del.error)||'no-subprocess')+',记录已降级)')
693
+ +'、写入 Formal/TODO.md;本次裁定**不定论**,修正形式化并重新跑通后再投票')
694
+ return {ok:true,target:key,status:'attempted',proof:'',decision:'defect',removedProof:!!(del&&del.ok)}
695
+ }
626
696
  /**
627
697
  * The per-round `formal` reply channel. This is the path that fires IN PRACTICE: a resident
628
698
  * that never calls a Lean tool still has to state its difficulty judgement. Every failure is
629
- * swallowed into the activity log — an end handler must never throw into the scheduler.
699
+ * swallowed into the activity log — an end handler must never throw into the scheduler — but
700
+ * each rejection still returns the preset's typed error so the caller/audit can see WHY a
701
+ * judgement was dropped instead of silently losing it.
630
702
  */
631
703
  async function applyFormalReply(rId,formalReply){
632
704
  try {
633
705
  const key=formalKey(String(formalReply.target||''))
634
- if(!key) return
706
+ if(!key){
707
+ logActivity('formal',(rId||'host')+' 的 formal 回执缺少 target(对象 id)——本次未记录(V4_INVALID_ARGUMENT)')
708
+ return {ok:false,code:'V4_INVALID_ARGUMENT',message:'formal.target(对象 id)是必填的'}
709
+ }
635
710
  const decision=String(formalReply.decision||'').trim()
636
711
  if(decision==='blocked'){
637
712
  const note=String(formalReply.note||'').trim()
638
713
  if(!note){
639
- logActivity('formal',(rId||'host')+" 的 formal.decision='blocked' 缺少 note(难度判断/阻塞原因)——本次未记录")
640
- return
714
+ logActivity('formal',(rId||'host')+" 的 formal.decision='blocked' 缺少 note(难度判断/阻塞原因)——本次未记录(V4_INVALID_ARGUMENT)")
715
+ return {ok:false,code:'V4_INVALID_ARGUMENT',message:"formal.decision='blocked' 必须写明 note(难度判断/阻塞原因)"}
641
716
  }
642
717
  await leanArchive(rId,{kind:'blocked',target:key,note})
718
+ return {ok:true,target:key,decision:'blocked',status:'blocked'}
719
+ } else if(decision==='defect'){
720
+ // §4.1: a fidelity defect is NOT a refutation. Accepting it as "0 / false" would make the
721
+ // framework fabricate a negative conclusion out of a broken formalization, so the ONLY
722
+ // thing this branch may do is RETRACT the passing proof and defer the verdict.
723
+ const note=String(formalReply.note||'').trim()
724
+ if(!note){
725
+ logActivity('formal',(rId||'host')+" 的 formal.decision='defect' 缺少 note(具体偏差)——本次未记录(V4_INVALID_ARGUMENT)")
726
+ return {ok:false,code:'V4_INVALID_ARGUMENT',message:"formal.decision='defect' 必须写明 note(具体偏差:写窄了/写宽了/换了对象/漏了条件…)"}
727
+ }
728
+ return await recordFormalDefect(rId,key,note)
643
729
  } else if(decision==='used'){
644
730
  const file=String(formalReply.file||('Formal/'+key+'.lean'))
645
731
  const prev=formalOf(key)
@@ -648,10 +734,16 @@ export function apply(ctx) {
648
734
  file,decision:'used',note:String(formalReply.note||prev.note||''),updatedAt:now(),
649
735
  }))
650
736
  try { await writeFormalIndex() } catch(e){ /* best-effort */ }
737
+ return {ok:true,target:key,decision:'used'}
651
738
  } else if(decision){
652
- logActivity('formal',(rId||'host')+" 的 formal.decision 只能是 'used' 或 'blocked'(收到 "+decision+")")
739
+ logActivity('formal',(rId||'host')+" 的 formal.decision 只能是 'used'、'blocked' 或 'defect'(收到 "+decision+")——本次未记录(V4_INVALID_ARGUMENT)")
740
+ return {ok:false,code:'V4_INVALID_ARGUMENT',message:"formal.decision 只能是 'used' | 'blocked' | 'defect'(收到 "+decision+")"}
653
741
  }
654
- } catch(e){ logActivity('formal','formal 回执处理失败:'+String((e&&e.message)||e)) }
742
+ return {ok:false,code:'V4_INVALID_ARGUMENT',message:'formal.decision 是必填的'}
743
+ } catch(e){
744
+ logActivity('formal','formal 回执处理失败:'+String((e&&e.message)||e))
745
+ return {ok:false,code:'V4_INVALID_ARGUMENT',message:String((e&&e.message)||e)}
746
+ }
655
747
  }
656
748
  /** The {mode, on, objects, todo} view the host tools report (state-storage transparency). */
657
749
  function formalView(){
@@ -782,7 +874,7 @@ export function apply(ctx) {
782
874
  +(formalOn()?('\n'+formalWorkLine()+'\n'):'')
783
875
  +'Reply with ONLY a JSON object:\n'
784
876
  +'{"summary":"<what you did / decided this round, 1-3 sentences>","input":"<optional: a message to the whole team, or \\"\\">","solved":false,"propose_verify":"<id|null>","propose_meeting":"<agenda|null>","propose_task":"<task title|null>","task_desc":"<optional: why this task matters / what it covers|null>","claim_task":"<task id|null>","task_done":"<task id|null>","contextPct":40'
785
- +(formalOn()?(',"formal":{"target":"<对象 id>","decision":"used|blocked","file":"Formal/<对象 id>.lean","note":"难度判断/阻塞原因"}'):'')
877
+ +(formalOn()?(',"formal":{"target":"<对象 id>","decision":"used|blocked|defect","file":"Formal/<对象 id>.lean","note":"难度判断/阻塞原因/具体偏差"}'):'')
786
878
  +'}'
787
879
  }
788
880
  function meetingPrompt(r, st){
@@ -1290,7 +1382,7 @@ export function apply(ctx) {
1290
1382
  +(formalOn()?(formalWorkLine()+'\n'):'')
1291
1383
  +'Reply with ONLY a JSON object:\n'
1292
1384
  +'{"summary":"<what you will do / what you advanced this round>","input":"<optional: a message to the whole team, or \\"\\">","solved":false,"propose_verify":"<id|null>","propose_meeting":"<agenda|null>","propose_task":"<task title|null>","task_desc":"<optional: why this task matters / what it covers|null>","claim_task":"<id|null>","contextPct":40'
1293
- +(formalOn()?(',"formal":{"target":"<对象 id>","decision":"used|blocked","file":"Formal/<对象 id>.lean","note":"难度判断/阻塞原因"}'):'')
1385
+ +(formalOn()?(',"formal":{"target":"<对象 id>","decision":"used|blocked|defect","file":"Formal/<对象 id>.lean","note":"难度判断/阻塞原因/具体偏差"}'):'')
1294
1386
  +'}'
1295
1387
  }
1296
1388
  function clearHeartbeat(){ if(heartbeatDisposer!==null){ try{ heartbeatDisposer() }catch(e){} heartbeatDisposer=null } }
@@ -867,6 +867,35 @@ Lean 这一批改动同时做了一次**静态提示词面**审计(`audit-pers
867
867
  三处已补入 persona 的 `prefix` 与 `text` **两个块**,并由 `audit-persona-surface.test.mjs`
868
868
  (双向一致性 + 未文档化工具显式快照 + `prefix`/`text` 逐行一致)长期守护。
869
869
 
870
+ ### 31.13 忠实性缺陷 `defect`:形式化写错了 ≠ 命题为假(2.3.1)
871
+
872
+ 首版提示词写的是"一致 → 1;发现任何偏离 → 0"。这条指令有一个致命后果:Lean 代码写的不是命题
873
+ 想说的(写窄了/写宽了/换了对象/漏了条件)时,投票 0 的含义是"**该命题为假**"——框架会把
874
+ "形式化不合格"记成"命题被证伪",甚至在 v4 的**全组一致**规则下把命题写进 `Verified/` 标注**假**。
875
+ 用来求真的机制反而**伪造出一个错误的否定结论**。
876
+
877
+ 按契约 §4.1 改为独立的 `defect` 档:
878
+
879
+ - **表决者**:不得投 1 或 0;给一个严格介于 0 与 1 之间的值(记为弃权)并在 `reason` 里写清偏差,
880
+ 同时用回执 `formal:{decision:'defect', note:'<具体偏差>'}` 记录(`note` 必填,缺则 `V4_INVALID_ARGUMENT`)。
881
+ - **框架**:把该对象**降级为 `attempted`**(无论此前是 `passed` 还是 `blocked`)、清空 `proof`、
882
+ 撤回归档证明 `Verified/Lean/<id>.lean`(`subprocess` 可删则删,否则用"已撤回"说明覆盖)、
883
+ `note` 记入记录与 `Formal/TODO.md`、活动日志公告。
884
+ - **`require` 档**:降级后 `formalGateOk` 为假 → 本次裁定**不定论**(`deferForFormal`),
885
+ 对象进「形式化待办」,修正形式化并重新跑通后再投票。
886
+ - **只有独立于这份 Lean 代码也能确定命题为假**时才投 0,并在 `reason` 里写清独立理由。
887
+
888
+ 同批补齐的提示词硬要求(契约 §6):注入文本里的工具名**一律全称**(`vibe_v4_lean_*`;缩写
889
+ `lean_archive` 不是注册名);归档可复用定义/引理**前先跑通**(`run=true` 或先 `vibe_v4_lean_run`),
890
+ 跑不通不许进 `Formal/Lib`/`Proved`;**工具链缺失**(`LEAN_NOT_FOUND`)时把代码归档并在 `note`
891
+ 写明"宿主无 Lean 工具链"——这算显式阻塞原因,门禁据此放行,不会因为装不了 Lean 而卡死。
892
+
893
+ **测试**:`formal-verify-v4.test.mjs` 增至 **226 条断言**(含 `defect` 端到端:降级 + 撤回归档证明 +
894
+ 进待办 + `require` 档不定论 + 缺 `note` 被拒;注入文本不得出现缩写工具名、不得出现"偏离 → 0"),
895
+ 并新增随包发布的人工复核语料 `prompt-corpus-v4/formal-verify-v4.{json,md}`
896
+ (覆盖 off 无 Lean 文字、encourage、**require**、passed 忠实性分支、平时工作轮、回执契约;
897
+ 工作区路径归一化为 `<WS>`、VibeMath 根为 `<VIBEMATH>`,可 diff、无本机路径)。
898
+
870
899
 
871
900
 
872
901