dsh-vibe-math 2.3.0 → 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 (40) hide show
  1. package/AUDIT-CHECKLIST.md +78 -3
  2. package/README.md +33 -2
  3. package/RELEASE-NOTES-2.3.1.md +134 -0
  4. package/RELEASE-NOTES-2.3.2.md +145 -0
  5. package/audit-formal-sensitivity.mjs +134 -39
  6. package/audit-prompt-invariants.mjs +414 -0
  7. package/audit-spec-traceability.mjs +173 -0
  8. package/audit-v5-integrity.mjs +5 -3
  9. package/docs/formal-verification.md +122 -19
  10. package/docs/generate_framework_diagram_v5.mjs +2 -1
  11. package/docs/test-timing.md +101 -0
  12. package/formal-verify-v2.test.mjs +526 -7
  13. package/formal-verify-v3.test.mjs +389 -10
  14. package/formal-verify-v4.test.mjs +462 -4
  15. package/formal-verify-v5.test.mjs +163 -4
  16. package/installer.js +3 -1
  17. package/package.json +12 -2
  18. package/prompt-corpus-persona/persona-corpus.json +2 -2
  19. package/prompt-corpus-persona/persona-corpus.md +6 -2
  20. package/prompt-corpus-v2/formal-verify-v2.json +484 -0
  21. package/prompt-corpus-v2/formal-verify-v2.md +5239 -0
  22. package/prompt-corpus-v3/formal-verify-v3.json +274 -100
  23. package/prompt-corpus-v3/formal-verify-v3.md +2057 -335
  24. package/prompt-corpus-v4/formal-verify-v4.json +89 -0
  25. package/prompt-corpus-v4/formal-verify-v4.md +283 -0
  26. package/prompt-corpus-v5/prompt-corpus-v5.json +186 -219
  27. package/prompt-corpus-v5/prompt-corpus-v5.md +485 -700
  28. package/prompt-v5-integrity.test.mjs +1272 -1085
  29. package/run-tests.mjs +118 -0
  30. package/vibe-math-v2/vibe-math-v2.js +341 -45
  31. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +129 -8
  32. package/vibe-math-v3/vibe-math-v3.js +162 -36
  33. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +21 -3
  34. package/vibe-math-v4/vibe-math-v4.js +201 -30
  35. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +54 -2
  36. package/vibe-math-v5/agent.cordis.yml +6 -2
  37. package/vibe-math-v5/vibe-math-v5.js +133 -28
  38. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +55 -5
  39. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +16 -2
  40. package//347/244/272/344/276/213/345/233/276//346/241/206/346/236/266/345/233/276-v5.svg +6 -5
@@ -562,25 +562,45 @@ export function apply(ctx) {
562
562
  } catch (e) {
563
563
  return { ok: false, code: 'LEAN_SPAWN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
564
564
  }
565
+ // 超时必须有**主动**兜底:`graceMs` 只是宿主侧的宽限,契约 §7 明确要求"对超时调用
566
+ // handle.terminate()"。此前 v2 只依赖 graceMs,从不终止进程:一个卡住的 Lean 会继续占着
567
+ // 资源,而框架已经报了超时——它与自己的契约不一致。这里与 `handle.done` 竞速:计时器到点
568
+ // 时尽力 terminate(),并给出一份可读的超时结果;`done` 先到就清掉计时器(不留悬挂 timer)。
569
+ let timedOut = false
570
+ let timer = null
565
571
  let outcome
566
572
  try {
567
- outcome = await handle.done
573
+ outcome = await Promise.race([
574
+ handle.done,
575
+ new Promise(function (resolve) {
576
+ timer = setTimeout(function () {
577
+ timedOut = true
578
+ try { if (handle && typeof handle.terminate === 'function') handle.terminate() } catch (e) { /* best effort:终止失败也要给出可读结果 */ }
579
+ resolve({ exitCode: null, signal: 'SIGTERM' })
580
+ }, cap)
581
+ }),
582
+ ])
568
583
  } catch (e) {
584
+ if (timer !== null) clearTimeout(timer)
569
585
  return { ok: false, code: 'LEAN_RUN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
570
586
  }
587
+ if (timer !== null) clearTimeout(timer)
571
588
  let out = '', err = ''
572
589
  try { if (handle.collected && handle.collected.stdout) out = handle.collected.stdout.readFrom(0).text } catch (e) { /* best effort */ }
573
590
  try { if (handle.collected && handle.collected.stderr) err = handle.collected.stderr.readFrom(0).text } catch (e) { /* best effort */ }
574
591
  const exitCode = outcome ? outcome.exitCode : null
575
592
  const ms = now() - started
576
593
  const ok = exitCode === 0
594
+ // 两种超时都算超时:我们自己的计时器到点(timedOut),或耗时已越过 cap(宿主的 graceMs
595
+ // 杀掉了进程时 done 会先返回一个非零 exitCode)。
596
+ const isTimeout = timedOut || ms >= cap
577
597
  // 输出截断到 ~4KB 再入库(避免把巨大的编译器输出写进状态)。
578
598
  return {
579
599
  ok: ok, exitCode: exitCode, signal: (outcome && outcome.signal) || null, ms: ms,
580
600
  command: argv.join(' '), file: rel,
581
601
  stdout: tailText(out, 4000), stderr: tailText(err, 4000),
582
- timedOut: ms >= cap,
583
- code: ok ? undefined : (ms >= cap ? 'LEAN_TIMEOUT' : 'LEAN_FAILED'),
602
+ timedOut: isTimeout,
603
+ code: ok ? undefined : (isTimeout ? 'LEAN_TIMEOUT' : 'LEAN_FAILED'),
584
604
  }
585
605
  }
586
606
  // 把一次运行结果写进对象的形式化记录(不提升 status,状态迁移见契约 §4)。
@@ -606,7 +626,7 @@ export function apply(ctx) {
606
626
  /**
607
627
  * 归档(对象 id)→ 验证对象(rId)的状态同步。
608
628
  *
609
- * v2 有两个 id 空间,而它们指的是同一个东西:代理用 `lean_archive target=<对象 id>`
629
+ * v2 有两个 id 空间,而它们指的是同一个东西:代理用 `vibe_math_lean_archive target=<对象 id>`
610
630
  * (`r-pGate` 这种 rId 是调度器内部标识,代理在提示词里看到的是对象 id),而验证提示词
611
631
  * 与 require 门禁问的是"**这一次验证对象**"的状态。只写一个键会让两边错位:
612
632
  * 对象明明已 passed,门禁却认为 rId 仍未被形式化,裁定被反复搁置。
@@ -628,6 +648,16 @@ export function apply(ctx) {
628
648
  }
629
649
  return ids
630
650
  }
651
+ /**
652
+ * 验证 id → 它对应的对象 id(`r-pGate` / `r-pGate-s0` / `r-pGate-pf1` / `r-pGate-rf2` → `pGate`)。
653
+ * v2 有两套 id 空间,这条映射是**唯一**的一处:门禁的合并查询、提示词取记录、以及回执通道
654
+ * (`blocked` / `defect` / `used`)都从这里得到对象 id,绝不另造第二套解析规则。
655
+ */
656
+ function formalObjectIdOf(id) {
657
+ const t = safeId(String(id == null ? '' : id))
658
+ const m = /^r-(.+?)(?:-(?:s\d+|pf\d+|rf\d+))?$/.exec(t)
659
+ return m ? m[1] : t
660
+ }
631
661
  /**
632
662
  * 门禁/提示词看到的对象状态 = 合并后的记录,**两个方向都要认**,因为 v2 里归档与验证
633
663
  * 用的是两套 id,而代理两种写法都会用:
@@ -642,13 +672,45 @@ export function apply(ctx) {
642
672
  const own = recs[t]
643
673
  if (formalGateOk(own)) return own
644
674
  // 验证 id → 其对象 id(r-<obj> / r-<obj>-s0 / r-<obj>-pf0 / r-<obj>-rf0)
645
- const m = /^r-(.+?)(?:-(?:s\d+|pf\d+|rf\d+))?$/.exec(t)
646
- if (m) { const obj = recs[m[1]]; if (formalGateOk(obj)) return obj }
675
+ const objId = formalObjectIdOf(t)
676
+ if (objId !== t) { const obj = recs[objId]; if (formalGateOk(obj)) return obj }
647
677
  // 对象 id → 其验证 id(本对象的第一个验证记录)
648
678
  const vr = recs['r-' + t]
649
679
  if (formalGateOk(vr)) return vr
650
680
  return own || { status: 'none' }
651
681
  }
682
+ /**
683
+ * 把同一条状态写进**两套 id 的全部记录**(契约 §8)。门禁、提示词、卡片三处**各读不同的一侧**,
684
+ * 所以只写一侧会造成静默错位:例如 `defect` 只写到 `r-<id>` 上,对象记录仍是 `passed`,
685
+ * 于是 `formalGateRecord` 从对象侧读回 `passed`、索引与卡片照旧写"Lean 通过"——
686
+ * 这正是"成对关系只做一半"的典型缺陷形态(AUDIT-CHECKLIST §3)。
687
+ */
688
+ async function putFormalBothIds(target, patch) {
689
+ const t = safeId(String(target == null ? '' : target))
690
+ if (!t) return []
691
+ const objectId = formalObjectIdOf(t)
692
+ const written = []
693
+ const write = async function (k) {
694
+ if (written.indexOf(k) !== -1) return
695
+ const prev = formalRecords()[k] || { status: 'none' }
696
+ await putFormal(k, Object.assign({}, prev, patch))
697
+ written.push(k)
698
+ }
699
+ await write(t) // 回执点名的那个 id(对象 id 或验证 id)
700
+ if (objectId !== t) await write(objectId) // 另一侧
701
+ // 同一对象的其它验证别名(r-<id>、r-<id>-s0、r-<id>-pf1、r-<id>-rf2):复用既有扫描,不新造映射。
702
+ const aliases = await syncVerificationTarget(objectId, patch.status, patch)
703
+ for (let i = 0; i < aliases.length; i++) await write(aliases[i])
704
+ // 实体化**规范验证别名** r-<对象id>:归档时写对象 id 是常见写法,此时验证侧可能**根本还没有**
705
+ // 记录;只降级对象侧会让"这一次验证"看起来从未被形式化过(门禁查询优先看 rId)。显式写下这条
706
+ // attempted/blocked 记录,两套 id 空间才真正一致(审计清单 §3:成对关系别只做一半)。
707
+ await write('r-' + objectId)
708
+ // syncVerificationTarget 用 `||` 合并 proof,表达不了"清空"——而 defect 恰恰必须清空 proof。
709
+ if (patch.proof === '') {
710
+ for (let i = 0; i < written.length; i++) await putFormal(written[i], Object.assign({}, formalRecords()[written[i]] || {}, { proof: '' }))
711
+ }
712
+ return written
713
+ }
652
714
 
653
715
  // ---- 提示词注入(在构造提示词的那一刻现算;契约 §6)----
654
716
  function formalPromptBlock(target) {
@@ -657,39 +719,205 @@ export function apply(ctx) {
657
719
  // 与门禁取同一条记录:验证对象记录优先,其次它对应的对象记录,避免"归档了却仍按未尝试提示"。
658
720
  const rec = target ? formalGateRecord(target) : { status: 'none' }
659
721
  const L = []
722
+ const vroot = vibeRoot().replace(/\\/g, '/')
660
723
  L.push('【Lean 形式化验证(' + (mode === 'require' ? '强制' : '鼓励') + '模式)】')
661
724
  if (rec.status === 'passed') {
662
725
  // 整个功能的意义所在:审查对象**变了**。证明正确性已由内核保证,剩下的唯一风险是
663
726
  // "这段 Lean 代码说的不是我们想说的"。
727
+ // 忠实性的**判定指引**(一致怎么投、发现偏差怎么投、什么情况才能投 0)在 formalVerifySection
728
+ // 里紧随其后给出——两段拼在同一条提示词里,合成契约 §6.1 的完整「忠实性分支」。
664
729
  L.push(' · 该对象已有**通过的 Lean 形式化证明**(' + (rec.proof || rec.file) + ',最近运行 exit 0)。')
665
730
  L.push(' **你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的')
666
- L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**(有偏差就指出偏差),')
667
- L.push(' 并据此给出 verdict。')
731
+ L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。')
668
732
  } else if (rec.status === 'blocked') {
669
733
  L.push(' · 该对象已被记录为**形式化阻塞**:' + (rec.note || '未说明') + '。')
670
734
  L.push(' 请复核这个判断是否成立;若你认为其实可以形式化,请指出来并动手做。')
671
735
  } else {
672
736
  L.push(' · 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先写 Lean 代码并执行。')
673
737
  L.push(' · 工具:vibe_math_lean_run(执行)· vibe_math_lean_archive(归档)· vibe_math_lean_lib(查已有可复用库)')
674
- L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' + vibeRoot().replace(/\\/g, '/') + '/Formal/Lib/,')
675
- L.push(' 已证引理放 ' + vibeRoot().replace(/\\/g, '/') + '/Formal/Proved/;写之前先 lean_lib 查重。')
676
- L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与')
677
- L.push(' 命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
738
+ L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' + vroot + '/Formal/Lib/,已证引理放 '
739
+ + vroot + '/Formal/Proved/;写之前先 vibe_math_lean_lib 查重。')
740
+ L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与命题原文逐条一致。'
741
+ + '请把注意力放在这种核对上,而不是重新做一遍推导。')
678
742
  if (mode === 'require') {
679
- L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(lean_archive')
680
- L.push(' kind=\'blocked\' note=…)。若两者都没有,本次裁定不会生效,会被记为未定论')
681
- L.push(' (原因 formal-required)并进入「形式化待办」。')
743
+ L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_math_lean_archive '
744
+ + 'kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,会被记为未定论'
745
+ + '(原因 formal-required)并进入「形式化待办」。')
682
746
  } else {
683
- L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断。')
747
+ L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision=\'blocked\' 时必须写明 note)。')
684
748
  }
749
+ L.push(' · 归档可复用定义/引理前先跑通(vibe_math_lean_archive run=true 或先 vibe_math_lean_run);跑不通不要入库。')
750
+ L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)或宿主不提供 subprocess 服务(NO_SUBPROCESS)时:把代码写下来归档,并在回执的 note 里写明'
751
+ + '"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
685
752
  }
686
753
  return L.join('\n')
687
754
  }
688
755
  function formalWorkLine() {
689
756
  if (!formalOn()) return ''
757
+ // `Formal/Proved/` 在项目根下**并不存在**(可复用库故意在项目树之外),只写相对路径会让代理
758
+ // 去项目里找一个永远找不到的目录;这里与验证段落一样给出 VibeMath 根的绝对路径(契约 §6.2)。
690
759
  return '【顺手形式化(' + (formalMode() === 'require' ? '强制' : '鼓励') + ')】把你工作中常用或可能复用的对象、假设、'
691
- + '新定义用 Lean 形式化定义并归档到全局可复用库(vibe_math_lean_archive kind=\'def\'),已成立的引理归到 Formal/Proved/'
692
- + '(kind=\'lemma\');写之前先 vibe_math_lean_lib 查重,避免重复定义。'
760
+ + '新定义用 Lean 形式化定义并归档到全局可复用库(vibe_math_lean_archive kind=\'def\'),已成立的引理归到 '
761
+ + vibeRoot().replace(/\\/g, '/') + '/Formal/Proved/(kind=\'lemma\');写之前先 vibe_math_lean_lib 查重,避免重复定义。'
762
+ + '归档前先跑通(vibe_math_lean_run 或 run=true);跑不通的定义不要进可复用库。'
763
+ }
764
+ /**
765
+ * 回执契约里的 `formal` 字段(契约 §6.3)。**必须真的出现在回执契约里**:契约写了字段而框架
766
+ * 不解析,就是一条"框架收不到"的死通道(审计清单 §2.2)。`off` 档返回空串,verifier 的 JSON 示例
767
+ * 因此与改动前逐字节一致。
768
+ */
769
+ function formalJsonField(target) {
770
+ if (!formalOn()) return ''
771
+ const id = String(target == null ? '' : target) || '<对象id>'
772
+ return ',"formal":{"target":"' + id + '","decision":"used|blocked|defect","file":"Formal/' + id + '.lean","note":"难度判断/阻塞原因/具体偏差"}'
773
+ }
774
+ /**
775
+ * 工作轮(solver / explorer)回执里的 formal 字段。这些角色的回执契约本身就是一段 JSON 模板,
776
+ * 直接改模板尾部容易把示例改成非法 JSON,所以在**契约说明**里给出同样的字段(与 v3 同一形态);
777
+ * 框架侧 `absorbFormalFromReply` 同时接受顶层 `formal` 与 `meta.formal`。
778
+ */
779
+ function formalReplyNote() {
780
+ if (!formalOn()) return ''
781
+ return '\n形式化回执(本模式):若你本轮对某个对象做了形式化难度判断,或发现已有 Lean 证明与命题原文不符,'
782
+ + '请在回执里加上 "formal":{"target":"<对象id>","decision":"used|blocked|defect","file":"Formal/<对象id>.lean",'
783
+ + '"note":"难度判断/阻塞原因/具体偏差"}(decision=\'blocked\'/\'defect\' 时必须写明 note,否则整条记录被拒绝;'
784
+ + 'decision=\'defect\' 会撤回该证明的「已通过」状态并写入「形式化待办」)。'
785
+ }
786
+ /**
787
+ * 撤回一份归档证明。删除是**尽力而为**,撤回却不是:
788
+ * 宿主没有 `subprocess` 服务、shell 删除失败、或 shell 退出 0 却没真的删掉(stub 宿主、权限问题)时,
789
+ * 必须把撤回通知**写进那个路径**——否则一份已被撤回的证明仍留在 `Verified/Lean/`,那是"所有人找
790
+ * 这个对象的证明"的地方;记录里的 `proof` 已经清空,于是目录与记录脱节,谁也不会再发现它
791
+ * (契约 §4.1 要求撤回,而不是"试过删除")。
792
+ * 返回**实际发生了哪一种**(deleted / overwritten / failed / unreachable),由调用方如实告知读者。
793
+ */
794
+ async function withdrawArchivedProof(rel) {
795
+ const abs = leanAbsPath(rel)
796
+ if (abs === null) return { rel: String(rel), outcome: 'unreachable' }
797
+ const sub = subprocessOf()
798
+ if (sub !== undefined && typeof sub.spawn === 'function') {
799
+ try { await runShell(rmCmd(abs)) } catch (e) { /* 落到下面的覆写 */ }
800
+ // 用 fs 复核"真的没了":`Remove-Item -ErrorAction SilentlyContinue` / `rm -f` 都可能静默失败。
801
+ if (await readTextAbs(abs) === undefined) return { rel: String(rel), outcome: 'deleted' }
802
+ }
803
+ const ok = await writeTextAbs(abs, '-- 已撤回(' + fmtTime() + '):该形式化被认定与命题原文不一致。\n'
804
+ + '-- 原代码保留在工作文件 Formal/' + String(rel).split('/').pop() + ';修正并重新跑通后重新归档。\n')
805
+ return { rel: String(rel), outcome: ok ? 'overwritten' : 'failed' }
806
+ }
807
+ /**
808
+ * 契约 §4.1:`defect` = **形式化不合格**,不是"命题为假"。
809
+ *
810
+ * 表决者逐条核对后发现 Lean 代码与命题原文不一致(写窄了/写宽了/换了对象/漏了条件…),
811
+ * 那是这条形式化写得不对,不是命题被证伪。因此:
812
+ * ① 把形式化记录**降级为 `attempted`**(无论此前是 `passed` 还是 `blocked`)、清空 `proof`;
813
+ * ② 撤回归档证明 `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean` 保留,代码不丢);
814
+ * ③ `note` 记入记录与 `Formal/TODO.md`,并在活动日志里公告;
815
+ * ④ `require` 档下 `formalGateOk` 随之为假 → 本次裁定**不定论**,修正形式化并重新跑通后再投票。
816
+ */
817
+ async function formalDefectDowngrade(target, note, who) {
818
+ const t = safeId(String(target || ''))
819
+ const objectId = formalObjectIdOf(t)
820
+ const before = formalRecords()
821
+ const proofs = []
822
+ const pushProof = function (p) { const s = String(p || ''); if (s && proofs.indexOf(s) === -1) proofs.push(s) }
823
+ // **同一对象的全部 id 别名**都要看:归档可能写在对象 id(`pX`)上,也可能写在验证 id
824
+ // (`r-pX` / `r-pX-s0` / `r-pX-pf1`)上,而回执点名的可以是任意一侧。只扫 `t` 与 `objectId`
825
+ // 会漏掉"归档用 rId、回执用对象 id"这一组合,于是那份被撤回的证明永远留在 `Verified/Lean/`
826
+ // 里(记录已清空 proof,没人再指向它)——这正是"成对关系只做一半"的缺陷形态(审计清单 §3)。
827
+ for (const k of Object.keys(before)) if (formalObjectIdOf(k) === objectId) pushProof((before[k] || {}).proof)
828
+ // 两套 id 一起降级——只降一侧会让另一侧继续"已通过",门禁/卡片就会照旧放行。
829
+ await putFormalBothIds(t, { status: 'attempted', decision: 'defect', note: note, proof: '', updatedAt: now() })
830
+ // 备选的归档证明路径 = 记录里记着的那份(一定存在过)+ 各个 id 直接对应的文件名。
831
+ const candidates = []
832
+ for (let i = 0; i < proofs.length; i++) if (proofs[i].indexOf('Verified/Lean/') === 0 && candidates.indexOf(proofs[i]) === -1) candidates.push(proofs[i])
833
+ const keys = Object.keys(before).filter(function (k) { return formalObjectIdOf(k) === objectId })
834
+ for (const id of keys.concat([t, objectId])) { const r = 'Verified/Lean/' + id + '.lean'; if (candidates.indexOf(r) === -1) candidates.push(r) }
835
+ // 只处理**确实存在**(或在记录里被引用)的路径:宿主没有 subprocess 时撤回会退化成"覆写通知",
836
+ // 若把从未存在过的文件名也一并处理,就会凭空造出 `Verified/Lean/<id>.lean`。
837
+ const rels = []
838
+ for (let i = 0; i < candidates.length; i++) {
839
+ if (proofs.indexOf(candidates[i]) !== -1) { rels.push(candidates[i]); continue }
840
+ const abs = leanAbsPath(candidates[i])
841
+ if (abs !== null && (await readTextAbs(abs)) !== undefined) rels.push(candidates[i])
842
+ }
843
+ const deleted = [], overwritten = [], failed = []
844
+ for (let i = 0; i < rels.length; i++) {
845
+ let r
846
+ try { r = await withdrawArchivedProof(rels[i]) } catch (e) { r = { rel: rels[i], outcome: 'failed' } }
847
+ if (r.outcome === 'deleted') deleted.push(r.rel)
848
+ else if (r.outcome === 'overwritten') overwritten.push(r.rel)
849
+ else failed.push(r.rel)
850
+ }
851
+ const list = formalTodo().filter(function (x) { return x.id !== objectId })
852
+ list.push({ id: objectId, at: now(), why: 'defect:形式化与命题原文不一致,需修正后重新跑通(' + note + ')' })
853
+ await putFormal(t, Object.assign({}, formalRecords()[t] || {}, { status: 'attempted', decision: 'defect', note: note, proof: '', updatedAt: now() }), list)
854
+ await writeFormalTodo()
855
+ await writeFormalIndex()
856
+ // 公告必须如实说明**撤回实际怎么完成的**:删除成功、只能覆写成撤回通知、还是两者都没做到。
857
+ // 否则读者会以为归档目录里已经干净了,而一份坏证明还躺在那里(契约 §4.1)。
858
+ logActivity('formal', '【形式化】' + who + ' 通过回执记录 ' + objectId + ' 的忠实性缺陷(decision=defect):' + note
859
+ + ' —— 已撤回「已通过」状态(降级 attempted'
860
+ + (deleted.length ? '、删除归档证明 ' + deleted.join('、') : '')
861
+ + (overwritten.length ? '、把归档证明覆写为撤回通知 ' + overwritten.join('、') + '(宿主无法删除,但该路径已不再是一份证明)' : '')
862
+ + (failed.length ? '、⚠ 归档证明 ' + failed.join('、') + ' 既未删除也未能覆写,记录已降级——请不要把它当作该对象的证明' : '')
863
+ + '、写入 Formal/TODO.md)。'
864
+ // 强度必须与档位一致(契约 §4.1 第 3 条):encourage 没有门禁,不能声称框架会搁置裁定。
865
+ + (formalRequired() ? '本次裁定不定论。' : '本档没有门禁:本次裁定是否定论由表决结果决定(靠表决者的弃权值阻止布尔一致结论)。'))
866
+ return { ok: true, decision: 'defect', target: objectId, named: t, status: 'attempted', note: note, cleared: rels, deleted: deleted, overwritten: overwritten, failed: failed }
867
+ }
868
+ /**
869
+ * 回执通道(契约 §4 / §6.3):代理**即使一次 Lean 工具都没调用**,也必须能留下"实现难度判断"或
870
+ * "忠实性缺陷"。`blocked` / `defect` 的 `note` 必填(决定必须显式、可审计,不允许静默跳过);
871
+ * 缺 note / 缺 target / 非法 decision 一律拒绝(`V2_INVALID_ARGUMENT`)且不留下任何记录。
872
+ */
873
+ async function absorbFormalReply(f, memberId) {
874
+ try {
875
+ if (!formalOn() || !f || typeof f !== 'object') return { ok: false, ignored: true }
876
+ const who = memberId || 'office'
877
+ const rawTarget = String(f.target == null ? '' : f.target).trim()
878
+ const decision = String(f.decision || '')
879
+ if (!rawTarget) {
880
+ // safeId('') 会返回 'anon':没有 target 的回执绝不能凭空造出一条 formal 记录。
881
+ logActivity('formal', '【形式化】' + who + ' 的 formal 回执没有 target,已拒绝(V2_INVALID_ARGUMENT)。')
882
+ return { ok: false, code: 'V2_INVALID_ARGUMENT', message: 'formal.target is required' }
883
+ }
884
+ const target = safeId(rawTarget)
885
+ const note = String(f.note || '').trim()
886
+ if (decision === 'blocked' || decision === 'defect') {
887
+ if (!note) {
888
+ logActivity('formal', '【形式化】' + who + ' 的 formal.decision=' + decision + ' 没有写明 note,已拒绝(V2_INVALID_ARGUMENT)——'
889
+ + target + ' 的难度判断/忠实性缺陷必须显式、可审计。')
890
+ return { ok: false, code: 'V2_INVALID_ARGUMENT', message: 'formal.decision=' + decision + ' requires a non-empty note (target ' + target + ')' }
891
+ }
892
+ if (decision === 'defect') return await formalDefectDowngrade(target, note, who)
893
+ await putFormalBothIds(target, { status: 'blocked', decision: 'blocked', note: note, updatedAt: now() })
894
+ await writeFormalIndex()
895
+ logActivity('formal', '【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化阻塞:' + note)
896
+ return { ok: true, decision: 'blocked', target: target, status: 'blocked', note: note }
897
+ }
898
+ if (decision === 'used') {
899
+ const file = String(f.file || ('Formal/' + target + '.lean'))
900
+ await putFormalBothIds(target, { status: 'attempted', decision: 'used', file: file, updatedAt: now() })
901
+ await writeFormalIndex()
902
+ logActivity('formal', '【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化草稿:' + file)
903
+ return { ok: true, decision: 'used', target: target, status: 'attempted', file: file }
904
+ }
905
+ logActivity('formal', '【形式化】' + who + ' 的 formal.decision 只能是 \'used\' | \'blocked\' | \'defect\'(收到 '
906
+ + String(f.decision) + '),已忽略(V2_INVALID_ARGUMENT)。')
907
+ return { ok: false, code: 'V2_INVALID_ARGUMENT', message: 'formal.decision must be one of used|blocked|defect' }
908
+ } catch (e) {
909
+ // 回执通道**绝不**把异常抛进调度循环:一次记账失败不该吞掉一次表决。
910
+ console.error('vibe-math-v2: absorbFormalReply failed: ' + String((e && e.message) || e))
911
+ return { ok: false, error: String((e && e.message) || e) }
912
+ }
913
+ }
914
+ /** 从一条代理回执里取出 formal 判断并落库(顶层 `formal` 或 `meta.formal` 都接受)。 */
915
+ async function absorbFormalFromReply(parsed, memberId) {
916
+ if (!formalOn() || !parsed || typeof parsed !== 'object') return { ok: false, ignored: true }
917
+ const f = (parsed.formal && typeof parsed.formal === 'object') ? parsed.formal
918
+ : ((parsed.meta && typeof parsed.meta.formal === 'object') ? parsed.meta.formal : null)
919
+ if (!f) return { ok: false, ignored: true }
920
+ return await absorbFormalReply(f, memberId)
693
921
  }
694
922
 
695
923
  // ---- 三份索引(框架维护;契约 §9)----
@@ -771,8 +999,8 @@ export function apply(ctx) {
771
999
  if (run.ok) logActivity('formal', (memberId || 'office') + ' 运行 Lean 通过:' + run.file + '(' + (run.ms / 1000).toFixed(1) + 's)' + (args.target ? '|对象 ' + args.target : ''))
772
1000
  return Object.assign({ ok: !!run.ok }, run, {
773
1001
  hint: run.ok
774
- ? '通过。若是某个对象的证明,请用 lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
775
- : '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 lean_archive kind=\'blocked\' 记录原因。',
1002
+ ? '通过。若是某个对象的证明,请用 vibe_math_lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库(归档时会先跑一次,跑不通不要入库)。'
1003
+ : '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_math_lean_archive kind=\'blocked\' 记录原因。',
776
1004
  })
777
1005
  }
778
1006
  async function leanArchive(memberId, o) {
@@ -800,7 +1028,14 @@ export function apply(ctx) {
800
1028
  const run = args.run === false ? null : await leanRunFile(abs)
801
1029
  await rebuildLeanLibIndexes()
802
1030
  logActivity('formal', (memberId || 'office') + ' 归档了' + (kind === 'def' ? '可复用定义' : '已证引理') + ' `' + name + '` → ' + rel + (run ? '(运行 ' + (run.ok ? '通过' : '未通过') + ')' : ''))
803
- return { ok: true, kind: kind, name: name, file: rel, run: run || undefined, note: '已并入全局可复用库,后续项目可直接 import 复用' }
1031
+ // 工具返回值同样是代理读到的文字:`run` 为红时**不能**声称"可直接 import 复用"——
1032
+ // 那份文件没有通过编译,"可复用库"里的它是有害的(契约 §6 第 3 条 / v2 实现方案 §9.4)。
1033
+ return {
1034
+ ok: true, kind: kind, name: name, file: rel, run: run || undefined,
1035
+ note: (run && !run.ok)
1036
+ ? '⚠ 该文件**运行未通过**(见 run.stderr):它已写入 ' + rel + ',但**不合格**,请不要当作可复用定义;请修复后用 vibe_math_lean_run(或再次归档 run=true)跑通。'
1037
+ : '已并入全局可复用库,后续项目可直接 import 复用',
1038
+ }
804
1039
  }
805
1040
  if (kind === 'proof') {
806
1041
  const rawTarget = String(args.target || '').trim()
@@ -818,25 +1053,35 @@ export function apply(ctx) {
818
1053
  const run = await leanRunFile(workRel)
819
1054
  const prev = formalOf(target)
820
1055
  const passed = !!run.ok
1056
+ // A RED re-archive invalidates the previous proof: the work file it proved has just been
1057
+ // overwritten by code that does not compile. Keeping the pointer (or leaving the archived
1058
+ // file) would produce "attempted + 归档证明 X.lean" in the index and let the fidelity prompt
1059
+ // print a proof path for code that no longer exists — `proof` is for `passed` only (§4).
1060
+ const stalePrev = passed ? '' : String(prev.proof || ('Verified/Lean/' + target + '.lean'))
821
1061
  const rec = Object.assign({}, prev, {
822
1062
  status: passed ? 'passed' : 'attempted',
823
1063
  file: workRel,
824
- proof: passed ? 'Verified/Lean/' + target + '.lean' : (prev.proof || ''),
1064
+ proof: passed ? 'Verified/Lean/' + target + '.lean' : '',
825
1065
  decision: 'used',
826
1066
  note: String(args.note || prev.note || ''),
827
1067
  run: { at: now(), ok: !!run.ok, exitCode: run.exitCode === undefined ? null : run.exitCode, ms: run.ms || 0, stdoutTail: tailText(run.stdout, 800), stderrTail: tailText(run.stderr, 800) },
828
1068
  updatedAt: now(),
829
1069
  })
1070
+ let withdrawn = null
830
1071
  if (passed) await writeText('Verified/Lean/' + target + '.lean', body)
1072
+ else if (stalePrev) { try { withdrawn = await withdrawArchivedProof(stalePrev) } catch (e) { withdrawn = { rel: stalePrev, outcome: 'failed' } } }
831
1073
  await putFormal(target, rec)
832
1074
  // ★ 让"归档"与"验证对象"两套 id 对齐。
833
- // 验证提示词与门禁关心的是**这一次验证**(rId,例如 r-pGate),而代理用 lean_archive
1075
+ // 验证提示词与门禁关心的是**这一次验证**(rId,例如 r-pGate),而代理用 vibe_math_lean_archive
834
1076
  // 归档时给的是**对象 id**(例如 pGate)。若不在这里把验证对象也标成同一状态,就会
835
1077
  // 出现"对象已 passed、门禁却仍认为 rId 未形式化"的错位:代理明明做了形式化,
836
1078
  // 裁定还是被反复搁置。两套记录都写,门禁与提示词任取其一都自洽。
837
1079
  await syncVerificationTarget(target, passed ? 'passed' : 'attempted', rec)
838
1080
  await rebuildLeanLibIndexes()
839
- logActivity('formal', (memberId || 'office') + ' 为 ' + target + ' 归档形式化证明 ' + workRel + '(运行 ' + (passed ? '通过,已归档到 ' + rec.proof + ',验证转为忠实性审查' : '未通过:' + tailText(run.stderr || run.message, 160)) + ')')
1081
+ logActivity('formal', (memberId || 'office') + ' 为 ' + target + ' 归档形式化证明 ' + workRel + '(运行 ' + (passed
1082
+ ? '通过,已归档到 ' + rec.proof + ',验证转为忠实性审查'
1083
+ : '未通过:' + tailText(run.stderr || run.message, 160)
1084
+ + ';已撤回上一份已通过状态与归档证明' + (withdrawn && withdrawn.outcome !== 'failed' ? '(' + withdrawn.outcome + ')' : '(⚠ 撤回失败,请不要把 ' + stalePrev + ' 当作该对象的证明)')) + ')')
840
1085
  return { ok: true, kind: kind, target: target, file: workRel, proof: rec.proof, passed: passed, run: run, status: rec.status }
841
1086
  }
842
1087
  if (kind === 'blocked') {
@@ -865,14 +1110,22 @@ export function apply(ctx) {
865
1110
  async function deferForFormal(target, why) {
866
1111
  const t = safeId(String(target || ''))
867
1112
  if (!t) return false
868
- const rec = formalOf(t)
869
1113
  if (!formalRequired()) return false
1114
+ // ★ 判定必须用**合并后**的记录(`formalGateRecord`,两套 id 都认),不能只看自己那一侧。
1115
+ // 代理是用**对象 id** 归档的(提示词里给它的就是对象 id),而验证/门禁问的是 rId;
1116
+ // `syncVerificationTarget` 只更新**已存在**的别名键,所以"归档写了 pX、r-pX 还没有记录"是
1117
+ // 首次形式化后的真实状态。那时 `formalOf('r-pX')` 返回 `{status:'none'}`,门禁会把一个
1118
+ // **已经 passed 的对象**判为未形式化(假阴性);更糟的是这次搁置本身会写下 r-pX =
1119
+ // status:'none',于是此后**每一轮**都继续搁置、继续重开一次完整辩论——对象永远无法定论。
1120
+ // 这正是实现方案 §四写明的"门禁与提示词取记录时两个方向都认(formalGateRecord)"。
1121
+ const rec = formalGateRecord(t)
870
1122
  if (formalGateOk(rec)) return false
1123
+ const own = formalOf(t)
871
1124
  const reason = 'formal-required:尚未取得 Lean 形式化通过,也没有显式阻塞记录(当前状态 ' + (rec.status || 'none') + ')' + (why ? '|' + why : '')
872
1125
  const list = formalTodo().filter(function (x) { return x.id !== t })
873
1126
  list.push({ id: t, at: now(), why: reason })
874
1127
  // 不改动对象的既有权重/概率字段:只补一条 formal 记录(status 保持 none/attempted)。
875
- await putFormal(t, rec.status === 'none' ? { status: 'none', deferredAt: now() } : Object.assign({}, rec, { deferredAt: now() }), list)
1128
+ await putFormal(t, own.status === 'none' ? { status: 'none', deferredAt: now() } : Object.assign({}, own, { deferredAt: now() }), list)
876
1129
  await writeFormalTodo()
877
1130
  await writeFormalIndex()
878
1131
  // v2 没有群聊文件(没有 Shared/Chat/),所以"群聊公告"落在它的可读通道上:
@@ -1136,7 +1389,13 @@ export function apply(ctx) {
1136
1389
  function knowledgeContextText() { const k = params.knowledgeContext ? String(params.knowledgeContext) : defaultKnowledgeContext(); return k ? ('\n' + k + '\n') : '' }
1137
1390
  // 平时工作提示词里的"顺手形式化"段落。**off 档必须返回空串**:off 是真正的无操作,
1138
1391
  // 提示词里不能出现任何 Lean 字样(contract §2 / §10.1)。
1139
- function formalWorkSection() { const t = formalWorkLine(); return t ? ('\n' + t + '\n') : '' }
1392
+ function formalWorkSection() {
1393
+ const t = formalWorkLine()
1394
+ if (!t) return ''
1395
+ // 回执契约(契约 §6.3):工作轮也必须被告知 formal 字段,否则"顺手形式化"里做出的难度判断
1396
+ // 无处可写,代理只能沉默——那正是 v2 首版死通道的成因。
1397
+ return '\n' + t + formalReplyNote() + '\n'
1398
+ }
1140
1399
  function capabilitiesText(role) {
1141
1400
  const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
1142
1401
  const netOn = role === 'solver' ? params.solverAllowNetwork : params.verifierAllowNetwork
@@ -1228,7 +1487,7 @@ export function apply(ctx) {
1228
1487
  return head
1229
1488
  }
1230
1489
  // 验证提示词里的形式化段落(review 与 debate 两条路径都必须带)。**off 档返回空串**。
1231
- // 形式化记录以「验证对象」为键(rId,例如 r-p1 或 r-q1-s0)——这正是 lean_archive 的 target,
1490
+ // 形式化记录以「验证对象」为键(rId,例如 r-p1 或 r-q1-s0)——这正是 vibe_math_lean_archive 的 target,
1232
1491
  // 于是"归档了证明 → 下一条验证提示词自动切换成忠实性审查"这条因果链闭合。
1233
1492
  function formalVerifySection(r) {
1234
1493
  if (!formalOn()) return ''
@@ -1239,7 +1498,25 @@ export function apply(ctx) {
1239
1498
  // 把"审查对象变了"这件事说透:手里已有机器核对过的证明时,重新推导是浪费,
1240
1499
  // 真正的风险是"这段代码说的不是我们想说的"。
1241
1500
  if (rec.status === 'passed') {
1242
- L.push(' ▸ 因此请把 Result 用在**忠实性**上:一致 → 1;发现任何偏离 → 0(或按不确定度给中间值并说明)。')
1501
+ // 忠实性审查的**判定指引**:形式化与命题原文不一致时,那是"形式化不合格",**不是**命题为假。
1502
+ // 让它"发现偏离就投 0"会伪造出一个错误的否定结论(契约 §4.1),所以这里明确禁止投 0,
1503
+ // 并给出 `defect` 回执——框架据此撤回证明、写入待办、本次裁定不定论。
1504
+ L.push(' ▸ 一致 → Result = 1。')
1505
+ L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
1506
+ L.push(" ① Result 给一个严格介于 0 与 1 之间的值(记为弃权),并在 Reason 里写清偏差;")
1507
+ L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
1508
+ if (formalMode() === 'require') {
1509
+ // 这一档真的有门禁,所以"本次裁定**不定论**"是框架**能兑现**的承诺(契约 §6.1)。
1510
+ L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办),本次裁定**不定论**;')
1511
+ } else {
1512
+ // encourage 档没有门禁:撤回证明 ≠ 搁置裁定。承诺一个框架无法强制的"不定论",会让表决者
1513
+ // 以为不必自己弃权——那正是"提示词承诺的强度档位必须与实现一致"这条不变式(契约 §4.1 第 3 条
1514
+ // / §6.1,审计清单 §1.7)。这里如实说明:阻止本轮定论的是**你的弃权值**。
1515
+ L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办)。**本档没有门禁**:')
1516
+ L.push(' 框架不会强制搁置本次裁定——请务必给出①里的弃权值,靠它阻止本轮得出布尔一致结论;')
1517
+ }
1518
+ L.push(' 修正形式化并重新跑通后再投票。')
1519
+ L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 Reason 里写清独立理由。')
1243
1520
  } else if (rec.status === 'blocked') {
1244
1521
  L.push(' ▸ 因此请把 Result 用在"这个阻塞判断是否成立 / 是否仍有别的形式化路线"上,并给出理由。')
1245
1522
  } else {
@@ -1259,7 +1536,7 @@ export function apply(ctx) {
1259
1536
  formalVerifySection(r) +
1260
1537
  '\nResult ∈ [0,1] = your probability that the TARGET is CORRECT: 1 ONLY when you are fully certain (for a bare proposition: Reason must be a complete proof; for a proof/refutation/solution: you verified every step and Reason confirms the whole chain); 0 ONLY when you are certain it is wrong (Reason must be a rigorous complete refutation / pinpoint the fatal flaw); otherwise a value strictly between 0 and 1.\n' +
1261
1538
  '\nIndependently output your initial review. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
1262
- '{"Result":0.5,"Reason":"detailed logic chain, potential counterexample, or supporting evidence"}'
1539
+ '{"Result":0.5,"Reason":"detailed logic chain, potential counterexample, or supporting evidence"' + formalJsonField(r && r.rId) + '}'
1263
1540
  }
1264
1541
  function verifierDebatePrompt(r, transcript) {
1265
1542
  let target = ''
@@ -1273,7 +1550,7 @@ export function apply(ctx) {
1273
1550
  '\nFULL DEBATE HISTORY SO FAR (每轮所有评审轮流发言的记录):\n' + transcript + '\n' +
1274
1551
  '\nRespond to the others (agree / rebut / add new evidence, referencing earlier rounds if needed). If you changed your Result because of them, state the reason explicitly.\n' +
1275
1552
  'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
1276
- '{"Result":0.5,"Reason":"updated logic chain / counterexample / proof / refutation","changed":"brief reason if you changed your Result, else null"}'
1553
+ '{"Result":0.5,"Reason":"updated logic chain / counterexample / proof / refutation","changed":"brief reason if you changed your Result, else null"' + formalJsonField(r && r.rId) + '}'
1277
1554
  }
1278
1555
 
1279
1556
  // ================= decisions (manual/auto) =================
@@ -1584,6 +1861,8 @@ export function apply(ctx) {
1584
1861
  async function handleExplorer(childId, meta, output) {
1585
1862
  delete agentRegistry[childId]
1586
1863
  const parsed = parseJson(output)
1864
+ // 回执通道(契约 §4 / §6.3):工作轮里做出的形式化难度判断也要落库。
1865
+ await absorbFormalFromReply(parsed, childId)
1587
1866
  const dirs = (parsed && parsed.directions) || []
1588
1867
  if (dirs.length === 0) { logActivity('explorer', 'problem ' + meta.qid + ' returned no directions (output head: ' + String(output || '').slice(0, 200) + ')'); await saveAll(); return }
1589
1868
  explorerRetries[meta.qid] = 0
@@ -1598,6 +1877,8 @@ export function apply(ctx) {
1598
1877
  async function handleSolver(childId, meta, output, stopReason) {
1599
1878
  const qid = meta.qid; const dirId = meta.direction
1600
1879
  const parsed = parseJson(output)
1880
+ // 回执通道(契约 §4 / §6.3):求解器/探索者的"顺手形式化"产物与难度判断都要落库。
1881
+ await absorbFormalFromReply(parsed, childId)
1601
1882
  const q = await findQ(qid); if (!q) { delete agentRegistry[childId]; return }
1602
1883
  const prog = parseProgress(q)
1603
1884
  const dir = prog.directions.find(function (d) { return d.id === dirId })
@@ -1724,6 +2005,10 @@ export function apply(ctx) {
1724
2005
  async function handleVerifier(childId, meta, output, stopReason) {
1725
2006
  const rId = meta.rId
1726
2007
  const parsed = parseJson(output)
2008
+ // 回执通道(契约 §4 / §6.3):验证者在回执里给出的难度判断 / 忠实性缺陷(`defect`)必须先于
2009
+ // 裁定落库——`defect` 会把形式化记录降级,于是紧随其后的 `settleVerdict` 会在 require 档
2010
+ // 把本次裁定正确地记为未定论。放在这里(而不是裁定之后)是有意的顺序依赖。
2011
+ await absorbFormalFromReply(parsed, childId)
1727
2012
  const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
1728
2013
  const Reason = (parsed && parsed.Reason) || ''
1729
2014
  let t = tasks['verify:' + rId]
@@ -1903,19 +2188,30 @@ export function apply(ctx) {
1903
2188
  if (q.判断命题 && !sol.来源列表) {
1904
2189
  const ap = await findProposition(q.判断命题)
1905
2190
  if (ap) {
1906
- ap.布尔估计 = v
1907
- // 收敛闸门:本条路径只在 v=1/0 时才写入证明/证伪条目,中间裁决(flat 默认给出
1908
- // 0.5,forced 给出加权浮点)会让 ap 停留在"中间布尔估计 + 两个列表皆空"的状态——
1909
- // 而这正是 buildVerifyCandidates 认定"裸命题需要验证"的条件。若不在此标记,该命题
1910
- // 会在每个 tick 重新入选、重开一轮完整辩论;又因 processVerify 每 tick 只跑一个验证,
1911
- // 其它对象被无限饿死,终止条件(所有问题已解决)永不可达。标记后不再重复消耗验证配额,
1912
- // 裁决值仍保留在 布尔估计 中。
1913
- ap.已验证 = true
1914
- if (v === 1) { ap.证明列表 = ap.证明列表 || []; ap.证明列表.push({ 完整过程: strongestReason(t, 1) || '判断问题解法验证通过', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
1915
- else if (v === 0) { ap.证伪列表 = ap.证伪列表 || []; ap.证伪列表.push({ 完整过程: strongestReason(t, 0) || '判断问题解法判定不成立', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
1916
- await upsertProposition(ap)
1917
- await writeVerifiedCardIfNeeded(ap)
1918
- logActivity('judge-sync', 'judge problem verdict ' + v + ' synced to proposition ' + ap.id)
2191
+ // ★ require 门禁:这条路径把**源命题**的 布尔估计 直接写成 0/1、置 优先级='never'、
2192
+ // 并标记 已验证(此后不再入选验证)——这就是一次对源命题的布尔裁定,必须先过门禁。
2193
+ // 原来的写法只让 writeVerifiedCardIfNeeded 去拦卡片,而它的返回值没人检查,概率字段
2194
+ // 早已落库:一个未形式化的命题会被侧面判为"假"并永久退出调度(v=1 时上面那道门禁
2195
+ // 拦得住,v=0 拦不住)。实现方案 §八把顺序写得很清楚:先拿卡片写入许可,再改
2196
+ // 布尔估计/优先级(契约 §8:门禁不通过时"不改变对象的既有权重/概率字段")。
2197
+ const judgeBool = (v === 1 || v === 0)
2198
+ if (judgeBool && await deferForFormal(ap.id, '「判断命题」问题解法的裁定转移到源命题(' + v + ')')) {
2199
+ logActivity('gate', t.rId + ' 对源命题 ' + ap.id + ' 的裁定 ' + v + ' 被 require 模式搁置为未定论(formal-required)')
2200
+ } else {
2201
+ ap.布尔估计 = v
2202
+ // 收敛闸门:本条路径只在 v=1/0 时才写入证明/证伪条目,中间裁决(flat 默认给出
2203
+ // 0.5,forced 给出加权浮点)会让 ap 停留在"中间布尔估计 + 两个列表皆空"的状态——
2204
+ // 而这正是 buildVerifyCandidates 认定"裸命题需要验证"的条件。若不在此标记,该命题
2205
+ // 会在每个 tick 重新入选、重开一轮完整辩论;又因 processVerify 每 tick 只跑一个验证,
2206
+ // 其它对象被无限饿死,终止条件(所有问题已解决)永不可达。标记后不再重复消耗验证配额,
2207
+ // 裁决值仍保留在 布尔估计 中。
2208
+ ap.已验证 = true
2209
+ if (v === 1) { ap.证明列表 = ap.证明列表 || []; ap.证明列表.push({ 完整过程: strongestReason(t, 1) || '判断问题解法验证通过', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
2210
+ else if (v === 0) { ap.证伪列表 = ap.证伪列表 || []; ap.证伪列表.push({ 完整过程: strongestReason(t, 0) || '判断问题解法判定不成立', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
2211
+ await upsertProposition(ap)
2212
+ await writeVerifiedCardIfNeeded(ap)
2213
+ logActivity('judge-sync', 'judge problem verdict ' + v + ' synced to proposition ' + ap.id)
2214
+ }
1919
2215
  }
1920
2216
  }
1921
2217
  if (v === 1) { q.已解决 = true; q.优先级 = 'never'; await writeVerifiedProblemCardIfNeeded(q) }
@@ -2146,7 +2442,7 @@ export function apply(ctx) {
2146
2442
  counts: r, todo: formalTodo(),
2147
2443
  objects: Object.keys(formalRecords()).map(function (k) { const rec = formalRecords()[k] || {}; return { target: k, status: rec.status, file: rec.file, proof: rec.proof, note: rec.note } }),
2148
2444
  paths: { project: 'Formal/(相对项目根)', lib: 'VibeMath/Formal/Lib/', proved: 'VibeMath/Formal/Proved/', proofs: 'Verified/Lean/' },
2149
- hint: '复用优先:先在 Lib/ 里找现成定义;新定义用 lean_archive kind=\'def\' 归档,已证引理用 kind=\'lemma\'。',
2445
+ hint: '复用优先:先在 Lib/ 里找现成定义;新定义用 vibe_math_lean_archive kind=\'def\' 归档,已证引理用 kind=\'lemma\'(归档前先跑通,跑不通不要入库)。',
2150
2446
  }
2151
2447
  })
2152
2448