dsh-vibe-math 2.7.2 → 2.7.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/README.en.md +11 -10
- package/README.md +13 -12
- package/cordis.patch.yml +112 -16
- package/docs/AUDIT-CHECKLIST.md +229 -3
- package/docs/COMPAT-AUDIT-ROUND2.md +35 -9
- package/docs/final-paper.md +2 -2
- package/docs/math-computation.md +13 -6
- package/docs/parameter-schema.md +37 -0
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +2 -2
- package/docs/release-notes/RELEASE-NOTES-2.7.2.md +3 -3
- package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
- package/docs/release-notes/TEMPLATE.md +6 -0
- package/docs/status-report-fields.md +167 -0
- package/docs/test-timing.md +8 -5
- package/installer.js +27 -17
- package/package.json +250 -203
- package/prompt-corpus-persona/persona-corpus.json +9 -9
- package/prompt-corpus-persona/persona-corpus.md +225 -33
- package/prompt-corpus-v2/formal-verify-v2.json +295 -115
- package/prompt-corpus-v2/formal-verify-v2.md +2287 -283
- package/prompt-corpus-v3/formal-verify-v3.json +105 -105
- package/prompt-corpus-v3/formal-verify-v3.md +213 -143
- package/prompt-corpus-v4/formal-verify-v4.json +18 -18
- package/prompt-corpus-v4/formal-verify-v4.md +49 -44
- package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
- package/prompt-corpus-v5/prompt-corpus-v5.md +451 -207
- package/scripts/update-doc-counts.mjs +71 -0
- package/tests/audit-artifact-docs.mjs +72 -0
- package/tests/audit-artifact-docs.mutants.mjs +58 -0
- package/tests/audit-installer-assertions.mutants.mjs +63 -0
- package/tests/audit-installer-compat.test.mjs +52 -4
- package/tests/audit-installer-policy.test.mjs +41 -8
- package/tests/audit-math-computation-contract.mjs +64 -8
- package/tests/audit-math-computation-contract.mutants.mjs +106 -0
- package/tests/audit-math-computation-parity.mjs +164 -3
- package/tests/audit-math-computation-sensitivity.mjs +4 -2
- package/tests/audit-package-membership.mjs +57 -0
- package/tests/audit-package-membership.mutants.mjs +28 -0
- package/tests/audit-participant-set-parity.mjs +160 -0
- package/tests/audit-path-discipline.mjs +83 -0
- package/tests/audit-path-discipline.mutants.mjs +51 -0
- package/tests/audit-persona-sensitivity.mjs +14 -0
- package/tests/audit-persona-surface.mutants.mjs +69 -0
- package/tests/audit-persona-surface.test.mjs +63 -22
- package/tests/audit-prompt-duplication.mjs +143 -0
- package/tests/audit-prompt-duplication.mutants.mjs +50 -0
- package/tests/audit-prompt-invariants.mjs +43 -5
- package/tests/audit-readme-bilingual.test.mjs +152 -0
- package/tests/audit-readme-counts.mjs +95 -0
- package/tests/audit-readme-counts.mutants.mjs +52 -0
- package/tests/audit-registration.mjs +78 -0
- package/tests/audit-status-report-fields.mjs +172 -0
- package/tests/audit-status-report-fields.mutants.mjs +64 -0
- package/tests/audit-v3-registration-parity.mjs +156 -0
- package/tests/audit-v5-integrity.mjs +300 -13
- package/tests/audit-v5-lean-abstention.mjs +392 -0
- package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
- package/tests/audit-v5-prompt-duplication.mjs +88 -0
- package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
- package/tests/audit-v5-sensitivity.mjs +12 -7
- package/tests/e2e-identity-a6.mutants.mjs +65 -0
- package/tests/e2e-v5-round2.test.mjs +458 -13
- package/tests/formal-verify-v2.test.mjs +91 -43
- package/tests/formal-verify-v3.mutants.mjs +117 -0
- package/tests/formal-verify-v3.test.mjs +85 -39
- package/tests/formal-verify-v4.mutants.mjs +152 -0
- package/tests/formal-verify-v4.test.mjs +541 -9
- package/tests/formal-verify-v5.test.mjs +68 -9
- package/tests/helpers/doc-counts-format.mjs +41 -0
- package/tests/helpers/math-computation-fake-seam.mjs +4 -0
- package/tests/math-computation-a1.mutants.mjs +72 -0
- package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
- package/tests/math-computation-discovery.mutants.mjs +76 -0
- package/tests/math-computation-shared.test.mjs +274 -5
- package/tests/math-computation-v2.test.mjs +767 -0
- package/tests/math-computation-v3.test.mjs +759 -0
- package/tests/math-computation-v4.mutants.mjs +133 -0
- package/tests/math-computation-v4.test.mjs +739 -0
- package/tests/prompt-v5-integrity.test.mjs +13 -2
- package/tests/run-tests.mjs +139 -9
- package/tests/run-tests.mutants.mjs +70 -0
- package/tests/selfdrive-v5.mjs +53 -1
- package/tests/selfdrive-v5.mutants.mjs +132 -0
- package/tests/v2-fix-probes.mutants.mjs +100 -0
- package/tests/v2-fix-probes.test.mjs +1039 -0
- package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
- package/tests/v3-fix-probes.mutants.mjs +95 -0
- package/tests/v3-fix-probes.test.mjs +891 -0
- package/tests/v5-institute-fixes.mutants.mjs +190 -0
- package/vibe-math-v2/agent.cordis.yml +62 -6
- package/vibe-math-v2/math-computation.js +130 -41
- package/vibe-math-v2/math-engines.js +12 -12
- package/vibe-math-v2/vibe-math-v2.js +388 -113
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +5 -1
- package/vibe-math-v3/agent.cordis.yml +72 -6
- package/vibe-math-v3/math-computation.js +130 -41
- package/vibe-math-v3/math-engines.js +12 -12
- package/vibe-math-v3/vibe-math-v3.js +422 -126
- package/vibe-math-v4/agent.cordis.yml +44 -8
- package/vibe-math-v4/math-computation.js +130 -41
- package/vibe-math-v4/math-engines.js +12 -12
- package/vibe-math-v4/vibe-math-v4.js +196 -43
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +2 -0
- package/vibe-math-v5/agent.cordis.yml +46 -12
- package/vibe-math-v5/math-computation.js +130 -41
- package/vibe-math-v5/math-engines.js +12 -12
- package/vibe-math-v5/vibe-math-v5.js +1173 -218
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +80 -12
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +2 -2
|
@@ -93,6 +93,11 @@ export function apply(ctx) {
|
|
|
93
93
|
// automatic parent creation). Reading on demand removes that dependency on mount order.
|
|
94
94
|
const subprocessOf = () => { try { return ctx.get('subprocess') } catch(e){ return undefined } }
|
|
95
95
|
const sandboxPolicyOf = () => { try { return ctx.get('sandboxPolicy') } catch(e){ return undefined } }
|
|
96
|
+
// G-7 invariant (class guard): AGENT-FACING compaction MUST take the agent-local instance via
|
|
97
|
+
// `compactionForAgent(agent)` - this preset mounts its compaction row inside the preset's `compaction`
|
|
98
|
+
// isolate realm, and a realm NEVER falls back to the host-root instance. Only NON-agent sites may use
|
|
99
|
+
// `compactionOf()`. Guarded by the text-level assertion in tests/formal-verify-v4.test.mjs.
|
|
100
|
+
|
|
96
101
|
const compactionOf = () => { try { return ctx.get('compaction') } catch(e){ return undefined } }
|
|
97
102
|
|
|
98
103
|
const sessions = new Map() // rootAgentId -> Session
|
|
@@ -339,8 +344,62 @@ export function apply(ctx) {
|
|
|
339
344
|
// escape the project tree. Keep every harmless character (incl. Chinese) and replace only
|
|
340
345
|
// separators/control chars; strip leading/trailing dots/dashes so the name is never '.'/'..'.
|
|
341
346
|
let warnedNoPolicy = false
|
|
347
|
+
let warnedDeferFormalTodo = false // F-6: name a swallowed deferForFormal failure once, WITH its consequence
|
|
348
|
+
let warnedIndexRebuild = false // F-6: name a swallowed settleLeanJob failure once, WITH its consequence
|
|
349
|
+
let warnedCompaction = false // F-6: name a swallowed realCompact failure once, WITH its consequence
|
|
350
|
+
let warnedWriteFormalTodo = false // F-6 (1/4): name a swallowed verify-conclusion write once, WITH its consequence
|
|
351
|
+
let warnedContextProbe = false // F-6 (:786): name a failed build-context comparison once (unknown != unchanged)
|
|
352
|
+
let warnedPaperWrite = false // G3: name a paper whose required artifact is missing after finalize (once)
|
|
353
|
+
let warnedMkdir = false // F-4c: name a failed mkdir ONCE (the deferred/distorted-diagnosis fix)
|
|
354
|
+
let warnedFenceDrift = false // F-5: name a resolved fence root that is NOT this session workspace (once)
|
|
355
|
+
let warnedCurrentProject = false // F-6: name a failed `.current` marker write once, WITH its consequence
|
|
342
356
|
function warnNoPolicyOnce(){ if(!warnedNoPolicy){ warnedNoPolicy=true; console.error('vibe-math-v4: sandboxPolicy unavailable; writes go out with no explicit policy') } }
|
|
343
|
-
function getPolicy(){ const sp=sandboxPolicyOf(); if(!sp){ warnNoPolicyOnce(); return undefined } try { if(rootAgent&&rootAgent.session)
|
|
357
|
+
function getPolicy(){ const sp=sandboxPolicyOf(); if(!sp){ warnNoPolicyOnce(); return undefined } try { if(rootAgent&&rootAgent.session){ const p=sp.resolve({session:rootAgent.session}); fenceRootDriftNote(p); return p } } catch(e){ warnNoPolicyOnce() } try { const p=sp.resolve({}); if(!warnedNoPolicy){ warnedNoPolicy=true; console.error('vibe-math-v4: falling back to sandboxPolicy.resolve({}) — the fence root is the host-configured workspace, not necessarily this session cwd') } return p } catch(e){ warnNoPolicyOnce() } return undefined }
|
|
358
|
+
/**
|
|
359
|
+
* F-5 (R9 ruling): the SESSION-scoped policy can resolve to a fence root that does not CONTAIN
|
|
360
|
+
* this session workspace - the host silently widened or re-homed the write fence. Shape mirrored
|
|
361
|
+
* from v3 (policyRootOf / warnFenceDriftOnce / checkFenceRoot returning p), with the conservative
|
|
362
|
+
* predicate the probe requires:
|
|
363
|
+
* - containment, never equality: an ANCESTOR root is a legitimate configuration, so equality
|
|
364
|
+
* alone would report normal setups as drift (once per session);
|
|
365
|
+
* - an array of roots (list-style fences) => accept when the workspace is inside ANY element;
|
|
366
|
+
* - no root field / non-string / an object shape we cannot interpret (e.g. {allow,deny}) =>
|
|
367
|
+
* NO-OP: today's behaviour. The live probe could not read the real host resolve() shape (no
|
|
368
|
+
* service is reachable from a plugin row in this sandbox), so we never guess - the no-op path
|
|
369
|
+
* is asserted positively in tests instead;
|
|
370
|
+
* - the `resolve({})` fallback is NEVER compared (it already has its own named warning and its
|
|
371
|
+
* root is by definition not this session cwd => comparing it could only add noise);
|
|
372
|
+
* - normalisation: separators -> "/", trailing slashes stripped, case-folded ONLY on win32 (on a
|
|
373
|
+
* case-sensitive host folding would hide a real drift = a false negative).
|
|
374
|
+
* Zero behaviour change: no refusal, and getPolicy() still returns the resolved policy.
|
|
375
|
+
*/
|
|
376
|
+
function policyRootCandidatesOf(p){
|
|
377
|
+
try {
|
|
378
|
+
if(!p || typeof p !== 'object') return []
|
|
379
|
+
let pick
|
|
380
|
+
if(p.workspaceRoot !== undefined) pick = p.workspaceRoot
|
|
381
|
+
else if(p.root !== undefined) pick = p.root
|
|
382
|
+
else if(p.cwd !== undefined) pick = p.cwd
|
|
383
|
+
else if(p.config && p.config.workspaceRoot !== undefined) pick = p.config.workspaceRoot
|
|
384
|
+
if(Array.isArray(pick)) return pick.filter(x => typeof x === 'string' && x)
|
|
385
|
+
return (typeof pick === 'string' && pick) ? [pick] : []
|
|
386
|
+
} catch(e) { return [] }
|
|
387
|
+
}
|
|
388
|
+
function fenceRootDriftNote(p){
|
|
389
|
+
try {
|
|
390
|
+
const cands = policyRootCandidatesOf(p)
|
|
391
|
+
if(!cands.length) return // no comparable root => documented NO-OP (asserted in tests)
|
|
392
|
+
const norm = (x) => { let v = String(x).replace(/\\/g, '/'); if(v.length > 1) v = v.replace(/\/+$/, ''); return process.platform === 'win32' ? v.toLowerCase() : v }
|
|
393
|
+
const session = norm(workspaceRoot())
|
|
394
|
+
if(!session || session === '.') return
|
|
395
|
+
const contained = cands.some((c) => { const r = norm(c); return !!r && (session === r || session.indexOf(r + '/') === 0) })
|
|
396
|
+
if(contained) return
|
|
397
|
+
if(!warnedFenceDrift){
|
|
398
|
+
warnedFenceDrift = true
|
|
399
|
+
console.error('vibe-math-v4: sandbox fence root does not CONTAIN this session workspace (resolved="' + cands.join('", "') + '", session="' + workspaceRoot() + '") - writes may be fenced away from the session workspace; boundary: a policy exposing no interpretable root field cannot be compared')
|
|
400
|
+
}
|
|
401
|
+
} catch(e) { /* a diagnostic must never break policy resolution */ }
|
|
402
|
+
}
|
|
344
403
|
function psQuote(p){ return "'"+String(p).replace(/'/g,"''")+"'" }
|
|
345
404
|
/** POSIX 单引号引用:把 ' 换成 '\'' 以安全嵌入任意路径。 */
|
|
346
405
|
function shQuote(p){ return "'"+String(p).replace(/'/g,"'\\''")+"'" }
|
|
@@ -440,12 +499,36 @@ export function apply(ctx) {
|
|
|
440
499
|
// project tree, NOT inside it: cross-project reuse is the whole point (spec §3). It is
|
|
441
500
|
// created here so the first `lean_archive kind='def'` never has to invent its parent.
|
|
442
501
|
const globalDirs=['Formal/Lib','Formal/Proved']
|
|
443
|
-
|
|
502
|
+
const res = await runShell(mkdirCmd([vibeRoot()+'/Projects'].concat(dirs.map(d=>base+'/'+d)).concat(globalDirs.map(d=>vibeRoot()+'/'+d))))
|
|
503
|
+
// F-4c: ensureDirs() returns the shell result and never throws, so a failed mkdir used to surface only
|
|
504
|
+
// later and as a DISTORTED diagnosis ('write failed' instead of 'mkdir failed, and where'). Name it here,
|
|
505
|
+
// once, through the same channel as warnNoPolicyOnce() (deferred/distorted diagnosis -> named warning).
|
|
506
|
+
try {
|
|
507
|
+
const _code = res && (res.exitCode !== undefined ? res.exitCode : res.code)
|
|
508
|
+
if (_code !== undefined && Number(_code) !== 0 && !warnedMkdir) {
|
|
509
|
+
warnedMkdir = true
|
|
510
|
+
console.error('vibe-math-v4: ensureDirs: mkdir failed (exit ' + _code + ') under ' + vibeRoot() + ' - later writes into a missing directory will fail; check the shell/policy')
|
|
511
|
+
}
|
|
512
|
+
} catch(e) { /* best effort: the warning must never break ensureDirs */ }
|
|
513
|
+
return res
|
|
444
514
|
}
|
|
445
515
|
async function readTextAbs(path){ try { const t=await fs.resolve(path); const s=await fs.stat(t); if(s===undefined) return undefined; return await fs.readText(t) } catch(e){ return undefined } }
|
|
446
516
|
async function writeTextAbs(path,content){ try { const t=await fs.resolve(path); await fs.writeText(t,content,undefined,undefined,getPolicy()); return true } catch(e){ return false } }
|
|
447
517
|
async function readCurrentProject(){ try { const t=await readTextAbs(vibeRoot()+'/.current'); if(t) return String(t).trim() } catch(e){} return currentProject }
|
|
448
|
-
|
|
518
|
+
// F-6 boundaries MEASURED on this revision (recorded, not fixed here - both would need a readText
|
|
519
|
+
// failure signal and therefore touch as many callers as writeTextAbs, which the lead deferred):
|
|
520
|
+
// - readCurrentProject(): a failed READ is swallowed inside readTextAbs (:514), so this function
|
|
521
|
+
// cannot tell "read failed" from "no marker"; its silent fallback stays.
|
|
522
|
+
// - assertWritable()'s outer `catch(e){}` (:491) CANNOT FIRE, because readText (:438) has already
|
|
523
|
+
// erased the failure: it swallows every error and returns undefined, so a present-but-unreadable
|
|
524
|
+
// file is treated as MISSING and the overwrite guard silently allows the write. The silent skip
|
|
525
|
+
// therefore happens ONE LAYER UP, not in the catch. Surfacing it means making readText REPORT
|
|
526
|
+
// failure - the same breadth as :515 writeTextAbs, which the lead deferred.
|
|
527
|
+
// - writeCurrentProject()'s own `catch` branch is likewise DEFENSIVE-ONLY: writeTextAbs never throws
|
|
528
|
+
// (it catches internally and returns a boolean), so the `ok===false` branch is the only reachable
|
|
529
|
+
// one; the exception path is kept as belt-and-braces and is recorded as unreachable (measured: a
|
|
530
|
+
// mutant that removes it cannot redden the N18 assertion, so it ships without a family entry).
|
|
531
|
+
async function writeCurrentProject(){ try { const ok=await writeTextAbs(vibeRoot()+'/.current', currentProject); if(ok===false && !warnedCurrentProject){ warnedCurrentProject=true; console.error('vibe-math-v4: the current-project marker was NOT persisted - the next session may load the previous project') } } catch(e){ if(!warnedCurrentProject){ warnedCurrentProject=true; console.error('vibe-math-v4: the current-project marker was NOT persisted - the next session may load the previous project') } } }
|
|
449
532
|
|
|
450
533
|
// ================= Lean formal verification ==============================
|
|
451
534
|
// Contract: docs/formal-verification.md (shared by v2/v3/v4/v5).
|
|
@@ -763,10 +846,16 @@ export function apply(ctx) {
|
|
|
763
846
|
// the job was in flight, RECORD that the current context differs (the compile itself was done
|
|
764
847
|
// with the recorded context, which is what the job id attests to).
|
|
765
848
|
let contextChanged=false
|
|
766
|
-
try {
|
|
849
|
+
try {
|
|
850
|
+
const nowCtx=await leanBuildContext()
|
|
767
851
|
contextChanged=JSON.stringify([nowCtx.engine,nowCtx.argv,nowCtx.searchPaths])!==JSON.stringify([(job.build||{}).engine,(job.build||{}).argv,(job.build||{}).searchPaths])
|
|
768
|
-
|
|
769
|
-
|
|
852
|
+
} catch(e){
|
|
853
|
+
// F-6 (:786): a comparison that could NOT run must not be recorded as "unchanged" -
|
|
854
|
+
// unknown != unchanged. Record null AND name it once, with the consequence.
|
|
855
|
+
contextChanged=null
|
|
856
|
+
if(!warnedContextProbe){ warnedContextProbe=true; console.error('vibe-math-v4: settleLeanJob: the build-context comparison could not be performed - contextChanged=false is NOT a measurement (recorded as null/unknown)') }
|
|
857
|
+
}
|
|
858
|
+
if(contextChanged===true) logActivity('formal','作业 '+job.jobId+' 的结果属于记录中的构建上下文(engine/argv/search-paths 已被改动,未影响该次编译的结论)')
|
|
770
859
|
const ok=!!(run&&run.ok)&&hashMatch
|
|
771
860
|
const timedOut=!!(run&&run.timedOut)
|
|
772
861
|
const state=ok?'settled':(timedOut?'timeout':'failed')
|
|
@@ -801,7 +890,7 @@ export function apply(ctx) {
|
|
|
801
890
|
}
|
|
802
891
|
}
|
|
803
892
|
await writeLeanJob(settled)
|
|
804
|
-
try { await rebuildLeanLibIndexes() } catch(e){ /* best-effort */ }
|
|
893
|
+
try { await rebuildLeanLibIndexes() } catch(e){ if(!warnedIndexRebuild){ warnedIndexRebuild=true; console.error('vibe-math-v4: settleLeanJob: rebuildLeanLibIndexes failed - the library indexes are STALE; residents may reuse a stale index until the next successful rebuild') } /* best-effort */ }
|
|
805
894
|
pushLeanNotice(settled,Object.assign({},run,{ok,hashMatch}))
|
|
806
895
|
return settled
|
|
807
896
|
}
|
|
@@ -856,6 +945,9 @@ export function apply(ctx) {
|
|
|
856
945
|
* §2.6 crash/restart recovery: scan `<project>/Formal/Jobs/*.json` AFTER the project dirs exist.
|
|
857
946
|
* It NEVER mints a `passed` anything unless the job record itself says exitCode===0 AND the file
|
|
858
947
|
* on disk still hashes to the enqueued content.
|
|
948
|
+
* INVARIANT (F2): after redriving, the recovery DRAINS the queue itself (`await runLeanQueue()`) and only
|
|
949
|
+
* then arms one heartbeat - it must NOT rely on an already-armed heartbeat. Deleting that drain silently
|
|
950
|
+
* restores the cold-recovery stall (see the §20 tripwire in tests/formal-verify-v4.test.mjs).
|
|
859
951
|
*/
|
|
860
952
|
async function recoverLeanJobs(opts){
|
|
861
953
|
const o=opts||{}
|
|
@@ -908,7 +1000,7 @@ export function apply(ctx) {
|
|
|
908
1000
|
}
|
|
909
1001
|
}
|
|
910
1002
|
}
|
|
911
|
-
if(queued+redriven+stale+completed){ await writeLeanLibIndexesSafe() ; if(leanQueue.length) armHeartbeat() }
|
|
1003
|
+
if(queued+redriven+stale+completed){ await writeLeanLibIndexesSafe() ; if(leanQueue.length){ await runLeanQueue() ; armHeartbeat() } } // cold-recovery drain: the heartbeat alone did not drain while the run was not yet live (verified by the §20 tripwire)
|
|
912
1004
|
return {scanned:(files||[]).length,queued,redriven,stale,completed}
|
|
913
1005
|
}
|
|
914
1006
|
async function writeLeanLibIndexesSafe(){ try { await rebuildLeanLibIndexes() } catch(e){ /* best-effort */ } }
|
|
@@ -1187,15 +1279,23 @@ export function apply(ctx) {
|
|
|
1187
1279
|
return rows
|
|
1188
1280
|
}
|
|
1189
1281
|
const libRows=await scan(formalLibRoot(),'Formal/Lib','def')
|
|
1190
|
-
await writeTextAbs(formalLibRoot()+'/Index.md',['# 可复用 Lean 定义库(跨项目)|'+currentProject,'',
|
|
1282
|
+
const _idxLibOk = await writeTextAbs(formalLibRoot()+'/Index.md',['# 可复用 Lean 定义库(跨项目)|'+currentProject,'',
|
|
1191
1283
|
'> 写新定义之前先查这里:能复用就不要重新定义。复用方式:`import Formal.Lib.<名称>`(模块根 = VibeMath 根,框架已加入编译搜索路径),或用 vibe_v4_lean_read 取原文逐字复制。','',
|
|
1192
1284
|
'| 名称 | 文件 | 类别 | 依赖 | 摘要 |','|---|---|---|---|---|']
|
|
1193
1285
|
.concat(libRows.length?libRows:['| (暂无) | | | | |']).join('\n')+'\n')
|
|
1194
1286
|
const provedRows=await scan(formalProvedRoot(),'Formal/Proved','lemma')
|
|
1195
|
-
await writeTextAbs(formalProvedRoot()+'/Index.md',['# 已成立的 Lean 命题 / 引理(机器已核对,可跨项目复用)|'+currentProject,'',
|
|
1287
|
+
const _idxProvedOk = await writeTextAbs(formalProvedRoot()+'/Index.md',['# 已成立的 Lean 命题 / 引理(机器已核对,可跨项目复用)|'+currentProject,'',
|
|
1196
1288
|
'> 这些文件是通过内核检查的引理,可直接 `import Formal.Proved.<名称>` 复用(模块根 = VibeMath 根)。','',
|
|
1197
1289
|
'| 名称 | 文件 | 类别 | 依赖 | 陈述 |','|---|---|---|---|---|']
|
|
1198
1290
|
.concat(provedRows.length?provedRows:['| (暂无) | | | | |']).join('\n')+'\n')
|
|
1291
|
+
// ③ MEASURED: these two writes ignored `writeTextAbs`'s boolean (it swallows and returns
|
|
1292
|
+
// false), so a failed index write looked like success - and that silence propagated to ALL SIX
|
|
1293
|
+
// callers (settleLeanJob, writeLeanLibIndexesSafe, the three leanArchive paths, the lean_lib
|
|
1294
|
+
// tool). Naming it HERE, once, covers every caller (same shape as ensureDirs/warnedMkdir).
|
|
1295
|
+
if((_idxLibOk === false || _idxProvedOk === false) && !warnedIndexRebuild){
|
|
1296
|
+
warnedIndexRebuild = true
|
|
1297
|
+
console.error('vibe-math-v4: rebuildLeanLibIndexes: the Lean library index could NOT be written (lib=' + _idxLibOk + ' proved=' + _idxProvedOk + ') - the index is STALE until the next successful rebuild, so reuse lookups may miss entries')
|
|
1298
|
+
}
|
|
1199
1299
|
await writeFormalIndex()
|
|
1200
1300
|
await writeFormalTodo()
|
|
1201
1301
|
return {lib:libRows.length,proved:provedRows.length,objects:Object.keys(formalRecords()).length}
|
|
@@ -1555,24 +1655,24 @@ export function apply(ctx) {
|
|
|
1555
1655
|
async function inboxText(rId){ const mb=mailboxes.get(rId)||[]; if(mb.length===0) return ' (no new messages)\n'; return mb.map(m=>' ['+senderLabel(m.from)+'] '+m.content).join('\n')+'\n' }
|
|
1556
1656
|
function residentLibraries(){
|
|
1557
1657
|
const base=frameworkRoot()
|
|
1558
|
-
return '
|
|
1658
|
+
return '你的资料库根目录(**绝对前缀**,下面每条都已带库根):'+base+'/\n'
|
|
1559
1659
|
+' Progress/<你>/progress.md —— 你的研究日志(叙述,可追加。主要内容是尝试过的各方法、路线、历程、进度,当前研究进展/进度、将来的计划与打算,及各路线、过程中遇到的障碍及其原因,对各路线、方法的看法、可行性评估,自己研究过程中的一些有价值看法、感想、猜想、理解。以及其它各种你认为有价值的值得记录的事物、经验、方法/想法、创新等都可进行记录)。\n'
|
|
1560
1660
|
+' Propos/<你>/<id>.md —— 你的命题/引理。格式:\n'
|
|
1561
1661
|
+' - ID: p-<id>; - 状态: 未定论; - 概率: <0-1>; - 价值程度: <0-1>; - 动机用途计划: <为何重要/打算怎么用>\n'
|
|
1562
1662
|
+' 然后 ## 陈述 <陈述>;## 证明尝试;## 证伪尝试。\n'
|
|
1563
1663
|
+' Methods/<你>/<id>.md —— 你的理论/方法/工具。格式:- ID: m-<id>; - 状态: 经验; - 可信断言: []; - 价值程度: <0-1>; - 动机用途计划: ...;然后 ## 核心内容;## 定义与记号;## 应用记录;## 改进历史。\n'
|
|
1564
1664
|
+' Subproblems/<你>/<id>.md —— 你的子问题。格式:- ID: s-<id>; - 状态: 求解中; - 价值程度: <0-1>; - 动机用途计划: ...;然后 ## 陈述;## 进度。\n'
|
|
1565
|
-
+' ⚠ 你的文件工具(读/写)按**会话 cwd**
|
|
1665
|
+
+' ⚠ 你的文件工具(读/写)按**会话 cwd** 解析路径:上面列出的相对子路径都要先拼上**成员库根** '+base+'/ 再使用(例如 '+base+'/Propos/r-1/p-1.md);计算产物同理——请用回执里的**绝对**字段 receipt.scriptAbs,或把 receipt.cwd 与 receipt.scriptPath 拼起来,不要用相对路径。\n'
|
|
1566
1666
|
}
|
|
1567
1667
|
function toolList(){
|
|
1568
1668
|
return 'vibe_v4_send_message {to, content} —— 给某常驻发消息(to=all 广播)。\n'
|
|
1569
1669
|
+'vibe_v4_meeting {agenda} —— 发起/参与会议(框架会把各常驻的实际 input 转给其他人,让大家看到并讨论/辩论)。\n'
|
|
1570
1670
|
+'vibe_v4_propose_task/claim_task/task_done/list_tasks —— 共享任务板(提议/认领/完成/查看;任务板是你们协调分工的载体)。\n'
|
|
1571
|
-
+'vibe_v4_publish_progress/record_proposition/record_method/record_subproblem —— 便捷记录器(可选;推荐直接用 fs
|
|
1671
|
+
+'vibe_v4_publish_progress/record_proposition/record_method/record_subproblem —— 便捷记录器(可选;推荐直接用 fs 写自己的文件,路径必须带库根 )。\n'
|
|
1572
1672
|
+'vibe_v4_read_progress {id} —— 只读某常驻的进展。\n'
|
|
1573
1673
|
+'vibe_v4_list_residents / vibe_v4_list_tasks —— 查看团队组成 / 开放任务。\n'
|
|
1574
1674
|
+'vibe_v4_report_context {pct} —— 上报上下文占比(框架据此压缩你的上下文)。\n'
|
|
1575
|
-
+'fs (read/write/list) —— 读取任意文件;写入你自己的文件(推荐直接用 fs 直接写自己的 md
|
|
1675
|
+
+'fs (read/write/list) —— 读取任意文件;写入你自己的文件(推荐直接用 fs 直接写自己的 md;**路径必须带库根 ,否则会落到别处**)。\n'
|
|
1576
1676
|
}
|
|
1577
1677
|
// A shared, complete context block so a resident always knows the situation: mission,
|
|
1578
1678
|
// work model, what it can do, which files it owns (+ formats), what others' files are,
|
|
@@ -1588,14 +1688,14 @@ export function apply(ctx) {
|
|
|
1588
1688
|
s.push('### 工作模式(会发生什么)')
|
|
1589
1689
|
s.push('1. 每人有一份持久、全组可见的专属资料库(见下)。')
|
|
1590
1690
|
s.push('2. 你们自由发消息、开会;**会议会把每个人实际说的话(input)转给其他人**,让你看得到、能回复、能讨论、能辩论。')
|
|
1591
|
-
s.push('3. 你独立研究,并**直接用 fs
|
|
1691
|
+
s.push('3. 你独立研究,并**直接用 fs 写入你自己的文件**(**路径必须带库根 **,按格式),供全组阅读。')
|
|
1592
1692
|
s.push('4. 任何"已确立"的东西须**全组一致**验证(全真或全假)才作数;否则只是带概率的工作估计。')
|
|
1593
1693
|
s.push('5. 只有**全组在会议上一致认为原问题已解决**,run 才停止。')
|
|
1594
1694
|
s.push('')
|
|
1595
1695
|
s.push('### 你负责的文件(你只写自己的;但可读任何人的)')
|
|
1596
1696
|
s.push(residentLibraries())
|
|
1597
1697
|
s.push('其他人把结论/进展写进他们的目录,你就能读到。**你应主动读别人的库**,对齐事实、彼此衔接、避免重复劳动。')
|
|
1598
|
-
if(level!=='full'){ s.push('(格式见你最初的说明;直接用 fs
|
|
1698
|
+
if(level!=='full'){ s.push('(格式见你最初的说明;直接用 fs 写自己的文件即可,**路径必须带库根 **。)') }
|
|
1599
1699
|
s.push('')
|
|
1600
1700
|
s.push('### 可用工具')
|
|
1601
1701
|
s.push(toolList())
|
|
@@ -1634,7 +1734,7 @@ export function apply(ctx) {
|
|
|
1634
1734
|
+(r.direction?('\n\n你被建议的初始方向(可自行调整/细化):\n'+r.direction+'\n'):'')
|
|
1635
1735
|
+'## 这是你的第一轮:独立头脑风暴\n'
|
|
1636
1736
|
+'独立地想清楚:你对这个问题的洞察 / 解决方向 / 关键子问题 / 可能的引理 / 粗略计划。你还未见到其他人,先独立产出。\n'
|
|
1637
|
-
+'把有价值的产物**直接用 fs
|
|
1737
|
+
+'把有价值的产物**直接用 fs 写进你自己的文件**(**路径带库根 **,按上面格式),并在 summary 里概述你的切入方向与初步结论(标注哪些是猜想、哪些凭你已确证)。\n'
|
|
1638
1738
|
+'Reply with ONLY a JSON object:\n'
|
|
1639
1739
|
+'{"summary":"<your insight / direction / rough plan, one tight paragraph>","solved":false}'
|
|
1640
1740
|
}
|
|
@@ -1646,7 +1746,7 @@ export function apply(ctx) {
|
|
|
1646
1746
|
await refreshMathAvailability()
|
|
1647
1747
|
const mathAvail=mathAvailabilityBlock()
|
|
1648
1748
|
return (params.residentPersona?params.residentPersona+'\n':'')
|
|
1649
|
-
+'Resident researcher '+r.rId+' — 第 '+r.rounds+' 轮。一切由你和团队讨论决定。动手前先**读别人的库**对齐事实、避免重复;把新进展/结论**直接用 fs
|
|
1749
|
+
+'Resident researcher '+r.rId+' — 第 '+r.rounds+' 轮。一切由你和团队讨论决定。动手前先**读别人的库**对齐事实、避免重复;把新进展/结论**直接用 fs 写进你自己的文件**(**路径带库根 **);想对团队说的话放 "input"(会转给其他常驻)。\n'
|
|
1650
1750
|
+'\n团队成员:\n'+banner()+'\n'
|
|
1651
1751
|
+'New items:\n'+ (await inboxText(r.rId)) +'\n'
|
|
1652
1752
|
// Settled background compiles are announced ONCE, in the next prompt constructed (spec §2.4):
|
|
@@ -1665,8 +1765,8 @@ export function apply(ctx) {
|
|
|
1665
1765
|
const prior=Object.entries(st.inputs).filter(([k])=>k!==r.rId).map(([k,iv])=>' ['+k+'] '+String(iv.input||iv.summary||'')).join('\n')
|
|
1666
1766
|
return (params.residentPersona?params.residentPersona+'\n':'')
|
|
1667
1767
|
+'Resident '+r.rId+' — 团队会议进行中。 A meeting is in progress (agenda: '+st.agenda+').'
|
|
1668
|
-
+'\n这是一场真实讨论:下面已有人发言(转给你),请先看,然后**加入讨论/补充/反驳/表决**。'
|
|
1669
|
-
+(prior?('\n\n### 已有发言(他人 input,已转发给你)\n'+prior):'
|
|
1768
|
+
+(prior?'\n这是一场真实讨论:下面已有人发言(转给你),请先看,然后**加入讨论/补充/反驳/表决**。':'\n本轮是**并行独立发言**:同轮内不会有人先发言给你(唤醒是并行的),你的 input 会在**下一次唤醒/下一轮**转给其他人;请先给出你自己的判断。')
|
|
1769
|
+
+(prior?('\n\n### 已有发言(他人 input,已转发给你)\n'+prior):'')
|
|
1670
1770
|
+'\n\n你可以:提议任务(propose_task)、认领开放任务(claim_task)、提议验证对象(propose_verify)、或对"原问题是否已解决"表决(voteSolved)。请把**你的实际发言**写进 "input"。'
|
|
1671
1771
|
+'\n**停止表决必须是绝对票**:`voteSolved:true` 只表示你认为原问题**已解决**;只要有一名在册常驻没投 `true`(投 false、弃权、或漏写这个字段),run 就**不会**停止。不确定就投 false。' +'\nReply with ONLY a JSON object:\n'
|
|
1672
1772
|
+'{"input":"<your real contribution to this discussion>","propose_task":"<task title or null>","task_desc":"...","claim_task":"<task id or null>","propose_verify":"<id or null>","voteSolved":true}'
|
|
@@ -1679,7 +1779,7 @@ export function apply(ctx) {
|
|
|
1679
1779
|
const others=Object.entries(src).map(([k,v])=>'- '+k+': 正确概率 '+String(v.prob!=null?Number(v.prob).toFixed(2):0.5)+' → '+v.reason).join('\n')
|
|
1680
1780
|
const L=[]
|
|
1681
1781
|
L.push((params.residentPersona?params.residentPersona+'\n':'')
|
|
1682
|
-
+'Resident '+r.rId+' — 团队验证。 The group is verifying object '+vs.targetId+'('+targetTypeWord(vs.targetType)+',提出者 '+vs.targetOwner+')。\n'
|
|
1782
|
+
+'Resident '+r.rId+' — 团队验证。 The group is verifying object '+vs.targetId+'('+targetTypeWord(vs.targetType)+(String(vs.targetOwner||'').trim()?(',提出者 '+vs.targetOwner):'')+')。\n'
|
|
1683
1783
|
+'请给出你对「该对象为真」的**正确概率 `verdict`**,仅一个 0–1 数值:**1 = 绝对为真,0 = 绝对为假,0.5 = 完全不确定,其余为介于其间的程度**(不要给 TRUE/FALSE,就给一个数值)。\n'
|
|
1684
1784
|
// ── THE VOTE CONTRACT (why the example below is 1 and not 0.9) ──────────────────────────
|
|
1685
1785
|
// Only an EXACT 1 or 0 is a vote; anything strictly between is the model's honest
|
|
@@ -1690,7 +1790,8 @@ export function apply(ctx) {
|
|
|
1690
1790
|
+'**投票契约**:只有**恰好 1**(你认为是**绝对**为真)和**恰好 0**(你认为是**绝对**为假)算表决;**严格介于 0 与 1 之间**(例如 0.9、0.95、0.5)是**弃权**——它是你对"该对象为真"的**概率估计**,不是你的一票。\n'
|
|
1691
1791
|
+' · 有把握认为它为真就投 **1**;不要为了"留一点余地"投 0.9——那会让全组永远无法定论。\n'
|
|
1692
1792
|
+' · 弃权会被如实统计:本轮没有人全票 → 对象**不会**停止验证,而是把全组的**平均概率**写回它在库中的卡片(`- 概率:`),带概率继续留在库里。\n'
|
|
1693
|
-
+' ·
|
|
1793
|
+
+' · 弃权的合法用途:你确实不确定(用 0.5 附近的值表达)。\n'
|
|
1794
|
+
+(formalOn()?(' · 另一种弃权:已归档的机器检查证明与命题原文不一致、你**不能**用 0 表达"命题为假"(此时请用 `formal` 回执的 `decision:"defect"` 报告偏差)。\n'):'')
|
|
1694
1795
|
+'判定规则:**仅当全体在册常驻都恰好给 1(都认为是真)、或都恰好给 0(都认为是假)**,才按「真/假」写入 Verified/ 并回写来源卡的「已验证·真/假」;否则**只按概率数值(一种程度)保留在库中**,附全组平均正确概率,不写成真/假。\n'
|
|
1695
1796
|
+'请给出你**诚实独立的判断**'
|
|
1696
1797
|
+(vs.stage==='debate'?',并参考他人意见:\n':'。\n')
|
|
@@ -2228,9 +2329,14 @@ export function apply(ctx) {
|
|
|
2228
2329
|
finalizeLock='verify'
|
|
2229
2330
|
let doSchedule=false
|
|
2230
2331
|
try {
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2332
|
+
const vs=verifyState; // F1: the requirement set is the FROZEN snapshot (live map fallback) - the ONE
|
|
2333
|
+
// predicate is MEMBERSHIP, exactly like the on-end path at :2247. A count-only check could be
|
|
2334
|
+
// satisfied by a ballot from someone NOT in the frozen set (a member added after the freeze, or a
|
|
2335
|
+
// late reply from a removed one), which would promote 真/假 while a frozen voter is still silent.
|
|
2336
|
+
const ids=(vs.rosterSnapshot||Array.from(residents.keys())).slice()
|
|
2337
|
+
const expected=ids.length
|
|
2338
|
+
const allVoted = ids.length>0 && ids.every(id=>vs.verdicts[id]!==undefined)
|
|
2339
|
+
const vals=ids.filter(id=>vs.verdicts[id]!==undefined).map(id=>vs.verdicts[id])
|
|
2234
2340
|
// verdict is a PURE 0-1 probability; only ALL=1 (true) or ALL=0 (false) is a binary verdict.
|
|
2235
2341
|
const allTrue = allVoted && vals.every(x=>Number(x.prob)===1)
|
|
2236
2342
|
const allFalse = allVoted && vals.every(x=>Number(x.prob)===0)
|
|
@@ -2278,7 +2384,7 @@ export function apply(ctx) {
|
|
|
2278
2384
|
if(formalOn() && formalTodos.some(t=>t.id===target)){
|
|
2279
2385
|
formalTodos=formalTodos.filter(t=>t.id!==target)
|
|
2280
2386
|
await saveFormal()
|
|
2281
|
-
try { await writeFormalTodo(); await writeFormalIndex() } catch(e){ /* best-effort */ }
|
|
2387
|
+
try { await writeFormalTodo(); await writeFormalIndex() } catch(e){ if(!warnedWriteFormalTodo){ warnedWriteFormalTodo=true; console.error('vibe-math-v4: closeVerify: writeFormalTodo/writeFormalIndex failed - the verify conclusion is NOT recorded on disk (best effort continues; it is lost on reload)') } /* best-effort */ }
|
|
2282
2388
|
}
|
|
2283
2389
|
logActivity('verify',target+' → Verified ('+(isTrue?'真':'假')+') by unanimous consensus'+(formalOn()?('|形式化: '+formalStatusLine(target)):''))
|
|
2284
2390
|
verifyState=null; wakeKind.clear(); await saveAll()
|
|
@@ -2305,7 +2411,7 @@ export function apply(ctx) {
|
|
|
2305
2411
|
await rewriteSourceProb(target,mean,vs.targetOwner)
|
|
2306
2412
|
if(!formalTodos.some(t=>t.id===target)) formalTodos.push({id:target,at:now(),why,verdict:isTrue?1:0})
|
|
2307
2413
|
await putFormal(target,rec,formalTodos) // single durable write of records + todo together
|
|
2308
|
-
try { await writeFormalTodo(); await writeFormalIndex() } catch(e){ /* best-effort */ }
|
|
2414
|
+
try { await writeFormalTodo(); await writeFormalIndex() } catch(e){ if(!warnedDeferFormalTodo){ warnedDeferFormalTodo=true; console.error('vibe-math-v4: deferForFormal: writeFormalTodo/writeFormalIndex failed - the formal-required deferral (TODO + index) is NOT persisted; the object stays unverified and this decision is lost on reload') } /* best-effort */ }
|
|
2309
2415
|
logActivity('verify',target+' 的表决结果为 '+(isTrue?'真':'假')+',但 **require 模式**要求先有 Lean 通过或显式阻塞记录,因此本轮**不定论**(已记入 Formal/TODO.md;原因 formal-required)')
|
|
2310
2416
|
verifyState=null; wakeKind.clear(); await saveAll()
|
|
2311
2417
|
// Scheduling is done by finalizeVerify (doSchedule=true) AFTER it releases finalizeLock:
|
|
@@ -2535,21 +2641,22 @@ export function apply(ctx) {
|
|
|
2535
2641
|
if(!agent || !agent.session) return
|
|
2536
2642
|
const compaction = compactionForAgent(agent)
|
|
2537
2643
|
if(compaction===undefined) return
|
|
2538
|
-
//
|
|
2539
|
-
//
|
|
2540
|
-
//
|
|
2541
|
-
|
|
2542
|
-
if(
|
|
2644
|
+
// F2 (user ruling): compaction is driven by the CONFIGURED THRESHOLD only. `compactIfNeeded` is the
|
|
2645
|
+
// POLICY verb, so a host that also offers the FORCING verb (`compactNow`) must NOT be forced - the
|
|
2646
|
+
// per-turn call is a cheap no-op below the threshold, and the core-rules re-anchor happens ONLY when
|
|
2647
|
+
// the policy call reports that it actually compacted.
|
|
2648
|
+
if(typeof compaction.compactIfNeeded !== 'function') return
|
|
2543
2649
|
try {
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2650
|
+
const signal = makeSignal(params.activityTimeoutMs||60000)
|
|
2651
|
+
const result = await compaction.compactIfNeeded(agent, 'pressure', signal)
|
|
2652
|
+
const didCompact = !!(result && ((result.shadowedSeqs||[]).length>0 || Number(result.shadowedTokenCount||0)>0))
|
|
2653
|
+
if(didCompact){
|
|
2547
2654
|
// the resident's real session was compacted → its context is now a summary.
|
|
2548
2655
|
// Flag needCompact so the NEXT wake re-anchors the core rules (they may have been blurred).
|
|
2549
2656
|
r.roundsSinceCompact=0; r.needCompact=true; r.contextPct=Math.min(r.contextPct||15,25)
|
|
2550
2657
|
logActivity('compact', r.rId+' real /compact (shadowed '+result.shadowedSeqs.length+' items, ~'+String(result.shadowedTokenCount||0)+' tokens)')
|
|
2551
2658
|
}
|
|
2552
|
-
} catch(e){ /* real compaction unavailable/failed; the soft directive already covers it */ }
|
|
2659
|
+
} catch(e){ if(!warnedCompaction){ warnedCompaction=true; console.error('vibe-math-v4: realCompact: the compaction call failed - context pressure was NOT compacted; the round/flag heuristic may desync from the real context size') } /* real compaction unavailable/failed; the soft directive already covers it */ }
|
|
2553
2660
|
}
|
|
2554
2661
|
|
|
2555
2662
|
// ---- liveness / scheduling ----
|
|
@@ -2646,7 +2753,12 @@ export function apply(ctx) {
|
|
|
2646
2753
|
// the top of the next pass — never in a tight loop here.
|
|
2647
2754
|
if(!r.childId) continue
|
|
2648
2755
|
let ok=false
|
|
2649
|
-
|
|
2756
|
+
// F4 (measured then reverted): this is the ONLY heartbeatPrompt wake site in v4 - the fairness/idle
|
|
2757
|
+
// fill. Both the soft-compaction design and the guards T13/A1 require THIS round to be a NORMAL
|
|
2758
|
+
// research round when contextPct >= compactThreshold (that is where the [CONTEXT COMPACT ...]
|
|
2759
|
+
// self-summary directive must land). Re-kinding it as its own kind therefore breaks them, and F4's
|
|
2760
|
+
// premise (a distinct non-research CHECKPOINT wake) has no separate path on this revision.
|
|
2761
|
+
try { ok = await wakeResident(r, await heartbeatPrompt(r), 'normal') } catch(e){ ok=false }
|
|
2650
2762
|
if(ok) started++
|
|
2651
2763
|
await saveAll()
|
|
2652
2764
|
if(!ok) continue // a failed wake must NOT stop the fill; try the next idle resident
|
|
@@ -2738,7 +2850,15 @@ export function apply(ctx) {
|
|
|
2738
2850
|
else if(typeof v.verdict==='string' && v.verdict.trim()!=='' && Number.isFinite(Number(v.verdict))){ p=clamp01(Number(v.verdict)) }
|
|
2739
2851
|
else { p=clamp01(Number(v.confidence)) }
|
|
2740
2852
|
// verdict is a PURE 0-1 probability (a degree); no binary TRUE/FALSE classification.
|
|
2741
|
-
|
|
2853
|
+
// F1: a ballot from a member who is NOT in the frozen participant set must NOT be recorded -
|
|
2854
|
+
// otherwise a late reply from a removed member re-creates its key and the count predicate can
|
|
2855
|
+
// reach `expected` while a frozen voter has no verdict. v5 states the same rule explicitly.
|
|
2856
|
+
const _inRoster = !Array.isArray(verifyState.rosterSnapshot) || verifyState.rosterSnapshot.indexOf(r.rId)!==-1
|
|
2857
|
+
if(!_inRoster){ logActivity('verify', r.rId+' 的表决未计入(不在本次验证的冻结名单中)') }
|
|
2858
|
+
else {
|
|
2859
|
+
verifyState.verdicts[r.rId]={prob:p,confidence:p,reason:String(v.reason||parsed.summary||'')}
|
|
2860
|
+
verifyState.lastVerdictAt=now()
|
|
2861
|
+
}
|
|
2742
2862
|
verifyState.lastVerdictAt=now()
|
|
2743
2863
|
await saveAll()
|
|
2744
2864
|
if(!running || autoDone) return // pause: freeze (resume refreshes the clocks and re-drives)
|
|
@@ -3358,6 +3478,7 @@ export function apply(ctx) {
|
|
|
3358
3478
|
if(ps.compile&&ps.compile.result==='ok') ps.files.push('paper.pdf')
|
|
3359
3479
|
ps.finalizedAt=now(); ps.status='done'
|
|
3360
3480
|
await paperWriteMeta(); await paperWriteLog()
|
|
3481
|
+
await paperWriteMeta(); await paperWriteLog()
|
|
3361
3482
|
// v2 §A1: the paper phase was entered from INSIDE the closing branch, BEFORE the completion
|
|
3362
3483
|
// flags — so the run only becomes "done" here, once the paper exists (or has degraded safely).
|
|
3363
3484
|
if(ps.deferCompletion && autoDone!==true){
|
|
@@ -3366,6 +3487,21 @@ export function apply(ctx) {
|
|
|
3366
3487
|
logActivity('stop','all residents agree: problem solved(一致性停止票;最终论文已产出)')
|
|
3367
3488
|
await saveAll()
|
|
3368
3489
|
}
|
|
3490
|
+
// G3: `done` is a CLAIM that the deliverable is complete. `final.md` is written unchecked and the
|
|
3491
|
+
// meta/log results are discarded, so VERIFY THE ARTIFACTS EXIST instead of trusting return values.
|
|
3492
|
+
// (readTextAbs returns undefined for a missing file and never throws on ENOENT.)
|
|
3493
|
+
const _paperNeed=['final.md']
|
|
3494
|
+
if(ps.cfg.format!=='tex') _paperNeed.push('paper.md')
|
|
3495
|
+
if(ps.cfg.format!=='md') _paperNeed.push('paper.tex')
|
|
3496
|
+
if(_paperNeed.indexOf('paper.md')===-1&&ps.files.indexOf('paper.md')!==-1) _paperNeed.push('paper.md')
|
|
3497
|
+
if(_paperNeed.indexOf('paper.tex')===-1&&ps.files.indexOf('paper.tex')!==-1) _paperNeed.push('paper.tex')
|
|
3498
|
+
_paperNeed.push('paper.meta.json','paper.log.md')
|
|
3499
|
+
const _paperGone=[]
|
|
3500
|
+
for(const _f of _paperNeed){ if((await readText(ps.dir+'/'+_f))===undefined) _paperGone.push(_f) }
|
|
3501
|
+
if(_paperGone.length){
|
|
3502
|
+
ps.status='failed' // terminal, like a failed compile: no complete deliverable is claimed
|
|
3503
|
+
if(!warnedPaperWrite){ warnedPaperWrite=true; console.error('vibe-math-v4: paperFinalize: required paper artifact(s) MISSING after finalize (' + _paperGone.join(', ') + ') - the paper is marked FAILED instead of done; the deliverable is incomplete and `files` never claims a missing artifact') }
|
|
3504
|
+
}
|
|
3369
3505
|
const warn=[ps.warning,ps.compile&&ps.compile.result==='failed'?ps.compile.error:''].filter(Boolean).join(';')
|
|
3370
3506
|
if(warn) logActivity('paper','最终论文完成(有警告):'+ps.dir+'|'+warn)
|
|
3371
3507
|
else logActivity('paper','最终论文完成:'+ps.dir+'('+ps.files.join('、')+(ps.compile?('|编译 '+(ps.compile.result||'')):'')+')')
|
|
@@ -3487,6 +3623,9 @@ export function apply(ctx) {
|
|
|
3487
3623
|
async function start({problem,residentCount,seedDirections}){
|
|
3488
3624
|
await loadSettings()
|
|
3489
3625
|
currentProject=await readCurrentProject(); if(!currentProject||currentProject==='default'){ currentProject='default'; }
|
|
3626
|
+
// F-3/F-4c verdicts: tool registration is wrapped once, in the registerTool helper (ctx.effect),
|
|
3627
|
+
// so no call site needs its own wrapper; and a failed mkdir is named ONCE inside ensureDirs itself,
|
|
3628
|
+
// which covers every caller (including this bare one) - no caller-side return check is needed.
|
|
3490
3629
|
await ensureDirs()
|
|
3491
3630
|
if(problem) problemText=String(problem)
|
|
3492
3631
|
if(!problemText) return {ok:false,message:'problem text required (pass problem, or use vibe_v4_configure first)'}
|
|
@@ -3590,11 +3729,23 @@ export function apply(ctx) {
|
|
|
3590
3729
|
if(verifyState && verifyState.lastVerdictAt) verifyState.lastVerdictAt=now()
|
|
3591
3730
|
logActivity('resume','restarted'+(crossProcess?' (cross-process: re-spawned)':needRespawn?' (re-spawned)':'')); await saveAll(); await scheduleNext(); return {ok:true,message:'resumed',project:currentProject}
|
|
3592
3731
|
}
|
|
3732
|
+
/** Round A F1/F2: the roster version must never be observable as unchanged while the frozen
|
|
3733
|
+
* participant set changed, and a frozen set must always travel WITH its own version. Every
|
|
3734
|
+
* roster change calls this AFTER all snapshot mutations (meeting + verify prunes included), so
|
|
3735
|
+
* {snapshot.rosterVersion, snapshot.rosterSnapshot} is one atomic pair and the live counter
|
|
3736
|
+
* still strictly increases on hire/fire. */
|
|
3737
|
+
function bumpRoster(){ rosterVersion++; if(meetingState&&Array.isArray(meetingState.rosterSnapshot)) meetingState.rosterVersion=rosterVersion; if(verifyState&&Array.isArray(verifyState.rosterSnapshot)) verifyState.rosterVersion=rosterVersion; return rosterVersion }
|
|
3738
|
+
/** The frozen participant set an operator should read right now (verify wins while it is live). */
|
|
3739
|
+
function activeRosterSnapshot(){ return (verifyState&&Array.isArray(verifyState.rosterSnapshot))?verifyState:((meetingState&&Array.isArray(meetingState.rosterSnapshot))?meetingState:null) }
|
|
3593
3740
|
function status(){ return { ok:true, running, phase, autoDone, project:currentProject, residentCount:residents.size,
|
|
3594
3741
|
// round-9 (P2): the roster VERSION travels with every status read, and an active verify/meeting
|
|
3595
3742
|
// reports the participant set it froze (never a re-derivation from the live map).
|
|
3596
3743
|
rosterVersion,
|
|
3597
|
-
|
|
3744
|
+
rosterVersion, liveRosterVersion: rosterVersion,
|
|
3745
|
+
// Round A F1: the pair is ATOMIC - `frozen.version` is the version the SNAPSHOT recorded when it
|
|
3746
|
+
// was frozen (and re-stamped on every roster change), never the live counter beside a stale set.
|
|
3747
|
+
frozen: (function(sn){ return {version: sn?sn.rosterVersion:null, participants: sn?sn.rosterSnapshot:null, kind: sn?(sn===verifyState?'verify':'meeting'):null} })(activeRosterSnapshot()),
|
|
3748
|
+
frozenParticipants: (activeRosterSnapshot()||{}).rosterSnapshot||null, // deprecated: use `frozen` (set + version together)
|
|
3598
3749
|
residents:listResidents(), busy:[...busy], taskboard:taskboard.length,
|
|
3599
3750
|
// Residents the host's live-child cap refused (maxActiveSubagents): queued, not lost. Without
|
|
3600
3751
|
// this the only trace would be the one console line, and `residentCount` alone cannot tell a
|
|
@@ -3605,8 +3756,8 @@ export function apply(ctx) {
|
|
|
3605
3756
|
// The coordination counters (`spoke k/N`, `voted k/N`, the verification round) used to live only
|
|
3606
3757
|
// in `report()`: from `status()` alone an operator could not tell "the group is progressing"
|
|
3607
3758
|
// from "the group is stuck at 2/4 votes", which is exactly how F1/F2/F7 stay invisible.
|
|
3608
|
-
consensus: meetingState?{kind:'meeting',id:meetingState.id,agenda:meetingState.agenda,round:meetingState.round,spoke:Object.keys(meetingState.inputs).length,expected:(meetingState.rosterSnapshot||Array.from(residents.keys())).length,solvedVotes:Object.values(meetingState.inputs).filter(iv=>iv.voteSolved===true).length,rosterVersion:meetingState.rosterVersion}
|
|
3609
|
-
:(verifyState?{kind:'verify',target:verifyState.targetId,targetType:verifyState.targetType,stage:verifyState.stage,round:verifyState.round,voted:Object.keys(verifyState.verdicts).length,expected:(verifyState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:verifyState.rosterVersion}:null),
|
|
3759
|
+
consensus: meetingState?{kind:'meeting',id:meetingState.id,agenda:meetingState.agenda,round:meetingState.round,rosterVersion:meetingState.rosterVersion,participants:meetingState.rosterSnapshot,spoke:Object.keys(meetingState.inputs).length,expected:(meetingState.rosterSnapshot||Array.from(residents.keys())).length,solvedVotes:Object.values(meetingState.inputs).filter(iv=>iv.voteSolved===true).length,rosterVersion:meetingState.rosterVersion}
|
|
3760
|
+
:(verifyState?{kind:'verify',target:verifyState.targetId,targetType:verifyState.targetType,stage:verifyState.stage,round:verifyState.round,rosterVersion:verifyState.rosterVersion,participants:verifyState.rosterSnapshot,voted:Object.keys(verifyState.verdicts).length,expected:(verifyState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:verifyState.rosterVersion}:null),
|
|
3610
3761
|
artifactCount: artifactCount, artifactBaseline: artifactBaseline,
|
|
3611
3762
|
paper: paperStatusSummary(),
|
|
3612
3763
|
// The Lean knobs and the per-object formal records are part of the readable status: without
|
|
@@ -3648,15 +3799,16 @@ export function apply(ctx) {
|
|
|
3648
3799
|
// residents) can never be true for the new member (not in the snapshot order) and the meeting is
|
|
3649
3800
|
// only ever released by the stuck watchdog instead of finalizing with everyone's input.
|
|
3650
3801
|
if(meetingState){ if(!Array.isArray(meetingState.order)) meetingState.order=Array.from(residents.keys()); if(!meetingState.order.includes(r.rId)) meetingState.order.push(r.rId) }
|
|
3802
|
+
rosterVersion++ // additions never extend a frozen set: the live counter moves ahead (staleness stays visible)
|
|
3651
3803
|
// P2: the roster CHANGED - bump the version so any frozen participant set is visibly stale.
|
|
3652
|
-
|
|
3804
|
+
|
|
3653
3805
|
// Mid-verify additions are automatically asked to vote (continueVerifyRound recomputes ids from
|
|
3654
3806
|
// the live residents map), so no extra handling is needed there.
|
|
3655
3807
|
return {ok:true,id:r.rId,direction:r.direction} }
|
|
3656
3808
|
async function removeMember(id){ const r=residents.get(id); if(!r) return noSuchResident(id,'vibe_v4_remove_member'); if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } residents.delete(id); busy.delete(id); mailboxes.delete(id); wakeKind.delete(id); if(currentResident===id) currentResident=''
|
|
3657
3809
|
// P2: the roster CHANGED - bump the version (a frozen participant set stays readable, but is
|
|
3658
3810
|
// now visibly stale; every view reports the same version it snapshotted).
|
|
3659
|
-
|
|
3811
|
+
|
|
3660
3812
|
// Reconcile in-progress coordination so a removed member cannot hang consensus or crash a round:
|
|
3661
3813
|
// drop its meeting speech / verify verdict and prune it from the meeting's speaking order so the
|
|
3662
3814
|
// find() there never selects a ghost. Its QUEUED verify proposals are deliberately KEPT: a
|
|
@@ -3672,6 +3824,7 @@ export function apply(ctx) {
|
|
|
3672
3824
|
// of freezing); the version bump above marks the set as changed.
|
|
3673
3825
|
if(meetingState&&Array.isArray(meetingState.rosterSnapshot)) meetingState.rosterSnapshot=meetingState.rosterSnapshot.filter(x=>x!==id)
|
|
3674
3826
|
if(verifyState&&Array.isArray(verifyState.rosterSnapshot)) verifyState.rosterSnapshot=verifyState.rosterSnapshot.filter(x=>x!==id)
|
|
3827
|
+
bumpRoster() // F2: AFTER every snapshot mutation - version and set stay one atomic pair
|
|
3675
3828
|
await saveAll()
|
|
3676
3829
|
// Re-drive the scheduler right away. If the removed member was the ONLY turn in flight (e.g. the
|
|
3677
3830
|
// last unspoken meeting speaker / the last unvoted voter, interrupted mid-turn), NO subagent/end
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
> `paper.lock.json`(最终论文定稿/写锁)等新功能之前;**功能与参数的权威描述以 `docs/` 为准**
|
|
6
6
|
> (`docs/math-computation.md`、`docs/formal-verification.md`、`docs/final-paper.md`、`docs/test-timing.md`),
|
|
7
7
|
> 参数表以 README 的双语「共用参数 / 各预设专属参数」表为准。本文保留其原始设计意图,不再逐条追平。
|
|
8
|
+
>
|
|
9
|
+
> 内部字段(**不构成对外契约**):`status.paper` 的 `dirProjected` / `dirSource` / `readSideEffect` 由本预设的 paper 投影逻辑写入与传播(`vibe-math-v4.js`),仅供内部状态与测试钉住;v5 已移除投影后端,故 v5 的 `status.paper` **没有**这三个字段。
|
|
8
10
|
|
|
9
11
|
> **一句话定位**:把 v3 的"中央规划器 + 确定性角色(explorer/solver/verifier/planner/method-keeper)"调度,升级为一组**持久化的常驻子代理**——它们之间**互相留言、集体开会**,自主决定接下来的一切任务(探索、尝试、验证、分工)。框架只做**媒介(消息总线/会议/任务板)、产物沉淀、共识验证、上下文管理、断点续跑**,**绝不分配任务**。像现实中的合作研究小组:人人有自己持续积累的笔记本与成果库,互相看、互相讨论、共同定论。
|
|
10
12
|
|