dsh-vibe-math 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUDIT-CHECKLIST.md +33 -3
- package/README.md +21 -0
- package/RELEASE-NOTES-2.3.1.md +134 -0
- package/audit-formal-sensitivity.mjs +125 -39
- package/audit-v5-integrity.mjs +5 -3
- package/docs/formal-verification.md +92 -12
- package/docs/test-timing.md +79 -0
- package/formal-verify-v2.test.mjs +286 -7
- package/formal-verify-v3.test.mjs +215 -8
- package/formal-verify-v4.test.mjs +282 -3
- package/formal-verify-v5.test.mjs +72 -0
- package/package.json +9 -2
- package/prompt-corpus-v2/formal-verify-v2.json +394 -0
- package/prompt-corpus-v2/formal-verify-v2.md +4250 -0
- package/prompt-corpus-v3/formal-verify-v3.json +159 -57
- package/prompt-corpus-v3/formal-verify-v3.md +1302 -285
- package/prompt-corpus-v4/formal-verify-v4.json +84 -0
- package/prompt-corpus-v4/formal-verify-v4.md +255 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +54 -16
- package/prompt-corpus-v5/prompt-corpus-v5.md +378 -153
- package/prompt-v5-integrity.test.mjs +1158 -1085
- package/run-tests.mjs +99 -0
- package/vibe-math-v2/vibe-math-v2.js +204 -22
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +77 -4
- package/vibe-math-v3/vibe-math-v3.js +82 -21
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +17 -0
- package/vibe-math-v4/vibe-math-v4.js +114 -22
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +29 -0
- package/vibe-math-v5/vibe-math-v5.js +81 -22
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +27 -4
package/run-tests.mjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PARALLEL TEST RUNNER — run every shipped suite (or a filtered subset) concurrently and report
|
|
4
|
+
* per-suite timings, so the strategy for the next run is chosen from DATA instead of guesswork.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the suites are wildly uneven (one suite is ~160 s, most are under 2 s), so a
|
|
7
|
+
* sequential sweep spends almost all of its wall time waiting for the slowest one. Running them
|
|
8
|
+
* with a worker pool makes the sweep bounded by the slowest SUITE rather than by their SUM.
|
|
9
|
+
* On this machine (4 cores) the sweep went from ~5.5 min to ~2 min; see docs/test-timing.md.
|
|
10
|
+
*
|
|
11
|
+
* Every suite already isolates itself (each creates its own mkdtemp workspace), so parallelism is
|
|
12
|
+
* safe. Suites that WRITE a corpus take a per-run corpus dir from an env var; this runner points
|
|
13
|
+
* them at a scratch dir when it runs a suite more than once, which it never does — but the probe
|
|
14
|
+
* runner (audit-formal-sensitivity.mjs) does, and it passes its own dirs.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node run-tests.mjs # every *.test.mjs, concurrency = min(4, cpus)
|
|
18
|
+
* node run-tests.mjs --concurrency=6
|
|
19
|
+
* node run-tests.mjs --only formal # substring match on the file name (repeatable, OR)
|
|
20
|
+
* node run-tests.mjs --exclude e2e-v4 # substring to skip (repeatable)
|
|
21
|
+
* node run-tests.mjs --json # machine-readable summary on stdout
|
|
22
|
+
*/
|
|
23
|
+
import { spawn } from 'node:child_process'
|
|
24
|
+
import { readdirSync } from 'node:fs'
|
|
25
|
+
import { cpus } from 'node:os'
|
|
26
|
+
import { join } from 'node:path'
|
|
27
|
+
import { fileURLToPath } from 'node:url'
|
|
28
|
+
|
|
29
|
+
const HERE = fileURLToPath(new URL('./', import.meta.url))
|
|
30
|
+
const argv = process.argv.slice(2)
|
|
31
|
+
const flag = (name) => argv.filter((a) => a.startsWith('--' + name + '=')).map((a) => a.split('=').slice(1).join('='))
|
|
32
|
+
const has = (name) => argv.includes('--' + name)
|
|
33
|
+
const only = flag('only')
|
|
34
|
+
const exclude = flag('exclude')
|
|
35
|
+
const asJson = has('json')
|
|
36
|
+
const concurrency = Math.max(1, Number(flag('concurrency')[0] || Math.min(4, cpus().length)))
|
|
37
|
+
|
|
38
|
+
let suites = readdirSync(HERE).filter((f) => f.endsWith('.test.mjs')).sort()
|
|
39
|
+
if (only.length) suites = suites.filter((f) => only.some((o) => f.includes(o)))
|
|
40
|
+
if (exclude.length) suites = suites.filter((f) => !exclude.some((o) => f.includes(o)))
|
|
41
|
+
if (!suites.length) { console.error('no suites matched'); process.exit(2) }
|
|
42
|
+
|
|
43
|
+
function runSuite(file) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const t0 = Date.now()
|
|
46
|
+
const child = spawn(process.execPath, [file], { cwd: HERE, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
47
|
+
let out = '', err = ''
|
|
48
|
+
child.stdout.on('data', (d) => { out += d.toString() })
|
|
49
|
+
child.stderr.on('data', (d) => { err += d.toString() })
|
|
50
|
+
child.on('error', (e) => resolve({ file, code: -1, ms: Date.now() - t0, out, err: err + '\n' + String(e) }))
|
|
51
|
+
child.on('close', (code) => resolve({ file, code, ms: Date.now() - t0, out, err }))
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const results = []
|
|
56
|
+
let cursor = 0
|
|
57
|
+
const started = Date.now()
|
|
58
|
+
async function worker(id) {
|
|
59
|
+
for (;;) {
|
|
60
|
+
const i = cursor++
|
|
61
|
+
if (i >= suites.length) return
|
|
62
|
+
const r = await runSuite(suites[i])
|
|
63
|
+
const tail = String(r.out).trim().split('\n').filter(Boolean).slice(-1)[0] || ''
|
|
64
|
+
results[i] = r
|
|
65
|
+
const mark = r.code === 0 ? 'PASS' : 'FAIL'
|
|
66
|
+
console.log(
|
|
67
|
+
mark + ' ' + r.file.padEnd(38) +
|
|
68
|
+
' exit=' + String(r.code).padStart(3) +
|
|
69
|
+
' ' + (r.ms / 1000).toFixed(1).padStart(6) + 's' +
|
|
70
|
+
(tail ? ' ' + tail.slice(0, 78) : '')
|
|
71
|
+
)
|
|
72
|
+
if (r.code !== 0) {
|
|
73
|
+
const lines = (r.out + '\n' + r.err).split('\n').filter(Boolean)
|
|
74
|
+
for (const l of lines.slice(-15)) console.log(' ' + l)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, suites.length) }, (_, i) => worker(i)))
|
|
79
|
+
|
|
80
|
+
const wall = (Date.now() - started) / 1000
|
|
81
|
+
const sum = results.reduce((a, r) => a + r.ms, 0) / 1000
|
|
82
|
+
const bad = results.filter((r) => r.code !== 0)
|
|
83
|
+
const slowest = results.slice().sort((a, b) => b.ms - a.ms).slice(0, 5)
|
|
84
|
+
|
|
85
|
+
if (asJson) {
|
|
86
|
+
console.log(JSON.stringify({
|
|
87
|
+
concurrency, wallSeconds: Number(wall.toFixed(1)), sumSeconds: Number(sum.toFixed(1)),
|
|
88
|
+
pass: results.length - bad.length, fail: bad.length,
|
|
89
|
+
suites: results.map((r) => ({ file: r.file, exit: r.code, seconds: Number((r.ms / 1000).toFixed(1)) })),
|
|
90
|
+
}, null, 2))
|
|
91
|
+
} else {
|
|
92
|
+
console.log('')
|
|
93
|
+
console.log('concurrency ' + concurrency + ' · wall ' + wall.toFixed(1) + 's · sum of suite times ' + sum.toFixed(1) + 's'
|
|
94
|
+
+ ' · speed-up x' + (sum / Math.max(wall, 0.001)).toFixed(2))
|
|
95
|
+
console.log('slowest: ' + slowest.map((r) => r.file.replace('.test.mjs', '') + ' ' + (r.ms / 1000).toFixed(1) + 's').join(' · '))
|
|
96
|
+
console.log('TOTAL ' + results.length + ' PASS ' + (results.length - bad.length) + ' FAIL ' + bad.length)
|
|
97
|
+
for (const b of bad) console.log(' FAILED: ' + b.file + ' (exit ' + b.code + ')')
|
|
98
|
+
}
|
|
99
|
+
process.exit(bad.length === 0 ? 0 : 1)
|
|
@@ -606,7 +606,7 @@ export function apply(ctx) {
|
|
|
606
606
|
/**
|
|
607
607
|
* 归档(对象 id)→ 验证对象(rId)的状态同步。
|
|
608
608
|
*
|
|
609
|
-
* v2 有两个 id 空间,而它们指的是同一个东西:代理用 `
|
|
609
|
+
* v2 有两个 id 空间,而它们指的是同一个东西:代理用 `vibe_math_lean_archive target=<对象 id>`
|
|
610
610
|
* (`r-pGate` 这种 rId 是调度器内部标识,代理在提示词里看到的是对象 id),而验证提示词
|
|
611
611
|
* 与 require 门禁问的是"**这一次验证对象**"的状态。只写一个键会让两边错位:
|
|
612
612
|
* 对象明明已 passed,门禁却认为 rId 仍未被形式化,裁定被反复搁置。
|
|
@@ -628,6 +628,16 @@ export function apply(ctx) {
|
|
|
628
628
|
}
|
|
629
629
|
return ids
|
|
630
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* 验证 id → 它对应的对象 id(`r-pGate` / `r-pGate-s0` / `r-pGate-pf1` / `r-pGate-rf2` → `pGate`)。
|
|
633
|
+
* v2 有两套 id 空间,这条映射是**唯一**的一处:门禁的合并查询、提示词取记录、以及回执通道
|
|
634
|
+
* (`blocked` / `defect` / `used`)都从这里得到对象 id,绝不另造第二套解析规则。
|
|
635
|
+
*/
|
|
636
|
+
function formalObjectIdOf(id) {
|
|
637
|
+
const t = safeId(String(id == null ? '' : id))
|
|
638
|
+
const m = /^r-(.+?)(?:-(?:s\d+|pf\d+|rf\d+))?$/.exec(t)
|
|
639
|
+
return m ? m[1] : t
|
|
640
|
+
}
|
|
631
641
|
/**
|
|
632
642
|
* 门禁/提示词看到的对象状态 = 合并后的记录,**两个方向都要认**,因为 v2 里归档与验证
|
|
633
643
|
* 用的是两套 id,而代理两种写法都会用:
|
|
@@ -642,13 +652,45 @@ export function apply(ctx) {
|
|
|
642
652
|
const own = recs[t]
|
|
643
653
|
if (formalGateOk(own)) return own
|
|
644
654
|
// 验证 id → 其对象 id(r-<obj> / r-<obj>-s0 / r-<obj>-pf0 / r-<obj>-rf0)
|
|
645
|
-
const
|
|
646
|
-
if (
|
|
655
|
+
const objId = formalObjectIdOf(t)
|
|
656
|
+
if (objId !== t) { const obj = recs[objId]; if (formalGateOk(obj)) return obj }
|
|
647
657
|
// 对象 id → 其验证 id(本对象的第一个验证记录)
|
|
648
658
|
const vr = recs['r-' + t]
|
|
649
659
|
if (formalGateOk(vr)) return vr
|
|
650
660
|
return own || { status: 'none' }
|
|
651
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* 把同一条状态写进**两套 id 的全部记录**(契约 §8)。门禁、提示词、卡片三处**各读不同的一侧**,
|
|
664
|
+
* 所以只写一侧会造成静默错位:例如 `defect` 只写到 `r-<id>` 上,对象记录仍是 `passed`,
|
|
665
|
+
* 于是 `formalGateRecord` 从对象侧读回 `passed`、索引与卡片照旧写"Lean 通过"——
|
|
666
|
+
* 这正是"成对关系只做一半"的典型缺陷形态(AUDIT-CHECKLIST §3)。
|
|
667
|
+
*/
|
|
668
|
+
async function putFormalBothIds(target, patch) {
|
|
669
|
+
const t = safeId(String(target == null ? '' : target))
|
|
670
|
+
if (!t) return []
|
|
671
|
+
const objectId = formalObjectIdOf(t)
|
|
672
|
+
const written = []
|
|
673
|
+
const write = async function (k) {
|
|
674
|
+
if (written.indexOf(k) !== -1) return
|
|
675
|
+
const prev = formalRecords()[k] || { status: 'none' }
|
|
676
|
+
await putFormal(k, Object.assign({}, prev, patch))
|
|
677
|
+
written.push(k)
|
|
678
|
+
}
|
|
679
|
+
await write(t) // 回执点名的那个 id(对象 id 或验证 id)
|
|
680
|
+
if (objectId !== t) await write(objectId) // 另一侧
|
|
681
|
+
// 同一对象的其它验证别名(r-<id>、r-<id>-s0、r-<id>-pf1、r-<id>-rf2):复用既有扫描,不新造映射。
|
|
682
|
+
const aliases = await syncVerificationTarget(objectId, patch.status, patch)
|
|
683
|
+
for (let i = 0; i < aliases.length; i++) await write(aliases[i])
|
|
684
|
+
// 实体化**规范验证别名** r-<对象id>:归档时写对象 id 是常见写法,此时验证侧可能**根本还没有**
|
|
685
|
+
// 记录;只降级对象侧会让"这一次验证"看起来从未被形式化过(门禁查询优先看 rId)。显式写下这条
|
|
686
|
+
// attempted/blocked 记录,两套 id 空间才真正一致(审计清单 §3:成对关系别只做一半)。
|
|
687
|
+
await write('r-' + objectId)
|
|
688
|
+
// syncVerificationTarget 用 `||` 合并 proof,表达不了"清空"——而 defect 恰恰必须清空 proof。
|
|
689
|
+
if (patch.proof === '') {
|
|
690
|
+
for (let i = 0; i < written.length; i++) await putFormal(written[i], Object.assign({}, formalRecords()[written[i]] || {}, { proof: '' }))
|
|
691
|
+
}
|
|
692
|
+
return written
|
|
693
|
+
}
|
|
652
694
|
|
|
653
695
|
// ---- 提示词注入(在构造提示词的那一刻现算;契约 §6)----
|
|
654
696
|
function formalPromptBlock(target) {
|
|
@@ -657,31 +699,36 @@ export function apply(ctx) {
|
|
|
657
699
|
// 与门禁取同一条记录:验证对象记录优先,其次它对应的对象记录,避免"归档了却仍按未尝试提示"。
|
|
658
700
|
const rec = target ? formalGateRecord(target) : { status: 'none' }
|
|
659
701
|
const L = []
|
|
702
|
+
const vroot = vibeRoot().replace(/\\/g, '/')
|
|
660
703
|
L.push('【Lean 形式化验证(' + (mode === 'require' ? '强制' : '鼓励') + '模式)】')
|
|
661
704
|
if (rec.status === 'passed') {
|
|
662
705
|
// 整个功能的意义所在:审查对象**变了**。证明正确性已由内核保证,剩下的唯一风险是
|
|
663
706
|
// "这段 Lean 代码说的不是我们想说的"。
|
|
707
|
+
// 忠实性的**判定指引**(一致怎么投、发现偏差怎么投、什么情况才能投 0)在 formalVerifySection
|
|
708
|
+
// 里紧随其后给出——两段拼在同一条提示词里,合成契约 §6.1 的完整「忠实性分支」。
|
|
664
709
|
L.push(' · 该对象已有**通过的 Lean 形式化证明**(' + (rec.proof || rec.file) + ',最近运行 exit 0)。')
|
|
665
710
|
L.push(' **你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的')
|
|
666
|
-
L.push(' 定义 / 对象 / 条件 / 假设 /
|
|
667
|
-
L.push(' 并据此给出 verdict。')
|
|
711
|
+
L.push(' 定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。')
|
|
668
712
|
} else if (rec.status === 'blocked') {
|
|
669
713
|
L.push(' · 该对象已被记录为**形式化阻塞**:' + (rec.note || '未说明') + '。')
|
|
670
714
|
L.push(' 请复核这个判断是否成立;若你认为其实可以形式化,请指出来并动手做。')
|
|
671
715
|
} else {
|
|
672
716
|
L.push(' · 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先写 Lean 代码并执行。')
|
|
673
717
|
L.push(' · 工具:vibe_math_lean_run(执行)· vibe_math_lean_archive(归档)· vibe_math_lean_lib(查已有可复用库)')
|
|
674
|
-
L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' +
|
|
675
|
-
|
|
676
|
-
L.push(' · **一旦 Lean
|
|
677
|
-
|
|
718
|
+
L.push(' · 工作目录:Formal/(相对项目根);可复用定义放 ' + vroot + '/Formal/Lib/,已证引理放 '
|
|
719
|
+
+ vroot + '/Formal/Proved/;写之前先 vibe_math_lean_lib 查重。')
|
|
720
|
+
L.push(' · **一旦 Lean 通过,你唯一需要确认的就是忠实性**:定义/对象/条件/假设/结论是否与命题原文逐条一致。'
|
|
721
|
+
+ '请把注意力放在这种核对上,而不是重新做一遍推导。')
|
|
678
722
|
if (mode === 'require') {
|
|
679
|
-
L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(
|
|
680
|
-
|
|
681
|
-
|
|
723
|
+
L.push(' · **本模式要求**:必须产出 Lean 形式化,或**必须**给出显式的阻塞原因(vibe_math_lean_archive '
|
|
724
|
+
+ 'kind=\'blocked\' note=… 或回执 formal.note)。若两者都没有,本次裁定不会生效,会被记为未定论'
|
|
725
|
+
+ '(原因 formal-required)并进入「形式化待办」。')
|
|
682
726
|
} else {
|
|
683
|
-
L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal
|
|
727
|
+
L.push(' · 若你判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断(decision=\'blocked\' 时必须写明 note)。')
|
|
684
728
|
}
|
|
729
|
+
L.push(' · 归档可复用定义/引理前先跑通(vibe_math_lean_archive run=true 或先 vibe_math_lean_run);跑不通不要入库。')
|
|
730
|
+
L.push(' · 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明'
|
|
731
|
+
+ '"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。')
|
|
685
732
|
}
|
|
686
733
|
return L.join('\n')
|
|
687
734
|
}
|
|
@@ -690,6 +737,118 @@ export function apply(ctx) {
|
|
|
690
737
|
return '【顺手形式化(' + (formalMode() === 'require' ? '强制' : '鼓励') + ')】把你工作中常用或可能复用的对象、假设、'
|
|
691
738
|
+ '新定义用 Lean 形式化定义并归档到全局可复用库(vibe_math_lean_archive kind=\'def\'),已成立的引理归到 Formal/Proved/'
|
|
692
739
|
+ '(kind=\'lemma\');写之前先 vibe_math_lean_lib 查重,避免重复定义。'
|
|
740
|
+
+ '归档前先跑通(vibe_math_lean_run 或 run=true);跑不通的定义不要进可复用库。'
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* 回执契约里的 `formal` 字段(契约 §6.3)。**必须真的出现在回执契约里**:契约写了字段而框架
|
|
744
|
+
* 不解析,就是一条"框架收不到"的死通道(审计清单 §2.2)。`off` 档返回空串,verifier 的 JSON 示例
|
|
745
|
+
* 因此与改动前逐字节一致。
|
|
746
|
+
*/
|
|
747
|
+
function formalJsonField(target) {
|
|
748
|
+
if (!formalOn()) return ''
|
|
749
|
+
const id = String(target == null ? '' : target) || '<对象id>'
|
|
750
|
+
return ',"formal":{"target":"' + id + '","decision":"used|blocked|defect","file":"Formal/' + id + '.lean","note":"难度判断/阻塞原因/具体偏差"}'
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* 工作轮(solver / explorer)回执里的 formal 字段。这些角色的回执契约本身就是一段 JSON 模板,
|
|
754
|
+
* 直接改模板尾部容易把示例改成非法 JSON,所以在**契约说明**里给出同样的字段(与 v3 同一形态);
|
|
755
|
+
* 框架侧 `absorbFormalFromReply` 同时接受顶层 `formal` 与 `meta.formal`。
|
|
756
|
+
*/
|
|
757
|
+
function formalReplyNote() {
|
|
758
|
+
if (!formalOn()) return ''
|
|
759
|
+
return '\n形式化回执(本模式):若你本轮对某个对象做了形式化难度判断,或发现已有 Lean 证明与命题原文不符,'
|
|
760
|
+
+ '请在回执里加上 "formal":{"target":"<对象id>","decision":"used|blocked|defect","file":"Formal/<对象id>.lean",'
|
|
761
|
+
+ '"note":"难度判断/阻塞原因/具体偏差"}(decision=\'blocked\'/\'defect\' 时必须写明 note,否则整条记录被拒绝;'
|
|
762
|
+
+ 'decision=\'defect\' 会撤回该证明的「已通过」状态并写入「形式化待办」)。'
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* 契约 §4.1:`defect` = **形式化不合格**,不是"命题为假"。
|
|
766
|
+
*
|
|
767
|
+
* 表决者逐条核对后发现 Lean 代码与命题原文不一致(写窄了/写宽了/换了对象/漏了条件…),
|
|
768
|
+
* 那是这条形式化写得不对,不是命题被证伪。因此:
|
|
769
|
+
* ① 把形式化记录**降级为 `attempted`**(无论此前是 `passed` 还是 `blocked`)、清空 `proof`;
|
|
770
|
+
* ② 删除归档证明 `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean` 保留,代码不丢);
|
|
771
|
+
* ③ `note` 记入记录与 `Formal/TODO.md`,并在活动日志里公告;
|
|
772
|
+
* ④ `require` 档下 `formalGateOk` 随之为假 → 本次裁定**不定论**,修正形式化并重新跑通后再投票。
|
|
773
|
+
*/
|
|
774
|
+
async function formalDefectDowngrade(target, note, who) {
|
|
775
|
+
const t = safeId(String(target || ''))
|
|
776
|
+
const objectId = formalObjectIdOf(t)
|
|
777
|
+
const before = formalRecords()
|
|
778
|
+
const proofs = []
|
|
779
|
+
const pushProof = function (p) { const s = String(p || ''); if (s && proofs.indexOf(s) === -1) proofs.push(s) }
|
|
780
|
+
pushProof((before[t] || {}).proof)
|
|
781
|
+
pushProof((before[objectId] || {}).proof)
|
|
782
|
+
// 两套 id 一起降级——只降一侧会让另一侧继续"已通过",门禁/卡片就会照旧放行。
|
|
783
|
+
await putFormalBothIds(t, { status: 'attempted', decision: 'defect', note: note, proof: '', updatedAt: now() })
|
|
784
|
+
// 删除归档证明:删记录里记着的那份,也删两边 id 直接对应的文件名(覆盖"归档时用对象 id、
|
|
785
|
+
// 回执时用验证 id"这种两侧写法不同的情形)。
|
|
786
|
+
const rels = []
|
|
787
|
+
for (let i = 0; i < proofs.length; i++) if (proofs[i].indexOf('Verified/Lean/') === 0 && rels.indexOf(proofs[i]) === -1) rels.push(proofs[i])
|
|
788
|
+
for (const id of [t, objectId]) { const r = 'Verified/Lean/' + id + '.lean'; if (rels.indexOf(r) === -1) rels.push(r) }
|
|
789
|
+
for (let i = 0; i < rels.length; i++) { try { await removeFile(rels[i]) } catch (e) { /* 删除失败不应影响记账 */ } }
|
|
790
|
+
const list = formalTodo().filter(function (x) { return x.id !== objectId })
|
|
791
|
+
list.push({ id: objectId, at: now(), why: 'defect:形式化与命题原文不一致,需修正后重新跑通(' + note + ')' })
|
|
792
|
+
await putFormal(t, Object.assign({}, formalRecords()[t] || {}, { status: 'attempted', decision: 'defect', note: note, proof: '', updatedAt: now() }), list)
|
|
793
|
+
await writeFormalTodo()
|
|
794
|
+
await writeFormalIndex()
|
|
795
|
+
logActivity('formal', '【形式化】' + who + ' 通过回执记录 ' + objectId + ' 的忠实性缺陷(decision=defect):' + note
|
|
796
|
+
+ ' —— 已撤回「已通过」状态(降级 attempted、删除归档证明 ' + rels.join('、') + '、写入 Formal/TODO.md),本次裁定不定论。')
|
|
797
|
+
return { ok: true, decision: 'defect', target: objectId, named: t, status: 'attempted', note: note, cleared: rels }
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* 回执通道(契约 §4 / §6.3):代理**即使一次 Lean 工具都没调用**,也必须能留下"实现难度判断"或
|
|
801
|
+
* "忠实性缺陷"。`blocked` / `defect` 的 `note` 必填(决定必须显式、可审计,不允许静默跳过);
|
|
802
|
+
* 缺 note / 缺 target / 非法 decision 一律拒绝(`V2_INVALID_ARGUMENT`)且不留下任何记录。
|
|
803
|
+
*/
|
|
804
|
+
async function absorbFormalReply(f, memberId) {
|
|
805
|
+
try {
|
|
806
|
+
if (!formalOn() || !f || typeof f !== 'object') return { ok: false, ignored: true }
|
|
807
|
+
const who = memberId || 'office'
|
|
808
|
+
const rawTarget = String(f.target == null ? '' : f.target).trim()
|
|
809
|
+
const decision = String(f.decision || '')
|
|
810
|
+
if (!rawTarget) {
|
|
811
|
+
// safeId('') 会返回 'anon':没有 target 的回执绝不能凭空造出一条 formal 记录。
|
|
812
|
+
logActivity('formal', '【形式化】' + who + ' 的 formal 回执没有 target,已拒绝(V2_INVALID_ARGUMENT)。')
|
|
813
|
+
return { ok: false, code: 'V2_INVALID_ARGUMENT', message: 'formal.target is required' }
|
|
814
|
+
}
|
|
815
|
+
const target = safeId(rawTarget)
|
|
816
|
+
const note = String(f.note || '').trim()
|
|
817
|
+
if (decision === 'blocked' || decision === 'defect') {
|
|
818
|
+
if (!note) {
|
|
819
|
+
logActivity('formal', '【形式化】' + who + ' 的 formal.decision=' + decision + ' 没有写明 note,已拒绝(V2_INVALID_ARGUMENT)——'
|
|
820
|
+
+ target + ' 的难度判断/忠实性缺陷必须显式、可审计。')
|
|
821
|
+
return { ok: false, code: 'V2_INVALID_ARGUMENT', message: 'formal.decision=' + decision + ' requires a non-empty note (target ' + target + ')' }
|
|
822
|
+
}
|
|
823
|
+
if (decision === 'defect') return await formalDefectDowngrade(target, note, who)
|
|
824
|
+
await putFormalBothIds(target, { status: 'blocked', decision: 'blocked', note: note, updatedAt: now() })
|
|
825
|
+
await writeFormalIndex()
|
|
826
|
+
logActivity('formal', '【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化阻塞:' + note)
|
|
827
|
+
return { ok: true, decision: 'blocked', target: target, status: 'blocked', note: note }
|
|
828
|
+
}
|
|
829
|
+
if (decision === 'used') {
|
|
830
|
+
const file = String(f.file || ('Formal/' + target + '.lean'))
|
|
831
|
+
await putFormalBothIds(target, { status: 'attempted', decision: 'used', file: file, updatedAt: now() })
|
|
832
|
+
await writeFormalIndex()
|
|
833
|
+
logActivity('formal', '【形式化】' + who + ' 通过回执记录 ' + target + ' 形式化草稿:' + file)
|
|
834
|
+
return { ok: true, decision: 'used', target: target, status: 'attempted', file: file }
|
|
835
|
+
}
|
|
836
|
+
logActivity('formal', '【形式化】' + who + ' 的 formal.decision 只能是 \'used\' | \'blocked\' | \'defect\'(收到 '
|
|
837
|
+
+ String(f.decision) + '),已忽略(V2_INVALID_ARGUMENT)。')
|
|
838
|
+
return { ok: false, code: 'V2_INVALID_ARGUMENT', message: 'formal.decision must be one of used|blocked|defect' }
|
|
839
|
+
} catch (e) {
|
|
840
|
+
// 回执通道**绝不**把异常抛进调度循环:一次记账失败不该吞掉一次表决。
|
|
841
|
+
console.error('vibe-math-v2: absorbFormalReply failed: ' + String((e && e.message) || e))
|
|
842
|
+
return { ok: false, error: String((e && e.message) || e) }
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/** 从一条代理回执里取出 formal 判断并落库(顶层 `formal` 或 `meta.formal` 都接受)。 */
|
|
846
|
+
async function absorbFormalFromReply(parsed, memberId) {
|
|
847
|
+
if (!formalOn() || !parsed || typeof parsed !== 'object') return { ok: false, ignored: true }
|
|
848
|
+
const f = (parsed.formal && typeof parsed.formal === 'object') ? parsed.formal
|
|
849
|
+
: ((parsed.meta && typeof parsed.meta.formal === 'object') ? parsed.meta.formal : null)
|
|
850
|
+
if (!f) return { ok: false, ignored: true }
|
|
851
|
+
return await absorbFormalReply(f, memberId)
|
|
693
852
|
}
|
|
694
853
|
|
|
695
854
|
// ---- 三份索引(框架维护;契约 §9)----
|
|
@@ -771,8 +930,8 @@ export function apply(ctx) {
|
|
|
771
930
|
if (run.ok) logActivity('formal', (memberId || 'office') + ' 运行 Lean 通过:' + run.file + '(' + (run.ms / 1000).toFixed(1) + 's)' + (args.target ? '|对象 ' + args.target : ''))
|
|
772
931
|
return Object.assign({ ok: !!run.ok }, run, {
|
|
773
932
|
hint: run.ok
|
|
774
|
-
? '通过。若是某个对象的证明,请用
|
|
775
|
-
: '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用
|
|
933
|
+
? '通过。若是某个对象的证明,请用 vibe_math_lean_archive kind=\'proof\' 归档(会写入 Verified/Lean/ 并把审查对象变成忠实性);若是可复用定义/引理,用 kind=\'def\'/\'lemma\' 归档到全局库(归档时会先跑一次,跑不通不要入库)。'
|
|
934
|
+
: '未通过。请按上面的编译器输出修复后重跑;若判断无法完成,用 vibe_math_lean_archive kind=\'blocked\' 记录原因。',
|
|
776
935
|
})
|
|
777
936
|
}
|
|
778
937
|
async function leanArchive(memberId, o) {
|
|
@@ -830,7 +989,7 @@ export function apply(ctx) {
|
|
|
830
989
|
if (passed) await writeText('Verified/Lean/' + target + '.lean', body)
|
|
831
990
|
await putFormal(target, rec)
|
|
832
991
|
// ★ 让"归档"与"验证对象"两套 id 对齐。
|
|
833
|
-
// 验证提示词与门禁关心的是**这一次验证**(rId,例如 r-pGate),而代理用
|
|
992
|
+
// 验证提示词与门禁关心的是**这一次验证**(rId,例如 r-pGate),而代理用 vibe_math_lean_archive
|
|
834
993
|
// 归档时给的是**对象 id**(例如 pGate)。若不在这里把验证对象也标成同一状态,就会
|
|
835
994
|
// 出现"对象已 passed、门禁却仍认为 rId 未形式化"的错位:代理明明做了形式化,
|
|
836
995
|
// 裁定还是被反复搁置。两套记录都写,门禁与提示词任取其一都自洽。
|
|
@@ -1136,7 +1295,13 @@ export function apply(ctx) {
|
|
|
1136
1295
|
function knowledgeContextText() { const k = params.knowledgeContext ? String(params.knowledgeContext) : defaultKnowledgeContext(); return k ? ('\n' + k + '\n') : '' }
|
|
1137
1296
|
// 平时工作提示词里的"顺手形式化"段落。**off 档必须返回空串**:off 是真正的无操作,
|
|
1138
1297
|
// 提示词里不能出现任何 Lean 字样(contract §2 / §10.1)。
|
|
1139
|
-
function formalWorkSection() {
|
|
1298
|
+
function formalWorkSection() {
|
|
1299
|
+
const t = formalWorkLine()
|
|
1300
|
+
if (!t) return ''
|
|
1301
|
+
// 回执契约(契约 §6.3):工作轮也必须被告知 formal 字段,否则"顺手形式化"里做出的难度判断
|
|
1302
|
+
// 无处可写,代理只能沉默——那正是 v2 首版死通道的成因。
|
|
1303
|
+
return '\n' + t + formalReplyNote() + '\n'
|
|
1304
|
+
}
|
|
1140
1305
|
function capabilitiesText(role) {
|
|
1141
1306
|
const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
|
|
1142
1307
|
const netOn = role === 'solver' ? params.solverAllowNetwork : params.verifierAllowNetwork
|
|
@@ -1228,7 +1393,7 @@ export function apply(ctx) {
|
|
|
1228
1393
|
return head
|
|
1229
1394
|
}
|
|
1230
1395
|
// 验证提示词里的形式化段落(review 与 debate 两条路径都必须带)。**off 档返回空串**。
|
|
1231
|
-
// 形式化记录以「验证对象」为键(rId,例如 r-p1 或 r-q1-s0)——这正是
|
|
1396
|
+
// 形式化记录以「验证对象」为键(rId,例如 r-p1 或 r-q1-s0)——这正是 vibe_math_lean_archive 的 target,
|
|
1232
1397
|
// 于是"归档了证明 → 下一条验证提示词自动切换成忠实性审查"这条因果链闭合。
|
|
1233
1398
|
function formalVerifySection(r) {
|
|
1234
1399
|
if (!formalOn()) return ''
|
|
@@ -1239,7 +1404,16 @@ export function apply(ctx) {
|
|
|
1239
1404
|
// 把"审查对象变了"这件事说透:手里已有机器核对过的证明时,重新推导是浪费,
|
|
1240
1405
|
// 真正的风险是"这段代码说的不是我们想说的"。
|
|
1241
1406
|
if (rec.status === 'passed') {
|
|
1242
|
-
|
|
1407
|
+
// 忠实性审查的**判定指引**:形式化与命题原文不一致时,那是"形式化不合格",**不是**命题为假。
|
|
1408
|
+
// 让它"发现偏离就投 0"会伪造出一个错误的否定结论(契约 §4.1),所以这里明确禁止投 0,
|
|
1409
|
+
// 并给出 `defect` 回执——框架据此撤回证明、写入待办、本次裁定不定论。
|
|
1410
|
+
L.push(' ▸ 一致 → Result = 1。')
|
|
1411
|
+
L.push(' ▸ **发现任何偏差,不要投 0**:偏差只说明**形式化不合格**,不代表命题为假。此时请:')
|
|
1412
|
+
L.push(" ① Result 给一个严格介于 0 与 1 之间的值(记为弃权),并在 Reason 里写清偏差;")
|
|
1413
|
+
L.push(" ② 用回执 formal:{decision:'defect', note:'<具体偏差>'} 记录它。框架会撤回这条证明的")
|
|
1414
|
+
L.push(' 「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办),本次裁定**不定论**;')
|
|
1415
|
+
L.push(' 修正形式化并重新跑通后再投票。')
|
|
1416
|
+
L.push(' ▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 0,并在 Reason 里写清独立理由。')
|
|
1243
1417
|
} else if (rec.status === 'blocked') {
|
|
1244
1418
|
L.push(' ▸ 因此请把 Result 用在"这个阻塞判断是否成立 / 是否仍有别的形式化路线"上,并给出理由。')
|
|
1245
1419
|
} else {
|
|
@@ -1259,7 +1433,7 @@ export function apply(ctx) {
|
|
|
1259
1433
|
formalVerifySection(r) +
|
|
1260
1434
|
'\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
1435
|
'\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"}'
|
|
1436
|
+
'{"Result":0.5,"Reason":"detailed logic chain, potential counterexample, or supporting evidence"' + formalJsonField(r && r.rId) + '}'
|
|
1263
1437
|
}
|
|
1264
1438
|
function verifierDebatePrompt(r, transcript) {
|
|
1265
1439
|
let target = ''
|
|
@@ -1273,7 +1447,7 @@ export function apply(ctx) {
|
|
|
1273
1447
|
'\nFULL DEBATE HISTORY SO FAR (每轮所有评审轮流发言的记录):\n' + transcript + '\n' +
|
|
1274
1448
|
'\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
1449
|
'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"}'
|
|
1450
|
+
'{"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
1451
|
}
|
|
1278
1452
|
|
|
1279
1453
|
// ================= decisions (manual/auto) =================
|
|
@@ -1584,6 +1758,8 @@ export function apply(ctx) {
|
|
|
1584
1758
|
async function handleExplorer(childId, meta, output) {
|
|
1585
1759
|
delete agentRegistry[childId]
|
|
1586
1760
|
const parsed = parseJson(output)
|
|
1761
|
+
// 回执通道(契约 §4 / §6.3):工作轮里做出的形式化难度判断也要落库。
|
|
1762
|
+
await absorbFormalFromReply(parsed, childId)
|
|
1587
1763
|
const dirs = (parsed && parsed.directions) || []
|
|
1588
1764
|
if (dirs.length === 0) { logActivity('explorer', 'problem ' + meta.qid + ' returned no directions (output head: ' + String(output || '').slice(0, 200) + ')'); await saveAll(); return }
|
|
1589
1765
|
explorerRetries[meta.qid] = 0
|
|
@@ -1598,6 +1774,8 @@ export function apply(ctx) {
|
|
|
1598
1774
|
async function handleSolver(childId, meta, output, stopReason) {
|
|
1599
1775
|
const qid = meta.qid; const dirId = meta.direction
|
|
1600
1776
|
const parsed = parseJson(output)
|
|
1777
|
+
// 回执通道(契约 §4 / §6.3):求解器/探索者的"顺手形式化"产物与难度判断都要落库。
|
|
1778
|
+
await absorbFormalFromReply(parsed, childId)
|
|
1601
1779
|
const q = await findQ(qid); if (!q) { delete agentRegistry[childId]; return }
|
|
1602
1780
|
const prog = parseProgress(q)
|
|
1603
1781
|
const dir = prog.directions.find(function (d) { return d.id === dirId })
|
|
@@ -1724,6 +1902,10 @@ export function apply(ctx) {
|
|
|
1724
1902
|
async function handleVerifier(childId, meta, output, stopReason) {
|
|
1725
1903
|
const rId = meta.rId
|
|
1726
1904
|
const parsed = parseJson(output)
|
|
1905
|
+
// 回执通道(契约 §4 / §6.3):验证者在回执里给出的难度判断 / 忠实性缺陷(`defect`)必须先于
|
|
1906
|
+
// 裁定落库——`defect` 会把形式化记录降级,于是紧随其后的 `settleVerdict` 会在 require 档
|
|
1907
|
+
// 把本次裁定正确地记为未定论。放在这里(而不是裁定之后)是有意的顺序依赖。
|
|
1908
|
+
await absorbFormalFromReply(parsed, childId)
|
|
1727
1909
|
const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
|
|
1728
1910
|
const Reason = (parsed && parsed.Reason) || ''
|
|
1729
1911
|
let t = tasks['verify:' + rId]
|
|
@@ -2146,7 +2328,7 @@ export function apply(ctx) {
|
|
|
2146
2328
|
counts: r, todo: formalTodo(),
|
|
2147
2329
|
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
2330
|
paths: { project: 'Formal/(相对项目根)', lib: 'VibeMath/Formal/Lib/', proved: 'VibeMath/Formal/Proved/', proofs: 'Verified/Lean/' },
|
|
2149
|
-
hint: '复用优先:先在 Lib/ 里找现成定义;新定义用
|
|
2331
|
+
hint: '复用优先:先在 Lib/ 里找现成定义;新定义用 vibe_math_lean_archive kind=\'def\' 归档,已证引理用 kind=\'lemma\'(归档前先跑通,跑不通不要入库)。',
|
|
2150
2332
|
}
|
|
2151
2333
|
})
|
|
2152
2334
|
|
|
@@ -317,12 +317,85 @@ v2 **没有会话投影**,所以记录与待办一起持久化在 v2 自己的
|
|
|
317
317
|
(`writeVerifiedCardIfNeeded(p, target)` / `writeVerifiedProblemCardIfNeeded(q, true)`),
|
|
318
318
|
拿到许可才写入 `布尔估计` / `已解决` / `优先级`;被搁置则整条跳过。
|
|
319
319
|
|
|
320
|
-
###
|
|
320
|
+
### 九、回执通道(`formal` 字段)与 `defect` 语义
|
|
321
|
+
|
|
322
|
+
> 对应契约 §4、§4.1、§6.3 与 §10 的第 8–11 条。**教训**:本特性首版只在提示词里写了
|
|
323
|
+
> "请在回执的 formal 字段写明难度判断",框架**从不解析**它——难度判断静默消失,而套件只断言
|
|
324
|
+
> "那句话存在",于是 177 条断言全绿却守着一条**死通道**。措辞断言不等于行为断言。
|
|
325
|
+
|
|
326
|
+
#### 9.1 回执真的会被解析并落库
|
|
327
|
+
|
|
328
|
+
- 回执契约里出现 `formal` 字段(`formalJsonField` 注入 `verifierReviewPrompt` 与
|
|
329
|
+
`verifierDebatePrompt` 的 JSON 示例;`formalReplyNote()` 注入 solver/explorer 工作轮的
|
|
330
|
+
"顺手形式化"段落):`{"formal":{"target":"p-x","decision":"used|blocked|defect","file":"Formal/p-x.lean","note":"难度判断/阻塞原因/具体偏差"}}`。
|
|
331
|
+
- `absorbFormalFromReply(parsed, memberId)` 接受**顶层 `formal`** 与 **`meta.formal`** 两种位置,
|
|
332
|
+
在三条解析回执的路径上都被调用:`handleVerifier`(验证者)、`handleSolver`、`handleExplorer`
|
|
333
|
+
(工作轮)。验证者路径里的调用**先于**裁定:`defect` 必须先把记录降级,紧随其后的
|
|
334
|
+
`settleVerdict` 才会在 `require` 档把本次裁定正确地记为未定论。
|
|
335
|
+
- `decision` 的落库:`blocked` → 记录 `blocked` + `note`;`defect` → 见 9.2;`used` → `attempted` + `file`。
|
|
336
|
+
- **拒绝规则**(返回 `V2_INVALID_ARGUMENT`,并在活动日志里公告,**不留下任何记录**):
|
|
337
|
+
`blocked`/`defect` 的 `note` 为空;缺 `target`(`safeId('')` 会返回 `anon`,绝不允许凭空造记录);
|
|
338
|
+
`decision` 不在三值之内。回执通道**绝不抛异常**进调度循环:一次记账失败不该吞掉一次表决。
|
|
339
|
+
|
|
340
|
+
#### 9.2 `defect`:忠实性缺陷**不是**"命题为假"
|
|
341
|
+
|
|
342
|
+
`passed` 只保证"这段 Lean 代码通过了内核检查",不保证它说的就是命题想说的。表决者逐条核对后
|
|
343
|
+
发现形式化写窄了/写宽了/换了对象/漏了条件时,那是**形式化不合格**,不是命题被证伪:
|
|
344
|
+
|
|
345
|
+
- 若让表决者"发现偏差 → 投 0",框架记下的是"**该命题为假**"——用求真更严格的机制,
|
|
346
|
+
反而伪造出一个错误的否定结论。
|
|
347
|
+
- 若只记成 `blocked`,门禁会**放行**(`blocked` 本就允许定论),等于带着一个坏形式化去定论。
|
|
348
|
+
|
|
349
|
+
因此 `defect` 是独立的一档,**语义固定**为:
|
|
350
|
+
|
|
351
|
+
1. **表决者**:不得投 `1` 或 `0`;给一个严格介于 0 与 1 之间的值(记为弃权),在 `Reason` 里写清偏差,
|
|
352
|
+
并用回执 `formal:{decision:'defect', note:'<具体偏差>'}` 记录(`note` 必填)。
|
|
353
|
+
2. **框架**(`formalDefectDowngrade`):把形式化记录**降级为 `attempted`**(无论此前是 `passed`
|
|
354
|
+
还是 `blocked`)、清空 `proof`、**删除** `Verified/Lean/<id>.lean`(工作文件 `Formal/<id>.lean`
|
|
355
|
+
保留,代码不丢)、`note` 记入记录与 `Formal/TODO.md`、在活动日志里公告。
|
|
356
|
+
3. **`require` 档**:降级后 `formalGateOk` 为假 ⇒ 本次裁定**不定论**(`formal-required`),
|
|
357
|
+
对象进入「形式化待办」。修正形式化并重新跑通后再投票。
|
|
358
|
+
4. **唯一可以投 0 的情形**:表决者**独立于这份 Lean 代码**也能确定命题为假(`Reason` 必须写清独立理由)。
|
|
359
|
+
提示词里因此明确写着"发现任何偏差,不要投 0",并且**不再出现**"偏离 → 0"这类指令。
|
|
360
|
+
|
|
361
|
+
#### 9.3 两套 id 必须同时同步
|
|
362
|
+
|
|
363
|
+
验证记录以 **rId**(`r-pGate`、`r-q1-s0`)为键,而归档/索引/卡片读的可能是 **对象 id**(`pGate`)。
|
|
364
|
+
回执通道(`blocked` / `defect` / `used`)通过 `putFormalBothIds` 一次写全两侧:回执点名的那个 id、
|
|
365
|
+
它对应的另一侧、以及同一对象的其它验证别名(`r-<id>`、`r-<id>-s0`、`r-<id>-pf1`、`r-<id>-rf2`,
|
|
366
|
+
复用 `syncVerificationTarget` 的既有扫描)。id 解析只有一处 `formalObjectIdOf`(门禁合并查询
|
|
367
|
+
`formalGateRecord` 也用它),**不新造第三套映射**。只写一侧的后果是静默错位:`defect` 只写到验证 id 上,
|
|
368
|
+
对象记录仍是 `passed`,`formalGateRecord` 从对象侧读回 `passed`、卡片照旧写"Lean 通过"。
|
|
369
|
+
|
|
370
|
+
#### 9.4 提示词的三条硬要求
|
|
371
|
+
|
|
372
|
+
- **工具名一律写全称**:注入文本里只出现注册名 `vibe_math_lean_run` / `vibe_math_lean_archive` /
|
|
373
|
+
`vibe_math_lean_lib`;**不得**出现 `lean_run` / `lean_archive` / `lean_lib` 这类缩写——
|
|
374
|
+
那不是注册名,代理照抄会调用一个不存在的工具(工具自己返回的 `hint` 同样算注入文本)。
|
|
375
|
+
- **归档可复用定义/引理前必须先跑通**:`vibe_math_lean_archive run=true` 或先 `vibe_math_lean_run`;
|
|
376
|
+
跑不通的代码不得进入 `Formal/Lib` / `Formal/Proved`,否则"可复用库"会被不编译的定义污染。
|
|
377
|
+
- **工具链缺失时的出路必须写出来**:`LEAN_NOT_FOUND` 时把代码写下来归档,并在回执的 `note` 里写明
|
|
378
|
+
"宿主无 Lean 工具链"——这算显式阻塞原因,`require` 档可以据此放行,代理不会因为装不了 Lean 而卡死。
|
|
379
|
+
|
|
380
|
+
#### 9.5 人可读语料
|
|
381
|
+
|
|
382
|
+
`prompt-corpus-v2/formal-verify-v2.json` 与 `.md` 由套件落盘(契约 §10.10):框架**真正发出**的每条提示词
|
|
383
|
+
原文,工作区路径归一化为 `<WS>`、VibeMath 根归一化为 `<VIBEMATH>`,因此可 diff、不泄露本机路径。
|
|
384
|
+
覆盖 off 档(无任何 Lean 文字)、encourage/require 的表决初评与辩论、`passed` 后的忠实性分支、
|
|
385
|
+
工作轮的「顺手形式化」段落与 formal 回执契约。套件带 `V2_CORPUS_DIR` 覆盖;当 `V2_PLUGIN` 指向
|
|
386
|
+
变异副本(灵敏度探针)时语料写到临时目录,避免把变异文本写进仓库。
|
|
387
|
+
|
|
388
|
+
### 十、测试
|
|
321
389
|
|
|
322
390
|
`formal-verify-v2.test.mjs`(仓库根):用 **mock `subprocess` 服务**(假 Lean:文件中含
|
|
323
391
|
`sorry` 或 `-- FAIL` 时 exit 1,否则 exit 0)跑通真实代码路径,覆盖 `off` 无操作、
|
|
324
392
|
参数校验与运行中切换、`encourage` 对 review/debate/work 三类提示词的注入、
|
|
325
|
-
`passed`
|
|
326
|
-
|
|
327
|
-
|
|
393
|
+
`passed` 后切忠实性措辞(并断言**不含**"偏离 → 0"、不含缩写工具名)、三个工具(绿灯/红灯/缺文件/
|
|
394
|
+
越界/非 .lean/缺工具链/四种归档/索引重建、`blocked` 必须有 note)、`require` 门禁(命题与问题两条
|
|
395
|
+
收口点、搁置→形式化→放行、卡片记录形式化状态、`resume` 后不遗忘、`encourage` 不设门禁),
|
|
396
|
+
以及**回执通道的行为断言**(契约 §10.8/§10.9):把带 `formal:{decision:'blocked'|'defect', note}`
|
|
397
|
+
的真实回执喂给框架,断言记录真的落库(`blocked` → `blocked` 且两套 id 同步;`defect` → `attempted`
|
|
398
|
+
+ `proof` 清空 + `Verified/Lean/<id>.lean` 被删 + `Formal/TODO.md` 出现条目 + 公告),断言缺 `note`
|
|
399
|
+
被 `V2_INVALID_ARGUMENT` 拒绝且不落记录,断言 `defect` 之后 `require` 档**不得定论**(无 Verified 卡片、
|
|
400
|
+
命题布尔估计不变),并落盘人可读语料(§9.5)。
|
|
328
401
|
套件支持 `V2_PLUGIN` 环境变量指向变异副本,供灵敏度探针使用。
|