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.
- package/AUDIT-CHECKLIST.md +78 -3
- package/README.md +33 -2
- package/RELEASE-NOTES-2.3.1.md +134 -0
- package/RELEASE-NOTES-2.3.2.md +145 -0
- package/audit-formal-sensitivity.mjs +134 -39
- package/audit-prompt-invariants.mjs +414 -0
- package/audit-spec-traceability.mjs +173 -0
- package/audit-v5-integrity.mjs +5 -3
- package/docs/formal-verification.md +122 -19
- package/docs/generate_framework_diagram_v5.mjs +2 -1
- package/docs/test-timing.md +101 -0
- package/formal-verify-v2.test.mjs +526 -7
- package/formal-verify-v3.test.mjs +389 -10
- package/formal-verify-v4.test.mjs +462 -4
- package/formal-verify-v5.test.mjs +163 -4
- package/installer.js +3 -1
- package/package.json +12 -2
- package/prompt-corpus-persona/persona-corpus.json +2 -2
- package/prompt-corpus-persona/persona-corpus.md +6 -2
- package/prompt-corpus-v2/formal-verify-v2.json +484 -0
- package/prompt-corpus-v2/formal-verify-v2.md +5239 -0
- package/prompt-corpus-v3/formal-verify-v3.json +274 -100
- package/prompt-corpus-v3/formal-verify-v3.md +2057 -335
- package/prompt-corpus-v4/formal-verify-v4.json +89 -0
- package/prompt-corpus-v4/formal-verify-v4.md +283 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +186 -219
- package/prompt-corpus-v5/prompt-corpus-v5.md +485 -700
- package/prompt-v5-integrity.test.mjs +1272 -1085
- package/run-tests.mjs +118 -0
- package/vibe-math-v2/vibe-math-v2.js +341 -45
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +129 -8
- package/vibe-math-v3/vibe-math-v3.js +162 -36
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +21 -3
- package/vibe-math-v4/vibe-math-v4.js +201 -30
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +54 -2
- package/vibe-math-v5/agent.cordis.yml +6 -2
- package/vibe-math-v5/vibe-math-v5.js +133 -28
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +55 -5
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +16 -2
- 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
|
|
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:
|
|
583
|
-
code: ok ? undefined : (
|
|
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 空间,而它们指的是同一个东西:代理用 `
|
|
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
|
|
646
|
-
if (
|
|
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/(相对项目根);可复用定义放 ' +
|
|
675
|
-
|
|
676
|
-
L.push(' · **一旦 Lean
|
|
677
|
-
|
|
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 形式化,或**必须**给出显式的阻塞原因(
|
|
680
|
-
|
|
681
|
-
|
|
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\'),已成立的引理归到
|
|
692
|
-
+ '
|
|
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
|
-
? '通过。若是某个对象的证明,请用
|
|
775
|
-
: '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用
|
|
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
|
-
|
|
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' :
|
|
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),而代理用
|
|
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
|
|
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,
|
|
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() {
|
|
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)——这正是
|
|
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
|
-
|
|
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
|
-
|
|
1907
|
-
//
|
|
1908
|
-
//
|
|
1909
|
-
//
|
|
1910
|
-
//
|
|
1911
|
-
//
|
|
1912
|
-
|
|
1913
|
-
ap
|
|
1914
|
-
|
|
1915
|
-
else
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
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/ 里找现成定义;新定义用
|
|
2445
|
+
hint: '复用优先:先在 Lib/ 里找现成定义;新定义用 vibe_math_lean_archive kind=\'def\' 归档,已证引理用 kind=\'lemma\'(归档前先跑通,跑不通不要入库)。',
|
|
2150
2446
|
}
|
|
2151
2447
|
})
|
|
2152
2448
|
|