dsh-vibe-math 2.7.0 → 2.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.en.md +681 -685
  2. package/README.md +9 -13
  3. package/cordis.patch.yml +12 -6
  4. package/docs/AUDIT-CHECKLIST.md +518 -518
  5. package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
  6. package/docs/final-paper.md +172 -172
  7. package/docs/formal-verification.md +588 -588
  8. package/docs/math-computation.md +187 -121
  9. package/docs/release-notes/README.en.md +48 -0
  10. package/docs/release-notes/README.md +47 -0
  11. package/docs/release-notes/RELEASE-NOTES-2.7.1.en.md +86 -0
  12. package/docs/release-notes/RELEASE-NOTES-2.7.1.md +86 -0
  13. package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +72 -0
  14. package/docs/release-notes/RELEASE-NOTES-2.7.2.md +72 -0
  15. package/docs/release-notes/TEMPLATE.md +120 -0
  16. package/docs/test-timing.md +154 -154
  17. package/package.json +208 -201
  18. package/prompt-corpus-persona/persona-corpus.json +8 -8
  19. package/prompt-corpus-persona/persona-corpus.md +24 -12
  20. package/prompt-corpus-v2/formal-verify-v2.json +176 -98
  21. package/prompt-corpus-v2/formal-verify-v2.md +1161 -96
  22. package/prompt-corpus-v3/formal-verify-v3.json +201 -63
  23. package/prompt-corpus-v3/formal-verify-v3.md +1401 -57
  24. package/prompt-corpus-v4/formal-verify-v4.json +10 -10
  25. package/prompt-corpus-v4/formal-verify-v4.md +40 -20
  26. package/prompt-corpus-v5/prompt-corpus-v5.json +81 -81
  27. package/prompt-corpus-v5/prompt-corpus-v5.md +199 -128
  28. package/tests/audit-math-computation-contract.mjs +56 -1
  29. package/tests/audit-math-computation-parity.mjs +30 -1
  30. package/tests/audit-math-computation-sensitivity.mjs +2 -2
  31. package/tests/audit-v5-sensitivity.mjs +3 -1
  32. package/tests/e2e-v5-round2.test.mjs +116 -0
  33. package/tests/formal-verify-v2.test.mjs +108 -2
  34. package/tests/formal-verify-v3.test.mjs +117 -2
  35. package/tests/formal-verify-v4.test.mjs +87 -0
  36. package/tests/helpers/math-computation-fake-seam.mjs +30 -2
  37. package/tests/math-computation-shared.test.mjs +430 -3
  38. package/vibe-math-v2/agent.cordis.yml +4 -2
  39. package/vibe-math-v2/math-computation.js +509 -55
  40. package/vibe-math-v2/math-engines.js +47 -6
  41. package/vibe-math-v2/vibe-math-v2.js +92 -12
  42. package/vibe-math-v3/agent.cordis.yml +4 -2
  43. package/vibe-math-v3/math-computation.js +509 -55
  44. package/vibe-math-v3/math-engines.js +47 -6
  45. package/vibe-math-v3/vibe-math-v3.js +77 -14
  46. package/vibe-math-v4/agent.cordis.yml +8 -4
  47. package/vibe-math-v4/math-computation.js +509 -55
  48. package/vibe-math-v4/math-engines.js +47 -6
  49. package/vibe-math-v4/vibe-math-v4.js +70 -15
  50. package/vibe-math-v5/agent.cordis.yml +8 -4
  51. package/vibe-math-v5/math-computation.js +509 -55
  52. package/vibe-math-v5/math-engines.js +47 -6
  53. package/vibe-math-v5/vibe-math-v5.js +152 -31
  54. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1660 -1655
  55. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
@@ -270,6 +270,24 @@ async function proposeAndVote(h, target, opts = {}) {
270
270
  // The verification may only have been QUEUED by the proposal round; finish it.
271
271
  return await settleVerify(h, opts.plan || (() => (opts.verdict === undefined ? 1 : opts.verdict)))
272
272
  }
273
+ /**
274
+ * Start a verification and KEEP IT IN FLIGHT (the first half of `proposeAndVote`): record a card, have
275
+ * r-1 propose verifying it, and stop driving as soon as the verify view is ACTIVE. Note the trap:
276
+ * `verifyInProgress` is already true while the verification is merely QUEUED, so `consensus.kind`
277
+ * is the only reliable "it is really running" signal.
278
+ */
279
+ async function startVerifyOnly(h, target) {
280
+ const card = { id: target, title: target, statement: '待验证对象 ' + target, prob: 0.8, value: 0.6, motivation: 'm' }
281
+ await h.callTool('vibe_v4_record_proposition', card, h.resAgent(h.childOf('r-1')))
282
+ const before = h.followups.length
283
+ await h.callTool('vibe_v4_message', { to: 'r-1', content: '请处理本轮工作。' })
284
+ for (let i = 0; i < 300 && h.followups.length === before; i++) await sleep(10)
285
+ await drive(h, () => ({ summary: '提议验证 ' + target + '。', solved: false, propose_verify: target, contextPct: 20 }), async () => {
286
+ const s = await h.callTool('vibe_v4_status', {})
287
+ return !!(s.consensus && s.consensus.kind === 'verify')
288
+ })
289
+ return await h.callTool('vibe_v4_status', {})
290
+ }
273
291
  /** Wake exactly the given resident with an ordinary work round and return that wake. */
274
292
  async function workWake(h, rId) {
275
293
  const before = h.followups.length
@@ -1479,6 +1497,75 @@ section('18 every daily-line injection site (coreRules/normal/heartbeat) obeys t
1479
1497
  await N.callTool('vibe_v4_set', { formalVerify: 'off', leanInitiative: 'normal' })
1480
1498
  }
1481
1499
 
1500
+ // ===============================================================
1501
+ // 22. cross-preset audit P0 (v4 station): the member-visible library text must state that the
1502
+ // residents' file tools resolve against the SESSION CWD, so the listed sub-paths must be used
1503
+ // with the ABSOLUTE prefix (and computation artifacts via receipt.scriptAbs / receipt.cwd).
1504
+ // ===============================================================
1505
+ section('22 audit P0: member-facing paths carry the session-cwd / absolute-prefix instruction')
1506
+ {
1507
+ const N = await establish()
1508
+ const texts = [await N.prompts('brainstorm', 'r-1'), await N.prompts('coreRules'), await N.prompts('normal', 'r-1')]
1509
+ const ok = texts.filter((t) => t.indexOf('会话 cwd') !== -1 && t.indexOf('绝对前缀') !== -1)
1510
+ assert(ok.length >= 1, '★ the member-visible library text says file tools resolve against the SESSION CWD and that listed paths need the ABSOLUTE prefix (found in ' + ok.length + '/3 variants)')
1511
+ assert(ok.some((t) => t.indexOf('receipt.scriptAbs') !== -1 || t.indexOf('receipt.cwd') !== -1), '★ …and names the absolute receipt field (receipt.scriptAbs / receipt.cwd) for computation artifacts')
1512
+ }
1513
+
1514
+ // ===============================================================
1515
+ // 23. cross-preset audit P1 (v4 stations): a member-facing not-found must be diagnosable — code +
1516
+ // live roster status + an actionable next step — including `removeMember` (previously a bare
1517
+ // {ok:false}) and the not-yet-started case.
1518
+ // ===============================================================
1519
+ section('23 audit P1: member-facing not-found carries code + status + next')
1520
+ {
1521
+ const N = await establish()
1522
+ const wrong = await N.callTool('vibe_v4_send_message', { to: 'r-404', content: 'hi' }, N.resAgent(N.childOf('r-1')))
1523
+ assert(wrong.ok === false && wrong.code === 'V4_NO_SUCH_RESIDENT' && !!wrong.next && !!wrong.next.tool, '★ wrong resident id ⇒ code V4_NO_SUCH_RESIDENT + next{tool} (not a bare message)')
1524
+ assert(wrong.status && wrong.status.residentCount >= 1 && wrong.status.running === true, '★ …plus LIVE roster status, so "wrong id" is distinguishable from "not started"')
1525
+ const kicked = await N.callTool('vibe_v4_remove_member', { id: 'r-404' })
1526
+ assert(kicked.ok === false && kicked.code === 'V4_NO_SUCH_RESIDENT' && !!kicked.next && kicked.next.tool === 'vibe_v4_remove_member', '★ vibe_v4_remove_member {id:r-404} ⇒ code + next naming that tool (was a bare {ok:false})')
1527
+ const fresh = await mount()
1528
+ const notStarted = await fresh.callTool('vibe_v4_publish_progress', { content: 'x' })
1529
+ assert(notStarted.ok === false && notStarted.code === 'V4_NO_SUCH_RESIDENT' && !!notStarted.next && !!notStarted.status && notStarted.status.running === false, '★ run not started ⇒ code + next + status.running=false')
1530
+ }
1531
+
1532
+ // ---------- D4 cross-view: mid-verify hire/fire must not split the views ----------
1533
+ section('D4 cross-view: one frozen participant set, seen identically by every view')
1534
+ {
1535
+ const h = await establish()
1536
+ const st = await startVerifyOnly(h, 'p-d4')
1537
+ assert(st.consensus && st.consensus.kind === 'verify', 'precondition: an ACTIVE verification for the cross-view check (' + JSON.stringify({ c: st.consensus && st.consensus.kind, ip: st.verifyInProgress }) + ')')
1538
+ const c0 = st.consensus
1539
+ const rp0 = await h.callTool('vibe_v4_report', {})
1540
+ const rv0 = rp0.verify
1541
+ assert(Array.isArray(st.frozenParticipants) && rv0, 'the frozen participant set and the report() verify view are both exposed')
1542
+ assert(c0.expected === st.frozenParticipants.length, '★ consensus.expected === the frozen participant set size')
1543
+ assert(Number(String(rv0.voted).split('/')[1]) === c0.expected, '★ the two views agree on the DENOMINATOR (status().consensus.expected vs report().verify k/N)')
1544
+ assert(rv0.rosterVersion === c0.rosterVersion && c0.rosterVersion === st.rosterVersion, '★ both views (and status) report the SAME rosterVersion')
1545
+ // HIRE mid-verify: the newcomer must NOT join the frozen set (that is what freezing means).
1546
+ const hire = await h.callTool('vibe_v4_add_member', { direction: 'D4 cross-view assertion' })
1547
+ const st2 = await h.callTool('vibe_v4_status', {})
1548
+ const rv2 = (await h.callTool('vibe_v4_report', {})).verify
1549
+ assert(st2.rosterVersion > c0.rosterVersion, 'hiring mid-verify bumps rosterVersion (' + c0.rosterVersion + ' -> ' + st2.rosterVersion + ')')
1550
+ assert(st2.consensus.expected === st2.frozenParticipants.length && st2.consensus.expected === c0.expected,
1551
+ '★ after a mid-verify HIRE the frozen set is UNCHANGED and both views still agree (newcomer did not join)')
1552
+ assert(rv2.rosterVersion === st2.consensus.rosterVersion,
1553
+ '★ after the HIRE the two FROZEN views (status().consensus + report().verify) still report the SAME rosterVersion')
1554
+ assert(st2.rosterVersion > rv2.rosterVersion,
1555
+ '★ …while status().rosterVersion is the LIVE counter and has moved past the snapshot (staleness is visible)')
1556
+ // FIRE mid-verify: the frozen set is pruned so the in-flight verification can still conclude.
1557
+ if (hire && hire.ok && hire.id) await h.callTool('vibe_v4_remove_member', { id: hire.id })
1558
+ const st3 = await h.callTool('vibe_v4_status', {})
1559
+ const rv3 = (await h.callTool('vibe_v4_report', {})).verify
1560
+ assert(st3.consensus.expected === st3.frozenParticipants.length && Number(String(rv3.voted).split('/')[1]) === st3.consensus.expected && rv3.rosterVersion === st3.consensus.rosterVersion,
1561
+ '★ after a mid-verify FIRE the two views STILL agree (the frozen set was pruned together)')
1562
+ assert(st3.frozenParticipants.indexOf(hire && hire.id) === -1, '★ the removed member is not in the frozen set any more (pruned on removal)')
1563
+ // …and the verification really does conclude after that churn (T26/T31 contract).
1564
+ await settleVerify(h, () => 1)
1565
+ const st4 = await h.callTool('vibe_v4_status', {})
1566
+ assert(st4.verifyInProgress === false, '★ the mid-verify hire/fire did NOT prevent the verification from concluding')
1567
+ }
1568
+
1482
1569
  // ===============================================================
1483
1570
  console.log('')
1484
1571
  console.log('passed=' + passed + ' failed=' + failed)
@@ -38,6 +38,18 @@ export function makeFakeHost(opts = {}) {
38
38
  exit: opts.exit === undefined ? 0 : opts.exit,
39
39
  hang: !!opts.hang,
40
40
  hangProbe: !!opts.hangProbe,
41
+ versionProbeFails: !!opts.versionProbeFails,
42
+ // round-7: the real host intermittently answers a spawn with NULL early in a session. The first
43
+ // `nullSpawnTimes` spawns return null so a test can prove the retry recovers from it.
44
+ nullSpawnTimes: Number(opts.nullSpawnTimes) || 0,
45
+ // round-7 (live root cause): the real host answers a spawn whose cwd does NOT exist with
46
+ // `spawned:true, exit:null` (retries cannot help). `projectRoot` can be pointed at a
47
+ // non-existent path to emulate a FRESH workspace.
48
+ projectRoot: opts.projectRoot || '/fake/project',
49
+ freshRoot: opts.projectRoot || null,
50
+ // round-7: null answers ONLY for package probes (classify === 'packages'), so a test can isolate
51
+ // "the precheck could not run" from "the engine could not be probed".
52
+ packageProbeNullTimes: Number(opts.packageProbeNullTimes) || 0,
41
53
  stdoutBytes: opts.stdoutBytes || 0,
42
54
  argError: opts.argError || null,
43
55
  probeCalls: 0,
@@ -56,9 +68,9 @@ export function makeFakeHost(opts = {}) {
56
68
  state.registrations.push({ name, description, parameters, handler })
57
69
  },
58
70
  params: () => (typeof opts.params === 'function' ? opts.params() : (opts.params || {})),
59
- projectRoot: () => opts.root || FAKE_ROOT,
71
+ projectRoot: () => state.freshRoot || opts.root || FAKE_ROOT,
60
72
  designator: opts.designator || 'vibe-math-v2',
61
- writeText: async (rel, text) => { files.set(String(rel).replace(/\\/g, '/'), String(text)); return true },
73
+ writeText: async (rel, text) => { files.set(String(rel).replace(/\\/g, '/'), String(text)); state.rootCreated = true; return true },
62
74
  // Optional capability flag (INTERFACE-FREEZE §4): a host that knows it has no subprocess service
63
75
  // says so, and the module reports MATH_NO_SUBPROCESS instead of a misleading ENGINE_NOT_FOUND.
64
76
  hasSubprocess: ('hasSubprocess' in opts) ? (() => opts.hasSubprocess) : undefined,
@@ -72,10 +84,18 @@ export function makeFakeHost(opts = {}) {
72
84
  if (typeof opts.listDir === 'function') return opts.listDir(rel)
73
85
  return (opts.listDir || []).slice()
74
86
  }) : undefined,
87
+ // round-7 (fix 2): OPTIONAL bundled-runtime discovery. `runtimeRoots` are absolute roots and
88
+ // `absTree` maps an absolute directory to its entries, so a test can fake DSH's layout
89
+ // (`<root>/dsh-runtimes/<tree>/dependencies/python/python.exe`).
90
+ runtimeRoots: ('runtimeRoots' in opts) ? (async () => (opts.runtimeRoots || [])) : undefined,
91
+ listDirAbs: opts.absTree ? (async (abs) => (opts.absTree[String(abs).replace(/\\/g, '/')] || [])) : undefined,
75
92
  resolveExecutable: async (cmd) => {
76
93
  if (opts.resolveThrows) throw new Error('resolve failed: ' + cmd)
77
94
  const raw = String(cmd)
78
95
  const base = raw.split(/[\\/]/).pop()
96
+ // round-6 (B): an explicit path map lets a test place the interpreter in a conda-style
97
+ // directory or next to a `uv` binary - the manager dispatch reads those facts.
98
+ if (opts.resolveMap && Object.prototype.hasOwnProperty.call(opts.resolveMap, base)) return opts.resolveMap[base]
79
99
  // A caller-supplied path (engine='cli') is accepted as-is: that is the whole point of the
80
100
  // escape hatch - the tool only has to resolve the command, not know the engine.
81
101
  if (raw.indexOf('/') !== -1 || raw.indexOf('\\') !== -1) return raw
@@ -85,10 +105,18 @@ export function makeFakeHost(opts = {}) {
85
105
  return '/fake/bin/' + base
86
106
  },
87
107
  spawn: async ({ argv, cwd, timeoutMs, stdoutCap, stderrCap }) => {
108
+ if (state.nullSpawnTimes > 0) { state.nullSpawnTimes--; state.spawns.push({ argv: argv.slice(), cwd, timeoutMs, stdoutCap, stderrCap, nullSpawn: true }); return null }
88
109
  state.spawns.push({ argv: argv.slice(), cwd, timeoutMs, stdoutCap, stderrCap })
110
+ // Emulate the real host: a spawn whose cwd does not exist answers with exit:null. Note the
111
+ // host's writeText creates parent directories (writeFileAtomic), so a RUN (which writes the
112
+ // receipt first) legitimately makes the project root exist before it spawns.
113
+ if (state.freshRoot && !state.rootCreated && String(cwd) === String(state.freshRoot)) return { exit: null, timedOut: false, killed: false, ms: 130, stdout: '', stderr: '' }
89
114
  const kind = classify(argv)
115
+ if (kind === 'packages' && state.packageProbeNullTimes > 0) { state.packageProbeNullTimes--; state.spawns[state.spawns.length - 1].nullSpawn = true; return null }
90
116
  if (kind === 'version') {
91
117
  if (state.hangProbe) return { exit: null, timedOut: true, killed: true, ms: timeoutMs, stdout: '', stderr: '' }
118
+ // round-7: a DETERMINISTIC probe failure (engine resolved, probe exits non-zero, no version).
119
+ if (state.versionProbeFails) return { exit: 1, timedOut: false, killed: false, ms: 5, stdout: '', stderr: 'probe boom' }
92
120
  const exeName = String(argv[0] || 'engine').split(/[\\/]/).pop()
93
121
  return { exit: 0, timedOut: false, killed: false, ms: 5, stdout: exeName + ' ' + state.version + '\n', stderr: '' }
94
122
  }