dsh-vibe-math 2.3.1 → 2.3.3
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 +59 -0
- package/README.md +16 -6
- package/RELEASE-NOTES-2.3.2.md +145 -0
- package/RELEASE-NOTES-2.3.3.md +115 -0
- package/audit-formal-sensitivity.mjs +11 -2
- package/audit-prompt-invariants.mjs +459 -0
- package/audit-spec-traceability.mjs +193 -0
- package/docs/formal-verification.md +37 -13
- package/docs/generate_framework_diagram_v5.mjs +2 -1
- package/docs/test-timing.md +32 -10
- package/formal-verify-v2.test.mjs +288 -2
- package/formal-verify-v3.test.mjs +230 -4
- package/formal-verify-v4.test.mjs +205 -5
- package/formal-verify-v5.test.mjs +114 -4
- package/installer.js +3 -1
- package/package.json +6 -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 +164 -44
- package/prompt-corpus-v2/formal-verify-v2.md +1374 -44
- package/prompt-corpus-v3/formal-verify-v3.json +236 -128
- package/prompt-corpus-v3/formal-verify-v3.md +1509 -400
- 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 +163 -38
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +58 -6
- package/vibe-math-v3/vibe-math-v3.js +95 -25
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +8 -7
- 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 +37 -14
- 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 +45 -14
- 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
|
@@ -60,7 +60,16 @@ const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '')
|
|
|
60
60
|
// MOCK HOST — the shape selfdrive-v4 / e2e-v4-fixes use, plus a fake Lean
|
|
61
61
|
// ===============================================================
|
|
62
62
|
let toolchainAvailable = true
|
|
63
|
+
// A host that exposes NO `subprocess` service at all (docs §7 → NO_SUBPROCESS, and §6 hard rule 4
|
|
64
|
+
// requires the injected guidance to name that code so the resident records a blocker instead of
|
|
65
|
+
// retrying forever).
|
|
66
|
+
let subprocessAvailable = true
|
|
67
|
+
// A stub host whose shell EXITS 0 WITHOUT deleting anything (a permissions quirk / a stub host).
|
|
68
|
+
// Used by §13 to prove the withdrawal is not a best-effort delete: the framework must confirm the
|
|
69
|
+
// file is gone through the fs service and fall back to overwriting it with a withdrawal notice.
|
|
70
|
+
let shellDeletesFiles = true
|
|
63
71
|
const leanRuns = [] // every spawn the framework made, for cwd/argv assertions
|
|
72
|
+
const terminated = [] // files whose handle the framework actively terminate()d (docs §7)
|
|
64
73
|
const shellCalls = [] // every platform-shell script (mkdir at mount, Remove-Item on defect)
|
|
65
74
|
|
|
66
75
|
function makeSubprocess() {
|
|
@@ -81,8 +90,8 @@ function makeSubprocess() {
|
|
|
81
90
|
const script = String(spec.argv[spec.argv.length - 1] || '')
|
|
82
91
|
shellCalls.push(script)
|
|
83
92
|
const m = script.match(/-LiteralPath\s+'((?:[^']|'')*)'/)
|
|
84
|
-
if (/Remove-Item/.test(script) && m) rmSync(m[1].replace(/''/g, "'"), { force: true })
|
|
85
|
-
if (/^rm -f /.test(script)) for (const q of script.slice(6).match(/'[^']*'/g) || []) rmSync(q.slice(1, -1), { force: true })
|
|
93
|
+
if (shellDeletesFiles && /Remove-Item/.test(script) && m) rmSync(m[1].replace(/''/g, "'"), { force: true })
|
|
94
|
+
if (shellDeletesFiles && /^rm -f /.test(script)) for (const q of script.slice(6).match(/'[^']*'/g) || []) rmSync(q.slice(1, -1), { force: true })
|
|
86
95
|
return {
|
|
87
96
|
done: Promise.resolve({ exitCode: 0, signal: null }),
|
|
88
97
|
collected: { stdout: { readFrom: () => ({ text: '', nextOffset: 0, lossy: false }) }, stderr: { readFrom: () => ({ text: '', nextOffset: 0, lossy: false }) } },
|
|
@@ -96,6 +105,16 @@ function makeSubprocess() {
|
|
|
96
105
|
const text = existsSync(file) ? readFileSync(file, 'utf8') : ''
|
|
97
106
|
const bad = /sorry|-- FAIL/.test(text)
|
|
98
107
|
leanRuns.push({ argv: spec.argv.slice(), file, cwd: spec.cwd, graceMs: spec.graceMs, stdio: spec.stdio })
|
|
108
|
+
// A HANG file never settles and never exits: the ONLY thing that can end it is the framework's
|
|
109
|
+
// own timeout calling handle.terminate() (docs §7). Records each terminate so the suite can
|
|
110
|
+
// assert the timeout path is ACTIVE, not merely reported.
|
|
111
|
+
if (/-- HANG/.test(text)) {
|
|
112
|
+
return {
|
|
113
|
+
done: new Promise(() => {}),
|
|
114
|
+
collected: { stdout: { readFrom: () => ({ text: '', nextOffset: 0, lossy: false }) }, stderr: { readFrom: () => ({ text: '', nextOffset: 0, lossy: false }) } },
|
|
115
|
+
terminate() { terminated.push(file) },
|
|
116
|
+
}
|
|
117
|
+
}
|
|
99
118
|
const stdout = bad ? '' : 'ok\n'
|
|
100
119
|
const stderr = bad ? 'error: declaration uses sorry\n' : ''
|
|
101
120
|
return {
|
|
@@ -116,7 +135,7 @@ function makeHost() {
|
|
|
116
135
|
const subprocess = makeSubprocess()
|
|
117
136
|
let ROOT
|
|
118
137
|
const ctx = {
|
|
119
|
-
get(name) { return name === 'subprocess' ? subprocess : undefined },
|
|
138
|
+
get(name) { return name === 'subprocess' && subprocessAvailable ? subprocess : undefined },
|
|
120
139
|
on(e, fn) { (listeners[e] = listeners[e] || []).push(fn) },
|
|
121
140
|
effect(fn) { const d = fn(); return () => { if (typeof d === 'function') d() } },
|
|
122
141
|
logger: { info() {}, warn() {}, error() {} },
|
|
@@ -273,6 +292,9 @@ const A = await establish()
|
|
|
273
292
|
assert(!/Lean/.test(all), 'no founding/round prompt mentions Lean in off mode')
|
|
274
293
|
assert(!/顺手形式化|Lean 通过|忠实性审查|vibe_v4_lean/.test(all), 'no founding/round prompt carries any Lean-formalization text in off mode')
|
|
275
294
|
assert(all.length > 0, 'sanity: the scan actually saw the founding prompts')
|
|
295
|
+
// `off` must not CREATE Lean state at all: the feature's durable record file must not appear
|
|
296
|
+
// for a session that never used the feature (v3's suite pins the same invariant).
|
|
297
|
+
assert(!existsSync(join(A.projectRoot, 'State', 'formal.json')), '★ off mode creates NO State/formal.json (a TRUE no-op, not just an empty record store)')
|
|
276
298
|
}
|
|
277
299
|
// an off-mode verification must behave exactly as before: unanimous 真 → Verified/, no formal record
|
|
278
300
|
const offSettled = await proposeAndVote(A, 'p-off', {
|
|
@@ -289,6 +311,39 @@ assert(!/顺手形式化/.test(await A.prompts('normal', 'r-1')), 'the work prom
|
|
|
289
311
|
assert(['vibe_v4_lean_run', 'vibe_v4_lean_archive', 'vibe_v4_lean_lib'].every(n => !!A.toolRegs.find(t => t.name === n)),
|
|
290
312
|
'the three Lean tools are registered in every mode (registration is static)')
|
|
291
313
|
|
|
314
|
+
// ★ The mode switch must be REACHABLE THROUGH THE TOOL SCHEMA (2.3.2 defect D1) ──────────────
|
|
315
|
+
// Every tool schema here is closed (`additionalProperties:false`), so a key the schema does not
|
|
316
|
+
// advertise is REJECTED by any schema-validating provider. v3 shipped 2.3.0/2.3.1 with all four Lean
|
|
317
|
+
// parameters missing from the set-params schema while every assertion in this file stayed green —
|
|
318
|
+
// because the suite calls the handler DIRECTLY and never inspects the registered schema. The feature
|
|
319
|
+
// could not be switched on at all through the tool interface.
|
|
320
|
+
{
|
|
321
|
+
const setSpec = A.toolRegs.find((t) => t.name === 'vibe_v4_set')
|
|
322
|
+
assert(!!setSpec, "vibe_v4_set is registered")
|
|
323
|
+
assert(setSpec.parameters && setSpec.parameters.type === 'object' && setSpec.parameters.additionalProperties === false,
|
|
324
|
+
'★ vibe_v4_set publishes a CLOSED object schema (an unlisted key is rejected, so the schema IS the contract)')
|
|
325
|
+
for (const k of ['formalVerify', 'leanCommand', 'leanArgs', 'leanTimeoutMs']) {
|
|
326
|
+
assert(Object.prototype.hasOwnProperty.call(setSpec.parameters.properties, k),
|
|
327
|
+
'★ the registered schema advertises ' + k + ' (every other surface documents it; a schema that omits it makes the switch unreachable)')
|
|
328
|
+
}
|
|
329
|
+
assert(JSON.stringify(setSpec.parameters.properties.formalVerify.enum) === JSON.stringify(['off', 'encourage', 'require']),
|
|
330
|
+
'the schema narrows formalVerify to the three real modes (a typo must not be a fourth)')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// A stray `formal` reply in OFF mode must be INERT (finding #1): the reply contract does not offer
|
|
334
|
+
// the field there, so honouring it would contradict "off is a TRUE no-op". The TOOLS stay usable.
|
|
335
|
+
{
|
|
336
|
+
await A.callTool('vibe_v4_record_proposition', { id: 'p-offreply', title: 'off', statement: '关模式下注入回执', prob: 0.8, value: 0.6, motivation: 'm' }, A.resAgent(A.childOf('r-1')))
|
|
337
|
+
const wOffR = await workWake(A, 'r-1')
|
|
338
|
+
A.fireEnd(wOffR.childId, { summary: '继续。', solved: false, contextPct: 20, formal: { target: 'p-offreply', decision: 'defect', note: '不应被记录' } })
|
|
339
|
+
await sleep(80)
|
|
340
|
+
const stOffReply = await A.callTool('vibe_v4_status', {})
|
|
341
|
+
assert(stOffReply.formal.objects.length === 0, '★ a stray `formal` reply in off mode records NO formal object')
|
|
342
|
+
assert(!existsSync(join(A.projectRoot, 'Formal', 'p-offreply.lean')), '★ and writes no formal working file')
|
|
343
|
+
assert(!existsSync(join(A.projectRoot, 'Formal', 'TODO.md')) || !/p-offreply/.test(readIf(join(A.projectRoot, 'Formal', 'TODO.md'))), '★ and creates no TODO entry')
|
|
344
|
+
assert(!existsSync(join(A.projectRoot, 'State', 'formal.json')), '★ and still no State/formal.json after the stray reply (the off-mode guard is a true no-op)')
|
|
345
|
+
}
|
|
346
|
+
|
|
292
347
|
// ---------- 2. parameter validation + runtime switching ----------
|
|
293
348
|
section('2 parameter validation and runtime switching')
|
|
294
349
|
const B = await establish()
|
|
@@ -401,6 +456,31 @@ assert(noTc.ok === false && noTc.code === 'LEAN_NOT_FOUND', '★ a missing toolc
|
|
|
401
456
|
assert(/仍可把形式化代码写下来归档/.test(noTc.message || ''), 'the failure explains the graceful degradation')
|
|
402
457
|
assert((await D.callTool('vibe_v4_status', {})).ok === true, 'the group did NOT crash on the missing toolchain (status still answers)')
|
|
403
458
|
toolchainAvailable = true
|
|
459
|
+
// No `subprocess` service at all (docs §7): the run must degrade to a typed NO_SUBPROCESS result —
|
|
460
|
+
// never a thrown error into the scheduler — and an object it was asked to record must stay
|
|
461
|
+
// `attempted` (an un-runnable host must not mint a `passed` record).
|
|
462
|
+
{
|
|
463
|
+
subprocessAvailable = false
|
|
464
|
+
const noSub = await D.callTool('vibe_v4_lean_run', { file: 'Formal/good.lean', target: 'p-nosub' }, r1)
|
|
465
|
+
assert(noSub.ok === false && noSub.code === 'NO_SUBPROCESS', '★ a host with no subprocess service returns NO_SUBPROCESS instead of crashing (got ' + noSub.code + ')')
|
|
466
|
+
assert(/no subprocess service/.test(String(noSub.message || '')), 'the failure is readable (it explains that Lean cannot be executed here)')
|
|
467
|
+
const stNoSub = await D.callTool('vibe_v4_status', {})
|
|
468
|
+
assert(stNoSub.ok === true, 'the group did NOT crash on the missing service (status still answers)')
|
|
469
|
+
const recNoSub = stNoSub.formal.objects.find((o) => o.target === 'p-nosub')
|
|
470
|
+
assert(recNoSub && recNoSub.status === 'attempted' && !recNoSub.proof, '★ the object record stays honest: attempted (never passed) when nothing could be executed')
|
|
471
|
+
subprocessAvailable = true
|
|
472
|
+
}
|
|
473
|
+
// A run that never finishes: the framework must ACTIVELY terminate it (docs §7) and still report a
|
|
474
|
+
// readable LEAN_TIMEOUT — relying on the host's own graceMs alone leaves the Lean process running.
|
|
475
|
+
{
|
|
476
|
+
writeFileSync(join(D.projectRoot, 'Formal', 'hang.lean'), '-- HANG\ntheorem t : 1 = 1 := rfl\n', 'utf8')
|
|
477
|
+
const t0 = Date.now()
|
|
478
|
+
const runHang = await D.callTool('vibe_v4_lean_run', { file: 'Formal/hang.lean', timeout_ms: 1000 }, r1)
|
|
479
|
+
const waited = Date.now() - t0
|
|
480
|
+
assert(runHang.ok === false && runHang.code === 'LEAN_TIMEOUT' && runHang.timedOut === true, '★ a run that never finishes is reported as LEAN_TIMEOUT (got ' + runHang.code + ')')
|
|
481
|
+
assert(terminated.some(f => /hang\.lean$/.test(String(f))), '★ the timeout path really calls handle.terminate() (docs §7) instead of leaving the Lean process running')
|
|
482
|
+
assert(waited >= 900 && waited < 15000, 'the timeout waited for the cap before terminating (' + waited + 'ms)')
|
|
483
|
+
}
|
|
404
484
|
|
|
405
485
|
// ---------- 5. a passing proof flips the review subject to fidelity ----------
|
|
406
486
|
section('5 a passing proof flips the review subject to fidelity')
|
|
@@ -414,6 +494,18 @@ const stD = await D.callTool('vibe_v4_status', {})
|
|
|
414
494
|
assert(stD.formal.passed.indexOf('p-proof') !== -1, 'status reports the object as Lean-passed')
|
|
415
495
|
const idxD = readIf(join(D.projectRoot, 'Formal', 'Index.md'))
|
|
416
496
|
assert(/p-proof/.test(idxD) && /passed/.test(idxD) && /Verified\/Lean\/p-proof\.lean/.test(idxD), 'Formal/Index.md indexes the object, its status and its archived proof')
|
|
497
|
+
// A later run recorded against an ALREADY-passed object records the run but must never strip
|
|
498
|
+
// `passed`/`proof`: doing so would silently remove the fidelity branch from the next voting prompt
|
|
499
|
+
// AND (in `require`) re-close the gate on an object that already has a green archived proof.
|
|
500
|
+
{
|
|
501
|
+
writeFileSync(join(D.projectRoot, 'Formal', 'p-proof.lean'), 'theorem p_proof : 1 = 2 := by sorry\n', 'utf8')
|
|
502
|
+
const rerun = await D.callTool('vibe_v4_lean_run', { file: 'Formal/p-proof.lean', target: 'p-proof' }, r1)
|
|
503
|
+
assert(rerun.ok === false, 'precondition: the recorded re-run is red')
|
|
504
|
+
const rec = (await D.callTool('vibe_v4_status', {})).formal.objects.find(o => o.target === 'p-proof')
|
|
505
|
+
assert(rec && rec.status === 'passed' && rec.proof === 'Verified/Lean/p-proof.lean',
|
|
506
|
+
'★ a RED re-run never downgrades a `passed` record (docs §31.6: only an explicit re-archive decides) — the object keeps its fidelity prompt and its require-mode gate')
|
|
507
|
+
writeFileSync(join(D.projectRoot, 'Formal', 'p-proof.lean'), 'theorem p_proof : 3 * 1 ^ 2 - 2 = (1:Nat) ^ 2 := by decide\n', 'utf8')
|
|
508
|
+
}
|
|
417
509
|
// a RED proof must NOT mint a Verified/Lean/ copy or mark the object passed
|
|
418
510
|
const arcRed = await D.callTool('vibe_v4_lean_archive', { kind: 'proof', target: 'p-red', content: 'theorem p_red : 1 = 2 := by sorry\n' }, r1)
|
|
419
511
|
assert(arcRed.ok === true && arcRed.passed === false && arcRed.status === 'attempted', 'a red proof is archived as attempted, not passed')
|
|
@@ -639,7 +731,7 @@ await G.callTool('vibe_v4_set', { formalVerify: 'encourage' })
|
|
|
639
731
|
assert(/实现难度/.test(enc), 'it still asks for the implementation-difficulty judgement')
|
|
640
732
|
assert(/工具:vibe_v4_lean_run(执行)· vibe_v4_lean_archive(归档)· vibe_v4_lean_lib(查已有可复用库)/.test(enc), 'it names all three tools in FULL')
|
|
641
733
|
assert(/归档可复用定义\/引理前先跑通(vibe_v4_lean_archive run=true 或先 vibe_v4_lean_run);跑不通不要入库。/.test(enc), '★ a reusable definition/lemma must be RUN GREEN before it is archived')
|
|
642
|
-
assert(/宿主没有 Lean 工具链(LEAN_NOT_FOUND)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"/.test(enc), '★
|
|
734
|
+
assert(/宿主没有 Lean 工具链(LEAN_NOT_FOUND)或根本没有 subprocess 服务(NO_SUBPROCESS)时:把代码写下来归档,并在回执的 note 里写明"宿主无 Lean 工具链"/.test(enc), '★ both unavailable-toolchain paths are written out (missing Lean OR no subprocess service) as explicit blocker reasons')
|
|
643
735
|
assert(/一旦 Lean 通过,你唯一需要确认的就是忠实性/.test(enc), 'a green Lean run still shrinks the open question to fidelity')
|
|
644
736
|
assert(/decision='blocked' 时必须写明 note/.test(enc), 'the encourage opt-out documents the mandatory note')
|
|
645
737
|
assert(!/偏离 → 0/.test(enc) && !/发现任何偏离/.test(enc), '★ no "deviation ⇒ 0" instruction anywhere in the encourage block')
|
|
@@ -650,6 +742,15 @@ await G.callTool('vibe_v4_set', { formalVerify: 'encourage' })
|
|
|
650
742
|
assert(/本次裁定不会生效/.test(req) && /进入「形式化待办」/.test(req), 'it warns the verdict is withheld as 未定论 (formal-required)')
|
|
651
743
|
assert(!/可以不做/.test(req), "'require' does NOT offer the encourage-mode opt-out")
|
|
652
744
|
assert(/归档可复用定义\/引理前先跑通/.test(req) && /宿主没有 Lean 工具链/.test(req), 'the run-before-archive and toolchain rules are in the require text as well')
|
|
745
|
+
assert(/LEAN_NOT_FOUND/.test(req) && /NO_SUBPROCESS/.test(req), '★ the require text names BOTH unavailable-toolchain codes (the escape route must be documented in every mode)')
|
|
746
|
+
// The sentence is written ONCE and reused by both modes: a second copy would drift (one branch
|
|
747
|
+
// kept up to date, the other stale). The anchor `(LEAN_NOT_FOUND)` therefore occurs exactly once
|
|
748
|
+
// in the plugin, which is also what the sensitivity probe table assumes.
|
|
749
|
+
{
|
|
750
|
+
const src = readFileSync(fileURLToPath(PLUGIN), 'utf8')
|
|
751
|
+
assert(src.split('(LEAN_NOT_FOUND)').length - 1 === 1, '★ the unavailable-toolchain guidance is written once (not duplicated per mode/branch)')
|
|
752
|
+
assert(src.split('(NO_SUBPROCESS)').length - 1 === 1, 'the same single sentence names NO_SUBPROCESS (no per-branch copy)')
|
|
753
|
+
}
|
|
653
754
|
assert(/\*\*本模式要求\*\*/.test(req), 'the require bullet replaces the encourage opt-out in place')
|
|
654
755
|
// normal / heartbeat / post-compact recap all carry the standing work line; the two that ARE a
|
|
655
756
|
// reply contract also document the `defect` decision (coreRules is a recap prefix, not a contract)
|
|
@@ -680,12 +781,36 @@ await G.callTool('vibe_v4_set', { formalVerify: 'encourage' })
|
|
|
680
781
|
addText('LEAN_NOT_FOUND message', (await G.callTool('vibe_v4_lean_run', { file: 'Formal/p-hint.lean' }, r1G)).message)
|
|
681
782
|
toolchainAvailable = true
|
|
682
783
|
for (const t of G.toolRegs.filter((x) => /lean/.test(x.name))) addText('tool description ' + t.name, t.description)
|
|
784
|
+
// The activity log is agent/host-readable text too (vibe_v4_report exposes `recentActivity`, and
|
|
785
|
+
// a host reads it out to the group): a bare abbreviation there names a tool that does not exist,
|
|
786
|
+
// for exactly the same reason as in a prompt.
|
|
787
|
+
try {
|
|
788
|
+
const log = JSON.parse(readIf(join(G.projectRoot, 'State', 'session.json')) || '{}').activityLog || []
|
|
789
|
+
for (const e of log) addText('activity log: ' + e.event, String((e && e.detail) || ''))
|
|
790
|
+
} catch (e) { /* the log is best-effort */ }
|
|
683
791
|
const bare = [/(^|[^a-z_])lean_run/, /(^|[^a-z_])lean_archive/, /(^|[^a-z_])lean_lib/]
|
|
684
792
|
const offenders = []
|
|
685
793
|
for (const s of scanned) for (const re of bare) if (re.test(s.text)) offenders.push(s.label + ' :: ' + re.source)
|
|
686
794
|
assert(offenders.length === 0, '★ no injected text (prompt, tool hint or tool description) uses a bare tool abbreviation (' + offenders.slice(0, 3).join(' | ') + ')')
|
|
687
795
|
assert(scanned.length >= 15, 'the sweep really covered the injected-text surface (' + scanned.length + ' texts)')
|
|
688
796
|
}
|
|
797
|
+
{
|
|
798
|
+
// docs §4.1-3 / §6.1: the fidelity branch's "the framework withholds the verdict" clause may only
|
|
799
|
+
// appear in `require` (the only mode with a gate). In `encourage` the framework still withdraws
|
|
800
|
+
// the proof and records the TODO, but it CANNOT hold the ballot — so the text must not promise
|
|
801
|
+
// that, and must instead point at the voter's own abstention.
|
|
802
|
+
await G.callTool('vibe_v4_set', { formalVerify: 'require' })
|
|
803
|
+
const reqFid = await G.prompts('verify', 'r-1', { target: 'p-hint', stage: 'independent' })
|
|
804
|
+
assert(/该对象已有\*\*通过的 Lean 形式化证明\*\*/.test(reqFid), 'precondition: p-hint is the passed/fidelity case in this prompt')
|
|
805
|
+
assert(/本次裁定\*\*不定论\*\*/.test(reqFid), "'require' fidelity text states the verdict will be withheld (it has a gate)")
|
|
806
|
+
await G.callTool('vibe_v4_set', { formalVerify: 'encourage' })
|
|
807
|
+
const encFid = await G.prompts('verify', 'r-1', { target: 'p-hint', stage: 'independent' })
|
|
808
|
+
assert(!/本次裁定\*\*不定论\*\*/.test(encFid), "★ 'encourage' does NOT promise a hold it cannot enforce (docs §4.1-3)")
|
|
809
|
+
assert(/本档没有门禁/.test(encFid) && /弃权值/.test(encFid), "★ instead it says the voter's own abstention is what keeps the ballot from concluding")
|
|
810
|
+
assert(/降级为 attempted、删除归档证明、写入形式化待办/.test(encFid), 'the withdrawal the framework CAN enforce is still stated in both modes')
|
|
811
|
+
assert(/发现任何偏差,不要投 0/.test(encFid), 'the fidelity rule itself is mode-independent')
|
|
812
|
+
await G.callTool('vibe_v4_set', { formalVerify: 'require' })
|
|
813
|
+
}
|
|
689
814
|
|
|
690
815
|
// ===============================================================
|
|
691
816
|
// 13. the `defect` reply channel (docs §4.1 / §10 item 8) — BEHAVIOURAL, not wording
|
|
@@ -716,6 +841,31 @@ const g1 = G.resAgent(G.childOf('r-1'))
|
|
|
716
841
|
assert(/\| p-defect \| attempted \|/.test(idx), 'Formal/Index.md downgrades the object to attempted')
|
|
717
842
|
assert(/只证了 n ≥ 1 的情形/.test(idx), 'the deviation is the record note in the index')
|
|
718
843
|
assert(activityOf(G).some((e) => /忠实性缺陷/.test(e.detail) && /p-defect/.test(e.detail)), '★ the retraction is announced in the activity log')
|
|
844
|
+
// the log line is agent/host-readable text, so it is bound by the same §4.1-3 rule as the prompt:
|
|
845
|
+
// in `encourage` it may not claim a hold the mode does not have.
|
|
846
|
+
{
|
|
847
|
+
const line = activityOf(G).filter((e) => /忠实性缺陷/.test(e.detail) && /p-defect[^-]/.test(e.detail)).pop() || { detail: '' }
|
|
848
|
+
assert(/本档没有门禁/.test(line.detail) && !/本次裁定\*\*不定论\*\*/.test(line.detail), "★ the encourage-mode activity line does not promise the 不定论 hold that only `require` enforces")
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
{
|
|
852
|
+
// §4.1 degraded withdrawal: the record must never be the ONLY thing withdrawn. A host whose shell
|
|
853
|
+
// "succeeds" without deleting (a stub host / a permissions quirk) must be caught by re-reading the
|
|
854
|
+
// file through the fs service, and the archive must then be OVERWRITTEN with a withdrawal notice —
|
|
855
|
+
// otherwise the retracted proof keeps sitting at the exact path everyone looks for the proof.
|
|
856
|
+
const arcD = await G.callTool('vibe_v4_lean_archive', { kind: 'proof', target: 'p-defect-degraded', content: 'theorem p_defect_degraded : 5 = 5 := rfl\n' }, g1)
|
|
857
|
+
assert(arcD.passed === true && existsSync(join(G.projectRoot, 'Verified', 'Lean', 'p-defect-degraded.lean')), 'precondition: the degraded-path object has a green archived proof')
|
|
858
|
+
shellDeletesFiles = false
|
|
859
|
+
const wD = await workWake(G, 'r-1')
|
|
860
|
+
G.fireEnd(wD.childId, { summary: '核对后发现偏差。', formal: { target: 'p-defect-degraded', decision: 'defect', note: '结论方向相反' }, contextPct: 20 })
|
|
861
|
+
await sleep(90)
|
|
862
|
+
shellDeletesFiles = true
|
|
863
|
+
const body = readIf(join(G.projectRoot, 'Verified', 'Lean', 'p-defect-degraded.lean'))
|
|
864
|
+
assert(!/p_defect_degraded/.test(body), '★ a shell that exits 0 without deleting does NOT leave the retracted proof readable — its original text is gone')
|
|
865
|
+
assert(/已撤回/.test(body) && /原代码保留在工作文件/.test(body), '★ the archived file was OVERWRITTEN with an explicit withdrawal notice instead (delete → confirm → overwrite)')
|
|
866
|
+
const recD = (await G.callTool('vibe_v4_status', {})).formal.objects.find((o) => o.target === 'p-defect-degraded')
|
|
867
|
+
assert(recD && recD.status === 'attempted' && recD.proof === '', 'the record is downgraded on the degraded path too')
|
|
868
|
+
assert(activityOf(G).some((e) => /覆盖归档证明/.test(e.detail) && /p-defect-degraded/.test(e.detail)), '★ the activity log says WHICH withdrawal path was taken (overwritten, not silently reported as deleted)')
|
|
719
869
|
}
|
|
720
870
|
{
|
|
721
871
|
const arc2 = await G.callTool('vibe_v4_lean_archive', { kind: 'proof', target: 'p-defect2', content: 'theorem p_defect2 : 3 + 3 = 6 := by decide\n' }, g1)
|
|
@@ -745,6 +895,27 @@ const g1 = G.resAgent(G.childOf('r-1'))
|
|
|
745
895
|
// ===============================================================
|
|
746
896
|
// 14. `require` after a defect: the retraction closes the gate (docs §4.1-3 / §10 item 9)
|
|
747
897
|
// ===============================================================
|
|
898
|
+
// 13b. a plain `used` judgement must NOT withdraw an ESTABLISHED proof (contract §4): only a
|
|
899
|
+
// fidelity defect retracts one. v2 shipped the unconditional downgrade and silently re-closed the gate.
|
|
900
|
+
section('13b a \`used\` reply must NOT downgrade an already-passed object')
|
|
901
|
+
{
|
|
902
|
+
const arcU = await G.callTool('vibe_v4_lean_archive', { kind: 'proof', target: 'p-usedkeep', content: 'theorem p_usedkeep : 2 + 2 = 4 := by decide\n' }, g1)
|
|
903
|
+
assert(arcU.passed === true && existsSync(join(G.projectRoot, 'Verified', 'Lean', 'p-usedkeep.lean')), 'used-keep: the object starts with a green archived proof')
|
|
904
|
+
const wU = await workWake(G, 'r-1')
|
|
905
|
+
G.fireEnd(wU.childId, { summary: '这一轮只是又写了一遍草稿。', formal: { target: 'p-usedkeep', decision: 'used', file: 'Formal/p-usedkeep.lean' }, contextPct: 20 })
|
|
906
|
+
await sleep(90)
|
|
907
|
+
const stU = await G.callTool('vibe_v4_status', {})
|
|
908
|
+
const recU = stU.formal.objects.find((o) => o.target === 'p-usedkeep')
|
|
909
|
+
assert(!!recU && recU.status === 'passed', '★ a `used` reply does NOT downgrade an already-passed object (got ' + JSON.stringify(recU) + ')')
|
|
910
|
+
assert(!!recU && recU.proof === 'Verified/Lean/p-usedkeep.lean', '★ and the proof pointer survives the reply')
|
|
911
|
+
assert(existsSync(join(G.projectRoot, 'Verified', 'Lean', 'p-usedkeep.lean')), '★ and the archived proof is still on disk')
|
|
912
|
+
assert(stU.formal.passed.indexOf('p-usedkeep') !== -1, '★ status still reports it as Lean-passed')
|
|
913
|
+
const rerunU = await G.callTool('vibe_v4_lean_run', { file: 'Formal/p-usedkeep.lean', target: 'p-usedkeep' }, g1)
|
|
914
|
+
assert(rerunU.ok === true, 'used-keep: the work file runs green')
|
|
915
|
+
const recU2 = (await G.callTool('vibe_v4_status', {})).formal.objects.find((o) => o.target === 'p-usedkeep')
|
|
916
|
+
assert(!!recU2 && recU2.status === 'passed', '★ a plain re-run does not downgrade a passed record (contract §4)')
|
|
917
|
+
}
|
|
918
|
+
|
|
748
919
|
section("14 'require' withholds the verdict after a defect, even on a unanimous 1")
|
|
749
920
|
const H = await establish()
|
|
750
921
|
await H.callTool('vibe_v4_set', { formalVerify: 'require' })
|
|
@@ -816,6 +987,12 @@ const K = await establish()
|
|
|
816
987
|
await K.callTool('vibe_v4_lean_archive', { kind: 'proof', target: 'p-corpus-passed', content: 'theorem p_corpus_passed : 1 + 1 = 2 := by decide\n' }, k1)
|
|
817
988
|
const fid = await K.prompts('verify', 'r-1', { target: 'p-corpus-passed', stage: 'independent' })
|
|
818
989
|
add('verify', 'passed/fidelity', fid)
|
|
990
|
+
// (d2) the SAME passed object in `encourage`: the promise must shrink to what that mode enforces
|
|
991
|
+
// (docs §4.1-3: no gate there, so no claim that the framework withholds the verdict).
|
|
992
|
+
await K.callTool('vibe_v4_set', { formalVerify: 'encourage' })
|
|
993
|
+
const fidEnc = await K.prompts('verify', 'r-1', { target: 'p-corpus-passed', stage: 'independent' })
|
|
994
|
+
add('verify', 'passed/fidelity (encourage)', fidEnc)
|
|
995
|
+
await K.callTool('vibe_v4_set', { formalVerify: 'require' })
|
|
819
996
|
// (e) a BLOCKED object
|
|
820
997
|
await K.callTool('vibe_v4_lean_archive', { kind: 'blocked', target: 'p-corpus-blocked', note: '需要未形式化的解析数论框架' }, k1)
|
|
821
998
|
add('verify', 'blocked/verify', await K.prompts('verify', 'r-1', { target: 'p-corpus-blocked', stage: 'independent' }))
|
|
@@ -845,7 +1022,8 @@ const K = await establish()
|
|
|
845
1022
|
'> 由 `formal-verify-v4.test.mjs` 落盘:非 `off` 模式下常驻**真正会读到**的 Lean 提示词原文',
|
|
846
1023
|
'> (`vibe_v4_prompts` 的只读回显 + 一条真实投递的工作轮 + 工具 `hint`)。',
|
|
847
1024
|
'> 工作区路径归一化为 `<WS>`,VibeMath 根归一化为 `<VIBEMATH>`:确定、可 diff、不含任何本机路径。', '',
|
|
848
|
-
'> 覆盖:`off`(无 Lean 文本)、`encourage`、**`require`**、对象 `passed`
|
|
1025
|
+
'> 覆盖:`off`(无 Lean 文本)、`encourage`、**`require`**、对象 `passed` 后的**忠实性分支**',
|
|
1026
|
+
'> (`encourage` / `require` 两种措辞各一份:只有 `require` 会声称"不定论")、',
|
|
849
1027
|
'> `blocked` 分支、平时工作轮的「顺手形式化」,以及回执契约里的 `formal` 字段。', '']
|
|
850
1028
|
for (let i = 0; i < corpus.length; i++) {
|
|
851
1029
|
const c = corpus[i]
|
|
@@ -865,6 +1043,9 @@ const K = await establish()
|
|
|
865
1043
|
assert(/【Lean 形式化验证(鼓励模式)】/.test(byLabel('encourage/verify').prompt), 'the corpus carries the encourage voting prompt')
|
|
866
1044
|
assert(/【Lean 形式化验证(强制模式)】/.test(byLabel('require/verify').prompt), '★ the corpus carries the REQUIRE voting prompt')
|
|
867
1045
|
assert(/不要投 0/.test(byLabel('passed/fidelity').prompt), '★ the corpus carries the passed/fidelity branch')
|
|
1046
|
+
assert(/本次裁定\*\*不定论\*\*/.test(byLabel('passed/fidelity').prompt), 'the require fidelity entry keeps the hold it really enforces')
|
|
1047
|
+
assert(!!byLabel('passed/fidelity (encourage)') && !/本次裁定\*\*不定论\*\*/.test(byLabel('passed/fidelity (encourage)').prompt)
|
|
1048
|
+
&& /本档没有门禁/.test(byLabel('passed/fidelity (encourage)').prompt), '★ the encourage fidelity entry does NOT promise the hold only require has (docs §4.1-3)')
|
|
868
1049
|
assert(/【顺手形式化(强制)】/.test(byLabel('require/normal').prompt), 'the corpus carries the ordinary work-round line')
|
|
869
1050
|
assert(/"decision":"used\|blocked\|defect"/.test(byLabel('formal reply contract (voting prompt)').prompt), '★ the corpus carries the `formal` reply contract line with decision=defect')
|
|
870
1051
|
assert(/【顺手形式化/.test(byLabel('require/real work wake').prompt), 'the corpus also keeps a prompt the framework REALLY delivered')
|
|
@@ -874,6 +1055,25 @@ const K = await establish()
|
|
|
874
1055
|
assert(corpus.every((c) => c.prompt && c.prompt.length > 20), 'every corpus entry carries real prompt text')
|
|
875
1056
|
}
|
|
876
1057
|
|
|
1058
|
+
// ===============================================================
|
|
1059
|
+
// 16. a FRESH run must not inherit the previous run's formal records (docs §31.6): object ids are
|
|
1060
|
+
// reused (p-*, r-1..), so the in-memory reset in `start()` has to REACH DISK even when the mode
|
|
1061
|
+
// has been switched back to `off` — otherwise a later `resume` restores a stale `passed` and
|
|
1062
|
+
// re-opens the require gate for an object of the new run. This is the exact hazard that makes
|
|
1063
|
+
// the `off`-mode persistence guard ("do not create State/formal.json" ) non-trivial.
|
|
1064
|
+
// ===============================================================
|
|
1065
|
+
section('16 a fresh run clears the persisted formal records (even in off mode)')
|
|
1066
|
+
{
|
|
1067
|
+
const stale = readIf(join(D.projectRoot, 'State', 'formal.json'))
|
|
1068
|
+
assert(/"p-proof"/.test(stale), 'precondition: the previous run left formal records on disk')
|
|
1069
|
+
await D.callTool('vibe_v4_set', { formalVerify: 'off' })
|
|
1070
|
+
const beforeSpawns = D.spawns.length
|
|
1071
|
+
const st = await D.callTool('vibe_v4_start', { problem: '新一轮:形式化清白起点', residentCount: 1 })
|
|
1072
|
+
assert(st.ok === true && D.spawns.length > beforeSpawns, 'the fresh run actually started (so the clean-slate assertion is falsifiable)')
|
|
1073
|
+
const now = readIf(join(D.projectRoot, 'State', 'formal.json'))
|
|
1074
|
+
assert(!/"p-proof"/.test(now) && !/"passed"/.test(now), '★ a fresh run clears the persisted formal records on disk, even in off mode (no stale `passed` can open the new run’s gate)')
|
|
1075
|
+
}
|
|
1076
|
+
|
|
877
1077
|
// ===============================================================
|
|
878
1078
|
console.log('')
|
|
879
1079
|
console.log('passed=' + passed + ' failed=' + failed)
|
|
@@ -70,7 +70,17 @@ const leanRuns = []
|
|
|
70
70
|
// This mirrors the one property that matters for the feature — an exit code that says
|
|
71
71
|
// "the kernel accepted this".
|
|
72
72
|
let toolchainAvailable = true
|
|
73
|
-
|
|
73
|
+
// A host with NO `subprocess` service at all: `ctx.get('subprocess')` returns undefined, which
|
|
74
|
+
// must become a readable NO_SUBPROCESS result (and must NOT stop the code from being archived).
|
|
75
|
+
let noSubprocess = false
|
|
76
|
+
// A run that HANGS: `hangLean` makes the fake toolchain return a `done` that only settles
|
|
77
|
+
// after `hangMs` (far beyond any cap the tests use), so the run can only be reported as a
|
|
78
|
+
// timeout by actually racing `done` against a timer. `terminations` records every
|
|
79
|
+
// `handle.terminate()` call, which is the observable proof that the guard fired.
|
|
80
|
+
let hangLean = false
|
|
81
|
+
let hangMs = 60000
|
|
82
|
+
const terminations = []
|
|
83
|
+
let subprocess = {
|
|
74
84
|
async resolveExecutable(cmd) {
|
|
75
85
|
if (!toolchainAvailable) throw new Error('spawn lean ENOENT')
|
|
76
86
|
if (String(cmd) !== 'lean') throw new Error('unknown executable ' + cmd)
|
|
@@ -83,13 +93,16 @@ const subprocess = {
|
|
|
83
93
|
leanRuns.push({ argv: spec.argv.slice(0, -1), file, cwd: spec.cwd })
|
|
84
94
|
const stdout = bad ? '' : 'ok\n'
|
|
85
95
|
const stderr = bad ? 'error: declaration uses sorry\n' : ''
|
|
96
|
+
const done = hangLean
|
|
97
|
+
? new Promise(r => setTimeout(() => r({ exitCode: 0, signal: null }), hangMs))
|
|
98
|
+
: Promise.resolve({ exitCode: bad ? 1 : 0, signal: null })
|
|
86
99
|
return {
|
|
87
|
-
done
|
|
100
|
+
done,
|
|
88
101
|
collected: {
|
|
89
102
|
stdout: { readFrom: () => ({ text: stdout, nextOffset: stdout.length, lossy: false }) },
|
|
90
103
|
stderr: { readFrom: () => ({ text: stderr, nextOffset: stderr.length, lossy: false }) },
|
|
91
104
|
},
|
|
92
|
-
terminate() {},
|
|
105
|
+
terminate() { terminations.push(file) },
|
|
93
106
|
}
|
|
94
107
|
},
|
|
95
108
|
}
|
|
@@ -125,7 +138,7 @@ const ctx = {
|
|
|
125
138
|
if (name === 'sessionProjections') return projections
|
|
126
139
|
if (name === 'sandboxPolicy') return undefined
|
|
127
140
|
if (name === 'compaction') return undefined
|
|
128
|
-
if (name === 'subprocess') return subprocess
|
|
141
|
+
if (name === 'subprocess') return noSubprocess ? undefined : subprocess
|
|
129
142
|
return undefined
|
|
130
143
|
},
|
|
131
144
|
on(e, fn) { (listeners[e] = listeners[e] || []).push(fn) },
|
|
@@ -291,6 +304,41 @@ assert(!/形式化/.test(readIf(join(instRootOf(RA), 'Verified', '命题', 'p-of
|
|
|
291
304
|
assert(!!toolRegs.find(t => t.name === 'vibe_v5_lean_run') && !!toolRegs.find(t => t.name === 'vibe_v5_lean_archive') && !!toolRegs.find(t => t.name === 'vibe_v5_lean_lib'),
|
|
292
305
|
'the three Lean tools are registered in every mode (registration is static)')
|
|
293
306
|
|
|
307
|
+
// ★ The mode switch must be REACHABLE THROUGH THE TOOL SCHEMA (2.3.2 defect D1) ──────────────
|
|
308
|
+
// Every tool schema here is closed (`additionalProperties:false`), so a key the schema does not
|
|
309
|
+
// advertise is REJECTED by any schema-validating provider. v3 shipped 2.3.0/2.3.1 with all four Lean
|
|
310
|
+
// parameters missing from the set-params schema while every assertion in this file stayed green —
|
|
311
|
+
// because the suite calls the handler DIRECTLY and never inspects the registered schema. The feature
|
|
312
|
+
// could not be switched on at all through the tool interface.
|
|
313
|
+
{
|
|
314
|
+
const setSpec = toolRegs.find((t) => t.name === 'vibe_v5_set')
|
|
315
|
+
assert(!!setSpec, "vibe_v5_set is registered")
|
|
316
|
+
assert(setSpec.parameters && setSpec.parameters.type === 'object' && setSpec.parameters.additionalProperties === false,
|
|
317
|
+
'★ vibe_v5_set publishes a CLOSED object schema (an unlisted key is rejected, so the schema IS the contract)')
|
|
318
|
+
for (const k of ['formalVerify', 'leanCommand', 'leanArgs', 'leanTimeoutMs']) {
|
|
319
|
+
assert(Object.prototype.hasOwnProperty.call(setSpec.parameters.properties, k),
|
|
320
|
+
'★ the registered schema advertises ' + k + ' (every other surface documents it; a schema that omits it makes the switch unreachable)')
|
|
321
|
+
}
|
|
322
|
+
assert(JSON.stringify(setSpec.parameters.properties.formalVerify.enum) === JSON.stringify(['off', 'encourage', 'require']),
|
|
323
|
+
'the schema narrows formalVerify to the three real modes (a typo must not be a fourth)')
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// A stray `formal` reply in OFF mode must be INERT: the field is not offered in the reply contract
|
|
327
|
+
// there, and honouring it would create Formal/ state in a mode documented as a "TRUE no-op" (the
|
|
328
|
+
// TOOLS stay usable on purpose — a tool call is deliberate, a stray reply field is not).
|
|
329
|
+
{
|
|
330
|
+
const wOff = await wakeAndReply(RA, 'r-1', {
|
|
331
|
+
progress: '关模式下的普通回轮。',
|
|
332
|
+
formal: { target: 'p-off-stray', decision: 'blocked', note: '不应被记录' },
|
|
333
|
+
contextPct: 20,
|
|
334
|
+
})
|
|
335
|
+
assert(!!wOff, 'off mode: a wake carrying a stray formal reply was fed to the framework')
|
|
336
|
+
const stOff2 = await callTool('vibe_v5_status', {}, RA)
|
|
337
|
+
assert((stOff2.formal.objects || []).length === 0, '★ a stray `formal` reply in off mode records NO formal object')
|
|
338
|
+
assert((stOff2.formal.todo || []).length === 0, '★ and adds nothing to the formalization TODO')
|
|
339
|
+
assert(!/p-off-stray/.test(readIf(join(instRootOf(RA), 'Formal', 'TODO.md'))), '★ and writes no TODO entry for it')
|
|
340
|
+
}
|
|
341
|
+
|
|
294
342
|
// ---------- 2. parameter validation ----------
|
|
295
343
|
section('2 parameter validation and runtime switching')
|
|
296
344
|
const RB = makeRoot()
|
|
@@ -333,6 +381,7 @@ await settle(); delivered.length = 0; await drainWakes(3, RC)
|
|
|
333
381
|
assert(/一旦 Lean 通过,你唯一需要确认的就是忠实性/.test(vp), 'the voting prompt states that a passing Lean run shrinks the question to fidelity')
|
|
334
382
|
assert(/归档可复用定义\/引理前先跑通/.test(vp), 'the voting prompt requires a GREEN RUN before archiving a reusable definition')
|
|
335
383
|
assert(/LEAN_NOT_FOUND/.test(vp) && /宿主无 Lean 工具链/.test(vp), 'the voting prompt says what to do when the host has no Lean toolchain')
|
|
384
|
+
assert(/NO_SUBPROCESS/.test(vp), '§6 rule 4: the no-toolchain route names NO_SUBPROCESS too (an agent that only knows LEAN_NOT_FOUND treats a service-less host as an unknown failure and retries)')
|
|
336
385
|
assert(noBareLeanTool(vp), 'no abbreviated tool name appears in the injected voting prompt')
|
|
337
386
|
assert(/实现难度/.test(vp), 'the voting prompt asks for the implementation-difficulty judgement')
|
|
338
387
|
assert(/可以不做,但请在回执的 formal 字段写明难度判断/.test(vp), "'encourage' explicitly allows skipping (with a recorded judgement)")
|
|
@@ -376,6 +425,42 @@ const runNoTc = await callTool('vibe_v5_lean_run', { file: 'Formal/good.lean' },
|
|
|
376
425
|
assert(runNoTc.ok === false && runNoTc.code === 'LEAN_NOT_FOUND', 'a missing toolchain returns LEAN_NOT_FOUND instead of crashing')
|
|
377
426
|
assert(/仍可把形式化代码写下来归档/.test(runNoTc.message), 'the failure explains the graceful degradation')
|
|
378
427
|
toolchainAvailable = true
|
|
428
|
+
// docs/formal-verification.md §7: a TIMEOUT must TERMINATE the process. `graceMs` is only a
|
|
429
|
+
// request to the host, so a run whose `done` never settles within the cap must be ended by
|
|
430
|
+
// `handle.terminate()` — otherwise a runaway toolchain lingers while we report LEAN_TIMEOUT.
|
|
431
|
+
{
|
|
432
|
+
const before = terminations.length
|
|
433
|
+
hangLean = true
|
|
434
|
+
hangMs = 60000
|
|
435
|
+
const runHang = await callTool('vibe_v5_lean_run', { file: 'Formal/good.lean', timeout_ms: 1000 }, childAgent(childOf(RD, 'r-1')))
|
|
436
|
+
hangLean = false
|
|
437
|
+
assert(runHang.ok === false && runHang.code === 'LEAN_TIMEOUT', '★ a run that outlives the cap is reported as LEAN_TIMEOUT (got ' + runHang.code + ')')
|
|
438
|
+
assert(runHang.timedOut === true, 'the result is flagged timedOut')
|
|
439
|
+
assert(terminations.slice(before).some(f => /good\.lean$/.test(String(f))), '★ the timeout path really called handle.terminate() (the toolchain is not left running)')
|
|
440
|
+
assert(runHang.signal === 'SIGTERM', 'the synthetic outcome names the signal that ended it')
|
|
441
|
+
}
|
|
442
|
+
// A host that exposes NO `subprocess` service must produce the readable NO_SUBPROCESS result
|
|
443
|
+
// (never a throw into the scheduler), must leave the object record honest (`attempted`, and NOT
|
|
444
|
+
// passed), and must still let the code be written down through the archive route.
|
|
445
|
+
{
|
|
446
|
+
noSubprocess = true
|
|
447
|
+
const runNoSvc = await callTool('vibe_v5_lean_run', { file: 'Formal/good.lean', target: 'p-nosub' }, childAgent(childOf(RD, 'r-1')))
|
|
448
|
+
assert(runNoSvc.ok === false && runNoSvc.code === 'NO_SUBPROCESS', '★ a host with no subprocess service returns NO_SUBPROCESS instead of throwing (got ' + runNoSvc.code + ')')
|
|
449
|
+
assert(typeof runNoSvc.message === 'string' && /no subprocess service/.test(runNoSvc.message), 'the result explains why Lean cannot run here')
|
|
450
|
+
const stNoSvc = await callTool('vibe_v5_status', {}, RD)
|
|
451
|
+
assert(stNoSvc.ok === true, 'the institute still answers status after that (nothing was thrown into the scheduling loop)')
|
|
452
|
+
const recNoSvc = (stNoSvc.formal.objects || []).find(o => o.target === 'p-nosub') || {}
|
|
453
|
+
assert(recNoSvc.status === 'attempted', 'the run is recorded as attempted (no toolchain = no proof), got ' + recNoSvc.status)
|
|
454
|
+
assert(!recNoSvc.proof, 'and it is NOT recorded as passed')
|
|
455
|
+
// The way out (contract §6 rule 4): the code can still be written down. The archive route
|
|
456
|
+
// itself needs no toolchain at all.
|
|
457
|
+
const arcNoSvc = await callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-nosub', content: 'theorem p_nosub : 1 + 1 = 2 := by decide\n' }, childAgent(childOf(RD, 'r-1')))
|
|
458
|
+
assert(arcNoSvc.ok === true && arcNoSvc.passed === false && arcNoSvc.run && arcNoSvc.run.code === 'NO_SUBPROCESS',
|
|
459
|
+
'the code is still written down and archived with no toolchain, and the record stays honest (' + JSON.stringify({ ok: arcNoSvc.ok, status: arcNoSvc.status, code: arcNoSvc.run && arcNoSvc.run.code }) + ')')
|
|
460
|
+
assert(existsSync(join(instD, 'Formal', 'p-nosub.lean')), 'the working file exists on disk even though nothing could execute it')
|
|
461
|
+
assert(!existsSync(join(instD, 'Verified', 'Lean', 'p-nosub.lean')), '★ nothing is promoted to Verified/Lean/ without a green run')
|
|
462
|
+
noSubprocess = false
|
|
463
|
+
}
|
|
379
464
|
|
|
380
465
|
// ---------- 5. archive a proof → the vote becomes a FIDELITY review ----------
|
|
381
466
|
section('5 a passing proof flips the review subject to fidelity')
|
|
@@ -405,6 +490,8 @@ await settle(); delivered.length = 0; await drainWakes(3, RD)
|
|
|
405
490
|
assert(/形式化不合格/.test(vp), 'it names the failure a formalisation defect, not a refutation')
|
|
406
491
|
assert(/decision:'defect'/.test(vp), 'it names the defect reply channel')
|
|
407
492
|
assert(!/偏离 → 0/.test(vp), '★ the old "any deviation → 0" instruction is GONE')
|
|
493
|
+
assert(/本档没有门禁:请务必给一个严格介于 0 与 1 之间的弃权值,以保证本轮无法得出一致结论/.test(vp),
|
|
494
|
+
'★ encourage mode is explicit that there is NO gate and the voter\'s abstention is what prevents a conclusion (wording shared with v2/v3/v4)')
|
|
408
495
|
assert(noBareLeanTool(vp), 'no abbreviated tool name appears in the fidelity prompt')
|
|
409
496
|
}
|
|
410
497
|
await drainWakes(10, RD)
|
|
@@ -516,6 +603,29 @@ assert(!!w2, 'r-2 was woken and answered')
|
|
|
516
603
|
const stF2 = await callTool('vibe_v5_status', {}, RF)
|
|
517
604
|
assert(stF2.formal.objects.some(o => o.target === 'p-reply2' && o.status === 'attempted'),
|
|
518
605
|
'a `formal.decision=used` reply records the object as attempted with its file')
|
|
606
|
+
// a plain `used` judgement must NOT withdraw an ESTABLISHED proof (contract §4): only a fidelity
|
|
607
|
+
// defect retracts one. v2 shipped the unconditional downgrade here; all four are asserted behaviourally.
|
|
608
|
+
await callTool('vibe_v5_record_proposition', { id: 'p-usedkeep', statement: '已有通过证明后再写一次 used 回执', value: 0.6, motive: 'm', p: 0.8 }, childAgent(childOf(RF, 'r-1')))
|
|
609
|
+
{
|
|
610
|
+
const arcU = await callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-usedkeep', content: 'theorem p_usedkeep : 2 + 2 = 4 := by decide\n' }, RF)
|
|
611
|
+
assert(arcU.ok === true && arcU.passed === true, 'used-keep: the object starts Lean-passed')
|
|
612
|
+
const wU = await wakeAndReply(RF, 'r-2', {
|
|
613
|
+
progress: '这一轮只是又写了一遍草稿。',
|
|
614
|
+
formal: { target: 'p-usedkeep', decision: 'used', file: 'Formal/p-usedkeep.lean' },
|
|
615
|
+
contextPct: 20,
|
|
616
|
+
})
|
|
617
|
+
assert(!!wU, 'r-2 was woken for the used judgement')
|
|
618
|
+
const stU = await callTool('vibe_v5_status', {}, RF)
|
|
619
|
+
const recU = stU.formal.objects.find((o) => o.target === 'p-usedkeep')
|
|
620
|
+
assert(!!recU && recU.status === 'passed', '★ a `used` reply does NOT downgrade an already-passed object (got ' + JSON.stringify(recU) + ')')
|
|
621
|
+
assert(!!recU && recU.proof === 'Verified/Lean/p-usedkeep.lean', '★ and the proof pointer survives the reply')
|
|
622
|
+
assert(existsSync(join(instF, 'Verified', 'Lean', 'p-usedkeep.lean')), '★ and the archived proof is still on disk')
|
|
623
|
+
assert(stU.formal.passed.indexOf('p-usedkeep') !== -1, '★ status still reports it as Lean-passed')
|
|
624
|
+
const rerunU = await callTool('vibe_v5_lean_run', { file: 'Formal/p-usedkeep.lean', target: 'p-usedkeep' }, RF)
|
|
625
|
+
assert(rerunU.ok === true, 'used-keep: the work file runs green')
|
|
626
|
+
const recU2 = (await callTool('vibe_v5_status', {}, RF)).formal.objects.find((o) => o.target === 'p-usedkeep')
|
|
627
|
+
assert(!!recU2 && recU2.status === 'passed', '★ a plain re-run does not downgrade a passed record (contract §4)')
|
|
628
|
+
}
|
|
519
629
|
// a blocker with no note must be refused AND reported back to the member
|
|
520
630
|
delivered.length = 0
|
|
521
631
|
const w3 = await wakeAndReply(RF, 'r-1', { formal: { target: 'p-reply3', decision: 'blocked' }, contextPct: 20 })
|
package/installer.js
CHANGED
|
@@ -52,7 +52,9 @@ const PRESETS = [
|
|
|
52
52
|
{
|
|
53
53
|
src: 'vibe-math-v5',
|
|
54
54
|
dst: 'vibe-math-v5',
|
|
55
|
-
|
|
55
|
+
// 架构图.md belongs here for the same reason 实现方案.md does: the installer's policy is to put
|
|
56
|
+
// the preset's documentation next to the preset, and the shipped v5 directory carries both.
|
|
57
|
+
files: ['agent.cordis.yml', 'preset.yml', 'vibe-math-v5.js', '实现方案.md', '架构图.md'],
|
|
56
58
|
},
|
|
57
59
|
]
|
|
58
60
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vibe-math",
|
|
3
3
|
"description": "Multi-agent mathematical problem-solving & verification frameworks for DeepSeek Harness — FOUR agent presets in one install: vibe-math-v2 (probability-driven: qs.json + Propos knowledge base + explorer→solver→review/debate verdict), vibe-math-v3 (THIRD-generation, recommended: paper-style Markdown knowledge base with Problems/Progress/Propos/Methods/Verified + planner-agent scheduling that decides the next N actions + universal theory/method invention library + agents write their own Markdown directly via a per-file write lock), and vibe-math-v4 (FOURTH-generation: persistent self-organizing resident subagents that message & meet to decide all tasks, verify only by unanimous consensus, /compact at a context threshold, and stop only when all agree the problem is solved), and vibe-math-v5 (FIFTH-generation research institute: an academician as the organizational centre who decomposes and ASSIGNS work and chairs meetings; permanent researchers who hold the vote and may hire/fire their own temp workers; temp workers with no vote; a group chat and meetings; a durable per-recipient mailbox; a compare-and-set task DAG; and a boolean m-vote consensus rule where an object enters Verified/ only when at least m voting members agree AND every one of them returns exactly 1 or exactly 0). Installing this bundle auto-installs all four presets (v1 was removed at v2.0.0).",
|
|
4
|
-
"version": "2.3.
|
|
4
|
+
"version": "2.3.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"audit-formal-sensitivity.mjs",
|
|
18
18
|
"audit-persona-sensitivity.mjs",
|
|
19
19
|
"audit-persona-surface.test.mjs",
|
|
20
|
+
"audit-prompt-invariants.mjs",
|
|
21
|
+
"audit-spec-traceability.mjs",
|
|
20
22
|
"audit-v5-integrity.mjs",
|
|
21
23
|
"audit-v5-sensitivity.mjs",
|
|
22
24
|
"cordis.patch.yml",
|
|
@@ -35,6 +37,8 @@
|
|
|
35
37
|
"RELEASE-NOTES-2.2.2.md",
|
|
36
38
|
"RELEASE-NOTES-2.3.0.md",
|
|
37
39
|
"RELEASE-NOTES-2.3.1.md",
|
|
40
|
+
"RELEASE-NOTES-2.3.2.md",
|
|
41
|
+
"RELEASE-NOTES-2.3.3.md",
|
|
38
42
|
"selfdrive-v5.mjs",
|
|
39
43
|
"示例图/框架图-v5.svg",
|
|
40
44
|
"docs/架构图.md",
|
|
@@ -98,7 +102,7 @@
|
|
|
98
102
|
},
|
|
99
103
|
"minVersion": "0.1.2-rc.1",
|
|
100
104
|
"testedVersion": "0.1.5-rc.2",
|
|
101
|
-
"compatNote": "依赖宿主提供的 subagents/agents/tools/commands/fs 服务与 @deepseek-ai/dsh-* 插件行;可选 subprocess/sandboxPolicy/compaction。persona 行同时携带 prefix 与 text 两个键,以兼容 0.1.3-alpha.2 的 schema 更名(prefix 必填)与 0.1.2 及更早的 text 键。已在 dsh-v0.1.5-rc.2(@deepseek-ai/dsh-persona 0.1.5-rc.2)上逐行校验全部预设行并通过(v2/v3/v4;v1 已于 v2.0.0 移除)。注意:DSH 0.1.2 起 subagents.startContinuable 的 agentOptions/toolFilter 需要宿主 provider 声明对应 capability(spawn/fork 进程内 provider 均支持),安装器启动时会做能力自检并在旧版宿主上告警。2026 兼容性修复:v2/v3 工具权限名表原先硬编码 web/fetch/bash(未注册名会使 tools.restrict() 抛错、子代理无法建立),现按真实注册名并加带守卫的重试;v4 真实 /compact 原先在 subagent/end 里查 agents.get()(该事件触发时子代理已移出注册表,属死代码),现改为在 subagent/start 捕获 Agent 引用;三套预设的可选服务改为惰性读取,不再在 apply() 快照;v4 的 tools/commands 注册补入 ctx.effect;安装器自检新增 subprocess/sandboxPolicy/compaction。v2.1.0 新增 v5 研究所体系:状态存于宿主 host-only 会话投影单元(键 vibeMathV5),因此自检新增 sessionProjections/sessions(均为可选;缺失时 v5 回退到加固 JSON 状态文件)。v5 不依赖任何 npm 实验包,纯 preset 内单文件实现。v2.3.1 是审计驱动的提示词/交互修复版(无破坏性变更,默认仍为 off):① 忠实性缺陷不再被记成「命题为假」——新增回执取值 decision='defect'(表决者发现 Lean 代码与命题原文不一致时不得投 0,给中间值并记录具体偏差;框架随即把该对象降级为 attempted、清空 proof、撤回归档证明 Verified/Lean/<id>.lean、写入 Formal/TODO.md,require 档下本次裁定不定论),encourage 档不承诺它无法强制的搁置;② 修复 v2 的 formal 回执通道是死代码(提示词要求写进回执、契约里却没有该字段、框架也从不解析)——补齐 formalJsonField/formalReplyNote/absorbFormalFromReply 并接进初评与辩论两条路径,套件改为行为断言而非措辞断言;③ 修复 v2/v3 忠实性分支的字段名错误(写成 verdict,真实字段是 Result,会导致该票被静默丢弃);④ 注入文本里的工具名一律改为注册名全称(v2/v3/v5 原先出现 lean_lib/lean_archive 缩写,含工具自身返回的 hint);⑤ 新增「归档可复用定义/引理前先跑通」与「宿主无 Lean 工具链(LEAN_NOT_FOUND)时把代码归档并在 note 写明,算显式阻塞原因」两条硬要求;⑥ 四套各自新增随包发布的人工复核语料 prompt-corpus-vN/(覆盖 off/encourage/require/忠实性/工作轮/回执契约),并修复 v5 语料路径归一化在 Windows 大小写差异下漏掉 VibeMath 根绝对路径、导致语料不确定且泄露本机路径的问题;⑦ 新增 16 条提示词灵敏度探针(删掉「不要投 0」、工具名换缩写、删掉 require 门禁措辞、回执契约去掉 defect,各四套),全套件断言 v2 261 / v3 247 / v4 226 / v5 120 / prompt-v5-integrity 588。v2.3.0 为四个架构新增可调控的 Lean 形式化验证(参数 formalVerify = off/encourage/require,默认 off):验证时按实现难度决定是否用 Lean 形式化(写代码+执行),一旦通过则审查对象从「推导是否正确」变成「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」;形式化代码归档为命题的证明(Verified/Lean/<id>.lean),可复用定义与已证引理归档到跨项目的 VibeMath/Formal/{Lib,Proved}/。require 档带门禁:真/假结论必须先有 Lean 通过或显式阻塞记录,否则记为未定论并进入形式化待办。共用契约 docs/formal-verification.md,四套各带 formal-verify-vN 套件(v2 177 / v3 189 / v4 144 / v5 88 断言)与 audit-formal-sensitivity.mjs 探针。同一次审计还发现并修复了一整类**静态提示词面**缺陷(persona ↔ 工具注册表,既有套件全部盲):v2/v3/v4 的 persona 从未列出无条件注册的三个 *_lean_* 工具,v4 的 vibe_v4_set 参数表漏了 formalVerify/leanCommand/leanArgs/leanTimeoutMs,v3 漏了 setup/save_settings/template,v4 漏了 vibe_v4_prompts,v5 漏了增删常驻研究员的工具、且 prefix 与 text 两个块存在文字漂移;现由 audit-persona-surface.test.mjs(197 断言:双向一致性 + 未文档化工具显式快照 + prefix/text 逐行一致 + 斜杠命令 hint/usage/实际分支三处一致 + Lean 参数/档位/路径,并生成随包发布的 prompt-corpus-persona/ 人读语料)与 audit-persona-sensitivity.mjs(11 条探针,含「未变异副本必须为绿」的对照)守护,AUDIT-CHECKLIST.md 新增 §1.6。v2.2.2 新增 v5 架构图(示例图/框架图-v5.svg + docs/generate_framework_diagram_v5.mjs 零依赖 Node 生成器 + vibe-math-v5/架构图.md 全套 Mermaid 细节图),并修复在绘制架构图时暴露的真实缺陷:会议进行中提出的验证会并发启动(会议与验证的互斥此前只做了单向),现改为排队。v2.2.1 把「全面检查必查清单」(AUDIT-CHECKLIST.md) 作为随包强制流程发布,提示词/交互正确性列为第一优先审计维度。v2.2.0 修复实测发现的提示词身份错乱:状态块改为显式接收它所描述的成员,创建成员时先落盘进编制再构造入职提示词,章程快照冻结在入职时,重建会话不再自称“刚入职”,所办调用不再被误判成某位研究员,框架反馈改为独立发送者投递,一次提示词不再重复投递同一条消息,并新增 prompt-v5-integrity 提示词完整性套件 + 可人工复核的提示词语料(随包发布)。",
|
|
105
|
+
"compatNote": "依赖宿主提供的 subagents/agents/tools/commands/fs 服务与 @deepseek-ai/dsh-* 插件行;可选 subprocess/sandboxPolicy/compaction。persona 行同时携带 prefix 与 text 两个键,以兼容 0.1.3-alpha.2 的 schema 更名(prefix 必填)与 0.1.2 及更早的 text 键。已在 dsh-v0.1.5-rc.2(@deepseek-ai/dsh-persona 0.1.5-rc.2)上逐行校验全部预设行并通过(v2/v3/v4;v1 已于 v2.0.0 移除)。注意:DSH 0.1.2 起 subagents.startContinuable 的 agentOptions/toolFilter 需要宿主 provider 声明对应 capability(spawn/fork 进程内 provider 均支持),安装器启动时会做能力自检并在旧版宿主上告警。2026 兼容性修复:v2/v3 工具权限名表原先硬编码 web/fetch/bash(未注册名会使 tools.restrict() 抛错、子代理无法建立),现按真实注册名并加带守卫的重试;v4 真实 /compact 原先在 subagent/end 里查 agents.get()(该事件触发时子代理已移出注册表,属死代码),现改为在 subagent/start 捕获 Agent 引用;三套预设的可选服务改为惰性读取,不再在 apply() 快照;v4 的 tools/commands 注册补入 ctx.effect;安装器自检新增 subprocess/sandboxPolicy/compaction。v2.1.0 新增 v5 研究所体系:状态存于宿主 host-only 会话投影单元(键 vibeMathV5),因此自检新增 sessionProjections/sessions(均为可选;缺失时 v5 回退到加固 JSON 状态文件)。v5 不依赖任何 npm 实验包,纯 preset 内单文件实现。v2.3.3 是 2.3.2 之后的确认轮(重新审计改动过的每一处:四套插件、四套套件、共享契约,以及审计脚本自身),修掉一处真实缺陷与两处同类/字面问题:① v2 的 formal.decision=used 回执**无条件**把记录写成 attempted —— 一句「这一轮碰了形式化」会把已经 passed 的对象在两个 id 空间上一起降级,而 proof 指针仍留着(记录自相矛盾),后果是后续审查提示词丢掉忠实性分支、require 档对一份已跑通的归档证明重新关门并把对象丢进形式化待办;现改为先取合并后的记录状态(formalGateRecord,两套 id 都认),passed/blocked 一律保留,只有从未尝试起步才写 attempted(与 v4 的 formalSetRun 缺陷同类,只是长在回执通道上且只长在一套里)。② v3 的 used 分支只保留 passed(prev.status === passed ? passed : attempted),会把 blocked 打回 attempted —— blocked 本身就是「门禁已放行」的记录,一句 used 就把门禁重新关上;现两者都保留,四套同构。③ 共享契约 docs/formal-verification.md §4 迁移表有两行与实现相反(used 行与两行 lean_run 都写成「→ attempted」),而规范文档写反最危险之处是下一次修改会照它写(2.3.2 修好 v2 后若只读契约就会把修复改回去);现已改正为「已是 passed/blocked 则保持原状」,并补上「lean_archive kind=proof 但运行失败 → attempted + 清空 proof + 撤回旧归档证明」这一行,四套实现方案与 v5 规格同步,audit-spec-traceability 增加 3 条字面校验(91 → 94)。④ 审计脚本自身的守卫加固:audit-prompt-invariants.mjs 的注释扫描器此前不认正则字面量,而四套源码都含一个「字符类里带双引号」的 sanitize 正则字面量,旧扫描器把那个引号当成字符串开头、此后整个文件词法状态都是错的(实测新旧逐行比对:v2 190 行 / v3 159 行 / v4 14 行 / v5 121 行不同,典型后果是真实代码行被当注释抹掉 → 真缺陷可能被 I1/I13/I14 漏看);现在扫描器支持正则字面量(字符类/转义/flags)并新增 X5–X7 三条自检(正则里的引号不得吞掉注释、字符串里的 // 必须保留、转义斜杠不得提前结束正则,且都保持行结构),修复前 X5 会变红。⑤ 行为断言补齐四套同构(此前只有 v4 断言「普通 run 不降级 passed」):四套各新增 used 回执不得降级已 passed 对象、不得把 blocked 打回 attempted、普通 lean_run 不得降级 passed,v2 还断言「used 之后 require 门禁仍放行(把该轮驱动到一致为真不会产生 formal-required 待办)」;本轮是先用断言复现(v2 修复前实测三条变红:got attempted),再修复。套件断言 v2 334 / v3 294 / v4 277 / v5 153 / prompt-v5-integrity 506,audit-prompt-invariants 151/0(--self-probe 5/5),audit-spec-traceability 94/0。v2.3.2 是对四个架构各做一轮深度审计 + 横向同构对照后的修复版(无破坏性变更,默认仍为 off),修掉三处会让「严格验证」失效或不可用的高危缺陷:① v3 的四个 Lean 参数从未写进 vibe_math_set_params 的参数 schema(该 schema 是 additionalProperties:false,遵守 schema 的 provider 会拒绝这个调用)→ 用户永远无法开启该功能,而套件全绿(它直接调 handler、绕过 schema);② v2 的 require 门禁只读验证侧自己的 id,而代理用对象 id 归档、别名同步只更新已存在的键 → 「归档了 passed、验证侧还没有记录」时门禁永远搁置,搁置本身又写下 rId=none,于是每轮重开一次辩论、对象永远无法定论并饿死其它对象;③ v4 的 formalSetRun 硬编码 status=attempted,把已验证对象的 passed 抹掉(与注释、规格、v2/v5 都矛盾)→ 一次随手 lean_run 就让对象丢掉「已形式化」,忠实性分支消失、require 档对已有绿色证明的对象重新关门。另修 v2 的一处门禁旁路(settleVerdict 的「判断命题」转移在 v=0 时直接写 布尔估计=0/已验证/优先级 never 并压入 正确概率:1 条目,完全不看门禁)。撤回语义按 v5 的正确做法统一四套:撤回归档证明时先删、再用 fs 复核文件真的没了、仍在则就地覆盖为撤回说明,并如实公告是哪一种(含两者都失败的告警);lean_archive kind=proof 跑红时不再保留 prev.proof(proof 只属于 passed)并撤回旧的归档证明。提示词/交互修复:忠实性分支改为按档位承诺(只有 require 有门禁,encourage 明确写「本档没有门禁:请务必给弃权值」);无 Lean 工具链的出路同时点名 LEAN_NOT_FOUND 与 NO_SUBPROCESS;v3 的失败提示不再让人去看并不存在的编译器输出;v4 活动日志里的缩写工具名改为注册名(并纳入注入文本扫描);v2 的工作轮不再把可复用引理指向项目内不存在的 Formal/Proved/;v2 的 kind=def/lemma 跑红不再声称可复用;v3 setup 的重复 plannerPersona 去重;v4 规格里的幽灵工具 vibe_v4_propose_verify 修正为回执字段。语料确定性:v5 语料此前的非确定有三层根因(心跳/会议依赖真实时钟与异步顺序、最闲成员抖动、写入端只按 kind 排序),现由套件虚拟时钟 + 单成员研究所内捕捉心跳 + 写入端全序排序(kind→owner→prompt)修复,连跑 6 次字节一致且套件 7–9 s → 1.7 s;v3 的 20 条 planner:* 随机 plan id 与 epoch 时间戳一并 scrub。新增两个随包发布的常驻守卫:audit-prompt-invariants.mjs(四套 × 26 条静态不变式 + 3 条跨套检查,把历次真实发生过的提示词缺陷类别编码住,当前 121/0)与 audit-spec-traceability.mjs(规格/README 承诺的工具必须真的注册,能识别「文档里说它不存在」的否定语境;四个 Lean 参数必须同时被文档与代码接受;契约 §7 的 terminate();契约 §8 的门禁收口点无旁路,当前 91/0),以及 run-tests.mjs(并行跑全部套件并打印耗时/加速比/最慢项,修掉 --only x 空格形式被静默忽略与 --json 混入人类输出两个 bug)。AUDIT-CHECKLIST.md 新增 §1.8「四套同构:任何语义修正必须四套同步」——本轮三处高危里有两处正是改一套或四套同写法却无人横向对照造成的。本轮最贵的一处缺陷还暴露出一整类既有测试全都盲的漏洞:四个预设的工具 schema 都由 objParams 以 additionalProperties:false 关闭,schema 没列出的键会被遵守 schema 的 provider 直接拒绝,而提示词/规格/状态行可以全都在说这个参数、套件也可以全绿(套件直接调 handler、绕过 schema)——因此新增三重守卫:① 四套各自的 formal-verify-vN 套件现在直接检查**真实注册的** schema 对象(封闭性 + 四个 Lean 参数 + formalVerify 的 enum 恰好三档,已用探针证明:去掉 v3 真实注册那份的 leanArgs 立刻变红);② audit-prompt-invariants.mjs 新增 I13(每一处 set 工具定义都必须声明这四个参数,且每一份 objParams 都必须关闭 schema);③ 新增 I14(schema 声明的每个键都必须被参数层真正接收:v2/v3 的闸门是 DEFAULT_PARAMS 键集、v4 是 k in params、v5 是 normalizeParams 的类型列表——声明而不接收 = 调用返回 ok:true 却什么都不发生)。该脚本同时新增 --self-probe:在内存里注入这些缺陷形状,要求对应不变式变红、未变异的对照跑仍为绿(5/5),现为 145 条不变式。套件断言 v2 319 / v3 283 / v4 269 / v5 145 / prompt-v5-integrity 506,e2e-v4-fixes 修掉并行下的抖动(T21 会议看门狗 80 ms 在 CPU 争用下提前放弃会议);全量并行回归连续 3 次 23/23 全绿(最新实测 wall 111.5 s / sum 221.5 s / x1.99)。v2.3.1 是审计驱动的提示词/交互修复版(无破坏性变更,默认仍为 off):① 忠实性缺陷不再被记成「命题为假」——新增回执取值 decision='defect'(表决者发现 Lean 代码与命题原文不一致时不得投 0,给中间值并记录具体偏差;框架随即把该对象降级为 attempted、清空 proof、撤回归档证明 Verified/Lean/<id>.lean、写入 Formal/TODO.md,require 档下本次裁定不定论),encourage 档不承诺它无法强制的搁置;② 修复 v2 的 formal 回执通道是死代码(提示词要求写进回执、契约里却没有该字段、框架也从不解析)——补齐 formalJsonField/formalReplyNote/absorbFormalFromReply 并接进初评与辩论两条路径,套件改为行为断言而非措辞断言;③ 修复 v2/v3 忠实性分支的字段名错误(写成 verdict,真实字段是 Result,会导致该票被静默丢弃);④ 注入文本里的工具名一律改为注册名全称(v2/v3/v5 原先出现 lean_lib/lean_archive 缩写,含工具自身返回的 hint);⑤ 新增「归档可复用定义/引理前先跑通」与「宿主无 Lean 工具链(LEAN_NOT_FOUND)时把代码归档并在 note 写明,算显式阻塞原因」两条硬要求;⑥ 四套各自新增随包发布的人工复核语料 prompt-corpus-vN/(覆盖 off/encourage/require/忠实性/工作轮/回执契约),并修复 v5 语料路径归一化在 Windows 大小写差异下漏掉 VibeMath 根绝对路径、导致语料不确定且泄露本机路径的问题;⑦ 新增 16 条提示词灵敏度探针(删掉「不要投 0」、工具名换缩写、删掉 require 门禁措辞、回执契约去掉 defect,各四套),全套件断言 v2 261 / v3 247 / v4 226 / v5 120 / prompt-v5-integrity 588。v2.3.0 为四个架构新增可调控的 Lean 形式化验证(参数 formalVerify = off/encourage/require,默认 off):验证时按实现难度决定是否用 Lean 形式化(写代码+执行),一旦通过则审查对象从「推导是否正确」变成「Lean 的定义/对象/条件/假设/结论是否忠实于命题原文」;形式化代码归档为命题的证明(Verified/Lean/<id>.lean),可复用定义与已证引理归档到跨项目的 VibeMath/Formal/{Lib,Proved}/。require 档带门禁:真/假结论必须先有 Lean 通过或显式阻塞记录,否则记为未定论并进入形式化待办。共用契约 docs/formal-verification.md,四套各带 formal-verify-vN 套件(v2 177 / v3 189 / v4 144 / v5 88 断言)与 audit-formal-sensitivity.mjs 探针。同一次审计还发现并修复了一整类**静态提示词面**缺陷(persona ↔ 工具注册表,既有套件全部盲):v2/v3/v4 的 persona 从未列出无条件注册的三个 *_lean_* 工具,v4 的 vibe_v4_set 参数表漏了 formalVerify/leanCommand/leanArgs/leanTimeoutMs,v3 漏了 setup/save_settings/template,v4 漏了 vibe_v4_prompts,v5 漏了增删常驻研究员的工具、且 prefix 与 text 两个块存在文字漂移;现由 audit-persona-surface.test.mjs(197 断言:双向一致性 + 未文档化工具显式快照 + prefix/text 逐行一致 + 斜杠命令 hint/usage/实际分支三处一致 + Lean 参数/档位/路径,并生成随包发布的 prompt-corpus-persona/ 人读语料)与 audit-persona-sensitivity.mjs(11 条探针,含「未变异副本必须为绿」的对照)守护,AUDIT-CHECKLIST.md 新增 §1.6。v2.2.2 新增 v5 架构图(示例图/框架图-v5.svg + docs/generate_framework_diagram_v5.mjs 零依赖 Node 生成器 + vibe-math-v5/架构图.md 全套 Mermaid 细节图),并修复在绘制架构图时暴露的真实缺陷:会议进行中提出的验证会并发启动(会议与验证的互斥此前只做了单向),现改为排队。v2.2.1 把「全面检查必查清单」(AUDIT-CHECKLIST.md) 作为随包强制流程发布,提示词/交互正确性列为第一优先审计维度。v2.2.0 修复实测发现的提示词身份错乱:状态块改为显式接收它所描述的成员,创建成员时先落盘进编制再构造入职提示词,章程快照冻结在入职时,重建会话不再自称“刚入职”,所办调用不再被误判成某位研究员,框架反馈改为独立发送者投递,一次提示词不再重复投递同一条消息,并新增 prompt-v5-integrity 提示词完整性套件 + 可人工复核的提示词语料(随包发布)。",
|
|
102
106
|
"compatibility": {
|
|
103
107
|
"dshReleases": {
|
|
104
108
|
"0.1.2-alpha.4": "compatible",
|