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
@@ -1382,7 +1382,7 @@ export function apply(ctx) {
1382
1382
  L.push(' "task_update": {"task_id":"t-3","expected_revision":2,"action":"complete|release|reopen|edit|set_dependencies|delete"},')
1383
1383
  L.push(' "input": "本轮会议/辩论的发言正文(会议轮用;也可直接用 say)",')
1384
1384
  if (formalOn()) {
1385
- L.push(' "formal": {"target":"p-x","decision":"used|blocked","file":"Formal/p-x.lean","note":"难度判断/阻塞原因"}')
1385
+ L.push(' "formal": {"target":"p-x","decision":"used|blocked|defect","file":"Formal/p-x.lean","note":"难度判断/阻塞原因"}')
1386
1386
  L.push(' ← Lean 形式化:' + (formalMode() === 'require'
1387
1387
  ? '**强制**:定论前必须有「Lean 已通过」或显式阻塞原因(note 必填),否则本轮裁定记为未定论,'
1388
1388
  : '**鼓励**:按实现难度自行决定;做了就归档,没做就写明难度判断,') + '详见提示词里的【Lean 形式化验证】段')
@@ -1511,11 +1511,9 @@ export function apply(ctx) {
1511
1511
  L.push(formalPromptBlock(vs.target))
1512
1512
  // Make the SHIFT explicit: with a machine-checked proof in hand, re-deriving is
1513
1513
  // wasted effort and the real risk is a statement that does not say what we meant.
1514
- if (rec.status === 'passed') {
1515
- L.push(' ▸ 因此请把 verdict 用在**忠实性**上:一致 → 1;发现任何偏离 → 0(或按不确定度给中间值并说明)。')
1516
- } else if (rec.status === 'blocked') {
1514
+ if (rec.status === 'blocked') {
1517
1515
  L.push(' ▸ 因此请把 verdict 用在"这个阻塞判断是否成立 / 是否仍有别的形式化路线"上,并给出理由。')
1518
- } else {
1516
+ } else if (rec.status !== 'passed') {
1519
1517
  L.push(' ▸ 若你在本轮把它形式化并跑通(vibe_v5_lean_archive kind=\'proof\'),后续轮次的')
1520
1518
  L.push(' 审查对象就会从"推导是否正确"变成"Lean 代码是否忠实于命题"。')
1521
1519
  }
@@ -1541,7 +1539,7 @@ export function apply(ctx) {
1541
1539
  // The formal field belongs in the VOTING contract too: voters are exactly the agents
1542
1540
  // who must either formalize the object or record why they judged it infeasible.
1543
1541
  L.push('若你本轮做了形式化或给出难度判断,请一并加上:')
1544
- L.push('{"formal":{"target":"' + vs.target + '","decision":"used|blocked","file":"Formal/' + vs.target + '.lean","note":"难度判断/阻塞原因"}}')
1542
+ L.push('{"formal":{"target":"' + vs.target + '","decision":"used|blocked|defect","file":"Formal/' + vs.target + '.lean","note":"难度判断/阻塞原因"}}')
1545
1543
  }
1546
1544
  return L.join('\n')
1547
1545
  }
@@ -1716,10 +1714,39 @@ export function apply(ctx) {
1716
1714
  return { ok: false, code: 'LEAN_SPAWN_FAILED', message: String((e && e.message) || e), file: rel, ms: now() - started }
1717
1715
  }
1718
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 }))
1719
1730
  try {
1720
- 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
1721
1743
  } catch (e) {
1744
+ if (timerDisposer) { try { timerDisposer() } catch (e2) { /* already settled */ } }
1722
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 */ } }
1723
1750
  }
1724
1751
  let out = '', err = ''
1725
1752
  try { if (handle.collected && handle.collected.stdout) out = handle.collected.stdout.readFrom(0).text } catch (e) { /* best effort */ }
@@ -1731,8 +1758,8 @@ export function apply(ctx) {
1731
1758
  ok, exitCode, signal: (outcome && outcome.signal) || null, ms,
1732
1759
  command: argv.join(' '), file: rel,
1733
1760
  stdout: tail(out, 4000), stderr: tail(err, 4000),
1734
- timedOut: ms >= cap,
1735
- 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'),
1736
1763
  }
1737
1764
  }
1738
1765
  async function formalSetRun(target, run) {
@@ -1751,6 +1778,53 @@ export function apply(ctx) {
1751
1778
  updatedAt: now(),
1752
1779
  }))
1753
1780
  }
1781
+ // Withdrawing an archived proof needs a DELETE, but the fs service exposes no unlink and
1782
+ // subprocess is optional. Use the shell when it is available; otherwise overwrite the
1783
+ // file with an explicit withdrawal notice, so it can never be read as the object's proof.
1784
+ async function removeArchivedProof(rel) {
1785
+ const abs = leanAbsPath(rel)
1786
+ if (abs === null) return false
1787
+ const sub = subprocessOf()
1788
+ if (sub !== undefined && typeof sub.spawn === 'function') {
1789
+ const script = isWindows()
1790
+ ? 'Remove-Item -LiteralPath ' + psQuote(abs) + ' -Force -ErrorAction SilentlyContinue'
1791
+ : 'rm -f ' + shQuote(abs)
1792
+ try { await runShell(script) } catch (e) { /* fall through to the overwrite */ }
1793
+ // A shell that exits 0 without removing anything (a stub host, a permissions quirk)
1794
+ // must not leave a withdrawn proof where everyone looks for proofs: verify, fall back.
1795
+ if (await readTextAbs(abs) === undefined) return true
1796
+ }
1797
+ return (await writeTextAbs(abs, '-- 已撤回(' + fmtTime() + '):该形式化被认定与命题原文不一致。\n'
1798
+ + '-- 原代码保留在工作文件 Formal/' + String(rel).split('/').pop() + ';修正并重新跑通后重新归档。\n')) !== false
1799
+ }
1800
+ // A formalization that says something else than the proposition is NOT a refutation:
1801
+ // withdraw the proof instead of letting the group conclude 假 (contract §4.1).
1802
+ async function recordFidelityDefect(memberId, rawTarget, note) {
1803
+ const t = idSafe(String(rawTarget || ''))
1804
+ const why = String(note || '').trim()
1805
+ if (!t) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'target is required' }
1806
+ if (!why) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: "decision='defect' 必须写明 note(具体偏差)" }
1807
+ const prev = formalOf(t)
1808
+ const proofRel = String(prev.proof || ('Verified/Lean/' + t + '.lean'))
1809
+ await putFormal(t, Object.assign({}, prev, {
1810
+ status: 'attempted', proof: '', decision: 'defect', note: why,
1811
+ fidelity: { at: now(), by: String(memberId || ''), note: why }, updatedAt: now(),
1812
+ }))
1813
+ const removed = await removeArchivedProof(proofRel)
1814
+ const list = formalTodo().filter((x) => x.id !== t)
1815
+ list.push({ id: t, at: now(), why: '形式化不合格(忠实性缺陷):' + why })
1816
+ await putFormal(t, formalOf(t), list)
1817
+ await writeFormalTodo()
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, ' 未能撤回(宿主删除与覆盖均失败);记录已降级,请不要把它当作该对象的证明。']
1824
+ await saveChatLine('【形式化】' + (memberId || '成员') + ' 认定 ' + t + ' 的形式化**不忠实**:' + why
1825
+ + ' —— 已撤回「已通过」状态' + (removed ? '并从 Verified/Lean/ 删除归档证明' : '') + ';请修正形式化、重新跑通后再投票。' + stillThere.join(''))
1826
+ return { ok: true, target: t, status: 'attempted', removed }
1827
+ }
1754
1828
  function formalPromptBlock(target) {
1755
1829
  if (!formalOn()) return ''
1756
1830
  const mode = formalMode()
@@ -1758,11 +1832,27 @@ export function apply(ctx) {
1758
1832
  const L = []
1759
1833
  L.push('【Lean 形式化验证(' + (mode === 'require' ? '强制' : '鼓励') + '模式)】')
1760
1834
  if (rec.status === 'passed') {
1761
- // The whole point of the feature: the review subject CHANGES.
1835
+ // The whole point of the feature: the review subject CHANGES. And the SECOND half of
1836
+ // that point (docs/formal-verification.md §4.1): a statement that says something else
1837
+ // than the proposition is NOT a refutation — "偏离 → 0" would make the very mechanism
1838
+ // that exists for rigour fabricate a false negative conclusion.
1762
1839
  L.push(' · 该对象已有**通过的 Lean 形式化证明**(' + (rec.proof || rec.file) + ',最近运行 exit 0)。')
1763
1840
  L.push(' **你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的')
1764
- L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**(有偏差就指出偏差),')
1765
- L.push(' 并据此给出 verdict。')
1841
+ L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。')
1842
+ L.push(' ▸ 一致 → verdict = 1。')
1843
+ L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
1844
+ L.push(' ① verdict 给一个严格介于 0 与 1 之间的值(记为弃权),并在 reason 里写清偏差;')
1845
+ L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
1846
+ // Only `require` actually GATES the conclusion; in `encourage` the framework still
1847
+ // withdraws the proof (and records the defect) but must not promise a hold it cannot
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.
1850
+ L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办)'
1851
+ + (mode === 'require'
1852
+ ? ',本次裁定**不定论**;'
1853
+ : '。本档没有门禁:请务必给一个严格介于 0 与 1 之间的弃权值,以保证本轮无法得出一致结论;'))
1854
+ L.push(' 修正形式化并重新跑通后再投票。')
1855
+ L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 reason 里写清独立理由。')
1766
1856
  } else if (rec.status === 'blocked') {
1767
1857
  L.push(' · 该对象已被记录为**形式化阻塞**:' + (rec.note || '未说明') + '。')
1768
1858
  L.push(' 请复核这个判断是否成立;若你认为其实可以形式化,请指出来并动手做。')
@@ -1770,16 +1860,18 @@ export function apply(ctx) {
1770
1860
  L.push(' · 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先写 Lean 代码并执行。')
1771
1861
  L.push(' · 工具:vibe_v5_lean_run(执行)· vibe_v5_lean_archive(归档)· vibe_v5_lean_lib(查已有可复用库)')
1772
1862
  L.push(' · 工作目录:Formal/(相对研究所根);可复用定义放 ' + formalLibRoot().replace(/\\/g, '/') + '/,')
1773
- L.push(' 已证引理放 ' + formalProvedRoot().replace(/\\/g, '/') + '/;写之前先 lean_lib 查重。')
1774
- L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与')
1863
+ L.push(' 已证引理放 ' + formalProvedRoot().replace(/\\/g, '/') + '/;写之前先 vibe_v5_lean_lib 查重。')
1864
+ L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义 / 对象 / 条件 / 假设 / 结论是否与')
1775
1865
  L.push(' 命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
1776
1866
  if (mode === 'require') {
1777
- L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(lean_archive')
1867
+ L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_v5_lean_archive')
1778
1868
  L.push(' kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,')
1779
1869
  L.push(' 会被记为未定论(原因 formal-required)并进入「形式化待办」。')
1780
1870
  } else {
1781
- L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断。')
1871
+ L.push(" · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision='blocked' 时必须写明 note)。")
1782
1872
  }
1873
+ L.push(' · 归档可复用定义/引理前先跑通(vibe_v5_lean_archive run=true 或先 vibe_v5_lean_run);跑不通不要入库。')
1874
+ L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)或根本没有 subprocess 服务(NO_SUBPROCESS)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这两种都算显式阻塞原因,定论门禁可以据此放行。')
1783
1875
  }
1784
1876
  return L.join('\n')
1785
1877
  }
@@ -1788,6 +1880,7 @@ export function apply(ctx) {
1788
1880
  return '【顺手形式化(' + (formalMode() === 'require' ? '强制' : '鼓励') + ')】把你工作中常用或可能复用的对象、假设、'
1789
1881
  + '新定义用 Lean 形式化定义并归档到全局可复用库(vibe_v5_lean_archive kind=\'def\'),已成立的引理归到 Proved/'
1790
1882
  + '(kind=\'lemma\');写之前先 vibe_v5_lean_lib 查重,避免重复定义。'
1883
+ + '归档前先跑通(vibe_v5_lean_run 或 run=true);跑不通的定义不要进可复用库。'
1791
1884
  + (formalMode() === 'require'
1792
1885
  ? '本模式下,任何要定论为真/假的对象都必须先有 Lean 通过或显式阻塞记录。'
1793
1886
  : '这会让后续的验证与证明省掉大量重复工作。')
@@ -1879,8 +1972,8 @@ export function apply(ctx) {
1879
1972
  }
1880
1973
  return Object.assign({ ok: !!run.ok }, run, {
1881
1974
  hint: run.ok
1882
- ? '通过。若是某个对象的证明,请用 lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
1883
- : '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 lean_archive kind=\'blocked\' 记录原因。',
1975
+ ? '通过。若是某个对象的证明,请用 vibe_v5_lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
1976
+ : '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_v5_lean_archive kind=\'blocked\' 记录原因。',
1884
1977
  })
1885
1978
  }
1886
1979
  async function leanArchive(memberId, o) {
@@ -1927,7 +2020,12 @@ export function apply(ctx) {
1927
2020
  const rec = Object.assign({}, prev, {
1928
2021
  status: passed ? 'passed' : 'attempted',
1929
2022
  file: workRel,
1930
- 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' : '',
1931
2029
  decision: 'used',
1932
2030
  note: String(args.note || prev.note || ''),
1933
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) },
@@ -3399,15 +3497,22 @@ export function apply(ctx) {
3399
3497
  // (4b) Lean formalization signal — the mandatory difficulty judgement. This is the
3400
3498
  // path that matters in practice: a member that never calls a Lean tool still has to
3401
3499
  // say "used / blocked", and `require` mode refuses to conclude without it.
3402
- 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') {
3403
3505
  const f = p.formal
3404
3506
  const target = idSafe(String(f.target || ''))
3405
3507
  if (target) {
3406
3508
  const decision = String(f.decision || '').trim()
3407
- if (decision === 'blocked') {
3408
- const note = String(f.note || '').trim()
3409
- if (!note) await notice(member.id, 'formal.decision=\'blocked\' 必须写明 note(难度判断/阻塞原因)——本次未记录。')
3410
- else {
3509
+ const note = String(f.note || '').trim()
3510
+ if (decision === 'blocked' || decision === 'defect') {
3511
+ if (!note) await notice(member.id, "formal.decision='" + decision + "' 必须写明 note(难度判断/阻塞原因/具体偏差)——本次未记录。")
3512
+ else if (decision === 'defect') {
3513
+ const r = await recordFidelityDefect(member.id, target, note)
3514
+ if (r.ok === false) await notice(member.id, '记录忠实性缺陷失败(' + (r.code || '') + '):' + (r.message || ''))
3515
+ } else {
3411
3516
  const r = await leanArchive(member.id, { kind: 'blocked', target, note })
3412
3517
  if (r.ok === false) await notice(member.id, '记录形式化阻塞失败(' + (r.code || '') + '):' + (r.message || ''))
3413
3518
  }
@@ -3420,7 +3525,7 @@ export function apply(ctx) {
3420
3525
  }))
3421
3526
  await writeFormalIndex()
3422
3527
  } else if (decision) {
3423
- await notice(member.id, 'formal.decision 只能是 \'used\' 或 \'blocked\'(收到 ' + decision + ')')
3528
+ await notice(member.id, "formal.decision 只能是 'used' / 'blocked' / 'defect'(收到 " + decision + ')')
3424
3529
  }
3425
3530
  }
3426
3531
  }
@@ -4009,7 +4114,7 @@ export function apply(ctx) {
4009
4114
  publishProgress, recordCard, readLibrary,
4010
4115
  // Lean formal verification (docs/formal-verification.md)
4011
4116
  formalMode, formalOn, formalRecords, formalTodo, formalOf, rebuildLeanLibIndexes,
4012
- leanArchive, leanRunTool, writeFormalIndex, writeFormalTodo,
4117
+ leanArchive, leanRunTool, writeFormalIndex, writeFormalTodo, recordFidelityDefect,
4013
4118
  leanRunToolApi: async (relPath, timeoutMs) => await leanRunFile(relPath, timeoutMs),
4014
4119
  // consensus / meetings
4015
4120
  maybeQueueVerify, castVerdict, currentVerify, hasVerifyInFlight, startMeeting, quorumM, voterCount,
@@ -4153,7 +4258,7 @@ export function apply(ctx) {
4153
4258
  // dynamic registration would depend on a runtime knob and break the effect discipline),
4154
4259
  // while the MODE only decides whether the framework TELLS members about them. In 'off'
4155
4260
  // mode they still work if a human or agent calls them deliberately.
4156
- 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))
4157
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))
4158
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) => {
4159
4264
  const r = a && a.refresh === false ? { lib: null, proved: null, objects: Object.keys(s.formalRecords()).length } : await s.rebuildLeanLibIndexes()
@@ -4163,7 +4268,7 @@ export function apply(ctx) {
4163
4268
  counts: r, todo: s.formalTodo(),
4164
4269
  objects: Object.keys(s.formalRecords()).map((k) => ({ target: k, status: (s.formalRecords()[k] || {}).status, file: (s.formalRecords()[k] || {}).file, proof: (s.formalRecords()[k] || {}).proof, note: (s.formalRecords()[k] || {}).note })),
4165
4270
  paths: { project: 'Formal/(相对研究所根)', lib: 'VibeMath/Formal/Lib/', proved: 'VibeMath/Formal/Proved/', proofs: 'Verified/Lean/' },
4166
- hint: '复用优先:先在 Lib/ 里找现成定义;新定义用 lean_archive kind=\'def\' 归档,已证引理用 kind=\'lemma\'。',
4271
+ hint: '复用优先:先在 Lib/ 里找现成定义;新定义用 vibe_v5_lean_archive kind=\'def\' 归档,已证引理用 kind=\'lemma\'。',
4167
4272
  verify: st.verify ? st.verify.target : null,
4168
4273
  }
4169
4274
  })
@@ -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`——同样的提示词(语气为必须),**加门禁**:真/假结论必须
@@ -1113,10 +1118,48 @@ Progress/,不要从头再来",**不得**复用 `【入职首轮】` 与"你
1113
1118
  **门禁位置**:`continueVerifyRound` 里"判定为真/假 → `closeVerify`"这**唯一**收口点
1114
1119
  (`finalizeUndecided` 不受影响)。门禁不通过时调用 `deferForFormal`。
1115
1120
 
1116
- **测试**:`formal-verify-v5.test.mjs`(88 断言,fake-Lean 通过 `subprocess` 服务注入,
1117
- 因此不需要真的装 Lean)+ `audit-v5-sensitivity.mjs` 里 9 个专打形式化的探针;
1118
- `prompt-v5-integrity.test.mjs` 另有一组用例把 Lean 相关提示词原文写进语料
1119
- (`lean-work` / `lean-verify` / `lean-fidelity`),供人工复核"忠实性审查"那段的措辞。
1121
+ **忠实性缺陷(`decision='defect'`,契约 §4.1)**:`passed` 只保证"这段代码过了内核",
1122
+ **不保证它说的就是命题想说的**。首版提示词写的是"发现偏离 → 0"——那会让框架把
1123
+ "形式化写错了"记成"**命题为假**",在全 0 一致规则下直接把命题写进 `Verified/` 标注假,
1124
+ 等于用求真更严格的机制**伪造出一个错误的否定结论**。现在:
1125
+
1126
+ - 表决者发现偏差时**不得投 0**,要给严格介于 0 与 1 之间的值(记为弃权)并用回执
1127
+ `formal:{decision:'defect', note:'<具体偏差>'}` 记录;
1128
+ - `recordFidelityDefect` 把该对象**降级为 `attempted`**、清空 `proof`、撤回
1129
+ `Verified/Lean/<id>.lean`(`subprocess` 可删则删,否则用"已撤回"说明覆盖,见
1130
+ `removeArchivedProof`)、写入 `Formal/TODO.md` 并群聊公告;**撤回失败**(宿主既不能删也不能
1131
+ 覆盖)时必须在群聊公告里明说"归档证明未能撤回",不能让一个已被判定不忠实的形式化继续躺在
1132
+ `Verified/Lean/` 里而无人知晓;
1133
+ - `require` 档下这次裁定**不定论**,对象进「形式化待办」,修正形式化并重新跑通后再投票;
1134
+ `encourage` 档**没有门禁**,因此注入文本必须明说"本档没有门禁:请务必给一个严格介于 0 与 1
1135
+ 之间的弃权值,以保证本轮无法得出一致结论"——不能只把"框架会搁置本次裁定"这句删掉了事;
1136
+ - 重新 `lean_archive kind='proof'` 但**运行未通过**时,`proof` 指针必须一并清空:工作文件已经被
1137
+ 这次失败的代码覆盖,旧指针指向的文件不再是该对象的证明(否则 `Formal/Index.md` 会同时报告
1138
+ 「运行 fail」和一条归档证明路径);
1139
+ - 只有表决者**独立于这份 Lean 代码**也能确定命题为假时才投 0。
1140
+
1141
+ **提示词硬要求(契约 §6,四套一致)**:注入文本里的工具名**一律全称**
1142
+ (`vibe_v5_lean_*`;缩写 `lean_archive` 不是注册名,代理照抄就调用了一个不存在的工具);
1143
+ 归档可复用定义/引理**前先跑通**(`run=true` 或先 `lean_run`),跑不通不许入库;
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 根的绝对路径,语料因此不确定且泄露本机路径——已修)。
1120
1163
 
1121
1164
 
1122
1165
  ---
@@ -1161,7 +1204,7 @@ Progress/,不要从头再来",**不得**复用 `【入职首轮】` 与"你
1161
1204
 
1162
1205
  | 工具 | 参数 | 说明 |
1163
1206
  |---|---|---|
1164
- | `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` 就把这次运行记到该对象上 |
1165
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` 必填) |
1166
1209
  | `vibe_v5_lean_lib` | `{ refresh? }` | **重建并返回**三处索引(本所 `Formal/Index.md`、全局 `Lib/Index.md`、`Proved/Index.md`)与逐对象形式化状态;写新定义前先查重、直接复用 |
1167
1210
 
@@ -1190,6 +1233,13 @@ Progress/,不要从头再来",**不得**复用 `【入职首轮】` 与"你
1190
1233
 
1191
1234
  Lean 工具另外会返回**非错误码**的失败原因(都属于"可读结果"而非异常):`NO_SUBPROCESS`(宿主没有 subprocess 服务)、`LEAN_NOT_FOUND`(解析不到 `leanCommand`)、`LEAN_SPAWN_FAILED`、`LEAN_RUN_FAILED`、`LEAN_FAILED`(编译器非零退出)、`LEAN_TIMEOUT`。
1192
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
+
1193
1243
  ### 15.4 每轮回执契约(`replySpec`)
1194
1244
 
1195
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"/>