dsh-vibe-math 2.6.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +161 -389
- package/README.md +161 -390
- package/cordis.patch.yml +62 -14
- package/docs/AUDIT-CHECKLIST.md +2 -2
- package/docs/final-paper.md +1 -0
- package/docs/formal-verification.md +184 -29
- package/docs/math-computation.md +163 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.0.en.md +151 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.0.md +115 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.1.en.md +86 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.1.md +86 -0
- package/docs/test-timing.md +24 -12
- package/installer.js +7 -4
- package/package.json +21 -3
- package/prompt-corpus-persona/persona-corpus.json +12 -12
- package/prompt-corpus-persona/persona-corpus.md +128 -32
- package/prompt-corpus-v2/formal-verify-v2.json +182 -92
- package/prompt-corpus-v2/formal-verify-v2.md +2379 -55
- package/prompt-corpus-v3/formal-verify-v3.json +215 -53
- package/prompt-corpus-v3/formal-verify-v3.md +2147 -22
- package/prompt-corpus-v4/formal-verify-v4.json +14 -14
- package/prompt-corpus-v4/formal-verify-v4.md +267 -14
- package/prompt-corpus-v5/prompt-corpus-v5.json +62 -62
- package/prompt-corpus-v5/prompt-corpus-v5.md +631 -3
- package/tests/audit-math-computation-contract.mjs +204 -0
- package/tests/audit-math-computation-parity.mjs +198 -0
- package/tests/audit-math-computation-sensitivity.mjs +231 -0
- package/tests/audit-persona-sensitivity.mjs +4 -4
- package/tests/audit-persona-surface.test.mjs +38 -10
- package/tests/audit-v5-integrity.mjs +14 -0
- package/tests/e2e-v5-round2.test.mjs +459 -2
- package/tests/formal-verify-v2.test.mjs +362 -8
- package/tests/formal-verify-v3.test.mjs +355 -6
- package/tests/formal-verify-v4.test.mjs +295 -6
- package/tests/formal-verify-v5.test.mjs +9 -1
- package/tests/helpers/math-computation-fake-seam.mjs +163 -0
- package/tests/math-computation-shared.test.mjs +806 -0
- package/tests/prompt-v5-integrity.test.mjs +5 -5
- package/vibe-math-v2/agent.cordis.yml +22 -8
- package/vibe-math-v2/math-computation.js +1279 -0
- package/vibe-math-v2/math-engines.js +197 -0
- package/vibe-math-v2/vibe-math-v2.js +853 -40
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
- package/vibe-math-v3/agent.cordis.yml +22 -8
- package/vibe-math-v3/math-computation.js +1279 -0
- package/vibe-math-v3/math-engines.js +197 -0
- package/vibe-math-v3/vibe-math-v3.js +844 -47
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
- package/vibe-math-v4/agent.cordis.yml +38 -6
- package/vibe-math-v4/math-computation.js +1279 -0
- package/vibe-math-v4/math-engines.js +197 -0
- package/vibe-math-v4/vibe-math-v4.js +816 -31
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +6 -0
- package/vibe-math-v5/agent.cordis.yml +42 -6
- package/vibe-math-v5/math-computation.js +1279 -0
- package/vibe-math-v5/math-engines.js +197 -0
- package/vibe-math-v5/vibe-math-v5.js +1132 -37
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +83 -3
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +251 -0
|
@@ -17,9 +17,17 @@
|
|
|
17
17
|
// Run: node tests/e2e-v5-round2.test.mjs
|
|
18
18
|
// ============================================================
|
|
19
19
|
import { mkdtempSync, existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs'
|
|
20
|
+
import { createHash } from 'node:crypto'
|
|
20
21
|
import { tmpdir } from 'node:os'
|
|
21
22
|
import { join, dirname, isAbsolute } from 'node:path'
|
|
22
23
|
|
|
24
|
+
// round-7 (fix 2) test isolation: engine discovery now also scans DSH's own runtime trees, and this
|
|
25
|
+
// machine HAS a bundled python. This suite injects ONE fake subprocess for everything (LaTeX + math),
|
|
26
|
+
// so an unexpected engine probe would be recorded as a compiler call and would even write a fake
|
|
27
|
+
// paper.pdf. Pinning DSH_HOME at an empty dir keeps the fixture's premise ("no math engine here")
|
|
28
|
+
// true and the suite deterministic; production behaviour is unaffected.
|
|
29
|
+
process.env.DSH_HOME = mkdtempSync(join(tmpdir(), 'vibe-v5-e2e-nodsh-'))
|
|
30
|
+
|
|
23
31
|
// V5_PLUGIN lets a sensitivity probe point this suite at a deliberately broken copy.
|
|
24
32
|
// Without it every probe against this suite silently tested the UNMUTATED plugin and was
|
|
25
33
|
// reported as a "detection" only because the probe's own spawn failed — i.e. the whole
|
|
@@ -59,6 +67,9 @@ function makeHost(opts) {
|
|
|
59
67
|
const o = opts || {}
|
|
60
68
|
const WS = o.ws || mkdtempSync(join(tmpdir(), 'vibe-v5r2-'))
|
|
61
69
|
const listeners = {}, toolRegs = [], commandRegs = [], spawns = [], wakes = [], interrupts = [], drains = []
|
|
70
|
+
// Every effect disposer, so a test can simulate a plugin UNLOAD (the Lean queue's disposer is
|
|
71
|
+
// registered first: it terminates in-flight compiles and marks them interrupted).
|
|
72
|
+
const effectDisposers = []
|
|
62
73
|
const liveAgents = new Map()
|
|
63
74
|
// The two services the fix removed. This host does not provide them, and records every
|
|
64
75
|
// request so the suite can assert the plugin never even LOOKS for them any more.
|
|
@@ -87,7 +98,7 @@ function makeHost(opts) {
|
|
|
87
98
|
return undefined
|
|
88
99
|
},
|
|
89
100
|
on(e, fn) { (listeners[e] = listeners[e] || []).push(fn) },
|
|
90
|
-
effect(fn) { const d = fn();
|
|
101
|
+
effect(fn) { const d = fn(); const disp = () => { if (typeof d === 'function') d() }; effectDisposers.push(disp); return disp },
|
|
91
102
|
logger: { info() {}, warn() {}, error() {} },
|
|
92
103
|
timeout(cb, ms) { const h = setTimeout(cb, ms); return () => clearTimeout(h) },
|
|
93
104
|
tools: { register(spec) { toolRegs.push(spec); return () => {} } },
|
|
@@ -194,7 +205,25 @@ function makeHost(opts) {
|
|
|
194
205
|
}
|
|
195
206
|
return null
|
|
196
207
|
}
|
|
197
|
-
|
|
208
|
+
// Any member's next prompt matching a predicate — used to capture the CHECKPOINT heartbeat,
|
|
209
|
+
// whose recipient is whichever member the scheduler picked.
|
|
210
|
+
async function peekWakeWhere(pred, maxWaitMs) {
|
|
211
|
+
const t0 = Date.now()
|
|
212
|
+
while (Date.now() - t0 < (maxWaitMs || 3000)) {
|
|
213
|
+
const i = wakes.findIndex(w => pred((w.blocks && w.blocks[0] && w.blocks[0].text) || ''))
|
|
214
|
+
if (i !== -1) {
|
|
215
|
+
const w = wakes.splice(i, 1)[0]
|
|
216
|
+
return { childId: w.childId, text: (w.blocks && w.blocks[0] && w.blocks[0].text) || '' }
|
|
217
|
+
}
|
|
218
|
+
if (wakes.length) {
|
|
219
|
+
const w = wakes.shift()
|
|
220
|
+
fireEnd(w.childId, { progress: '其他成员推进中', solved: false, contextPct: 10 })
|
|
221
|
+
}
|
|
222
|
+
await sleep(20)
|
|
223
|
+
}
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
return { WS, ctx, ROOT, ROOT_SESSION, removedServiceQueries, spawns, wakes, interrupts, drains, toolRegs, commandRegs, listeners, effectDisposers, callTool, childAgent, fireEnd, spawnOf, childOf, labelOf, kindOf, settleSpawns, drain, peekWakeOf, peekWakeWhere, set plannedVotes(v) { plannedVotes = v }, get plannedVotes() { return plannedVotes }, set solvePlan(v) { solvePlan = v } }
|
|
198
227
|
}
|
|
199
228
|
|
|
200
229
|
const pluginModule = await import(PLUGIN.href + '?t=' + Date.now())
|
|
@@ -239,7 +268,59 @@ function fakeLatex(opts) {
|
|
|
239
268
|
const paperKindOf = (p) => /【最终论文·撰写/.test(p) ? 'write'
|
|
240
269
|
: /【最终论文·互审/.test(p) ? 'review'
|
|
241
270
|
: /【最终论文·定稿/.test(p) ? 'final' : ''
|
|
271
|
+
|
|
272
|
+
// ── fake Lean toolchain (docs/formal-verification.md §7) ────────────────────
|
|
273
|
+
// Injected through the SAME `subprocess` service the plugin compiles with, so detection,
|
|
274
|
+
// argv assembly, the queue and the settle logic all run unchanged; only the toolchain is fake.
|
|
275
|
+
// exitFor(argv, file) -> exitCode | undefined terminated -> count of terminate() calls
|
|
276
|
+
// defer: true every spawn stays alive until releaseAll() (for timeout/dispose/build-context)
|
|
277
|
+
function fakeLean(opts) {
|
|
278
|
+
const o = opts || {}
|
|
279
|
+
const calls = []
|
|
280
|
+
return {
|
|
281
|
+
calls,
|
|
282
|
+
terminated: 0,
|
|
283
|
+
async resolveExecutable(cmd) {
|
|
284
|
+
const name = String(cmd || 'lean')
|
|
285
|
+
if (o.missing) throw new Error('ENOENT: ' + name)
|
|
286
|
+
return 'C:/fake/' + name
|
|
287
|
+
},
|
|
288
|
+
spawn(spec) {
|
|
289
|
+
const argv = (spec.argv || []).map(String)
|
|
290
|
+
// Only the LEAN engine is a compile: the plugin also uses the subprocess service for
|
|
291
|
+
// platform shell helpers (directory setup) AND for the math_computation engine probe
|
|
292
|
+
// (which resolves its own candidates through the same seam) — neither may pollute the
|
|
293
|
+
// Lean call log, nor be deferred by the gate.
|
|
294
|
+
const head = String(argv[0] || '')
|
|
295
|
+
if (!/(^|[\\/])lean$/i.test(head)) {
|
|
296
|
+
return { done: Promise.resolve({ exitCode: 0, stdout: '', stderr: '' }), terminate() { /* not a Lean compile */ } }
|
|
297
|
+
}
|
|
298
|
+
const file = argv[argv.length - 1]
|
|
299
|
+
calls.push({ argv, file, cwd: spec.cwd })
|
|
300
|
+
let exitCode = 0
|
|
301
|
+
if (o.alwaysFail) exitCode = 1
|
|
302
|
+
else if (typeof o.exitFor === 'function') { const r = o.exitFor(argv, file); if (r !== undefined && r !== null) exitCode = r }
|
|
303
|
+
const self = this
|
|
304
|
+
if (!o.defer) return { done: Promise.resolve({ exitCode }), terminate() { self.terminated += 1 } }
|
|
305
|
+
let settled = false
|
|
306
|
+
let resolveDone = null
|
|
307
|
+
const done = new Promise((resolve) => { resolveDone = resolve })
|
|
308
|
+
const finish = (v) => { if (!settled) { settled = true; resolveDone(v) } }
|
|
309
|
+
o._release = o._release || []
|
|
310
|
+
o._release.push(() => finish({ exitCode }))
|
|
311
|
+
return { done, terminate() { self.terminated += 1; finish({ exitCode: null, signal: 'SIGTERM' }) } }
|
|
312
|
+
},
|
|
313
|
+
releaseAll() { for (const f of (o._release || []).splice(0)) { try { f() } catch (e) { /* ignore */ } } },
|
|
314
|
+
}
|
|
315
|
+
}
|
|
242
316
|
const paperDirOf = (h, id) => join(h.WS, 'VibeMath', 'Projects', 'default', 'Institutes', 'institute', 'Paper', id || 'institute')
|
|
317
|
+
// Seed a .lean work file inside the institute tree (a run job needs an existing file).
|
|
318
|
+
function seedLeanFile(h, rel, text) {
|
|
319
|
+
const abs = join(h.WS, 'VibeMath', 'Projects', 'default', 'Institutes', 'institute', ...String(rel).split('/'))
|
|
320
|
+
mkdirSync(dirname(abs), { recursive: true })
|
|
321
|
+
writeFileSync(abs, text, 'utf8')
|
|
322
|
+
return abs
|
|
323
|
+
}
|
|
243
324
|
function listFilesUnder(root, rel) {
|
|
244
325
|
const out = []
|
|
245
326
|
const base = rel === undefined ? '' : rel
|
|
@@ -1408,6 +1489,382 @@ console.log('\n[33] an existing Paper/<id>/paper.pdf is never overwritten or del
|
|
|
1408
1489
|
assert(fake2.calls.length === 0, 'no compiler process was launched for the already-delivered pdf')
|
|
1409
1490
|
}
|
|
1410
1491
|
|
|
1492
|
+
// ---------- 34. Lean async/initiative/search params: closed schema + coercion ----------
|
|
1493
|
+
console.log('\n[34] Lean async params: closed schema, coercion, visibleParams, embedded sha256')
|
|
1494
|
+
{
|
|
1495
|
+
const h = makeHost({ pluginModule })
|
|
1496
|
+
const setSpec = h.toolRegs.find(t => t.name === 'vibe_v5_set')
|
|
1497
|
+
const keys = ['leanAsync', 'leanInitiative', 'leanSearchPaths', 'leanJobsMaxParallel']
|
|
1498
|
+
assert(keys.every(k => Object.prototype.hasOwnProperty.call(setSpec.parameters.properties, k)),
|
|
1499
|
+
'★ vibe_v5_set advertises all four new Lean keys (the schema is closed, so an unlisted key is unreachable)')
|
|
1500
|
+
assert(JSON.stringify(setSpec.parameters.properties.leanInitiative.enum) === JSON.stringify(['off', 'normal', 'eager']),
|
|
1501
|
+
'leanInitiative is a closed enum in the schema')
|
|
1502
|
+
const p0 = (await h.callTool('vibe_v5_status', {})).params
|
|
1503
|
+
assert(p0.leanAsync === true && p0.leanInitiative === 'normal' && Array.isArray(p0.leanSearchPaths) && p0.leanSearchPaths.length === 0 && p0.leanJobsMaxParallel === 1,
|
|
1504
|
+
'★ the documented defaults are live and visibleParams exposes all four (' + JSON.stringify({ a: p0.leanAsync, i: p0.leanInitiative, sp: p0.leanSearchPaths, mp: p0.leanJobsMaxParallel }) + ')')
|
|
1505
|
+
const r = await h.callTool('vibe_v5_set', { leanAsync: 'false', leanInitiative: 'bogus', leanJobsMaxParallel: 0, leanSearchPaths: '/libA, /libB' })
|
|
1506
|
+
assert(r.params.leanAsync === false, "★ leanAsync:'false' normalises to FALSE (a string must never stay truthy)")
|
|
1507
|
+
assert(r.params.leanInitiative === 'normal', 'a bogus leanInitiative falls back to the documented default')
|
|
1508
|
+
assert(r.params.leanJobsMaxParallel === 1, 'leanJobsMaxParallel:0 floors at 1')
|
|
1509
|
+
assert(JSON.stringify(r.params.leanSearchPaths) === JSON.stringify(['/libA', '/libB']),
|
|
1510
|
+
'leanSearchPaths accepts a comma string and keeps the given order (' + JSON.stringify(r.params.leanSearchPaths) + ')')
|
|
1511
|
+
const help = String(setSpec.description || '')
|
|
1512
|
+
assert(/leanAsync/.test(help) && /leanInitiative/.test(help) && /leanSearchPaths/.test(help) && /leanJobsMaxParallel/.test(help),
|
|
1513
|
+
'the set-tool help text names all four (the switch is discoverable)')
|
|
1514
|
+
const H = pluginModule.__testHelpers
|
|
1515
|
+
assert(H.sha256Hex('abc') === 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' &&
|
|
1516
|
+
H.sha256Hex('') === 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
|
1517
|
+
'★ the embedded sha256 matches the published vectors (job ids/dedupe rest on it)')
|
|
1518
|
+
assert(H.sha256Hex('中文abc') === createHash('sha256').update('中文abc', 'utf8').digest('hex'), 'it also matches node:crypto on non-ASCII input')
|
|
1519
|
+
assert(H.leanHasSearchFlag(['-R', 'x']) && H.leanHasSearchFlag(['--search-path=o']) && H.leanHasSearchFlag(['--root', 'r']) && !H.leanHasSearchFlag(['-j4']),
|
|
1520
|
+
'the search-flag guard recognises -R / --search-path(=) / --root and nothing else')
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// ---------- 35. async Lean: enqueue → settle(ok) → passed ----------
|
|
1524
|
+
console.log('\n[35] async Lean: enqueue returns at once, settle(ok) is the only path to passed')
|
|
1525
|
+
{
|
|
1526
|
+
const fake = fakeLean({})
|
|
1527
|
+
const h = makeHost({ pluginModule, subprocess: fake })
|
|
1528
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1529
|
+
await h.settleSpawns()
|
|
1530
|
+
await h.callTool('vibe_v5_set', { formalVerify: 'encourage' })
|
|
1531
|
+
const arch = await h.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-async', content: 'theorem p_async : 1 + 1 = 2 := by decide\n' })
|
|
1532
|
+
assert(arch.ok === true && arch.async && arch.async.state === 'queued' && arch.status === 'attempted',
|
|
1533
|
+
'★ the archive ENQUEUES and returns async:{jobId,state} with the object at attempted (' + JSON.stringify(arch).slice(0, 150) + ')')
|
|
1534
|
+
assert(fake.calls.length === 0, '★ the compiler has NOT been started when the tool returns')
|
|
1535
|
+
const st0 = await h.callTool('vibe_v5_status', {})
|
|
1536
|
+
assert(st0.formal.passed.indexOf('p-async') === -1, 'the queued object is not passed')
|
|
1537
|
+
const listed = await h.callTool('vibe_v5_lean_job', {})
|
|
1538
|
+
assert(listed.ok === true && listed.jobs.some(j => j.jobId === arch.jobId), 'lean_job lists the job (' + JSON.stringify(listed.jobs).slice(0, 140) + ')')
|
|
1539
|
+
const waited = await h.callTool('vibe_v5_lean_job', { jobId: arch.jobId, waitMs: 3000 })
|
|
1540
|
+
assert(waited.ok === true && waited.job.state === 'settled' && waited.job.exitCode === 0 && waited.job.proof === 'Verified/Lean/p-async.lean',
|
|
1541
|
+
'★ lean_job {jobId,waitMs} waits for the settle and reports the archived proof (' + JSON.stringify(waited.job).slice(0, 170) + ')')
|
|
1542
|
+
const st1 = await h.callTool('vibe_v5_status', {})
|
|
1543
|
+
assert(st1.formal.passed.indexOf('p-async') !== -1 && st1.formal.objects.find(o => o.target === 'p-async').async.jobId === arch.jobId,
|
|
1544
|
+
'★ ONLY the settled(ok) job set the object passed, and its receipt is on the record')
|
|
1545
|
+
assert(existsSync(join(h.WS, 'VibeMath', 'Projects', 'default', 'Institutes', 'institute', 'Verified', 'Lean', 'p-async.lean')),
|
|
1546
|
+
'the proof is archived under Verified/Lean/<id>.lean')
|
|
1547
|
+
const argv = fake.calls[0].argv
|
|
1548
|
+
const root = join(h.WS, 'VibeMath').replace(/\\/g, '/')
|
|
1549
|
+
const si = argv.indexOf('--search-path')
|
|
1550
|
+
assert(si > 0 && argv[si + 1] === root && si + 2 === argv.length - 1 && /\.lean$/.test(argv[argv.length - 1]),
|
|
1551
|
+
'★ argv carries `--search-path <ABSOLUTE VibeMath root>` right before the file (' + JSON.stringify(argv) + ')')
|
|
1552
|
+
assert(fake.calls.length === 1, 'exactly one compile ran for the settle (got ' + fake.calls.length + ')')
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// ---------- 36. failure / timeout / dispose never verify ----------
|
|
1556
|
+
console.log('\n[36] async Lean: failure, timeout and session dispose never verify')
|
|
1557
|
+
{
|
|
1558
|
+
const fakeFail = fakeLean({ alwaysFail: true })
|
|
1559
|
+
const h = makeHost({ pluginModule, subprocess: fakeFail })
|
|
1560
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1561
|
+
await h.settleSpawns()
|
|
1562
|
+
const a = await h.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-fail', content: 'theorem p_fail : 1 = 2 := by decide\n' })
|
|
1563
|
+
const w = await h.callTool('vibe_v5_lean_job', { jobId: a.jobId, waitMs: 3000 })
|
|
1564
|
+
assert(w.job.state === 'failed', '★ a failed compile settles as failed (' + w.job.state + ')')
|
|
1565
|
+
assert(!existsSync(join(h.WS, 'VibeMath', 'Projects', 'default', 'Institutes', 'institute', 'Verified', 'Lean', 'p-fail.lean')),
|
|
1566
|
+
'★ no Verified/Lean file is written for a failure')
|
|
1567
|
+
assert((await h.callTool('vibe_v5_status', {})).formal.passed.indexOf('p-fail') === -1, 'the failed object stays attempted')
|
|
1568
|
+
// (b) the budget terminates the process
|
|
1569
|
+
const fakeHang = fakeLean({ defer: true })
|
|
1570
|
+
const h2 = makeHost({ pluginModule, subprocess: fakeHang })
|
|
1571
|
+
await h2.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1572
|
+
await h2.settleSpawns()
|
|
1573
|
+
seedLeanFile(h2, 'Formal/good.lean', 'theorem good : 1 + 1 = 2 := by decide\n')
|
|
1574
|
+
const r = await h2.callTool('vibe_v5_lean_run', { file: 'Formal/good.lean', timeout_ms: 1000 })
|
|
1575
|
+
assert(r.ok === true && r.async.state === 'queued', 'the run is queued')
|
|
1576
|
+
const w2 = await h2.callTool('vibe_v5_lean_job', { jobId: r.jobId, waitMs: 5000 })
|
|
1577
|
+
assert(w2.job.state === 'timeout' && fakeHang.terminated >= 1,
|
|
1578
|
+
'★ the per-job budget TERMINATED the process and the job is timeout (' + JSON.stringify({ s: w2.job.state, t: fakeHang.terminated }) + ')')
|
|
1579
|
+
// (c) session dispose (the plugin-unload disposer)
|
|
1580
|
+
const fakeGate = fakeLean({ defer: true })
|
|
1581
|
+
const h3 = makeHost({ pluginModule, subprocess: fakeGate })
|
|
1582
|
+
await h3.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1583
|
+
await h3.settleSpawns()
|
|
1584
|
+
const a3 = await h3.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-dispose', content: 'theorem p_dispose : 1 + 1 = 2 := by decide\n' })
|
|
1585
|
+
await sleep(40)
|
|
1586
|
+
assert(fakeGate.calls.length >= 1, 'precondition: the compile is in flight')
|
|
1587
|
+
h3.effectDisposers[0]()
|
|
1588
|
+
assert(fakeGate.terminated >= 1, '★ dispose TERMINATED the in-flight compiler (no orphan process)')
|
|
1589
|
+
const w3 = await h3.callTool('vibe_v5_lean_job', { jobId: a3.jobId })
|
|
1590
|
+
assert(w3.job.state === 'interrupted', '★ the job is marked interrupted, not settled (' + w3.job.state + ')')
|
|
1591
|
+
assert((await h3.callTool('vibe_v5_status', {})).formal.passed.indexOf('p-dispose') === -1, '★ dispose NEVER verifies an object')
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// ---------- 37. content-hash dedupe ----------
|
|
1595
|
+
console.log('\n[37] identical re-archive is de-duplicated (no rewrite, no recompile)')
|
|
1596
|
+
{
|
|
1597
|
+
const fake = fakeLean({})
|
|
1598
|
+
const h = makeHost({ pluginModule, subprocess: fake })
|
|
1599
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1600
|
+
await h.settleSpawns()
|
|
1601
|
+
const PROOF = 'theorem p_dup : 2 + 2 = 4 := by decide\n'
|
|
1602
|
+
const one = await h.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-dup', content: PROOF })
|
|
1603
|
+
await h.callTool('vibe_v5_lean_job', { jobId: one.jobId, waitMs: 3000 })
|
|
1604
|
+
const before = fake.calls.length
|
|
1605
|
+
const again = await h.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-dup', content: PROOF })
|
|
1606
|
+
assert(again.deduped === true && fake.calls.length === before,
|
|
1607
|
+
'★ identical content is deduped: no new compile (' + JSON.stringify({ d: again.deduped, before, after: fake.calls.length }) + ')')
|
|
1608
|
+
const diff = await h.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-dup', content: PROOF + '-- changed\n' })
|
|
1609
|
+
assert(diff.deduped !== true && diff.async && diff.async.state === 'queued', 'different content is NOT deduped (a new job is queued)')
|
|
1610
|
+
const DEF = 'def ZDup := Fin 2\n'
|
|
1611
|
+
const d1 = await h.callTool('vibe_v5_lean_archive', { kind: 'def', name: 'ZDup', content: DEF })
|
|
1612
|
+
await h.callTool('vibe_v5_lean_job', { jobId: d1.jobId, waitMs: 3000 })
|
|
1613
|
+
const callsAfter = fake.calls.length
|
|
1614
|
+
const d2 = await h.callTool('vibe_v5_lean_archive', { kind: 'def', name: 'ZDup', content: DEF })
|
|
1615
|
+
assert(d2.deduped === true && fake.calls.length === callsAfter, '★ the global library dedupes on the same content hash too')
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// ---------- 38. lean_read: verbatim text + path guard ----------
|
|
1619
|
+
console.log('\n[38] lean_read returns archived text verbatim and refuses a path-shaped name')
|
|
1620
|
+
{
|
|
1621
|
+
const fake = fakeLean({})
|
|
1622
|
+
const h = makeHost({ pluginModule, subprocess: fake })
|
|
1623
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1624
|
+
await h.settleSpawns()
|
|
1625
|
+
const body = 'def ZRead : Nat := 7\n'
|
|
1626
|
+
const a = await h.callTool('vibe_v5_lean_archive', { kind: 'def', name: 'ZRead', content: body })
|
|
1627
|
+
await h.callTool('vibe_v5_lean_job', { jobId: a.jobId, waitMs: 3000 })
|
|
1628
|
+
const rd = await h.callTool('vibe_v5_lean_read', { name: 'ZRead' })
|
|
1629
|
+
assert(rd.ok === true && rd.kind === 'lib' && rd.text === body && rd.bytes === body.length && /^[0-9a-f]{64}$/.test(rd.sha256) && rd.truncated === false,
|
|
1630
|
+
'★ lean_read returns the archived text verbatim with sha/bytes (' + JSON.stringify({ k: rd.kind, b: rd.bytes }) + ')')
|
|
1631
|
+
const esc = await h.callTool('vibe_v5_lean_read', { name: '../ZRead' })
|
|
1632
|
+
assert(esc.ok === false && esc.code === 'V5_INVALID_ARGUMENT', 'a path-shaped name is REFUSED, never sanitised into another file (' + JSON.stringify(esc).slice(0, 90) + ')')
|
|
1633
|
+
assert((await h.callTool('vibe_v5_lean_read', { name: 'C:/x/ZRead' })).ok === false, 'an absolute name is refused')
|
|
1634
|
+
const miss = await h.callTool('vibe_v5_lean_read', { name: 'NoSuchThing' })
|
|
1635
|
+
assert(miss.ok === false && miss.code === 'V5_NOT_FOUND', 'an unknown name is V5_NOT_FOUND')
|
|
1636
|
+
assert((await h.callTool('vibe_v5_lean_read', { name: 'ZRead', kind: 'bogus' })).ok === false, 'a bogus kind is refused')
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// ---------- 39. a job only settles the BUILD CONTEXT it was queued under ----------
|
|
1640
|
+
console.log('\n[39] a job only settles the build context it was queued under')
|
|
1641
|
+
{
|
|
1642
|
+
const fake = fakeLean({ defer: true })
|
|
1643
|
+
const h = makeHost({ pluginModule, subprocess: fake })
|
|
1644
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1645
|
+
await h.settleSpawns()
|
|
1646
|
+
seedLeanFile(h, 'Formal/good.lean', 'theorem good : 1 + 1 = 2 := by decide\n')
|
|
1647
|
+
await h.callTool('vibe_v5_lean_run', { file: 'Formal/good.lean' })
|
|
1648
|
+
await sleep(40)
|
|
1649
|
+
const proof = await h.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-ctx', content: 'theorem p_ctx : 3 + 3 = 6 := by decide\n' })
|
|
1650
|
+
await h.callTool('vibe_v5_set', { leanArgs: ['-Dctx=1'] }) // retune AFTER queueing
|
|
1651
|
+
for (let i = 0; i < 10; i++) { fake.releaseAll(); await sleep(25) }
|
|
1652
|
+
const w = await h.callTool('vibe_v5_lean_job', { jobId: proof.jobId, waitMs: 1000 })
|
|
1653
|
+
assert(w.job.state === 'failed' && w.job.buildMatched === false,
|
|
1654
|
+
'★ a job compiled under retuned args does NOT settle (' + JSON.stringify({ s: w.job.state, b: w.job.buildMatched }) + ')')
|
|
1655
|
+
const st = await h.callTool('vibe_v5_status', {})
|
|
1656
|
+
assert(st.formal.passed.indexOf('p-ctx') === -1, '★ the object stays attempted on a build-context mismatch')
|
|
1657
|
+
assert(!existsSync(join(h.WS, 'VibeMath', 'Projects', 'default', 'Institutes', 'institute', 'Verified', 'Lean', 'p-ctx.lean')),
|
|
1658
|
+
'no Verified/Lean file for a mismatched build')
|
|
1659
|
+
// (b) the CONTENT changed after the job was queued: the compile succeeds, but it compiled
|
|
1660
|
+
// something else — the object must stay attempted.
|
|
1661
|
+
const blocker2 = await h.callTool('vibe_v5_lean_run', { file: 'Formal/good.lean' })
|
|
1662
|
+
await sleep(40)
|
|
1663
|
+
const p2 = await h.callTool('vibe_v5_lean_archive', { kind: 'proof', target: 'p-hash', content: 'theorem p_hash : 4 + 4 = 8 := by decide\n' })
|
|
1664
|
+
seedLeanFile(h, 'Formal/p-hash.lean', 'theorem p_hash : 4 + 4 = 8 := by decide\n-- edited while queued\n')
|
|
1665
|
+
for (let i = 0; i < 10; i++) { fake.releaseAll(); await sleep(25) }
|
|
1666
|
+
const w2 = await h.callTool('vibe_v5_lean_job', { jobId: p2.jobId, waitMs: 1000 })
|
|
1667
|
+
assert(w2.job.exitCode === 0 && w2.job.state === 'failed' && w2.job.buildMatched === true,
|
|
1668
|
+
'★ exit 0 with CHANGED content does not settle either (' + JSON.stringify({ s: w2.job.state, e: w2.job.exitCode, b: w2.job.buildMatched, blocker: blocker2.jobId }) + ')')
|
|
1669
|
+
assert(!existsSync(join(h.WS, 'VibeMath', 'Projects', 'default', 'Institutes', 'institute', 'Verified', 'Lean', 'p-hash.lean')),
|
|
1670
|
+
'a changed-content compile writes no proof either')
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// ---------- 40. leanJobsMaxParallel (default 1 = serial) ----------
|
|
1674
|
+
console.log('\n[40] leanJobsMaxParallel caps simultaneous compiles')
|
|
1675
|
+
{
|
|
1676
|
+
const fake = fakeLean({ defer: true })
|
|
1677
|
+
const h = makeHost({ pluginModule, subprocess: fake })
|
|
1678
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 0 })
|
|
1679
|
+
await h.settleSpawns()
|
|
1680
|
+
seedLeanFile(h, 'Formal/a.lean', 'theorem a : 1 = 1 := rfl\n')
|
|
1681
|
+
seedLeanFile(h, 'Formal/b.lean', 'theorem b : 2 = 2 := rfl\n')
|
|
1682
|
+
await h.callTool('vibe_v5_lean_run', { file: 'Formal/a.lean' })
|
|
1683
|
+
await h.callTool('vibe_v5_lean_run', { file: 'Formal/b.lean' })
|
|
1684
|
+
await sleep(50)
|
|
1685
|
+
assert(fake.calls.length === 1, '★ with the default maxParallel=1 only ONE compile is in flight (got ' + fake.calls.length + ')')
|
|
1686
|
+
const listed = await h.callTool('vibe_v5_lean_job', {})
|
|
1687
|
+
assert(listed.maxParallel === 1 && listed.jobs.filter(j => j.state === 'queued').length === 1,
|
|
1688
|
+
'lean_job reports maxParallel=1 with the second job queued (' + JSON.stringify(listed).slice(0, 150) + ')')
|
|
1689
|
+
const q = listed.jobs.find(j => j.state === 'queued')
|
|
1690
|
+
await h.callTool('vibe_v5_set', { leanJobsMaxParallel: 2 })
|
|
1691
|
+
await h.callTool('vibe_v5_lean_job', { jobId: q.jobId, waitMs: 150 })
|
|
1692
|
+
await sleep(30)
|
|
1693
|
+
assert(fake.calls.length === 2, '★ raising maxParallel to 2 starts the queued job as well (got ' + fake.calls.length + ')')
|
|
1694
|
+
for (let i = 0; i < 8; i++) { fake.releaseAll(); await sleep(20) }
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// ---------- 41. the daily/verify Lean text is gated by leanInitiative ----------
|
|
1698
|
+
console.log('\n[41] leanInitiative gates the daily Lean reminders; the async rule is in both blocks')
|
|
1699
|
+
{
|
|
1700
|
+
const h = makeHost({ pluginModule })
|
|
1701
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 1 })
|
|
1702
|
+
await h.settleSpawns()
|
|
1703
|
+
const promptFor = async (member, plan) => {
|
|
1704
|
+
if (plan) await h.callTool('vibe_v5_set', plan)
|
|
1705
|
+
await h.callTool('vibe_v5_say', { to: member, text: '请继续推进。' }, h.childAgent(h.childOf('acad')))
|
|
1706
|
+
const w = await h.peekWakeOf(member, 3000)
|
|
1707
|
+
if (!w) return ''
|
|
1708
|
+
h.fireEnd(w.childId, { progress: '收到。', solved: false, contextPct: 10 })
|
|
1709
|
+
await sleep(25)
|
|
1710
|
+
return w.text
|
|
1711
|
+
}
|
|
1712
|
+
const normal = await promptFor('r-1', { formalVerify: 'off', leanInitiative: 'normal' })
|
|
1713
|
+
assert(normal.indexOf('【顺手形式化') === -1, "leanInitiative=normal + formalVerify=off keeps today's behaviour (no daily Lean line)")
|
|
1714
|
+
const eager = await promptFor('r-1', { leanInitiative: 'eager' })
|
|
1715
|
+
assert(eager.indexOf('【顺手形式化') !== -1 && eager.indexOf('**主动**') !== -1,
|
|
1716
|
+
'★ leanInitiative=eager injects the daily line even when formalVerify is off')
|
|
1717
|
+
assert(eager.indexOf('判断标准:①') !== -1 && eager.indexOf('先 vibe_v5_lean_lib 查已有库') !== -1 && eager.indexOf('**查不到再新写**') !== -1,
|
|
1718
|
+
'★ the three criteria + reuse-first ride in the daily line')
|
|
1719
|
+
assert(eager.indexOf('import Formal.Lib.<name>') !== -1 && eager.indexOf('vibe_v5_lean_read') !== -1, 'the import/read reuse routes are named')
|
|
1720
|
+
assert(eager.indexOf('没把握就记 blocked') !== -1, 'the no-confidence→blocked rule rides in the daily line')
|
|
1721
|
+
assert(eager.indexOf('leanAsync=true') !== -1 && eager.indexOf('不得把该对象当成已通过') !== -1, '★ the async honesty rule rides in the daily line')
|
|
1722
|
+
const offInit = await promptFor('r-1', { formalVerify: 'encourage', leanInitiative: 'off' })
|
|
1723
|
+
assert(offInit.indexOf('【顺手形式化') === -1, '★ leanInitiative=off suppresses the daily line even when formalVerify is on')
|
|
1724
|
+
// The VERIFY block carries the async rule too.
|
|
1725
|
+
await h.callTool('vibe_v5_set', { formalVerify: 'encourage', leanInitiative: 'normal' })
|
|
1726
|
+
await h.callTool('vibe_v5_record_proposition', { id: 'p-leanv', statement: '异步落地前不得当已通过', value: 0.6, motive: 'm', p: 0.9 }, h.childAgent(h.childOf('r-1')))
|
|
1727
|
+
await h.callTool('vibe_v5_propose_verify', { target: 'p-leanv', kind: 'proposition', reason: '测试' }, h.childAgent(h.childOf('r-1')))
|
|
1728
|
+
const vw = await h.peekWakeOf('r-1', 3000)
|
|
1729
|
+
const vtxt = vw ? vw.text : ''
|
|
1730
|
+
assert(/形式化只写你有把握的版本/.test(vtxt) && /不得\*\*在它落地前声称已通过/.test(vtxt),
|
|
1731
|
+
'★ the verify block carries the confidence + async-honesty lines (' + JSON.stringify(vtxt.slice(-260)) + ')')
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// ---------- 42. per-injection-site gate: every Lean prompt site, both directions ----------
|
|
1735
|
+
// Lens-1 (HIGH coverage gap): the gate `leanInitiative !== 'off' && (formalOn() || eager)` is
|
|
1736
|
+
// correct, but a static scan found ZERO assertions naming `eager` per SITE. v5 has exactly two
|
|
1737
|
+
// DAILY-line sites (normalPrompt, checkpointPrompt) plus two mode-gated Lean sites (briefBlock's
|
|
1738
|
+
// `[形式化]` state line and verifyPrompt's formalPromptBlock). Each site below is asserted in BOTH
|
|
1739
|
+
// directions: (1) formalVerify=off + leanInitiative=eager ⇒ that site HAS the daily line (with the
|
|
1740
|
+
// `**主动**` mark) while the VERIFY block stays absent; (2) leanInitiative=off ⇒ that site has NO
|
|
1741
|
+
// daily line while the mode/verify text still follows formalVerify.
|
|
1742
|
+
console.log('\n[42] every Lean prompt site obeys the leanInitiative gate in both directions')
|
|
1743
|
+
{
|
|
1744
|
+
const DAILY = '【顺手形式化'
|
|
1745
|
+
const EAGER = '**主动**'
|
|
1746
|
+
// The verify-phase BLOCK header (its mode suffix distinguishes it from the normal prompt's
|
|
1747
|
+
// reply contract, which also *mentions* 【Lean 形式化验证】).
|
|
1748
|
+
const VERIFY_BLOCK = '【Lean 形式化验证('
|
|
1749
|
+
const STATE = '[形式化]'
|
|
1750
|
+
const h = makeHost({ pluginModule })
|
|
1751
|
+
await h.callTool('vibe_v5_start', { problem: PROBLEM, researcherCount: 1 })
|
|
1752
|
+
await h.settleSpawns()
|
|
1753
|
+
const setAndAssert = async (plan) => {
|
|
1754
|
+
const r = await h.callTool('vibe_v5_set', plan)
|
|
1755
|
+
assert(r.ok === true, 'precondition: vibe_v5_set applied ' + JSON.stringify(plan) + ' (' + JSON.stringify(r).slice(0, 120) + ')')
|
|
1756
|
+
return r.params
|
|
1757
|
+
}
|
|
1758
|
+
// Pending turns must be ANSWERED, never dropped: a discarded wake leaves its member busy
|
|
1759
|
+
// forever, so the scheduler would have no idle member left to send a heartbeat to.
|
|
1760
|
+
const flushWakes = async (rounds) => {
|
|
1761
|
+
for (let i = 0; i < (rounds || 8); i++) {
|
|
1762
|
+
const w = h.wakes.shift()
|
|
1763
|
+
if (!w) return
|
|
1764
|
+
h.fireEnd(w.childId, { progress: '收到。', solved: false, contextPct: 10 })
|
|
1765
|
+
await sleep(10)
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
const normalFor = async (member) => {
|
|
1769
|
+
await h.callTool('vibe_v5_say', { to: member, text: '请继续推进。' }, h.childAgent(h.childOf('acad')))
|
|
1770
|
+
const w = await h.peekWakeOf(member, 3000)
|
|
1771
|
+
if (!w) return ''
|
|
1772
|
+
h.fireEnd(w.childId, { progress: '收到。', solved: false, contextPct: 10 })
|
|
1773
|
+
await sleep(25)
|
|
1774
|
+
return w.text
|
|
1775
|
+
}
|
|
1776
|
+
// ── SITE 1: normalPrompt ────────────────────────────────────────────────────
|
|
1777
|
+
const pA = await setAndAssert({ formalVerify: 'off', leanInitiative: 'eager' })
|
|
1778
|
+
assert(pA.formalVerify === 'off' && pA.leanInitiative === 'eager', 'precondition: formalVerify=off + leanInitiative=eager is live')
|
|
1779
|
+
await flushWakes()
|
|
1780
|
+
const s1eager = await normalFor('r-1')
|
|
1781
|
+
assert(s1eager.indexOf(DAILY) !== -1 && s1eager.indexOf(EAGER) !== -1,
|
|
1782
|
+
'S1 normalPrompt: formalVerify=off + leanInitiative=eager ⇒ the daily line IS injected (with **主动**)')
|
|
1783
|
+
assert(s1eager.indexOf(VERIFY_BLOCK) === -1 && s1eager.indexOf(STATE) === -1,
|
|
1784
|
+
'S1 normalPrompt: …while the VERIFY block and the [形式化] state line stay ABSENT (formalVerify=off)')
|
|
1785
|
+
const pB = await setAndAssert({ formalVerify: 'encourage', leanInitiative: 'off' })
|
|
1786
|
+
assert(pB.formalVerify === 'encourage' && pB.leanInitiative === 'off', 'precondition: formalVerify=encourage + leanInitiative=off is live')
|
|
1787
|
+
await sleep(30) // let prompts built with the previous params drain
|
|
1788
|
+
await flushWakes()
|
|
1789
|
+
const s1off = await normalFor('r-1')
|
|
1790
|
+
assert(s1off.indexOf(DAILY) === -1, 'S1 normalPrompt: leanInitiative=off ⇒ NO daily line')
|
|
1791
|
+
assert(s1off.indexOf(VERIFY_BLOCK) === -1 && s1off.indexOf(STATE) !== -1,
|
|
1792
|
+
'S1 normalPrompt: …but the mode-gated [形式化] state line still follows formalVerify=encourage (stateIdx=' + s1off.indexOf(STATE) + ')')
|
|
1793
|
+
// ── SITE 2: checkpointPrompt (the heartbeat) ────────────────────────────────
|
|
1794
|
+
// Delivered to the longest-idle member that owns no in-progress task, so the queue is answered
|
|
1795
|
+
// FIFO (a live institute keeps working) until a 【心跳检查】 turn shows up.
|
|
1796
|
+
const checkpointFor = async (plan) => {
|
|
1797
|
+
await setAndAssert(plan)
|
|
1798
|
+
await flushWakes()
|
|
1799
|
+
const seen = []
|
|
1800
|
+
for (let i = 0; i < 30; i++) {
|
|
1801
|
+
// A tuning call drives one scheduling pass, exactly like a real institute where events keep
|
|
1802
|
+
// arriving; the heartbeat branch then picks the longest-idle member.
|
|
1803
|
+
await h.callTool('vibe_v5_set', { maxParallel: i % 2 === 0 ? 3 : 4 })
|
|
1804
|
+
const w = await h.peekWakeWhere(() => true, 300)
|
|
1805
|
+
if (!w) continue
|
|
1806
|
+
seen.push(w.text.slice(0, 20))
|
|
1807
|
+
const isCheckpoint = w.text.indexOf('【心跳检查') !== -1
|
|
1808
|
+
h.fireEnd(w.childId, { progress: '继续推进。', solved: false, contextPct: 10 })
|
|
1809
|
+
await sleep(15)
|
|
1810
|
+
if (isCheckpoint) return { text: w.text, seen }
|
|
1811
|
+
}
|
|
1812
|
+
return { text: '', seen }
|
|
1813
|
+
}
|
|
1814
|
+
await h.callTool('vibe_v5_set', { activityTimeoutMs: 40, chatDigestMs: 600000, stallAutoMeetingMs: 600000 })
|
|
1815
|
+
const s2a = await checkpointFor({ formalVerify: 'off', leanInitiative: 'eager' })
|
|
1816
|
+
const s2st = await h.callTool('vibe_v5_status', {})
|
|
1817
|
+
assert(s2a.text !== '' && s2a.text.indexOf(DAILY) !== -1 && s2a.text.indexOf(EAGER) !== -1,
|
|
1818
|
+
'S2 checkpointPrompt (heartbeat): formalVerify=off + leanInitiative=eager ⇒ the daily line IS injected (seen=' + JSON.stringify(s2a.seen) + ' status=' + JSON.stringify({ verify: s2st.verify && s2st.verify.target, meeting: s2st.meeting, paper: s2st.paper && s2st.paper.stage, busy: (s2st.members || []).filter((m) => m.busy).map((m) => m.id), undecided: s2st.undecided }) + ')')
|
|
1819
|
+
assert(s2a.text.indexOf(VERIFY_BLOCK) === -1, 'S2 checkpointPrompt: the VERIFY block stays absent with formalVerify=off')
|
|
1820
|
+
const s2b = await checkpointFor({ formalVerify: 'encourage', leanInitiative: 'off' })
|
|
1821
|
+
assert(s2b.text !== '' && s2b.text.indexOf(DAILY) === -1,
|
|
1822
|
+
'S2 checkpointPrompt: leanInitiative=off ⇒ NO daily line (seen=' + JSON.stringify(s2b.seen) + ')')
|
|
1823
|
+
assert(s2b.text.indexOf(STATE) !== -1, 'S2 checkpointPrompt: …while the [形式化] state line still follows formalVerify=encourage')
|
|
1824
|
+
// ── SITE 3: verifyPrompt's formalPromptBlock ────────────────────────────────
|
|
1825
|
+
// Finish any verification that is already in flight (answering every voter turn) so that the
|
|
1826
|
+
// NEXT verify wake is necessarily built with the params we set afterwards.
|
|
1827
|
+
const drainVerify = async () => {
|
|
1828
|
+
for (let i = 0; i < 25; i++) {
|
|
1829
|
+
const st = await h.callTool('vibe_v5_status', {})
|
|
1830
|
+
if (!st.verify) return true
|
|
1831
|
+
await h.callTool('vibe_v5_set', { maxParallel: i % 2 === 0 ? 3 : 4 })
|
|
1832
|
+
const w = await h.peekWakeWhere((p) => p.indexOf('【求真表决') !== -1, 250)
|
|
1833
|
+
if (w) {
|
|
1834
|
+
const tm = /"target"\s*:\s*"([^"]+)"/.exec(w.text)
|
|
1835
|
+
h.fireEnd(w.childId, { verdict: { target: tm ? tm[1] : st.verify.target, verdict: 0.5, reason: '存疑' }, contextPct: 20 })
|
|
1836
|
+
}
|
|
1837
|
+
await sleep(20)
|
|
1838
|
+
}
|
|
1839
|
+
return false
|
|
1840
|
+
}
|
|
1841
|
+
const verifyFor = async (plan) => {
|
|
1842
|
+
await drainVerify()
|
|
1843
|
+
await setAndAssert(plan)
|
|
1844
|
+
const id = 'p-site-' + Math.random().toString(36).slice(2, 8)
|
|
1845
|
+
await h.callTool('vibe_v5_record_proposition', { id, statement: '站点门控', value: 0.6, motive: 'm', p: 0.9 }, h.childAgent(h.childOf('r-1')))
|
|
1846
|
+
await h.callTool('vibe_v5_propose_verify', { target: id, kind: 'proposition', reason: 'x' }, h.childAgent(h.childOf('r-1')))
|
|
1847
|
+
for (let i = 0; i < 25; i++) {
|
|
1848
|
+
await h.callTool('vibe_v5_set', { maxParallel: i % 2 === 0 ? 3 : 4 })
|
|
1849
|
+
const w = await h.peekWakeWhere((p) => p.indexOf('【求真表决') !== -1, 300)
|
|
1850
|
+
if (!w) continue
|
|
1851
|
+
const tm = /"target"\s*:\s*"([^"]+)"/.exec(w.text)
|
|
1852
|
+
h.fireEnd(w.childId, { verdict: { target: tm ? tm[1] : id, verdict: 0.5, reason: '存疑' }, contextPct: 20 })
|
|
1853
|
+
await sleep(25)
|
|
1854
|
+
return w.text
|
|
1855
|
+
}
|
|
1856
|
+
return ''
|
|
1857
|
+
}
|
|
1858
|
+
const s3off = await verifyFor({ formalVerify: 'off', leanInitiative: 'eager' })
|
|
1859
|
+
assert(s3off.indexOf('【求真表决') !== -1 && s3off.indexOf(VERIFY_BLOCK) === -1,
|
|
1860
|
+
'S3 verifyPrompt: formalVerify=off ⇒ NO verify-phase Lean block even when leanInitiative=eager')
|
|
1861
|
+
assert(s3off.indexOf(DAILY) === -1, 'S3 verifyPrompt: the daily line is never injected into the verify prompt')
|
|
1862
|
+
const s3on = await verifyFor({ formalVerify: 'encourage', leanInitiative: 'off' })
|
|
1863
|
+
assert(s3on.indexOf('【求真表决') !== -1 && s3on.indexOf(VERIFY_BLOCK) !== -1,
|
|
1864
|
+
'S3 verifyPrompt: formalVerify=encourage ⇒ the verify-phase block appears even with leanInitiative=off')
|
|
1865
|
+
assert(s3on.indexOf(DAILY) === -1, 'S3 verifyPrompt: …and the daily line is still absent there')
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1411
1868
|
console.log('')
|
|
1412
1869
|
console.log('passed=' + passed + ' failed=' + failed)
|
|
1413
1870
|
if (failed) { console.error('FAILURES:'); for (const f of failures) console.error(' - ' + f); process.exit(1) }
|