dsh-vibe-math 2.3.1 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUDIT-CHECKLIST.md +45 -0
- package/README.md +16 -6
- package/RELEASE-NOTES-2.3.2.md +145 -0
- package/audit-formal-sensitivity.mjs +11 -2
- package/audit-prompt-invariants.mjs +414 -0
- package/audit-spec-traceability.mjs +173 -0
- package/docs/formal-verification.md +33 -10
- package/docs/generate_framework_diagram_v5.mjs +2 -1
- package/docs/test-timing.md +32 -10
- package/formal-verify-v2.test.mjs +242 -2
- package/formal-verify-v3.test.mjs +176 -4
- package/formal-verify-v4.test.mjs +184 -5
- package/formal-verify-v5.test.mjs +91 -4
- package/installer.js +3 -1
- package/package.json +5 -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 +134 -44
- package/prompt-corpus-v2/formal-verify-v2.md +1033 -44
- package/prompt-corpus-v3/formal-verify-v3.json +200 -128
- package/prompt-corpus-v3/formal-verify-v3.md +948 -243
- package/prompt-corpus-v4/formal-verify-v4.json +8 -3
- package/prompt-corpus-v4/formal-verify-v4.md +38 -10
- package/prompt-corpus-v5/prompt-corpus-v5.json +175 -246
- package/prompt-corpus-v5/prompt-corpus-v5.md +341 -781
- package/prompt-v5-integrity.test.mjs +136 -22
- package/run-tests.mjs +30 -11
- package/vibe-math-v2/vibe-math-v2.js +149 -35
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +53 -5
- package/vibe-math-v3/vibe-math-v3.js +88 -23
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +7 -6
- package/vibe-math-v4/vibe-math-v4.js +103 -24
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +34 -11
- package/vibe-math-v5/agent.cordis.yml +6 -2
- package/vibe-math-v5/vibe-math-v5.js +56 -10
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +40 -13
- 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
|
@@ -298,7 +298,6 @@ export function apply(ctx) {
|
|
|
298
298
|
{ name: 'plannerEnabled', type: 'boolean', description: 'false = 完全走内置启发式调度(规划代理禁用)', suggestion: true },
|
|
299
299
|
{ name: 'plannerProvider', type: 'string', description: '规划代理模型 provider(空 = 继承根代理)', suggestion: '' },
|
|
300
300
|
{ name: 'plannerModel', type: 'string', description: '规划代理模型 id(空 = 继承根代理)', suggestion: '' },
|
|
301
|
-
{ name: 'plannerPersona', type: 'string', description: '注入规划代理提示词开头的人格/要求', suggestion: '' },
|
|
302
301
|
{ name: 'planMinIntervalMs', type: 'integer', description: '两次规划调用的最小间隔(毫秒);系统空闲且有工作时忽略', suggestion: 30000 },
|
|
303
302
|
{ name: 'plannerMaxFails', type: 'integer', description: '规划代理连续失败达此值 → 自动降级启发式', suggestion: 3 },
|
|
304
303
|
{ name: 'methodKeepIntervalMs', type: 'integer', description: 'Method Keeper 定时整理间隔(0 = 事件驱动)', suggestion: 0 },
|
|
@@ -1577,9 +1576,9 @@ export function apply(ctx) {
|
|
|
1577
1576
|
}
|
|
1578
1577
|
async function writeVerifiedPropositionCardIfNeeded(p) {
|
|
1579
1578
|
if (p.概率 !== 1 && p.概率 !== 0) return false
|
|
1580
|
-
//
|
|
1581
|
-
//
|
|
1582
|
-
|
|
1579
|
+
// 门禁只在**唯一的收口点**(writeVerifiedCardIfChanged)判定:那里的语义是"新卡要过门、
|
|
1580
|
+
// 已存在的卡只做刷新(可能要把被 defect 撤回的形式化状态如实改掉)"。在这里提前 return false
|
|
1581
|
+
// 会把两种情形一起挡掉,于是 require 档下一张已存在的卡片会永久宣称「形式化: Lean 通过」。
|
|
1583
1582
|
const proofs1 = (p.proofs || []).filter(function (x) { return x.prob === 1 })
|
|
1584
1583
|
const refutes1 = (p.refutes || []).filter(function (x) { return x.prob === 1 })
|
|
1585
1584
|
const parts = []
|
|
@@ -1590,7 +1589,7 @@ export function apply(ctx) {
|
|
|
1590
1589
|
}
|
|
1591
1590
|
async function writeVerifiedProblemCardIfNeeded(q) {
|
|
1592
1591
|
if (q.状态 !== '已解决') return false
|
|
1593
|
-
|
|
1592
|
+
// 同 writeVerifiedPropositionCardIfNeeded:门禁统一在 writeVerifiedCardIfChanged 里判定。
|
|
1594
1593
|
const sols1 = (q.solutions || []).filter(function (s) { return s.prob === 1 })
|
|
1595
1594
|
const parts = []
|
|
1596
1595
|
for (let i = 0; i < sols1.length; i++) parts.push('【解法 #' + (i + 1) + '】' + (sols1[i].text || ''))
|
|
@@ -1603,8 +1602,13 @@ export function apply(ctx) {
|
|
|
1603
1602
|
const existing = await readText(rel)
|
|
1604
1603
|
// 最后一道闸门:require 模式下没有 passed/blocked 记录就不允许**新写** Verified 卡片。
|
|
1605
1604
|
// 卡片已经存在(切到 require 之前就已定论)只做刷新,不算"新的定论",因此不记待办。
|
|
1606
|
-
|
|
1607
|
-
|
|
1605
|
+
//
|
|
1606
|
+
// 但"只做刷新"必须**真的刷新**:`defect` 会撤回形式化(降级 attempted、proof 清空、归档证明
|
|
1607
|
+
// 删除/覆盖),若在这里连刷新也一起 return false,那张已存在的卡片会永久宣称
|
|
1608
|
+
// 「形式化: Lean 通过(Verified/Lean/<id>.lean)」——指向一份已经不存在的证明。门禁管的是
|
|
1609
|
+
// "能不能宣告新结论",不是"能不能说实话"。所以:不存在 → 记待办并拒绝新写;已存在 → 照常刷新。
|
|
1610
|
+
if (formalBlocksConclusion(card.id) && existing === undefined) {
|
|
1611
|
+
await deferForFormal(card.id, formalRequiredWhy(card.id), card.结论 === true)
|
|
1608
1612
|
return false
|
|
1609
1613
|
}
|
|
1610
1614
|
const md = composeVerifiedMd(card)
|
|
@@ -2621,7 +2625,12 @@ export function apply(ctx) {
|
|
|
2621
2625
|
L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
|
|
2622
2626
|
L.push(' ① Result 给一个严格介于 0 与 1 之间的值(记为弃权),并在 Reason 里写清偏差;')
|
|
2623
2627
|
L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
|
|
2624
|
-
|
|
2628
|
+
// §4.1 第 3 条:只有 `require` 真的有门禁。`encourage` 档框架**仍然**撤回证明并记入待办,
|
|
2629
|
+
// 但**不得**承诺一个它无法强制的"不定论"——那里靠表决者自己的弃权票使表决无法得出一致结论。
|
|
2630
|
+
L.push(' 「已通过」状态(降级为 attempted、删除或就地覆盖归档证明、写入形式化待办)'
|
|
2631
|
+
+ (mode === 'require'
|
|
2632
|
+
? ',本次裁定**不定论**;'
|
|
2633
|
+
: '。**本档没有门禁**:请务必给弃权值,以保证本轮无法得出一致结论;'))
|
|
2625
2634
|
L.push(' 修正形式化并重新跑通后再投票。')
|
|
2626
2635
|
L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 Reason 里写清独立理由。')
|
|
2627
2636
|
} else if (rec.status === 'blocked') {
|
|
@@ -2633,13 +2642,15 @@ export function apply(ctx) {
|
|
|
2633
2642
|
L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' + (vibeRoot() + '/Formal/Lib/').replace(/\\/g, '/')
|
|
2634
2643
|
+ ',已证引理放 ' + (vibeRoot() + '/Formal/Proved/').replace(/\\/g, '/') + ';写之前先 vibe_math_lean_lib 查重。')
|
|
2635
2644
|
L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。')
|
|
2645
|
+
// 把"这么做的收益"说出来:本轮通过,下一轮的审查对象就整体换掉了(不是再加一道苦役)。
|
|
2646
|
+
L.push(' ▸ 若你在本轮把它形式化并跑通(vibe_math_lean_archive kind=\'proof\'),后续轮次的审查对象就会从"推导是否正确"变成"Lean 代码是否忠实于命题"。')
|
|
2636
2647
|
if (mode === 'require') {
|
|
2637
2648
|
L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_math_lean_archive kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,会被记为未定论(原因 formal-required)并进入「形式化待办」。')
|
|
2638
2649
|
} else {
|
|
2639
2650
|
L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision=\'blocked\' 时必须写明 note)。')
|
|
2640
2651
|
}
|
|
2641
2652
|
L.push(' · 归档可复用定义/引理前先跑通(vibe_math_lean_archive run=true 或先 vibe_math_lean_run);跑不通不要入库。')
|
|
2642
|
-
L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
|
|
2653
|
+
L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)或宿主不提供 subprocess 服务(NO_SUBPROCESS)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
|
|
2643
2654
|
}
|
|
2644
2655
|
return L.join('\n')
|
|
2645
2656
|
}
|
|
@@ -2762,7 +2773,10 @@ export function apply(ctx) {
|
|
|
2762
2773
|
}
|
|
2763
2774
|
L.push('')
|
|
2764
2775
|
if (formalTodo().length) {
|
|
2765
|
-
|
|
2776
|
+
// §4.1 第 3 条:只有 `require` 真的搁置定论;`encourage` 档的待办只是"这份形式化要重做"的记录。
|
|
2777
|
+
L.push(formalMode() === 'require'
|
|
2778
|
+
? '## 形式化待办(require 模式:定论被搁置)'
|
|
2779
|
+
: '## 形式化待办(encourage 档:框架不搁置定论,靠表决者弃权)')
|
|
2766
2780
|
for (const t of formalTodo()) L.push('- ' + t.id + ' —— ' + (t.why || 'formal-required') + '(' + fmtTime(t.at) + ')')
|
|
2767
2781
|
L.push('')
|
|
2768
2782
|
}
|
|
@@ -2771,7 +2785,10 @@ export function apply(ctx) {
|
|
|
2771
2785
|
async function writeFormalTodo() {
|
|
2772
2786
|
const list = formalTodo()
|
|
2773
2787
|
const L = ['# 形式化待办|' + currentProject + '|' + fmtTime(), '',
|
|
2774
|
-
|
|
2788
|
+
// 说清这一档**实际**会发生什么:只有 require 有门禁会把定论记为未定论。
|
|
2789
|
+
formalMode() === 'require'
|
|
2790
|
+
? '> 这些对象在 `require` 模式下尚不具备「Lean 已通过」或「显式阻塞记录」,因此**定论被搁置**。'
|
|
2791
|
+
: '> 这些对象尚未取得「Lean 已通过」或「显式阻塞记录」。本档(encourage)**没有定论门禁**,框架不会搁置裁定——请在投票时给出严格介于 0 与 1 之间的弃权值,并尽快修正形式化。',
|
|
2775
2792
|
'> 完成形式化(vibe_math_lean_archive kind=\'proof\')或记录阻塞原因(kind=\'blocked\')后,重新提议验证即可。', '']
|
|
2776
2793
|
if (!list.length) L.push('(暂无)')
|
|
2777
2794
|
for (const t of list) L.push('- ' + t.id + '|' + (t.why || 'formal-required') + '|' + fmtTime(t.at))
|
|
@@ -2868,10 +2885,16 @@ export function apply(ctx) {
|
|
|
2868
2885
|
await writeFormalIndex()
|
|
2869
2886
|
}
|
|
2870
2887
|
if (run.ok) await formalAnnounce('【形式化】' + (memberId || 'scheduler') + ' 运行 Lean 通过:' + run.file + '(' + ((run.ms || 0) / 1000).toFixed(1) + 's)' + (target ? '|对象 ' + target : ''))
|
|
2888
|
+
// 失败提示必须与失败原因一致:工具链缺失 / 宿主没有 subprocess 服务时**没有任何编译器输出**
|
|
2889
|
+
// 可以"按它修复",把它当成普通编译错误会让代理反复重试而不是走"写下代码 + 记录显式阻塞"
|
|
2890
|
+
// 这条出路(契约 §6 硬要求 4)。
|
|
2891
|
+
const noHost = run.code === 'LEAN_NOT_FOUND' || run.code === 'NO_SUBPROCESS' || run.code === 'LEAN_SPAWN_FAILED'
|
|
2871
2892
|
return Object.assign({ ok: !!run.ok }, run, {
|
|
2872
2893
|
hint: run.ok
|
|
2873
2894
|
? '通过。若是某个对象的证明,请用 vibe_math_lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库。'
|
|
2874
|
-
:
|
|
2895
|
+
: (noHost
|
|
2896
|
+
? '本宿主无法执行 Lean(' + run.code + '),没有编译器输出可以修:把形式化代码写下来并用 vibe_math_lean_archive 归档,并在回执的 note 里写明原因——这算显式阻塞原因,定论门禁可以据此放行。'
|
|
2897
|
+
: '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_math_lean_archive kind=\'blocked\' 记录原因。'),
|
|
2875
2898
|
})
|
|
2876
2899
|
}
|
|
2877
2900
|
async function leanArchive(memberId, o) {
|
|
@@ -2914,20 +2937,28 @@ export function apply(ctx) {
|
|
|
2914
2937
|
if (body === undefined && from) { const r = await readFrom(); if (r.err) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: r.err }; body = r.body }
|
|
2915
2938
|
if (body === undefined) return { ok: false, code: 'V3_INVALID_ARGUMENT', message: 'provide content, or from=<existing .lean file>' }
|
|
2916
2939
|
const workRel = 'Formal/' + target + '.lean'
|
|
2917
|
-
|
|
2940
|
+
// 用绝对路径写:`writeText` 写失败时只会抛(由工具包装层变成通用 error),
|
|
2941
|
+
// 这一行的 V3_WRITE_FAILED 分支就永远不可能触发;writeTextAbs 会如实返回 false。
|
|
2942
|
+
if (!await writeTextAbs(frameworkRoot() + '/' + workRel, body)) return { ok: false, code: 'V3_WRITE_FAILED', message: 'could not write ' + workRel }
|
|
2918
2943
|
const run = await leanRunFile(workRel)
|
|
2919
2944
|
const prev = formalOf(target)
|
|
2920
2945
|
const passed = !!run.ok
|
|
2946
|
+
// A RED re-archive invalidates the previous proof: the work file it proved was just
|
|
2947
|
+
// overwritten by code that does not compile. Keeping the pointer (or the archived file)
|
|
2948
|
+
// would produce "attempted + 归档证明 X.lean" in the index and let the fidelity prompt print
|
|
2949
|
+
// a proof path for code that no longer exists — `proof` is for `passed` only (contract §4).
|
|
2950
|
+
const stalePrev = passed ? '' : String(prev.proof || ('Verified/Lean/' + target + '.lean'))
|
|
2921
2951
|
const rec = Object.assign({}, prev, {
|
|
2922
2952
|
status: passed ? 'passed' : 'attempted',
|
|
2923
2953
|
file: workRel,
|
|
2924
|
-
proof: passed ? 'Verified/Lean/' + target + '.lean' :
|
|
2954
|
+
proof: passed ? 'Verified/Lean/' + target + '.lean' : '',
|
|
2925
2955
|
decision: 'used',
|
|
2926
2956
|
note: String(args.note || prev.note || ''),
|
|
2927
2957
|
run: { at: now(), ok: !!run.ok, exitCode: run.exitCode === undefined ? null : run.exitCode, ms: run.ms || 0, stdoutTail: formalTail(run.stdout, 800), stderrTail: formalTail(run.stderr, 800) },
|
|
2928
2958
|
updatedAt: now(),
|
|
2929
2959
|
})
|
|
2930
2960
|
if (passed) await writeText('Verified/Lean/' + target + '.lean', body)
|
|
2961
|
+
else if (stalePrev) { try { await withdrawArchivedProof(stalePrev) } catch (e) { /* 撤回失败已在公告里如实说明 */ } }
|
|
2931
2962
|
await putFormal(target, rec)
|
|
2932
2963
|
await upsertFormalAnchor(target)
|
|
2933
2964
|
await rebuildLeanLibIndexes()
|
|
@@ -2951,15 +2982,38 @@ export function apply(ctx) {
|
|
|
2951
2982
|
}
|
|
2952
2983
|
return { ok: false, code: 'V3_INVALID_ARGUMENT', message: "kind must be 'def' | 'lemma' | 'proof' | 'blocked'" }
|
|
2953
2984
|
}
|
|
2985
|
+
/**
|
|
2986
|
+
* 撤回归档证明:**删除**既不是唯一手段,也不是想当然就能成功的手段。
|
|
2987
|
+
*
|
|
2988
|
+
* fs 服务没有 unlink,`subprocess` 服务是**可选**的(`runShell` 在没有它时只返回 no-subprocess),
|
|
2989
|
+
* 删除命令本身也可能静默失败(桩宿主、权限、宿主不提供 shell)。而归档证明就躺在
|
|
2990
|
+
* `Verified/Lean/<id>.lean`——所有人都去那个路径找"这条结论的证明"——所以"删掉了"必须被
|
|
2991
|
+
* **回读验证**:删不掉就用撤回声明**就地覆盖**,使它不可能再被读成一份通过的证明。
|
|
2992
|
+
* 返回:'deleted'(确认已不在)| 'overwritten'(已覆盖为撤回声明)| 'failed'(两者都没成功)。
|
|
2993
|
+
*/
|
|
2994
|
+
async function withdrawArchivedProof(rel) {
|
|
2995
|
+
const abs = leanAbsPath(rel)
|
|
2996
|
+
if (abs === null) return 'failed'
|
|
2997
|
+
const sub = subprocessOf()
|
|
2998
|
+
if (sub !== undefined && typeof sub.spawn === 'function') {
|
|
2999
|
+
try { await removeFile(rel) } catch (e) { /* 落到覆盖兜底 */ }
|
|
3000
|
+
// 退出码 0 不等于文件真的没了(桩宿主 / 权限怪癖 / 删除被静默忽略):必须回读确认。
|
|
3001
|
+
if (await readTextAbs(abs) === undefined) return 'deleted'
|
|
3002
|
+
}
|
|
3003
|
+
const notice = '-- 已撤回(' + fmtTime() + '):该形式化被认定与命题原文不一致。\n'
|
|
3004
|
+
+ '-- 原代码保留在工作文件 Formal/' + String(rel).split('/').pop() + ';修正并重新跑通后重新归档。\n'
|
|
3005
|
+
return (await writeTextAbs(abs, notice)) !== false ? 'overwritten' : 'failed'
|
|
3006
|
+
}
|
|
2954
3007
|
/**
|
|
2955
3008
|
* §4.1 的落地点:表决者认定这条**已通过的** Lean 形式化不忠实于命题原文。
|
|
2956
3009
|
*
|
|
2957
3010
|
* 偏差不是"命题为假",而恰好是"这次形式化不合格",因此这里的动作与 blocked 不同:
|
|
2958
3011
|
* ① 无论此前是 `passed` 还是 `blocked`,一律**降级**为 `attempted`(都让位于"需重做");
|
|
2959
|
-
* ② 清空 `proof`
|
|
3012
|
+
* ② 清空 `proof` 并**撤下** `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean` 保留,代码不丢);
|
|
2960
3013
|
* ③ 把具体偏差写进记录与 `Formal/TODO.md`,并公告。
|
|
2961
3014
|
* 之后 `formalGateOk` 为假:require 档本次裁定**不定论**,对象进入「形式化待办」——修正形式化
|
|
2962
|
-
*
|
|
3015
|
+
* 并重新跑通后再投票。encourage 档没有门禁,公告里**不得**声称框架搁置了裁定(§4.1 第 3 条)。
|
|
3016
|
+
* 绝不把这条路径写成 `0`(那会让框架记下"命题为假")。
|
|
2963
3017
|
*/
|
|
2964
3018
|
async function formalRecordDefect(target, note, who) {
|
|
2965
3019
|
const id = formalId(target)
|
|
@@ -2974,7 +3028,7 @@ export function apply(ctx) {
|
|
|
2974
3028
|
updatedAt: now(),
|
|
2975
3029
|
}))
|
|
2976
3030
|
// 归档证明必须消失,否则 Verified/Lean/ 里会留下一份"看起来已通过"的不忠实代码。
|
|
2977
|
-
await
|
|
3031
|
+
const withdrawn = await withdrawArchivedProof(archived)
|
|
2978
3032
|
// 待办条目按 id 去重、就地刷新:note 进入 TODO.md 的 why 列(require 档据此搁置定论)。
|
|
2979
3033
|
const list = formalTodo()
|
|
2980
3034
|
const i = list.findIndex(function (x) { return x && x.id === id })
|
|
@@ -2985,10 +3039,17 @@ export function apply(ctx) {
|
|
|
2985
3039
|
await upsertFormalAnchor(id)
|
|
2986
3040
|
await rebuildLeanLibIndexes()
|
|
2987
3041
|
await saveAll()
|
|
3042
|
+
// 如实说出**哪一种**撤回发生了:删除成功 / 就地覆盖 / 两者都失败(后者必须让人手动处理,
|
|
3043
|
+
// 否则一份不忠实的代码会静静留在"证明"的路径上而无人知晓)。
|
|
2988
3044
|
await formalAnnounce('【形式化】' + who + ' 认定 ' + id + ' 的 Lean 形式化存在**忠实性缺陷**:' + note
|
|
2989
3045
|
+ '。这不是"命题为假",而是**形式化不合格**:已撤回其「已通过」状态(降级为 attempted)、'
|
|
2990
|
-
+ '删除归档证明 ' + archived
|
|
2991
|
-
|
|
3046
|
+
+ (withdrawn === 'deleted' ? '删除归档证明 ' + archived
|
|
3047
|
+
: withdrawn === 'overwritten' ? '归档证明 ' + archived + ' 无法删除(宿主不支持删除),已**就地覆盖为撤回声明**'
|
|
3048
|
+
: '归档证明 ' + archived + ' **未能撤回**(宿主删除与覆盖均失败,请手动删除,不要把它当作该对象的证明)')
|
|
3049
|
+
+ '、写入 Formal/TODO.md;'
|
|
3050
|
+
+ (formalMode() === 'require'
|
|
3051
|
+
? '本次裁定**不定论**,修正形式化并重新跑通(vibe_math_lean_archive kind=\'proof\')后再投票。'
|
|
3052
|
+
: '**本档没有门禁**:框架不会替你搁置裁定,请给弃权值以避免得出真/假一致结论;修正形式化并重新跑通(vibe_math_lean_archive kind=\'proof\')后再投票。'))
|
|
2992
3053
|
return { ok: true, kind: 'defect', target: id, status: 'attempted', proof: '', note: note, archived: archived }
|
|
2993
3054
|
}
|
|
2994
3055
|
/**
|
|
@@ -3391,7 +3452,7 @@ export function apply(ctx) {
|
|
|
3391
3452
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
|
|
3392
3453
|
registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json + Logs/报告.md.', objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return await buildReport() })
|
|
3393
3454
|
registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); await saveSettings(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
|
|
3394
|
-
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, plannerPersona: { type: 'string' }, methodKeeperPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, planningHorizon: { type: 'integer' }, plannerEnabled: { type: 'boolean' }, plannerProvider: { type: 'string' }, plannerModel: { type: 'string' }, planMinIntervalMs: { type: 'integer' }, plannerMaxFails: { type: 'integer' }, methodKeepIntervalMs: { type: 'integer' }, methodKeepEvery: { type: 'integer' }, methodAutoPromote: { type: 'boolean' }, indexAutoRebuild: { type: 'boolean' }, projectLockTimeoutMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); return { ok: true, params: params } })
|
|
3455
|
+
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象未达到 Lean 已通过或已记录显式阻塞原因之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式。', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, plannerPersona: { type: 'string' }, methodKeeperPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, planningHorizon: { type: 'integer' }, plannerEnabled: { type: 'boolean' }, plannerProvider: { type: 'string' }, plannerModel: { type: 'string' }, planMinIntervalMs: { type: 'integer' }, plannerMaxFails: { type: 'integer' }, methodKeepIntervalMs: { type: 'integer' }, methodKeepEvery: { type: 'integer' }, methodAutoPromote: { type: 'boolean' }, indexAutoRebuild: { type: 'boolean' }, projectLockTimeoutMs: { type: 'integer' }, formalVerify: { type: 'string', enum: ['off', 'encourage', 'require'] }, leanCommand: { type: 'string' }, leanArgs: { type: 'array', items: { type: 'string' } }, leanTimeoutMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); return { ok: true, params: params } })
|
|
3395
3456
|
registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), async function () { await refreshParams(); const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } })
|
|
3396
3457
|
registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), async function () { return await saveSettings() })
|
|
3397
3458
|
registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), async function (args) { return await createTemplate((args && args.where) || 'global') })
|
|
@@ -3631,8 +3692,12 @@ export function apply(ctx) {
|
|
|
3631
3692
|
// ================= session surface =================
|
|
3632
3693
|
return {
|
|
3633
3694
|
sessionId: sessionId,
|
|
3634
|
-
scheduler
|
|
3635
|
-
tickInFlight
|
|
3695
|
+
// 这两个必须是**取值器**而不是快照:`scheduler` 会在 loadState/setProject 里被整体重新赋值,
|
|
3696
|
+
// 而 `tickInFlight` 每次 tick 都会翻转。快照会让 apply 级的定时器守卫(见文件末尾
|
|
3697
|
+
// `!s.tickInFlight && s.scheduler.gate === null`)永远读到最初的值——那个守卫就再也拦不住
|
|
3698
|
+
// 任何东西(tick() 内部还有一道实时守卫,所以此前没有可观测后果,但那是巧合而非设计)。
|
|
3699
|
+
get scheduler() { return scheduler },
|
|
3700
|
+
get tickInFlight() { return tickInFlight },
|
|
3636
3701
|
scheduleTick: scheduleTick,
|
|
3637
3702
|
onChildEnd: onChildEnd,
|
|
3638
3703
|
dispatchVibeCommand: dispatchVibeCommand,
|
|
@@ -3666,7 +3731,7 @@ export function apply(ctx) {
|
|
|
3666
3731
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
|
|
3667
3732
|
registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json + Logs/报告.md.', objParams({}), 'vibe_math_report')
|
|
3668
3733
|
registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), 'vibe_math_set_mode')
|
|
3669
|
-
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, plannerPersona: { type: 'string' }, methodKeeperPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, planningHorizon: { type: 'integer' }, plannerEnabled: { type: 'boolean' }, plannerProvider: { type: 'string' }, plannerModel: { type: 'string' }, planMinIntervalMs: { type: 'integer' }, plannerMaxFails: { type: 'integer' }, methodKeepIntervalMs: { type: 'integer' }, methodKeepEvery: { type: 'integer' }, methodAutoPromote: { type: 'boolean' }, indexAutoRebuild: { type: 'boolean' }, projectLockTimeoutMs: { type: 'integer' } }), 'vibe_math_set_params')
|
|
3734
|
+
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象未达到 Lean 已通过或已记录显式阻塞原因之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式。', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, plannerPersona: { type: 'string' }, methodKeeperPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, planningHorizon: { type: 'integer' }, plannerEnabled: { type: 'boolean' }, plannerProvider: { type: 'string' }, plannerModel: { type: 'string' }, planMinIntervalMs: { type: 'integer' }, plannerMaxFails: { type: 'integer' }, methodKeepIntervalMs: { type: 'integer' }, methodKeepEvery: { type: 'integer' }, methodAutoPromote: { type: 'boolean' }, indexAutoRebuild: { type: 'boolean' }, projectLockTimeoutMs: { type: 'integer' }, formalVerify: { type: 'string', enum: ['off', 'encourage', 'require'] }, leanCommand: { type: 'string' }, leanArgs: { type: 'array', items: { type: 'string' } }, leanTimeoutMs: { type: 'integer' } }), 'vibe_math_set_params')
|
|
3670
3735
|
registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
|
|
3671
3736
|
registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), 'vibe_math_save_settings')
|
|
3672
3737
|
registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), 'vibe_math_template')
|
|
@@ -498,7 +498,7 @@ VibeMath/ # 工作区级
|
|
|
498
498
|
| `vibe_math_decide` | 扩展支持 `node: 'plan'`(计划审批)与 `node: 'method-promote'` |
|
|
499
499
|
| `vibe_math_lean_run` | `{file, target?, timeout_ms?}`:在一个 `.lean` 文件上执行 Lean 工具链并**如实**回报 `{ok, exitCode, signal, ms, command, stdout, stderr, file}`;给了 `target` 就把这次运行记到该对象上。**绝不抛异常到调度循环**:宿主没有 subprocess 服务 → `NO_SUBPROCESS`,工具链解析失败 → `LEAN_NOT_FOUND`,超时 → `LEAN_TIMEOUT`(并调用 `handle.terminate()`) |
|
|
500
500
|
| `vibe_math_lean_archive` | `{kind, ...}` 一个工具覆盖四种归档:`kind='def'`/`'lemma'` → 全局 `<VibeMath 根>/Formal/{Lib,Proved}/<name>.lean` 并重建对应索引(`content` 或 `from` 二选一;`run:false` 跳过执行);`kind='proof'` → 写 `Formal/<target>.lean`,该文件最近一次运行 ok 时**同时**写 `Verified/Lean/<target>.lean` 并把对象标为 `passed`;`kind='blocked'` → 记录显式难度判断/阻塞原因(`note` 必填,空则拒绝) |
|
|
501
|
-
| `vibe_math_lean_lib` | `{refresh?}`:扫描并重建三处索引(项目 `Formal/Index.md`、全局 `Lib/Index.md`、`Proved/Index.md`),返回可复用库清单(供写新定义前查重、直接复用);`refresh:false`
|
|
501
|
+
| `vibe_math_lean_lib` | `{refresh?}`:扫描并重建三处索引(项目 `Formal/Index.md`、全局 `Lib/Index.md`、`Proved/Index.md`),返回可复用库清单(供写新定义前查重、直接复用);`refresh:false` 只读不重建。另有 `objects`(每个对象的形式化状态)与 `verify`(当前在飞验证任务的标识,无则 `null`)两个字段,与 v5 的同名输出一致 |
|
|
502
502
|
|
|
503
503
|
### 11.1 Lean 形式化验证(v3 实现要点)
|
|
504
504
|
|
|
@@ -534,8 +534,8 @@ VibeMath/ # 工作区级
|
|
|
534
534
|
|
|
535
535
|
**门禁落点**(契约 §8 要求唯一收口点,不散落):
|
|
536
536
|
- 判定入口:`settleVerdict`(辩论/评审裁定,在**改对象之前**拦下)与 `processStatusUpdates`(自报概率 1 的第二条"宣告定论"入口);
|
|
537
|
-
- 写卡收口点:`writeVerifiedPropositionCardIfNeeded` / `writeVerifiedProblemCardIfNeeded` → `writeVerifiedCardIfChanged
|
|
538
|
-
- 不通过时统一走 `deferForFormal`:记未定论(`formal-required`)+ 写 `Formal/TODO.md` + 公告(活动日志 + `Logs/形式化.md`),**不改变对象的既有权重/概率字段**,绝不卡死调度——对象留在原库,完成形式化或记录阻塞后重新提议验证即可;达到 `passed`/`blocked` 时自动把该对象从待办中移除(待办的含义就是"还不满足门禁"
|
|
537
|
+
- 写卡收口点:`writeVerifiedPropositionCardIfNeeded` / `writeVerifiedProblemCardIfNeeded` → `writeVerifiedCardIfChanged`。门禁**只在这唯一的收口点**判定:卡片**不存在**时 require 模式下没有 `passed`/`blocked` 就**不写**(并记待办);卡片**已存在**时只做刷新——包括把被 `defect` 撤回的形式化状态如实改成 `已尝试未通过`(否则一张定论卡片会永久宣称「形式化: Lean 通过(Verified/Lean/<id>.lean)」而那份证明已被撤回。门禁管的是"能不能宣告新结论",不是"能不能说实话")。
|
|
538
|
+
- 不通过时统一走 `deferForFormal`:记未定论(`formal-required`)+ 写 `Formal/TODO.md` + 公告(活动日志 + `Logs/形式化.md`),**不改变对象的既有权重/概率字段**,绝不卡死调度——对象留在原库,完成形式化或记录阻塞后重新提议验证即可;达到 `passed`/`blocked` 时自动把该对象从待办中移除(待办的含义就是"还不满足门禁")。`Formal/TODO.md` 与 `Formal/Index.md` 的待办段按**当前档位**措辞:`require` 说"定论被搁置",`encourage` 说明本档没有门禁、框架不会搁置裁定(靠表决者弃权),不承诺它无法强制的行为(契约 §4.1 第 3 条)。
|
|
539
539
|
- **防空转**:已经被记为「形式化待办」而形式化又没补齐的对象,`createVerifyTask` 不再为它建验证任务(否则同一个 `rId` 每个 tick 都会被重建、每轮再派一批验证器,而结果每轮都被同一道门拦下——纯烧验证预算)。一旦 `passed`/`blocked` 落库,门禁条件满足,候选自然重新出现并继续验证,无需人工干预。回退启发式在跳过这类候选时**不占用本轮名额**(`created` 才 `return`),否则排在待办对象后面的候选会被永久饿死。
|
|
540
540
|
|
|
541
541
|
**错误码**(一律作为结果返回,不抛进调度循环):
|
|
@@ -558,8 +558,9 @@ VibeMath/ # 工作区级
|
|
|
558
558
|
**`defect` 是独立的一档,不是"命题为假"**(契约 §4.1)。`passed` 只保证"这段 Lean 代码通过了内核检查",**不保证它说的就是命题想说的**。表决者逐条核对后发现写窄了 / 写宽了 / 换了对象 / 漏了条件时,那是**形式化不合格**:若把它记成 0,框架记下的会是"该命题为假"——用来求真更严格的机制,反而伪造出一个错误的否定结论;若只记成 `blocked`,门禁又会**放行**(`blocked` 本就允许定论),等于带着一个坏形式化去定论。
|
|
559
559
|
|
|
560
560
|
- **回执**:`formal:{target, decision:'defect', note:'<具体偏差>'}`。`note` **必填**,为空则整条记录被**拒绝**(返回 `V3_INVALID_ARGUMENT`),形式化记录与归档证明**保持不变**。顶层 `formal` 与 `meta.formal` 两种写法都接受:验证回执走 `handleVerifier`,solver / explorer / 重派生等工作轮回执走 `absorbFormalFromReply`。
|
|
561
|
-
- **落库**(`formalRecordDefect`,契约 §4.1):无论此前是 `passed` 还是 `blocked`,一律**降级**为 `attempted`(都让位于"形式化不合格,需重做");清空 `proof
|
|
562
|
-
-
|
|
561
|
+
- **落库**(`formalRecordDefect`,契约 §4.1):无论此前是 `passed` 还是 `blocked`,一律**降级**为 `attempted`(都让位于"形式化不合格,需重做");清空 `proof`;**撤下**归档证明 `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean` 保留,代码不丢);`note` 写入形式化记录与 `Formal/TODO.md`(按 id 去重、就地刷新);对象卡锚点刷新为 `已尝试未通过`;公告到活动日志与 `Logs/形式化.md`。
|
|
562
|
+
- **撤回归档证明不是"尽力删除"**(`withdrawArchivedProof`):`fs` 服务没有 unlink,`subprocess` 又是可选服务,删除命令也可能静默失败(桩宿主 / 权限 / 宿主不提供 shell),而 `Verified/Lean/<id>.lean` 正是所有人找"这条结论的证明"的路径。因此删除后**必须回读确认**;确认不了就用撤回声明**就地覆盖**该文件(`-- 已撤回(<时间>):该形式化被认定与命题原文不一致。` + 指向保留的工作文件),使它不可能再被读成一份通过的证明;两种兜底都失败时公告里明确写出"**未能撤回**,请手动删除"。公告如实说明发生的是**哪一种**(删除 / 就地覆盖 / 失败)。
|
|
563
|
+
- **`require` 档**:降级后 `formalGateOk` 为假,本次裁定**不定论**——不改对象的既有权重/概率字段、不写 `Verified/` 卡片,对象进入「形式化待办」;修正形式化并重新跑通(`vibe_math_lean_archive kind='proof'`)后再投票。**`encourage` 档没有门禁**:框架仍然撤回证明并写入待办,但注入文本与公告都**不得**声称框架搁置了裁定(那是它无法强制的行为,契约 §4.1 第 3 条)——只有表决者自己的弃权值能阻止一致性结论。
|
|
563
564
|
- **唯一可以投 0 的情形**:表决者**独立于这份 Lean 代码**也能确定命题为假,且 `Reason` 写清独立理由。
|
|
564
565
|
|
|
565
566
|
**提示词硬要求**(契约 §6 顶部的五条,v3 全部落地,并由套件对**每条捕获的提示词**做正则扫描,而不是只看一两处):
|
|
@@ -567,7 +568,7 @@ VibeMath/ # 工作区级
|
|
|
567
568
|
1. **工具名一律全称**:注入文本(含工具自己返回的 `hint` 字段)里只允许 `vibe_math_lean_run` / `vibe_math_lean_archive` / `vibe_math_lean_lib`。`lean_run` / `lean_archive` / `lean_lib` 这类缩写**不是注册名**,代理照抄会去调用一个不存在的工具;套件用 `/(^|[^a-z_])lean_(run|archive|lib)/` 扫描全部语料,命中即红。
|
|
568
569
|
2. **回执字段名必须与真实契约一致**:v3 的表决值字段是 **`Result`**(v4/v5 才是 `verdict`)。写错字段名 = 那一票被静默丢弃(退回默认 0.5)。忠实性分支因此写的是 `一致 → Result = 1`;套件断言每条表决提示词含 `Result` 且不含 `verdict`。
|
|
569
570
|
3. **归档可复用定义/引理前必须先跑通**:`vibe_math_lean_archive` 支持 `run:true`,或先 `vibe_math_lean_run`;**跑不通的代码不得进入 `Formal/Lib` / `Formal/Proved`**,否则"可复用库"会被不编译的定义污染。验证提示词与「顺手形式化」一行都写明了这条纪律(`归档前先跑通(vibe_math_lean_run 或 run=true);跑不通的定义不要进可复用库。`)。
|
|
570
|
-
4. **工具链缺失时的出路必须写出来**:`LEAN_NOT_FOUND` 时把代码写下来归档,并在回执的 `note` 里写明"宿主无 Lean 工具链"——这算**显式阻塞原因**,`require` 档可以据此放行,代理不会因为装不了 Lean 而卡死。
|
|
571
|
+
4. **工具链缺失时的出路必须写出来**:`LEAN_NOT_FOUND` 时把代码写下来归档,并在回执的 `note` 里写明"宿主无 Lean 工具链"——这算**显式阻塞原因**,`require` 档可以据此放行,代理不会因为装不了 Lean 而卡死。v3 的注入文本**同时点名 `NO_SUBPROCESS`**(宿主不提供 subprocess 服务、因此根本无法执行 Lean 的另一种失败形态),否则代理不认识这个码,会把它当成未知故障反复重试而不是记录阻塞。`vibe_math_lean_run` 返回的失败 `hint` 也按错误码区分:工具链缺失 / 无 subprocess / spawn 失败时给出"没有编译器输出可修,写下代码并归档 + 记录阻塞原因",只有真正的编译失败才说"按上面的编译器输出修复后重跑"。
|
|
571
572
|
5. **忠实性缺陷不得用 0 表达**:只有独立于 Lean 代码也能确定命题为假时才投 0(§4.1 第 4 条)。套件断言注入文本**不含** `偏离 → 0` 这类"偏差即假"的指令,且含"不要投 0 / 记为形式化不合格 / 走待办"的要求。
|
|
572
573
|
|
|
573
574
|
---
|
|
@@ -65,7 +65,7 @@ export function apply(ctx) {
|
|
|
65
65
|
// is persisted through v4's OWN durable State/*.json mechanism (State/formal.json) and must
|
|
66
66
|
// survive `resume` — otherwise a require-mode object would lose the very record that decides
|
|
67
67
|
// whether its verdict may take effect.
|
|
68
|
-
let formal = {}, formalTodos = []
|
|
68
|
+
let formal = {}, formalTodos = [], formalPersisted = false
|
|
69
69
|
let meetingState = null, verifyState = null, pendingVerify = [], pendingMeeting = null // pendingVerify: FIFO queue (several residents may independently propose different objects before any verify runs — a single slot silently DROPPED all but the last proposal)
|
|
70
70
|
let busy = new Set(), wakeKind = new Map(), currentResident = ''
|
|
71
71
|
let finalizeLock = null // 'meeting'|'verify' while a consensus finalize is running (reentry guard)
|
|
@@ -294,7 +294,13 @@ export function apply(ctx) {
|
|
|
294
294
|
await saveFormal()
|
|
295
295
|
return true
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
// Once formal state has been persisted (or restored from disk) the file must KEEP being
|
|
298
|
+
// written even after the mode is switched back to `off` — otherwise `start()`'s clean slate
|
|
299
|
+
// would never reach disk and a later `resume` would restore a stale `passed` record for a
|
|
300
|
+
// reused object id (the exact hazard that reset exists to prevent). A session that never
|
|
301
|
+
// touched the feature writes NO State/formal.json at all: `off` is a TRUE no-op (v3 guards
|
|
302
|
+
// its own State/formal.json the same way).
|
|
303
|
+
function saveFormal(){ formalPersisted=true; return writeJson('State/formal.json',{records:formal,todo:formalTodos}) }
|
|
298
304
|
// `passed` requires a GREEN RUN, not merely an archived file: a proof file that has never
|
|
299
305
|
// been executed proves nothing, so a hand-written file cannot buy its way past the gate.
|
|
300
306
|
const formalGateOk=(rec)=>!!rec&&(rec.status==='passed'||rec.status==='blocked')
|
|
@@ -374,16 +380,44 @@ export function apply(ctx) {
|
|
|
374
380
|
try {
|
|
375
381
|
handle=sub.spawn({ argv, cwd:frameworkRoot(), stdio:{stdin:'ignore',stdout:{maxBytes:64*1024},stderr:{maxBytes:64*1024}}, graceMs:cap })
|
|
376
382
|
} catch(e){ return {ok:false,code:'LEAN_SPAWN_FAILED',message:String((e&&e.message)||e),file:rel,ms:now()-started} }
|
|
383
|
+
// A timeout must be ACTIVELY enforced. `graceMs` is only the host's own grace window; the
|
|
384
|
+
// contract (§7) additionally requires `handle.terminate()` on timeout, and a host that
|
|
385
|
+
// ignores/exceeds graceMs would otherwise leave the Lean process running while the framework
|
|
386
|
+
// reports LEAN_TIMEOUT. Race `done` against a cap-ms timer that terminates the handle and
|
|
387
|
+
// resolves a synthetic outcome. Note: the plugin runtime has no global setTimeout — the
|
|
388
|
+
// `timer` service (ctx.timeout) is the only timer, and it returns the disposer we clear
|
|
389
|
+
// below when `done` wins the race.
|
|
390
|
+
let killedByUs=false, timerDispose=null
|
|
377
391
|
let outcome
|
|
378
|
-
try {
|
|
379
|
-
|
|
392
|
+
try {
|
|
393
|
+
outcome=await Promise.race([
|
|
394
|
+
handle.done,
|
|
395
|
+
new Promise(function(resolve){
|
|
396
|
+
if(typeof ctx.timeout!=='function') return // no timer service: the host's graceMs is all we have
|
|
397
|
+
timerDispose=ctx.timeout(function(){
|
|
398
|
+
killedByUs=true
|
|
399
|
+
try { if(typeof handle.terminate==='function') handle.terminate() } catch(e){ /* best effort */ }
|
|
400
|
+
resolve({exitCode:null,signal:'SIGTERM'})
|
|
401
|
+
}, cap)
|
|
402
|
+
}),
|
|
403
|
+
])
|
|
404
|
+
} catch(e){
|
|
405
|
+
if(timerDispose){ try { timerDispose() } catch(_e){} }
|
|
406
|
+
return {ok:false,code:'LEAN_RUN_FAILED',message:String((e&&e.message)||e),file:rel,ms:now()-started}
|
|
407
|
+
}
|
|
408
|
+
if(timerDispose){ try { timerDispose() } catch(e){ /* already fired */ } }
|
|
380
409
|
let out='',err=''
|
|
381
410
|
try { if(handle.collected&&handle.collected.stdout) out=handle.collected.stdout.readFrom(0).text } catch(e){ /* best effort */ }
|
|
382
411
|
try { if(handle.collected&&handle.collected.stderr) err=handle.collected.stderr.readFrom(0).text } catch(e){ /* best effort */ }
|
|
383
412
|
const exitCode=outcome?outcome.exitCode:null
|
|
384
413
|
const ms=now()-started
|
|
385
414
|
const ok=exitCode===0
|
|
386
|
-
|
|
415
|
+
// Two ways a timeout is observed: OUR timer won (we terminated the handle), or the HOST's own
|
|
416
|
+
// grace window killed the process first and its `done` beat our timer to the race — the latter
|
|
417
|
+
// is recognised by "non-zero exit after at least cap ms", which is what the spec's
|
|
418
|
+
// LEAN_TIMEOUT row describes. Keeping both means a real hang is never reported as a plain
|
|
419
|
+
// compile failure just because the host's kill resolved first.
|
|
420
|
+
const timedOut=killedByUs||(!ok&&ms>=cap)
|
|
387
421
|
return {
|
|
388
422
|
ok, exitCode, signal:(outcome&&outcome.signal)||null, ms,
|
|
389
423
|
command:argv.join(' '), file:rel,
|
|
@@ -393,11 +427,17 @@ export function apply(ctx) {
|
|
|
393
427
|
}
|
|
394
428
|
// Record one run against an object. `passed`/`blocked` are NEVER downgraded by a later red
|
|
395
429
|
// run (only an explicit re-archive decides those); everything else becomes `attempted`.
|
|
430
|
+
// The comment used to describe a transition the body did not implement: `status:'attempted'`
|
|
431
|
+
// was hardcoded, so ANY `lean_run {target}` on a passed object silently stripped `passed`
|
|
432
|
+
// (and kept the now-stale `proof` pointer). That is not a cosmetic slip: it removes the
|
|
433
|
+
// fidelity branch from the next voting prompt AND, in `require` mode, re-closes the gate on an
|
|
434
|
+
// object that already has a green archived proof. docs/formal-verification.md §4 only maps
|
|
435
|
+
// `none`/`attempted` → `attempted`, and v2/v5 preserve passed/blocked here.
|
|
396
436
|
async function formalSetRun(target,run){
|
|
397
437
|
const key=formalKey(target); if(!key) return
|
|
398
438
|
const prev=formalOf(key)
|
|
399
439
|
await putFormal(key,Object.assign({},prev,{
|
|
400
|
-
status:'attempted',
|
|
440
|
+
status:prev.status==='passed'?'passed':(prev.status==='blocked'?'blocked':'attempted'),
|
|
401
441
|
file:run.file||prev.file||'',
|
|
402
442
|
decision:prev.decision||'used',
|
|
403
443
|
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)},
|
|
@@ -424,7 +464,14 @@ export function apply(ctx) {
|
|
|
424
464
|
L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
|
|
425
465
|
L.push(" ① verdict 给一个严格介于 0 与 1 之间的值(记为弃权),并在 reason 里写清偏差;")
|
|
426
466
|
L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
|
|
427
|
-
|
|
467
|
+
// Only `require` actually GATES the conclusion. In `encourage` the framework still
|
|
468
|
+
// withdraws the proof (and records the defect + TODO), but it CANNOT hold the ballot —
|
|
469
|
+
// promising "本次裁定不定论" there would promise behaviour the framework does not have
|
|
470
|
+
// (docs §4.1-3/§6.1); the voter's own abstention is what keeps the ballot from concluding.
|
|
471
|
+
L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办)'
|
|
472
|
+
+(formalMode()==='require'
|
|
473
|
+
?',本次裁定**不定论**;'
|
|
474
|
+
:';本档没有门禁:请务必给弃权值,以保证本轮无法得出一致结论;'))
|
|
428
475
|
L.push(' 修正形式化并重新跑通后再投票。')
|
|
429
476
|
L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 reason 里写清独立理由。')
|
|
430
477
|
} else if(rec.status==='blocked'){
|
|
@@ -442,7 +489,7 @@ export function apply(ctx) {
|
|
|
442
489
|
L.push(" · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision='blocked' 时必须写明 note)。")
|
|
443
490
|
}
|
|
444
491
|
L.push(' · 归档可复用定义/引理前先跑通(vibe_v4_lean_archive run=true 或先 vibe_v4_lean_run);跑不通不要入库。')
|
|
445
|
-
L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"
|
|
492
|
+
L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)或根本没有 subprocess 服务(NO_SUBPROCESS)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"——这两种都算显式阻塞原因,定论门禁可以据此放行。')
|
|
446
493
|
if(rec.status==='attempted'){
|
|
447
494
|
L.push(' ▸ 该对象已有形式化尝试但尚未通过(最近一次 '+(rec.run?(rec.run.ok?'通过':'未通过'):'无运行记录')+')。')
|
|
448
495
|
L.push(" 请修复后重跑(vibe_v4_lean_run),跑通后用 kind='proof' 归档。")
|
|
@@ -553,7 +600,10 @@ export function apply(ctx) {
|
|
|
553
600
|
await writeFormalIndex()
|
|
554
601
|
}
|
|
555
602
|
} catch(e){ /* the index is best-effort; a run result must always come back */ }
|
|
556
|
-
|
|
603
|
+
// The activity log is part of the injected-text surface (a host reads it out of
|
|
604
|
+
// vibe_v4_report, and residents may be quoted it): use the REGISTERED tool name, never the
|
|
605
|
+
// bare `lean_run` abbreviation (docs §6 hard requirement 1).
|
|
606
|
+
logActivity('formal',(memberId||'host')+' vibe_v4_lean_run '+(run.file||String(args.file||''))+' → '+(run.ok?'通过':(run.code||'未通过')))
|
|
557
607
|
return Object.assign({ok:!!run.ok},run,{
|
|
558
608
|
mode:formalMode(),
|
|
559
609
|
hint: run.ok
|
|
@@ -641,24 +691,38 @@ export function apply(ctx) {
|
|
|
641
691
|
return {ok:false,code:'V4_INVALID_ARGUMENT',message:"kind must be 'def' | 'lemma' | 'proof' | 'blocked'"}
|
|
642
692
|
}
|
|
643
693
|
/**
|
|
644
|
-
* Withdraw an archived proof (spec §4.1).
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
694
|
+
* Withdraw an archived proof (spec §4.1). The RECORD is authoritative, but the FILE must not
|
|
695
|
+
* survive at the exact path everyone looks for "this object's proof": a host whose shell
|
|
696
|
+
* cannot delete (no `subprocess`, a stub shell that exits 0 without removing anything, a
|
|
697
|
+
* permission quirk) would otherwise leave the retracted proof readable as the object's proof
|
|
698
|
+
* while the record already says `attempted`. So the withdrawal is: delete → CONFIRM through
|
|
699
|
+
* the fs service that it is really gone → if it still exists, OVERWRITE it with an explicit
|
|
700
|
+
* withdrawal notice. Returns WHICH path it took ({ok,how:'deleted'|'overwritten'|'failed'})
|
|
701
|
+
* so the caller/activity log can say so out loud instead of reporting a best-effort delete as
|
|
702
|
+
* done. Every path goes through `leanAbsPath`, so this can only ever touch a `.lean` file
|
|
703
|
+
* inside the VibeMath root (a hand-edited State/formal.json must not become an arbitrary-file
|
|
704
|
+
* delete).
|
|
650
705
|
*/
|
|
651
706
|
async function withdrawProof(rel){
|
|
652
707
|
const raw=String(rel==null?'':rel)
|
|
653
|
-
if(!raw) return {ok:false,skipped:true}
|
|
708
|
+
if(!raw) return {ok:false,how:'failed',skipped:true}
|
|
654
709
|
const abs=leanAbsPath(raw)
|
|
655
710
|
if(abs===null||!/\.lean$/.test(abs)){
|
|
656
711
|
logActivity('formal','拒绝删除越界的归档证明路径:'+raw)
|
|
657
|
-
return {ok:false,skipped:true}
|
|
712
|
+
return {ok:false,how:'failed',skipped:true}
|
|
713
|
+
}
|
|
714
|
+
const sub=subprocessOf()
|
|
715
|
+
if(sub!==undefined&&typeof sub.spawn==='function'){
|
|
716
|
+
const r=await runShell(rmCmd([abs]),vibeRoot())
|
|
717
|
+
// Exit 0 is NOT proof of deletion: confirm through the fs service before believing it.
|
|
718
|
+
if(r&&r.ok&&await readTextAbs(abs)===undefined) return {ok:true,how:'deleted',abs}
|
|
658
719
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
720
|
+
// Fallback: make the file impossible to read as this object's proof any more. The withdrawn
|
|
721
|
+
// code itself is NOT lost — it stays in the working file Formal/<id>.lean.
|
|
722
|
+
const notice='-- 已撤回('+fmtTime()+'):该形式化被认定与命题原文不一致。\n'
|
|
723
|
+
+'-- 原代码保留在工作文件 Formal/'+String(raw).split('/').pop()+';修正并重新跑通后重新归档。\n'
|
|
724
|
+
if(await writeTextAbs(abs,notice)) return {ok:true,how:'overwritten',abs}
|
|
725
|
+
return {ok:false,how:'failed',abs}
|
|
662
726
|
}
|
|
663
727
|
/**
|
|
664
728
|
* §4.1 `defect`: a voter checked the Lean code against the proposition and found a FIDELITY
|
|
@@ -687,11 +751,18 @@ export function apply(ctx) {
|
|
|
687
751
|
// leaves an orphaned file with a truthful record, never a record still claiming `passed`.
|
|
688
752
|
await putFormal(key,rec,todo)
|
|
689
753
|
const del=await withdrawProof(proofRel)
|
|
754
|
+
const how=del&&del.how==='deleted'?'删除归档证明 '+proofRel
|
|
755
|
+
:del&&del.how==='overwritten'?'覆盖归档证明 '+proofRel+'(宿主无法删除,已写入撤回说明,原证明内容不再可读)'
|
|
756
|
+
:'⚠ 归档证明 '+proofRel+' 未能撤回(删除与覆盖均失败)——它仍停留在"该对象的证明"的位置,请不要把它当作该对象的证明'
|
|
690
757
|
try { await writeFormalTodo(); await writeFormalIndex() } catch(e){ /* best-effort */ }
|
|
758
|
+
// The activity log is agent/host-readable, so the same §4.1-3 rule applies here as in the
|
|
759
|
+
// prompt: only `require` actually GATES the verdict, so only there may this say 不定论.
|
|
691
760
|
logActivity('formal',(rId||'host')+' 报告 '+key+' 存在**忠实性缺陷**(formal.decision=defect):'+note
|
|
692
|
-
+' ——已撤回「已通过」状态(→ attempted)、'+
|
|
693
|
-
+'、写入 Formal/TODO.md
|
|
694
|
-
|
|
761
|
+
+' ——已撤回「已通过」状态(→ attempted)、'+how
|
|
762
|
+
+'、写入 Formal/TODO.md;'+(formalMode()==='require'
|
|
763
|
+
?'require 门禁使本次裁定**不定论**,修正形式化并重新跑通后再投票'
|
|
764
|
+
:'本档没有门禁:本轮能否定论取决于表决者是否给出弃权值,修正形式化并重新跑通后再投票'))
|
|
765
|
+
return {ok:true,target:key,status:'attempted',proof:'',decision:'defect',removedProof:!!(del&&del.ok),withdrawal:del?del.how:'failed'}
|
|
695
766
|
}
|
|
696
767
|
/**
|
|
697
768
|
* The per-round `formal` reply channel. This is the path that fires IN PRACTICE: a resident
|
|
@@ -702,6 +773,11 @@ export function apply(ctx) {
|
|
|
702
773
|
*/
|
|
703
774
|
async function applyFormalReply(rId,formalReply){
|
|
704
775
|
try {
|
|
776
|
+
// `off` is a TRUE no-op: the reply contract only offers the `formal` field in non-off modes,
|
|
777
|
+
// so a stray / stale / hallucinated one must NOT create Lean state (v2/v3 guard here too;
|
|
778
|
+
// without this, off mode could still be made to write Formal/ records, TODO entries and
|
|
779
|
+
// announcements). The TOOLS stay usable in off mode on purpose — a tool call is deliberate.
|
|
780
|
+
if(!formalOn()) return {ok:false,ignored:true}
|
|
705
781
|
const key=formalKey(String(formalReply.target||''))
|
|
706
782
|
if(!key){
|
|
707
783
|
logActivity('formal',(rId||'host')+' 的 formal 回执缺少 target(对象 id)——本次未记录(V4_INVALID_ARGUMENT)')
|
|
@@ -764,7 +840,9 @@ export function apply(ctx) {
|
|
|
764
840
|
await writeJson('State/mailboxes.json', Object.fromEntries(mailboxes))
|
|
765
841
|
await writeJson('State/taskboard.json', taskboard)
|
|
766
842
|
await writeJson('State/decisions.json', decisions)
|
|
767
|
-
|
|
843
|
+
// `off` mode must not CREATE Lean state: a run that never used the feature leaves no
|
|
844
|
+
// State/formal.json behind. Seeded/live records (or a mode that is on) still persist.
|
|
845
|
+
if(formalOn()||formalPersisted||Object.keys(formal).length||formalTodos.length) await writeJson('State/formal.json', {records:formal,todo:formalTodos})
|
|
768
846
|
await writeJson('State/session.json', {running,autoDone,phase,problemId,problemText,runId,meetings,reports,lastActivityAt,lastProgressAt,activityLog,processEpoch,artifactCount})
|
|
769
847
|
}
|
|
770
848
|
async function loadAll(){
|
|
@@ -779,6 +857,7 @@ export function apply(ctx) {
|
|
|
779
857
|
if(fm&&typeof fm==='object'){
|
|
780
858
|
formal=(fm.records&&typeof fm.records==='object')?fm.records:{}
|
|
781
859
|
formalTodos=Array.isArray(fm.todo)?fm.todo:[]
|
|
860
|
+
if(Object.keys(formal).length||formalTodos.length) formalPersisted=true
|
|
782
861
|
}
|
|
783
862
|
}
|
|
784
863
|
|