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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SPEC/README ↔ CODE TRACEABILITY (all four presets).
|
|
4
|
+
*
|
|
5
|
+
* This class of drift has produced real bugs here twice: a `/v4` usage string that advertised a
|
|
6
|
+
* `message` subcommand no branch implemented, and personas that never named registered tools. The
|
|
7
|
+
* rule is asymmetric on purpose:
|
|
8
|
+
*
|
|
9
|
+
* · a tool the SPEC/README advertises that the code does NOT register → FAIL (a documented
|
|
10
|
+
* capability that does not exist is a lie the agent will act on);
|
|
11
|
+
* · a tool the code registers that the spec never names → NOTE (documenting
|
|
12
|
+
* every member-only tool in the spec is not always desirable, but the gap must be visible);
|
|
13
|
+
* · the four Lean parameters must be documented in the spec AND the README AND accepted by the
|
|
14
|
+
* code — a parameter nobody documents cannot be discovered, and one nobody accepts cannot be set.
|
|
15
|
+
*
|
|
16
|
+
* Run: node audit-spec-traceability.mjs [--json]
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
19
|
+
import { fileURLToPath } from 'node:url'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
|
|
22
|
+
const HERE = fileURLToPath(new URL('./', import.meta.url))
|
|
23
|
+
const read = (rel) => (existsSync(join(HERE, rel)) ? readFileSync(join(HERE, rel), 'utf8') : null)
|
|
24
|
+
|
|
25
|
+
const PRESETS = [
|
|
26
|
+
{ tag: 'v2', js: 'vibe-math-v2/vibe-math-v2.js', spec: 'vibe-math-v2/实现方案.md', prefix: 'vibe_math_' },
|
|
27
|
+
{ tag: 'v3', js: 'vibe-math-v3/vibe-math-v3.js', spec: 'vibe-math-v3/实现方案.md', prefix: 'vibe_math_' },
|
|
28
|
+
{ tag: 'v4', js: 'vibe-math-v4/vibe-math-v4.js', spec: 'vibe-math-v4/实现方案.md', prefix: 'vibe_v4_' },
|
|
29
|
+
{ tag: 'v5', js: 'vibe-math-v5/vibe-math-v5.js', spec: 'vibe-math-v5/实现方案.md', prefix: 'vibe_v5_' },
|
|
30
|
+
]
|
|
31
|
+
// Tokens that look like tool names but are FILE names / namespace prose, not tools.
|
|
32
|
+
const NOT_A_TOOL = new Set([
|
|
33
|
+
'vibe_math_setting.json', 'vibe_math_installed.json', 'vibe_math_lean', 'vibe_math_lean_',
|
|
34
|
+
'vibe_v4_setting.json', 'vibe_v5_state', 'vibe_v5_state.json', 'vibe_v5_lean', 'vibe_v5_lean_',
|
|
35
|
+
'vibe_v4_lean', 'vibe_v4_lean_', 'vibe_math_state', 'vibe_math_state.json',
|
|
36
|
+
])
|
|
37
|
+
const LEAN_PARAMS = ['formalVerify', 'leanCommand', 'leanArgs', 'leanTimeoutMs']
|
|
38
|
+
|
|
39
|
+
const README = read('README.md') || ''
|
|
40
|
+
const findings = []
|
|
41
|
+
const notes = []
|
|
42
|
+
let passed = 0
|
|
43
|
+
const ok = (cond, label, detail) => {
|
|
44
|
+
if (cond) { passed++; return true }
|
|
45
|
+
findings.push(label + (detail ? ' — ' + detail : ''))
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
// The README documents ALL FOUR presets at once, and v2/v3 share the `vibe_math_` prefix, so a
|
|
49
|
+
// repo-wide token is legitimate if ANY preset registers it. The per-preset SPEC is checked strictly
|
|
50
|
+
// against that preset's own registry.
|
|
51
|
+
const ALL_TOOLS = new Set()
|
|
52
|
+
for (const P of PRESETS) {
|
|
53
|
+
const src = read(P.js)
|
|
54
|
+
if (!src) continue
|
|
55
|
+
for (const m of src.matchAll(/registerTool\(\s*'([A-Za-z0-9_]+)'/g)) ALL_TOOLS.add(m[1])
|
|
56
|
+
}
|
|
57
|
+
/** Collect `vibe_*` tokens that are used as TOOL names (not file names such as vibe_math_setting.json). */
|
|
58
|
+
function toolTokens(text) {
|
|
59
|
+
const out = new Set()
|
|
60
|
+
for (const m of text.matchAll(/\b(vibe_(?:math|v4|v5)_[A-Za-z0-9_]+)/g)) {
|
|
61
|
+
const after = text[m.index + m[0].length] || ''
|
|
62
|
+
if (after === '.') continue // a file name, e.g. vibe_math_setting.json
|
|
63
|
+
// A doc may name a tool precisely to say it does NOT exist ("v4 没有 vibe_v4_propose_verify 这个工具",
|
|
64
|
+
// "写了不存在的工具 …"). That is documentation of the fix, not a phantom capability — skip it, or
|
|
65
|
+
// the guard would forbid the very sentence that records the correction.
|
|
66
|
+
const lineStart = text.lastIndexOf('\n', m.index) + 1
|
|
67
|
+
const lineEnd = text.indexOf('\n', m.index)
|
|
68
|
+
const line = text.slice(lineStart, lineEnd === -1 ? text.length : lineEnd)
|
|
69
|
+
if (/没有|不存在|并非|不是工具|未注册|从未注册|无此|does not exist|no such|never registered|not a tool/i.test(line)) continue
|
|
70
|
+
out.add(m[1])
|
|
71
|
+
}
|
|
72
|
+
return out
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const P of PRESETS) {
|
|
76
|
+
const js = read(P.js)
|
|
77
|
+
const spec = read(P.spec)
|
|
78
|
+
if (!js || !spec) { findings.push(P.tag + ': missing plugin or spec file'); continue }
|
|
79
|
+
|
|
80
|
+
const codeTools = new Set([...js.matchAll(/registerTool\(\s*'([A-Za-z0-9_]+)'/g)].map((m) => m[1]))
|
|
81
|
+
ok(codeTools.size > 0, P.tag + ': the plugin registers tools at all')
|
|
82
|
+
|
|
83
|
+
// direction 1 (FAIL): anything the spec names as a tool must exist in THIS preset; anything the
|
|
84
|
+
// README names must exist in at least one preset (the README covers all four).
|
|
85
|
+
for (const [label, text, own] of [['实现方案', spec, true], ['README', README, false]]) {
|
|
86
|
+
for (const t of toolTokens(text)) {
|
|
87
|
+
if (NOT_A_TOOL.has(t)) continue
|
|
88
|
+
if (t.endsWith('_')) continue // a `vibe_x_*` wildcard placeholder
|
|
89
|
+
if (!t.startsWith(P.prefix)) continue
|
|
90
|
+
const known = own ? codeTools.has(t) : ALL_TOOLS.has(t)
|
|
91
|
+
if (!known) {
|
|
92
|
+
findings.push(P.tag + ' [' + label + ']: documents tool ' + t
|
|
93
|
+
+ (own ? ' but the plugin never registers it' : ' but NO preset registers it'))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// direction 2 (NOTE): registered but not documented anywhere
|
|
99
|
+
const md = spec + '\n' + README
|
|
100
|
+
const undocumented = [...codeTools].filter((t) => !md.includes(t)).sort()
|
|
101
|
+
if (undocumented.length) notes.push(P.tag + ': registered but not named in spec/README: ' + undocumented.join(', '))
|
|
102
|
+
|
|
103
|
+
// the four Lean parameters: documented in spec + README, and accepted by the code
|
|
104
|
+
for (const prm of LEAN_PARAMS) {
|
|
105
|
+
ok(spec.includes(prm), P.tag + ': 实现方案 documents ' + prm)
|
|
106
|
+
ok(README.includes(prm), P.tag + ': README documents ' + prm)
|
|
107
|
+
ok(js.includes(prm), P.tag + ': the plugin accepts ' + prm)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// the Lean tools must appear in the spec's tool table AND in the code
|
|
111
|
+
for (const t of [P.prefix + 'lean_run', P.prefix + 'lean_archive', P.prefix + 'lean_lib']) {
|
|
112
|
+
ok(codeTools.has(t), P.tag + ': registers ' + t)
|
|
113
|
+
ok(spec.includes(t), P.tag + ': 实现方案 names ' + t)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// the contract §7 requires an ACTIVE stop on timeout: `handle.terminate()`. Relying on the
|
|
117
|
+
// host's `graceMs` alone lets a runaway Lean process linger while the framework reports
|
|
118
|
+
// LEAN_TIMEOUT — three of four presets shipped that way, so this is now guarded statically.
|
|
119
|
+
ok(/terminate\s*\(/.test(js), P.tag + ': the Lean run path actively terminates on timeout (contract §7)')
|
|
120
|
+
|
|
121
|
+
// the contract §8 puts the `require` gate at the ONE choke point that writes a Verified card.
|
|
122
|
+
// A card writer reachable WITHOUT passing a gated function would be a gate bypass, i.e. the
|
|
123
|
+
// framework could conclude a verdict that `require` is supposed to withhold.
|
|
124
|
+
{
|
|
125
|
+
const cfg = {
|
|
126
|
+
v2: { writers: ['writeVerifiedCardIfNeeded', 'writeVerifiedProblemCardIfNeeded'], gate: /formalRequired\s*\(|formalGateOk\s*\(|formalVerdictDeferred\s*\(/ },
|
|
127
|
+
v3: { writers: ['writeVerifiedCardIfChanged', 'writeVerifiedPropositionCardIfNeeded', 'writeVerifiedProblemCardIfNeeded'], gate: /formalBlocksConclusion\s*\(/ },
|
|
128
|
+
v4: { writers: ['writeVerifiedCard'], gate: /formalGateOk\s*\(/ },
|
|
129
|
+
v5: { writers: ['writeVerifiedCard'], gate: /formalGateOk\s*\(/ },
|
|
130
|
+
}[P.tag]
|
|
131
|
+
const lines = js.split(/\r?\n/)
|
|
132
|
+
const fns = []
|
|
133
|
+
for (let i = 0; i < lines.length; i++) {
|
|
134
|
+
const m = /^(\s*)(?:async\s+)?function\s+([A-Za-z0-9_$]+)\s*\(/.exec(lines[i])
|
|
135
|
+
if (m) fns.push({ name: m[2], indent: m[1].length, start: i })
|
|
136
|
+
}
|
|
137
|
+
for (let k = 0; k < fns.length; k++) {
|
|
138
|
+
const f = fns[k]
|
|
139
|
+
let end = lines.length
|
|
140
|
+
for (const g of fns) if (g.start > f.start && g.indent <= f.indent) { end = g.start; break }
|
|
141
|
+
f.body = lines.slice(f.start, end).join('\n')
|
|
142
|
+
}
|
|
143
|
+
const byName = (n) => fns.find((f) => f.name === n)
|
|
144
|
+
for (const w of cfg.writers) {
|
|
145
|
+
const f = byName(w)
|
|
146
|
+
if (!f) { ok(false, P.tag + ' §8: the documented card writer ' + w + ' does not exist'); continue }
|
|
147
|
+
const selfGated = cfg.gate.test(f.body)
|
|
148
|
+
const callers = fns.filter((g) => g !== f && new RegExp('(^|[^A-Za-z0-9_$])' + w + '\\s*\\(').test(g.body))
|
|
149
|
+
const allCallersGatedOrRecursive = callers.length > 0 && callers.every((c) => cfg.gate.test(c.body) || fns.some((d) => cfg.gate.test(d.body) && new RegExp('(^|[^A-Za-z0-9_$])' + c.name + '\\s*\\(').test(d.body)))
|
|
150
|
+
ok(selfGated || allCallersGatedOrRecursive,
|
|
151
|
+
P.tag + ' §8: ' + w + ' is reachable only through a gated function (no gate bypass)',
|
|
152
|
+
'callers: ' + callers.map((c) => c.name).join(', '))
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
notes.push(P.tag + ': ' + codeTools.size + ' tools registered; spec ' + spec.length + 'B')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// cross-preset: the shared contract must name the four parameters too
|
|
160
|
+
const contract = read('docs/formal-verification.md') || ''
|
|
161
|
+
for (const prm of LEAN_PARAMS) ok(contract.includes(prm), 'contract documents ' + prm)
|
|
162
|
+
|
|
163
|
+
const out = { passed, failed: findings.length, findings, notes }
|
|
164
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(out, null, 2))
|
|
165
|
+
else {
|
|
166
|
+
console.log('-- spec/README ↔ code traceability --')
|
|
167
|
+
for (const n of notes) console.log(' note ' + n)
|
|
168
|
+
console.log('')
|
|
169
|
+
for (const f of findings) console.error(' FAIL ' + f)
|
|
170
|
+
console.log('')
|
|
171
|
+
console.log('TRACEABILITY: ' + passed + ' passed, ' + findings.length + ' failed')
|
|
172
|
+
}
|
|
173
|
+
process.exit(findings.length === 0 ? 0 : 1)
|
package/audit-v5-integrity.mjs
CHANGED
|
@@ -217,7 +217,7 @@ notes.push('composition rows: ' + v5rows.length + '; non-v4 package rows: ' + v5
|
|
|
217
217
|
const codeTools = new Set()
|
|
218
218
|
for (const m of raw.matchAll(/registerTool\(\s*'(vibe_v5_[a-z_]+)'/g)) codeTools.add(m[1])
|
|
219
219
|
// documented-but-wildcarded placeholders are not real tools
|
|
220
|
-
const IGNORE = new Set(['vibe_v5_', 'vibe_v5_record_'])
|
|
220
|
+
const IGNORE = new Set(['vibe_v5_', 'vibe_v5_record_', 'vibe_v5_lean_', 'vibe_v5_task_'])
|
|
221
221
|
for (const t of planTools) {
|
|
222
222
|
if (IGNORE.has(t)) continue
|
|
223
223
|
if (!codeTools.has(t)) findings.push('plan names tool ' + t + ' but the plugin never registers it')
|
|
@@ -335,8 +335,10 @@ notes.push('composition rows: ' + v5rows.length + '; non-v4 package rows: ' + v5
|
|
|
335
335
|
'verify', 'verify-debate', 'meeting', 'meeting-proposal', 'inbox-dm', 'inbox-voters', 'inbox-chat',
|
|
336
336
|
'inbox-office', 'inbox-assign', 'inbox-nudge', 'inbox-office-assign', 'inbox-office-nudge',
|
|
337
337
|
'notice', 'notice-claim', 'after-failure',
|
|
338
|
-
// the Lean formal-verification interaction must be reviewable by a human too
|
|
339
|
-
|
|
338
|
+
// the Lean formal-verification interaction must be reviewable by a human too — including
|
|
339
|
+
// the `require` gate wording and the state a member sees AFTER a fidelity defect withdrew
|
|
340
|
+
// a proof (both were missing from the first corpus, so nobody could read them).
|
|
341
|
+
'lean-work', 'lean-verify', 'lean-fidelity', 'lean-require', 'lean-after-defect']
|
|
340
342
|
for (const k of need) if (!kinds.has(k)) findings.push('the prompt corpus is missing a ' + k + ' prompt')
|
|
341
343
|
const all = (c.prompts || []).map(p => p.prompt + '\n' + (p.charter || '')).join('\n')
|
|
342
344
|
if (/你是 \?/.test(all)) findings.push('the prompt corpus contains a wrong-identity "你是 ?" brief')
|
|
@@ -46,9 +46,18 @@
|
|
|
46
46
|
|
|
47
47
|
### `off`(默认)—— 不额外进行任何要求
|
|
48
48
|
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
- **成员提示词**(按模式动态注入的那部分)里**不出现**任何 Lean 相关内容;验证流程、门禁、归档全部不变,
|
|
50
|
+
也不写入任何形式化状态。这是真正的"无操作",四套都有断言与探针守着。
|
|
51
|
+
- **回执通道在 `off` 档必须失效**:`formal` 字段本来就不在 `off` 档的回执契约里,所以一个残留/幻觉/被
|
|
52
|
+
引用的 `formal` 回执**不得**创建形式化记录(否则 `off` 就不是无操作了)。四个架构都必须在这个入口
|
|
53
|
+
上加 `formalOn()` 守卫。
|
|
54
|
+
- 三个 Lean 工具**仍然注册且可用**(注册是静态的,与既有 `ctx.effect` 纪律一致);`off` 只是不主动
|
|
55
|
+
向成员宣讲它们。代理/人主动调用时照常工作——**工具调用是刻意行为,回执字段不是**,这条区别就是
|
|
56
|
+
上一条守卫的理由。
|
|
57
|
+
- **主代理的 persona 是静态的,不受档位影响**:它**必须始终**文档化这三个工具与四个参数
|
|
58
|
+
(否则用户在 `off` 档根本发现不了这个开关,也就无法打开它)。"成员提示词里没有 Lean 文字"与
|
|
59
|
+
"persona 里写着这个能力"**不矛盾**,两者都由测试守着(前者见各 `formal-verify-vN` 的 `off` 小节,
|
|
60
|
+
后者见 `audit-persona-surface.test.mjs`)。
|
|
52
61
|
|
|
53
62
|
### `encourage` —— 鼓励但不强制
|
|
54
63
|
|
|
@@ -121,8 +130,8 @@
|
|
|
121
130
|
"status": "none" | "attempted" | "passed" | "blocked",
|
|
122
131
|
"file": "Formal/p-1.lean", // 工作文件(可为空)
|
|
123
132
|
"proof": "Verified/Lean/p-1.lean",// 归档证明(仅 passed)
|
|
124
|
-
"decision": "used" | "blocked",
|
|
125
|
-
"note": "…", // blocked 时必填:难度判断 / 阻塞原因
|
|
133
|
+
"decision": "used" | "blocked" | "defect", // 代理的显式判断(defect 见 §4.1)
|
|
134
|
+
"note": "…", // blocked / defect 时必填:难度判断 / 阻塞原因 / 具体偏差
|
|
126
135
|
"run": { "at": 0, "ok": true, "exitCode": 0, "ms": 0, "stdoutTail": "", "stderrTail": "" },
|
|
127
136
|
"updatedAt": 0
|
|
128
137
|
}
|
|
@@ -136,9 +145,35 @@
|
|
|
136
145
|
| `lean_run` 失败 | `none` → `attempted`(记录失败输出,供代理修复) |
|
|
137
146
|
| `lean_archive{kinds:'proof', target, from|content}` + 该文件最近一次运行 `ok` | → `passed`,写 `Verified/Lean/<id>.lean` |
|
|
138
147
|
| `lean_archive{kinds:'blocked', target, note}` | → `blocked`(`note` 必填) |
|
|
139
|
-
| 回执里 `formal:{target, decision:'blocked', note}` | → `blocked` |
|
|
148
|
+
| 回执里 `formal:{target, decision:'blocked', note}` | → `blocked`(`note` 必填) |
|
|
149
|
+
| **回执里 `formal:{target, decision:'defect', note}`** | **撤回 `passed`:→ `attempted`,清空 `proof`、删除 `Verified/Lean/<id>.lean`、把 `note` 写入记录与 `Formal/TODO.md`、公告**(`note` 必填) |
|
|
140
150
|
| 回执里 `formal:{target, decision:'used', file}` | → `attempted`(记录文件) |
|
|
141
151
|
|
|
152
|
+
### 4.1 `defect`:忠实性缺陷**不是**"命题为假"
|
|
153
|
+
|
|
154
|
+
`passed` 只保证"这段 Lean 代码通过了内核检查",**不保证它说的就是命题想说的**。当表决者逐条核对后
|
|
155
|
+
发现 Lean 代码与命题原文不一致(写窄了 / 写宽了 / 换了对象 / 漏了条件…),那是**形式化不合格**,
|
|
156
|
+
不是命题被证伪。两种混淆的后果都很严重:
|
|
157
|
+
|
|
158
|
+
- 若让表决者"发现偏差 → 投 0",框架记下的是"**该命题为假**";在 v5 的全 0 一致规则下,
|
|
159
|
+
一个写错的形式化会直接把命题写进 `Verified/` 并标注**假**——用来求真更严格的机制,
|
|
160
|
+
反而**伪造出一个错误的否定结论**。
|
|
161
|
+
- 若只把偏差记成 `blocked`,门禁会**放行**(`blocked` 本就允许定论),等于带着一个坏形式化去定论。
|
|
162
|
+
|
|
163
|
+
因此 `defect` 是独立的一档,语义固定为:
|
|
164
|
+
|
|
165
|
+
1. **表决者**:不得投 `1` 或 `0`;给一个严格介于 0 与 1 之间的值(记为弃权)并在 `Reason` 里写清偏差;
|
|
166
|
+
同时用回执 `formal:{decision:'defect', note:'<具体偏差>'}` 记录(`note` 必填)。
|
|
167
|
+
2. **框架**:把该对象的形式化记录**降级为 `attempted`**(无论此前是 `passed` 还是 `blocked`——都让位于
|
|
168
|
+
"形式化不合格,需重做")、清空 `proof`、删除 `Verified/Lean/<id>.lean`
|
|
169
|
+
(工作文件 `Formal/<id>.lean` 保留,代码不丢)、`note` 记入记录与 `Formal/TODO.md`、公告。
|
|
170
|
+
3. **`require` 档**:降级后 `formalGateOk` 为假,本次裁定**不定论**,对象进入「形式化待办」——
|
|
171
|
+
修正形式化并重新跑通后再投票。这正是"形式化不合格 ⇒ 重做",而不是"命题为假"。
|
|
172
|
+
`encourage` 档没有门禁,框架**仍然**撤回证明并记入待办,但**不得在提示词里承诺一个它无法强制的
|
|
173
|
+
"不定论"**;那里靠表决者自己的弃权(§6.1 第 ① 条)使表决无法得出布尔一致结论。
|
|
174
|
+
4. **唯一可以投 0 的情形**:表决者**独立于这份 Lean 代码**也能确定命题为假(并能给出独立理由)。
|
|
175
|
+
此时 `Reason` 必须写清独立理由,不得以"Lean 与命题不一致"作为投 0 的依据。
|
|
176
|
+
|
|
142
177
|
---
|
|
143
178
|
|
|
144
179
|
## 5. 工具(每个架构三个,前缀各自不同)
|
|
@@ -176,6 +211,22 @@
|
|
|
176
211
|
|
|
177
212
|
## 6. 提示词注入(在构造提示词时现算)
|
|
178
213
|
|
|
214
|
+
> **硬要求(四套一致,逐字级别的约束)**
|
|
215
|
+
> 1. **工具名一律写全称**(`<prefix>lean_run` / `<prefix>lean_archive` / `<prefix>lean_lib`)。
|
|
216
|
+
> 注入文本里**不得**出现 `lean_run` / `lean_archive` / `lean_lib` 这类缩写——那不是注册名,
|
|
217
|
+
> 代理照抄会调用一个不存在的工具(工具自己返回的 `hint` 字段同样算注入文本)。
|
|
218
|
+
> 2. **回执字段名必须与该架构真实契约一致**:v2/v3 的评审值字段是 `Result`,v4/v5 是 `verdict`。
|
|
219
|
+
> 写错字段名 = 那一票被静默丢弃。
|
|
220
|
+
> 3. **归档可复用定义/引理前必须先跑通**:`<prefix>lean_archive` 支持 `run:true`,
|
|
221
|
+
> 或先 `<prefix>lean_run`。跑不通的代码不得进入 `Formal/Lib` / `Formal/Proved`——
|
|
222
|
+
> 否则"可复用库"会被不编译的定义污染。
|
|
223
|
+
> 4. **工具链缺失时的出路必须写出来**:`LEAN_NOT_FOUND`(解析不到 `leanCommand`)与
|
|
224
|
+
> `NO_SUBPROCESS`(宿主没有 subprocess 服务)**都算"这台宿主上没有 Lean"**:把代码写下来并归档,
|
|
225
|
+
> 在 `note` 里写明原因;这算显式阻塞原因,`require` 档可以据此放行,代理不会因为装不了 Lean 而卡死。
|
|
226
|
+
> 提示词里应把**两个**错误码都点出来——只写 `LEAN_NOT_FOUND` 时,遇到 `NO_SUBPROCESS` 的代理
|
|
227
|
+
> 会以为自己遇到了另一种失败而反复重试。
|
|
228
|
+
> 5. **忠实性缺陷不得用 0 表达**(§4.1 第 4 条):只有独立于 Lean 代码也能确定命题为假时才投 0。
|
|
229
|
+
|
|
179
230
|
### 6.1 验证提示词
|
|
180
231
|
|
|
181
232
|
`encourage`:
|
|
@@ -184,10 +235,13 @@
|
|
|
184
235
|
【Lean 形式化验证(鼓励模式)】
|
|
185
236
|
· 请先判断该对象的**实现难度**:若能在可接受的工作量内形式化,优先用 Lean 写形式化代码并执行。
|
|
186
237
|
· 工具:<prefix>lean_run(执行)· <prefix>lean_archive(归档)· <prefix>lean_lib(查已有可复用库)
|
|
187
|
-
· 工作目录:<项目根>/Formal/(可复用定义放 <VibeMath 根>/Formal/Lib/,已证引理放 Formal/Proved/)
|
|
238
|
+
· 工作目录:<项目根>/Formal/(可复用定义放 <VibeMath 根>/Formal/Lib/,已证引理放 <VibeMath 根>/Formal/Proved/)
|
|
188
239
|
· **一旦 Lean 通过,你唯一需要确认的就是忠实性**:Lean 代码里的定义/对象/条件/假设/结论
|
|
189
240
|
是否与命题原文逐条一致。请把注意力放在这种核对上,而不是重新做一遍推导。
|
|
190
241
|
· 若判断不值得或无法形式化,可以不做,但请在回执的 formal 字段写明难度判断。
|
|
242
|
+
· 归档可复用定义/引理前先跑通(<prefix>lean_archive run=true 或先 <prefix>lean_run);跑不通不要入库。
|
|
243
|
+
· 宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在会诊/回执的 note 里写明
|
|
244
|
+
"宿主无 Lean 工具链"——这算显式阻塞原因,定论门禁可以据此放行。
|
|
191
245
|
```
|
|
192
246
|
|
|
193
247
|
`require`:同样内容,但"可以不做"改为"**必须**产出 Lean 形式化,或**必须**给出显式的阻塞原因",
|
|
@@ -198,13 +252,21 @@
|
|
|
198
252
|
(原因 formal-required)并进入「形式化待办」。
|
|
199
253
|
```
|
|
200
254
|
|
|
201
|
-
|
|
202
|
-
|
|
255
|
+
**忠实性分支(两种模式都加,只要模式非 off)**:如果该对象**已经** `formal.status === 'passed'`,
|
|
256
|
+
验证提示词把审查对象换成忠实性,并**明确禁止**把偏差写成"假":
|
|
203
257
|
|
|
204
258
|
```
|
|
205
|
-
· 该对象已有**通过的 Lean 形式化证明**(<proof
|
|
206
|
-
|
|
207
|
-
|
|
259
|
+
· 该对象已有**通过的 Lean 形式化证明**(<proof 路径>,最近运行 exit 0)。
|
|
260
|
+
**你不需要重新检查推导**。你的任务是**忠实性审查**:逐条核对 Lean 代码里的
|
|
261
|
+
定义 / 对象 / 条件 / 假设 / 结论是否与命题原文**完全一致**。
|
|
262
|
+
▸ 一致 → 投 <真值 1>。
|
|
263
|
+
▸ **发现任何偏差,不要投 <0>**:偏差只说明**形式化不合格**,不代表命题为假。此时请:
|
|
264
|
+
① 投票给一个严格介于 0 与 1 之间的值(记为弃权),并在理由里写清偏差;
|
|
265
|
+
② 用回执 `formal:{decision:'defect', note:'<具体偏差>'}` 记录它。框架会撤回这条证明的
|
|
266
|
+
「已通过」状态(降级为 attempted、删除归档证明、写入形式化待办);`require` 档下
|
|
267
|
+
本次裁定**不定论**,`encourage` 档**不得**声称框架会强制搁置(那里靠你的弃权阻止定论)。
|
|
268
|
+
修正形式化并重新跑通后再投票。
|
|
269
|
+
▸ 只有当你**独立于这份 Lean 代码**也能确定命题为假时,才投 <0>,并在理由里写清独立理由。
|
|
208
270
|
```
|
|
209
271
|
|
|
210
272
|
### 6.2 平时工作提示词(solver / explorer / 常驻 / method-keeper 等)
|
|
@@ -212,17 +274,25 @@
|
|
|
212
274
|
```
|
|
213
275
|
【顺手形式化(<模式>)】把你工作中常用或可能复用的对象、假设、新定义,
|
|
214
276
|
用 Lean 形式化定义并归档到全局可复用库(<prefix>lean_archive kind='def'),
|
|
215
|
-
已成立的引理归到 Formal/Proved/(kind='lemma');写之前先 <prefix>lean_lib 查重,避免重复定义。
|
|
277
|
+
已成立的引理归到 <VibeMath 根>/Formal/Proved/(kind='lemma');写之前先 <prefix>lean_lib 查重,避免重复定义。
|
|
278
|
+
归档前先跑通(<prefix>lean_run 或 lean_archive run=true):跑不通的定义不要进可复用库。
|
|
216
279
|
```
|
|
217
280
|
|
|
218
281
|
### 6.3 回执契约
|
|
219
282
|
|
|
220
|
-
非 `off`
|
|
283
|
+
非 `off` 模式时,在每轮回执契约里加入(**必须真的被框架解析并落库**——只把字段写进提示词而不实现
|
|
284
|
+
解析,等于让代理的难度判断静默消失;每个架构都必须有"回执 → 记录"的行为断言,不能只断言措辞):
|
|
221
285
|
|
|
222
286
|
```
|
|
223
|
-
"formal": {"target":"p-x","decision":"used|blocked","file":"Formal/p-x.lean","note":"
|
|
287
|
+
"formal": {"target":"p-x","decision":"used|blocked|defect","file":"Formal/p-x.lean","note":"难度判断/阻塞原因/具体偏差"},
|
|
224
288
|
```
|
|
225
289
|
|
|
290
|
+
- `decision='blocked'` 与 `decision='defect'` 时 `note` 必填,否则整条记录被拒绝(返回该架构的
|
|
291
|
+
`*_INVALID_ARGUMENT`)。
|
|
292
|
+
- `decision='defect'` 的落库见 §4.1:**降级 + 删除归档证明 + 写入待办**。
|
|
293
|
+
- 这些字段必须出现在**该架构每一类会被表决者/研究者读到的回执契约**里(v2 的初评与辩论两条路径、
|
|
294
|
+
v3 的初评/辩论/工作轮、v4 的验证与常规/心跳轮、v5 的回执契约与心跳轮)。
|
|
295
|
+
|
|
226
296
|
---
|
|
227
297
|
|
|
228
298
|
## 7. Run 语义(实现要点)
|
|
@@ -271,22 +341,34 @@
|
|
|
271
341
|
|
|
272
342
|
### 9.2 `<VibeMath 根>/Formal/Lib/Index.md`
|
|
273
343
|
|
|
344
|
+
**必需列**:`名称 | 文件 | 类别 | 摘要`。
|
|
345
|
+
**可选列**:若该架构为库文件保留运行记录,可增加 `最近运行`(v3 就是这样做的,v2/v4/v5 没有);
|
|
346
|
+
**没有记录就不许加这一列、更不许写假数据**——"说得出这个文件还编不编得过"是有价值的信息,
|
|
347
|
+
但编一个不说实话的运行状态比没有更糟。
|
|
348
|
+
|
|
274
349
|
```markdown
|
|
275
350
|
# 可复用 Lean 定义库(跨项目)
|
|
276
|
-
| 名称 | 文件 | 类别 | 摘要 | 最近运行 |
|
|
277
|
-
|
|
351
|
+
| 名称 | 文件 | 类别 | 摘要 | 最近运行 | ← 最后一列可选
|
|
352
|
+
|---|---|---|---|---|---|
|
|
278
353
|
| ZMod5 | Lib/ZMod5.lean | def | 模 5 剩余类与基本引理 | ok |
|
|
279
354
|
```
|
|
280
355
|
|
|
281
356
|
### 9.3 `<VibeMath 根>/Formal/Proved/Index.md`
|
|
282
357
|
|
|
358
|
+
**必需列**:`名称 | 文件 | 陈述`。
|
|
359
|
+
**可选列**:`依赖`(仅当该架构记录依赖关系时)、`最近运行`(同上)、`类别`(v2/v4/v5 用它标注
|
|
360
|
+
`lemma`;v3 不加,因为整张表都是引理)。
|
|
361
|
+
|
|
283
362
|
```markdown
|
|
284
363
|
# 已成立的 Lean 命题 / 引理(机器已核对,可跨项目复用)
|
|
285
|
-
| 名称 | 文件 | 陈述 | 依赖 | 最近运行 |
|
|
286
|
-
|
|
364
|
+
| 名称 | 文件 | 陈述 | 依赖 | 最近运行 | ← 后两列可选
|
|
365
|
+
|---|---|---|---|---|---|
|
|
287
366
|
| pell_sq_odd | Proved/pell_sq_odd.lean | … | ZMod5 | ok |
|
|
288
367
|
```
|
|
289
368
|
|
|
369
|
+
> 三份索引都是**给人/代理看的摘要**,不是权威状态(权威状态在对象记录/会话投影里)。
|
|
370
|
+
> 跨架构只强制"必需列",因为强行对齐可选列会逼某个架构去记录它并不维护的数据。
|
|
371
|
+
|
|
290
372
|
---
|
|
291
373
|
|
|
292
374
|
## 10. 测试要求(每个架构都要有)
|
|
@@ -309,6 +391,25 @@
|
|
|
309
391
|
反向:persona 里的每个 `vibe_*` 名字必须真的注册。`audit-persona-sensitivity.mjs` 用变异副本
|
|
310
392
|
证明这套断言会变红。**教训**:本特性首版在 v2/v3/v4 上"工具已注册、persona 从未列出",
|
|
311
393
|
而当时所有既有套件全绿——因为 e2e 套件直接 `apply(ctx)`,从不加载 YAML。
|
|
394
|
+
8. **回执通道必须是行为断言,不是措辞断言**(放进各架构的 `formal-verify-vN.test.mjs`):
|
|
395
|
+
构造一条带 `formal:{decision:'blocked'|'defect', note}` 的**代理回执**喂给框架,断言记录真的落库
|
|
396
|
+
(`blocked` → `blocked`;`defect` → `attempted` + `proof` 清空 + 归档文件被删 + 待办条目出现)。
|
|
397
|
+
**教训**:v2 首版只在提示词里写了"请在回执的 formal 字段写明难度判断",框架从不解析它;
|
|
398
|
+
而套件只断言"那句话存在",于是 177 条断言全绿却守着一个**死通道**——这正是
|
|
399
|
+
`AUDIT-CHECKLIST.md` §2.2 说的"只断言包含某些关键词"。
|
|
400
|
+
9. **忠实性语义必须有断言**(四套都要):断言注入文本**不含**"偏离 → 0"这类把形式化缺陷等同命题为假
|
|
401
|
+
的指令,且含"不要投 0 / 记为形式化不合格 / 走待办"的要求;并断言 `defect` 路径真的不得定论
|
|
402
|
+
(`require` 档下对象留在未定论 + `Formal/TODO.md`)。
|
|
403
|
+
10. **每个架构都要有人可读的 Lean 提示词语料**(`prompt-corpus-vN/`,随包发布):至少覆盖
|
|
404
|
+
`off` 不出文本、`encourage`、**`require`**、`passed` 忠实性分支、以及平时工作轮的"顺手形式化";
|
|
405
|
+
语料必须把工作区与 VibeMath 根**归一化为 `<WS>` / `<VIBEMATH>`**(大小写与分隔符无关——
|
|
406
|
+
Windows 下 `os.tmpdir()` 的大小写可能与插件渲染的不同),并把**时间戳归一化为 `<TIME>`**,
|
|
407
|
+
保证逐字节确定性、可 diff、不泄露本机路径。**教训**:首版只有 v3/v5 有语料,v2/v4 的 Lean
|
|
408
|
+
提示词只能翻源码;`require` 档文本不在任何语料里;v5 语料既有绝对临时路径又有时间戳,
|
|
409
|
+
每跑一次都变。
|
|
410
|
+
11. **提示词硬要求的探针**(放进 `audit-formal-sensitivity.mjs`):把注入文本里的工具名改成缩写
|
|
411
|
+
(`lean_archive`)、删掉 `require` 档的要求段落、把忠实性分支改回"偏离 → 0"——三者都必须让
|
|
412
|
+
对应套件**变红**。
|
|
312
413
|
|
|
313
414
|
---
|
|
314
415
|
|
|
@@ -317,5 +418,7 @@
|
|
|
317
418
|
- **不内置 Lean**:本框架不安装工具链、不下载依赖。工具链不存在时优雅降级(记录 `LEAN_NOT_FOUND`)。
|
|
318
419
|
- **不判"忠实性"**:忠实性由代理/人审查并投票决定;框架只负责把审查焦点**换成**忠实性
|
|
319
420
|
(因为证明正确性已由内核保证)。框架不会假装自己能判断 Lean 代码是否对应命题。
|
|
421
|
+
- **`defect` 也不是框架的判断**:框架不判断 Lean 代码是否忠实,只提供"表决者认定不忠实时"的
|
|
422
|
+
一档落库语义(§4.1)——**降级 + 待办 + 不定论**,而不是把它记成"命题为假"。
|
|
320
423
|
- **不把 Lean 通过等同于"命题为真"**:`passed` 只表示"形式化代码通过内核检查",
|
|
321
424
|
该代码是否忠实于命题仍需 m 票审查。这正是 §0 表格里"审查对象变化"的含义。
|
|
@@ -186,10 +186,11 @@ const ccw = (IN_R - IN_L - 2 * 22) / 3
|
|
|
186
186
|
chips.forEach((c, i) => {
|
|
187
187
|
card(IN_L + (i % 3) * (ccw + 22), i < 3 ? 620 : 708, ccw, 78, c, { stroke: C.frame, fs: 12.5, sfs: 10.5 })
|
|
188
188
|
})
|
|
189
|
-
card(IN_L, 796, IN_R - IN_L,
|
|
189
|
+
card(IN_L, 796, IN_R - IN_L, 90, [
|
|
190
190
|
'Lean 形式化验证(可调参数 formalVerify = off / encourage / require)',
|
|
191
191
|
'encourage:验证时按实现难度决定是否形式化;require:真/假结论必须先有「Lean 通过」或显式阻塞记录',
|
|
192
192
|
'★ 一旦 Lean 通过,审查对象就变了:不再是「推导对不对」,而是「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」',
|
|
193
|
+
'★ 忠实性缺陷(decision=defect)≠ 命题为假:撤回「已通过」+ 撤回归档证明 + 进「形式化待办」,绝不记成 0/假',
|
|
193
194
|
'归档:证明 → Verified/Lean/<对象>.lean|可复用定义 → VibeMath/Formal/Lib/|已证引理 → VibeMath/Formal/Proved/',
|
|
194
195
|
], { stroke: C.gate, fs: 12.5, sfs: 10.5 })
|
|
195
196
|
card(IN_L, 880, IN_R - IN_L, 38, [
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 测试与脚本耗时基线(决定每次跑什么、怎么跑)
|
|
2
|
+
|
|
3
|
+
> **为什么要写下来**:这套仓库的测试耗时极不均匀(一个套件 100 s,大多数不到 2 s;探针脚本要
|
|
4
|
+
> 把同一套件跑十几遍)。不看数据就会犯两种错:要么每次都全量顺序跑(浪费 30 分钟),要么为了
|
|
5
|
+
> 省时间去砍测例(降低覆盖)。**先看基线,再选策略**;每次跑完把实测时间跟本表对一下,偏差大
|
|
6
|
+
> 就更新本表。
|
|
7
|
+
|
|
8
|
+
## 1. 怎么跑(并行是默认)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
node run-tests.mjs # 全部 *.test.mjs,并行(并发 = min(4, CPU 核数))
|
|
12
|
+
node run-tests.mjs --only formal # 只跑名字含 formal 的套件
|
|
13
|
+
node run-tests.mjs --concurrency=6 # 手动指定并发
|
|
14
|
+
node audit-formal-sensitivity.mjs # 49 条不变式探针,并行(--concurrency=N / --only=<preset> / --list)
|
|
15
|
+
node audit-persona-sensitivity.mjs # 11 条提示词面探针(串行,本身只要几秒)
|
|
16
|
+
node audit-prompt-invariants.mjs # 静态:四套的提示词/工具面不变式(< 0.1s)
|
|
17
|
+
node audit-prompt-invariants.mjs --self-probe # 证明上面那 145 条不变式真的会变红(5 个自探针)
|
|
18
|
+
node audit-spec-traceability.mjs # 静态:规格/README ↔ 代码可追溯(< 0.1s)
|
|
19
|
+
node audit-v5-integrity.mjs # 静态:v5 完整性/理念门禁(≈3 s)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
两个并行 runner 都会打印**每项耗时 + 汇总(wall / sum / speed-up / 最慢几项)**。跑完请读这几行。
|
|
23
|
+
|
|
24
|
+
## 2. 基线(本机:4 核 / 8 GB,Windows,实测)
|
|
25
|
+
|
|
26
|
+
| 脚本 | 串行(sum) | 并行(wall) | 实测输出 |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `run-tests.mjs`(23 个套件) | 221.5 s | **111.5 s**(并发 4,speed-up x1.99) | 关键路径 = `e2e-v4-fixes` 98.1 s |
|
|
29
|
+
| `audit-formal-sensitivity.mjs`(49 探针) | 612.0 s | **154.6 s**(并发 4,speed-up x3.96) | 关键路径 = 12 个 v2 探针(每个 ≈32 s) |
|
|
30
|
+
| `audit-persona-sensitivity.mjs`(11 探针) | ≈ 5 s | — | 本身很快,不需要并行 |
|
|
31
|
+
| `audit-prompt-invariants.mjs`(145 条) | 0.3 s | — | 静态 |
|
|
32
|
+
| `audit-prompt-invariants.mjs --self-probe`(5 探针) | 1.5 s | — | 每个探针 = 一次自我重跑(0.3 s) |
|
|
33
|
+
| `audit-spec-traceability.mjs`(91 条) | 0.3 s | — | 静态 |
|
|
34
|
+
| `audit-v5-integrity.mjs` | ≈ 3 s | — | 静态审计 |
|
|
35
|
+
| `prompt-v5-integrity.test.mjs` | 1.6 s | — | 虚拟时钟下生成 v5 语料(语料字节稳定) |
|
|
36
|
+
|
|
37
|
+
> 优化前:全量回归 ≈ 5.5 min(串行,`formal-verify-v2` 单独 186 s);
|
|
38
|
+
> 探针脚本 ≈ **38 min**(49 条串行,其中 12 条 × `formal-verify-v2` 162 s)。
|
|
39
|
+
> 现在:**1.9 min / 2.6 min**。
|
|
40
|
+
|
|
41
|
+
单套件耗时(并行时的关键路径按此排序):
|
|
42
|
+
|
|
43
|
+
| 套件 | 耗时 | 备注 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `e2e-v4-fixes.test.mjs` | **≈ 98 s** | 9 个用例是**轮次采样**型(如 T13 采样 400 轮、T19/T25 多轮);时间 ≈ 轮数 × 框架自身的 40 ms 计时粒度 |
|
|
46
|
+
| `formal-verify-v2.test.mjs` | **≈ 36 s** | 曾为 186 s:见 §3 |
|
|
47
|
+
| `e2e-regression.test.mjs` | ≈ 14 s | |
|
|
48
|
+
| `e2e-business.test.mjs` | ≈ 13 s | |
|
|
49
|
+
| `e2e-d9-d13.test.mjs` | ≈ 13 s | |
|
|
50
|
+
| `e2e-v3.test.mjs` | ≈ 12 s | |
|
|
51
|
+
| `formal-verify-v3.test.mjs` | ≈ 12 s | |
|
|
52
|
+
| 其余 16 个 | ≤ 6 s | 其中 8 个 < 1 s |
|
|
53
|
+
|
|
54
|
+
## 3. 已经做过的优化(别再重复踩)
|
|
55
|
+
|
|
56
|
+
1. **v2 套件 186 s → 32 s**(5.8×,断言数不变 261):
|
|
57
|
+
- 插件用 `setInterval(..., 1000)` 轮询调度器,套件的每次 `tick()` 都得等满 1 秒;
|
|
58
|
+
套件现在**只把 `setInterval` 快进到 25 ms**(自己的 `sleep` 用 `setTimeout`,不受影响),
|
|
59
|
+
插件内部"该不该 tick"仍按真实 200 ms 下限判断,**生产代码零改动**;
|
|
60
|
+
- `verifyWithDebate` 的 16 次循环**从不提前退出**(判据是 `autoDone` 之类永远不会发生的条件),
|
|
61
|
+
于是每次调用都烧满 16×1.3 s ≈ 21 s。现在按"连续 3 轮没有新 followup"提前退出。
|
|
62
|
+
2. **v4-fixes 的 12 个轮询循环加了"安静即停"**(105 s → 98 s):原判据
|
|
63
|
+
`autoDone || running===false` 对活着的 run 永远不成立,循环只是空转;
|
|
64
|
+
现在连续 100 次无待答 followup 就停(`V4_IDLE_POLLS` 可调)。
|
|
65
|
+
**再往下压就要砍采样深度了**——那 9 个慢用例(T13/T19/T22/T23/T25/T27/T2/T9/T20)是在
|
|
66
|
+
观察"多轮之后某个指令**没有**泄漏/重复",轮数是它们的不变式本体,不要再动。
|
|
67
|
+
3. **两个 runner 并行**(本轮新增):探针 38 min → 2.6 min(sum 612 s,wall 154.6 s,x3.96);
|
|
68
|
+
全量回归 5.5 min → 1.9 min(sum 221.5 s,wall 111.5 s,x1.99)。
|
|
69
|
+
4. **虚拟时钟**(`prompt-v5-integrity.test.mjs`,本轮新增):v5 研究所由 `ctx.timeout` + `Date.now()`
|
|
70
|
+
驱动,真实时钟下"哪个成员被心跳/会议唤醒"取决于负载与毫秒差 → **随包语料每跑一次都变**(无法 diff,
|
|
71
|
+
真实缺陷会被淹没)。套件现在把 `ctx.timeout` 接到**虚拟时钟**、`sleep(n)` 推进虚拟时间:
|
|
72
|
+
套件 **7–9 s → 1.7 s**,且语料连续 6 次运行**字节一致**。语料写入端另加**全序排序**
|
|
73
|
+
(kind → owner → prompt),使文件成为"记录集合"的纯函数——只按 kind 排序时,同 kind 内仍会随
|
|
74
|
+
异步 drain 顺序变化(真实事故:两条会议提示词顺序互换)。
|
|
75
|
+
> 想给别的套件套用同一手法前请注意:若套件的等待助手用 `Date.now()` 做**超时判据**、又用
|
|
76
|
+
> `setInterval` 轮询(例如 `e2e-v4-fixes.test.mjs` 的 `waitFor`),冻结时钟会让判据永不超时;
|
|
77
|
+
> 那种情况必须连**轮询定时器**一起虚拟化,不能只改 `sleep`。
|
|
78
|
+
|
|
79
|
+
## 4. 并行安全(为什么可以并发)
|
|
80
|
+
|
|
81
|
+
- 每个套件/探针都自建 `mkdtempSync` 工作区,互不共享状态;
|
|
82
|
+
- **会写语料的套件必须给不同的语料目录**:`V2_CORPUS_DIR` / `V3_CORPUS_DIR` / `V4_CORPUS_DIR` /
|
|
83
|
+
`V5_CORPUS_DIR`。探针 runner 为**每个探针**分配独立目录,否则同一套件的并发实例会互相覆盖语料;
|
|
84
|
+
- `audit-persona-sensitivity.mjs` 用 `PERSONA_ROOT` 指向变异副本,且在覆盖模式下**不写**语料;
|
|
85
|
+
- `audit-prompt-invariants.mjs --self-probe` 用 `PROMPT_INVARIANTS_MUTATE`(JSON `[rel, from, to]`)
|
|
86
|
+
在**内存里**变异一个文件并自我重跑,**不碰磁盘**,因此可与任何东西并发;
|
|
87
|
+
变异串里不要用 NUL 分隔(环境变量不允许 NUL 字节)。
|
|
88
|
+
|
|
89
|
+
## 5. 策略建议(按目的选最小代价的组合)
|
|
90
|
+
|
|
91
|
+
| 目的 | 跑什么 | 预期 |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| 改了某个架构的插件 | `node run-tests.mjs --only <vN>` + `node audit-formal-sensitivity.mjs --only=vN` | 30 s – 2 min |
|
|
94
|
+
| 改了提示词/人设 | `node run-tests.mjs --only persona --only prompt` + `node audit-persona-sensitivity.mjs` | ≈ 15 s |
|
|
95
|
+
| **改了任何工具的参数 schema / 参数处理** | `node audit-prompt-invariants.mjs --self-probe` + `node run-tests.mjs --only formal` | ≈ 40 s(v2 套件占大头) |
|
|
96
|
+
| 改了共享契约 / 发版前 | `node run-tests.mjs` + `node audit-formal-sensitivity.mjs` + `node audit-persona-sensitivity.mjs` + `node audit-prompt-invariants.mjs --self-probe` + `node audit-spec-traceability.mjs` + `node audit-v5-integrity.mjs` | ≈ 4.5 min |
|
|
97
|
+
| 只想快速看提示词/文档有没有漂移 | `node audit-prompt-invariants.mjs && node audit-spec-traceability.mjs` | **< 0.5 s** |
|
|
98
|
+
| 只想知道"快不快" | `node run-tests.mjs --json` | 读 `wallSeconds` / `slowest` |
|
|
99
|
+
|
|
100
|
+
**每次跑完都要看那几行 timing**:如果某个套件突然比基线慢很多,先怀疑新增的固定等待,
|
|
101
|
+
再怀疑它是否在等一个永远不会发生的条件(这正是 v2 套件 186 s 的成因)。
|