dsh-vibe-math 2.7.2 → 2.8.0
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/LICENSE +21 -21
- package/README.en.md +682 -681
- package/README.md +683 -682
- package/cordis.patch.yml +112 -16
- package/docs/AUDIT-CHECKLIST.md +755 -518
- package/docs/COMPAT-AUDIT-ROUND2.md +354 -328
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/audit-methodology.md +93 -0
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +613 -588
- package/docs/generate_framework_diagram_v2.py +114 -114
- package/docs/generate_framework_diagram_v3.py +127 -127
- package/docs/math-computation.md +195 -187
- package/docs/parameter-schema.md +37 -0
- package/docs/release-notes/README.en.md +2 -0
- package/docs/release-notes/README.md +2 -0
- package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
- package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
- package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
- package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
- package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
- package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
- package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
- package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
- package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
- package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
- package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
- package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
- package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
- package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
- package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
- package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
- package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
- package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
- 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/RELEASE-NOTES-2.8.0.en.md +65 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
- package/docs/release-notes/TEMPLATE.md +6 -0
- package/docs/release-playbook.md +103 -0
- package/docs/slv-playbook.md +117 -0
- package/docs/status-report-fields.md +167 -0
- package/docs/test-timing.md +158 -154
- package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
- package/installer.js +793 -783
- package/package.json +260 -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 +2290 -282
- package/prompt-corpus-v3/formal-verify-v3.json +112 -112
- package/prompt-corpus-v3/formal-verify-v3.md +227 -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 +573 -207
- package/scripts/clean-temp.mjs +234 -0
- 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-engine-faces.mjs +298 -0
- package/tests/audit-formal-sensitivity.mjs +345 -342
- package/tests/audit-installer-assertions.mutants.mjs +63 -0
- package/tests/audit-installer-compat.test.mjs +256 -208
- 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 +5 -3
- 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 +323 -309
- package/tests/audit-persona-surface.mutants.mjs +69 -0
- package/tests/audit-persona-surface.test.mjs +431 -390
- package/tests/audit-prompt-duplication.mjs +143 -0
- package/tests/audit-prompt-duplication.mutants.mjs +51 -0
- package/tests/audit-prompt-invariants.mjs +589 -508
- 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-spec-traceability.mjs +193 -193
- 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 +795 -508
- 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 +433 -427
- package/tests/e2e-identity-a6.mutants.mjs +65 -0
- package/tests/e2e-v5-round2.test.mjs +2453 -1981
- package/tests/formal-verify-v2.test.mjs +1778 -1724
- package/tests/formal-verify-v3.mutants.mjs +117 -0
- package/tests/formal-verify-v3.test.mjs +1724 -1671
- package/tests/formal-verify-v4.mutants.mjs +152 -0
- package/tests/formal-verify-v4.test.mjs +2114 -1574
- package/tests/formal-verify-v5.test.mjs +790 -724
- 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 +392 -6
- package/tests/math-computation-v2.test.mjs +779 -0
- package/tests/math-computation-v3.test.mjs +771 -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 +1291 -1280
- package/tests/run-tests.mjs +393 -202
- package/tests/run-tests.mutants.mjs +70 -0
- package/tests/selfdrive-v5.mjs +560 -508
- package/tests/selfdrive-v5.mutants.mjs +132 -0
- package/tests/temp-hygiene.mutants.mjs +80 -0
- package/tests/v2-fix-probes.mutants.mjs +104 -0
- package/tests/v2-fix-probes.test.mjs +1040 -0
- package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
- package/tests/v2v3-interrupt.mutants.mjs +111 -0
- package/tests/v3-fix-probes.mutants.mjs +99 -0
- package/tests/v3-fix-probes.test.mjs +892 -0
- package/tests/v4-final-paper.mutants.mjs +108 -0
- package/tests/v5-institute-fixes.mutants.mjs +218 -0
- package/vibe-math-v2/agent.cordis.yml +378 -322
- package/vibe-math-v2/math-computation.js +144 -53
- package/vibe-math-v2/math-engines.js +12 -12
- package/vibe-math-v2/preset.yml +2 -2
- package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
- package/vibe-math-v2/vibe-math-v2.js +5097 -4755
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +491 -487
- package/vibe-math-v3/agent.cordis.yml +422 -356
- package/vibe-math-v3/math-computation.js +144 -53
- package/vibe-math-v3/math-engines.js +12 -12
- package/vibe-math-v3/preset.yml +2 -2
- package/vibe-math-v3/vibe-math-v3.js +6235 -5873
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
- package/vibe-math-v4/agent.cordis.yml +438 -402
- package/vibe-math-v4/math-computation.js +144 -53
- package/vibe-math-v4/math-engines.js +12 -12
- package/vibe-math-v4/preset.yml +2 -2
- package/vibe-math-v4/vibe-math-v4.js +4490 -4269
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1013
- package/vibe-math-v5/agent.cordis.yml +46 -12
- package/vibe-math-v5/math-computation.js +144 -53
- package/vibe-math-v5/math-engines.js +12 -12
- package/vibe-math-v5/vibe-math-v5.js +1266 -229
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1660
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// ============================================================================================
|
|
2
|
+
// CROSS-PRESET PARTICIPANT-SET PARITY (P4 / item 6.3)
|
|
3
|
+
//
|
|
4
|
+
// Expectation source: `_oneoff/review/participant-set-map.md` (table first, then this guard).
|
|
5
|
+
//
|
|
6
|
+
// The four presets deliberately use DIFFERENT names for the same concept - "which set of members is
|
|
7
|
+
// this decision counting, and as of which roster state":
|
|
8
|
+
// · v2 `voteCount(t, round)` -> { participants, expected, round, allParticipantsReported }
|
|
9
|
+
// · v3 `voteCount(t, round)` -> { participants, expected, round } (NO allParticipantsReported:
|
|
10
|
+
// its readers compose their own predicate - a deliberate difference)
|
|
11
|
+
// · v4 `activeRosterSnapshot()` -> `frozen: { version, participants, kind }` (atomic; deprecated
|
|
12
|
+
// alias `frozenParticipants`)
|
|
13
|
+
// · v5 `rosterSnapshot()` -> `quorumView(): { m, mode, voters, voterCount, started, phase, rosterVersion }`
|
|
14
|
+
//
|
|
15
|
+
// So a behavioural "the numbers agree" test would have to unify those contracts, which is exactly what
|
|
16
|
+
// must NOT happen (v2's P2 regression came from collapsing two predicates). This guard is therefore
|
|
17
|
+
// STATIC: it pins, per preset, (a) that the participant set has exactly ONE producer, (b) that the set
|
|
18
|
+
// and its roster version/expectation are surfaced TOGETHER, and (c) the deliberate v2/v3 difference.
|
|
19
|
+
//
|
|
20
|
+
// Usage: node tests/audit-participant-set-parity.mjs
|
|
21
|
+
// ============================================================================================
|
|
22
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
23
|
+
import { join, resolve } from 'node:path'
|
|
24
|
+
import { fileURLToPath } from 'node:url'
|
|
25
|
+
|
|
26
|
+
const REPO = resolve(fileURLToPath(new URL('..', import.meta.url)))
|
|
27
|
+
const read = (rel) => existsSync(join(REPO, rel)) ? readFileSync(join(REPO, rel), 'utf8') : ''
|
|
28
|
+
|
|
29
|
+
let passed = 0, failed = 0
|
|
30
|
+
const failures = []
|
|
31
|
+
function ok(cond, label, detail) {
|
|
32
|
+
if (cond) { passed++; console.log(' ok ' + label); return true }
|
|
33
|
+
failed++; failures.push(label + (detail ? ' — ' + detail : '')); console.log(' FAIL ' + label + (detail ? ' — ' + detail : ''))
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
const occurrences = (s, re) => (s.match(re) || []).length
|
|
37
|
+
/** The body of a named function, by brace counting from its `function <name>(` occurrence. */
|
|
38
|
+
function body(src, name) {
|
|
39
|
+
const i = src.indexOf('function ' + name + '(')
|
|
40
|
+
if (i === -1) return ''
|
|
41
|
+
let depth = 0, started = false
|
|
42
|
+
for (let j = i; j < src.length; j++) {
|
|
43
|
+
if (src[j] === '{') { depth++; started = true }
|
|
44
|
+
else if (src[j] === '}') { depth--; if (started && depth === 0) return src.slice(i, j + 1) }
|
|
45
|
+
}
|
|
46
|
+
return src.slice(i)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---- `--self-probe`: the SAME predicates against deliberately broken source strings -----------
|
|
50
|
+
// Shipped form of the load-bearing proof (was `_oneoff/auditR2/participant-set-proof.mjs`): six cases,
|
|
51
|
+
// each GREEN on the real source and RED on the mutated string. No repo mutation, no fixtures.
|
|
52
|
+
// The predicates mirror the numbered assertions below — update them together.
|
|
53
|
+
function selfProbe() {
|
|
54
|
+
const v2s = read('vibe-math-v2/vibe-math-v2.js')
|
|
55
|
+
const v3s = read('vibe-math-v3/vibe-math-v3.js')
|
|
56
|
+
const v4s = read('vibe-math-v4/vibe-math-v4.js')
|
|
57
|
+
const v5s = read('vibe-math-v5/vibe-math-v5.js')
|
|
58
|
+
const ATOMIC = /frozen:\s*\(function\(sn\)\{\s*return \{version: sn\?sn\.rosterVersion:null, participants: sn\?sn\.rosterSnapshot:null, kind:/
|
|
59
|
+
const cases = [
|
|
60
|
+
['v2 produces allParticipantsReported',
|
|
61
|
+
/allParticipantsReported: participants\.length > 0 && thisRoundIds\.length === participants\.length/.test(v2s),
|
|
62
|
+
/allParticipantsReported: participants\.length > 0 && thisRoundIds\.length === participants\.length/.test(v2s.replace(/allParticipantsReported:/g, 'renamedField:'))],
|
|
63
|
+
['v3 deliberately LACKS the field', !/allParticipantsReported/.test(v3s),
|
|
64
|
+
!/allParticipantsReported/.test(v3s.replace('function voteCount(t, round) {', 'function voteCount(t, round) { const allParticipantsReported = 1;'))],
|
|
65
|
+
['v4 frozen is atomic', ATOMIC.test(v4s),
|
|
66
|
+
ATOMIC.test(v4s.replace(/frozen:\s*\(function\(sn\)\{\s*return \{version: sn\?sn\.rosterVersion:null, participants: sn\?sn\.rosterSnapshot:null, kind:/, 'frozen: {version: rosterVersion, participants: null, kind: null}, x: (function(sn){ return {version: sn?sn.rosterVersion:null, participants: sn?sn.rosterSnapshot:null, kind:'))],
|
|
67
|
+
['v4 removal prunes both frozen sets', occurrences(v4s.slice(v4s.indexOf('function removeMember')), /rosterSnapshot\.filter\(x=>x!==id\)/g) >= 2,
|
|
68
|
+
occurrences(v4s.slice(v4s.indexOf('function removeMember')).replace(/rosterSnapshot\.filter\(x=>x!==id\)/g, 'noop()'), /rosterSnapshot\.filter\(x=>x!==id\)/g) >= 2],
|
|
69
|
+
['v5 quorumView surfaces rosterVersion', /\brosterVersion:/.test(v5s.slice(v5s.indexOf('function quorumView'), v5s.indexOf('function quorumView') + 900)),
|
|
70
|
+
/\brosterVersion:/.test(v5s.slice(v5s.indexOf('function quorumView'), v5s.indexOf('function quorumView') + 900).replace(/rosterVersion:/g, 'ver:'))],
|
|
71
|
+
['exactly ONE v2 producer', occurrences(v2s, /function voteCount\(t, round\)/g) === 1,
|
|
72
|
+
occurrences(v2s + '\n function voteCount(t, round) { return null }\n', /function voteCount\(t, round\)/g) === 1],
|
|
73
|
+
]
|
|
74
|
+
let bad = 0
|
|
75
|
+
for (const [name, greenNow, redWhenBroken] of cases) {
|
|
76
|
+
const good = greenNow === true && redWhenBroken === false
|
|
77
|
+
if (!good) bad++
|
|
78
|
+
console.log((good ? ' ok ' : ' FAIL ') + name + ' :: green-now=' + greenNow + ' broken-goes-red=' + !redWhenBroken)
|
|
79
|
+
}
|
|
80
|
+
console.log('=== PARTICIPANT-SET SELF-PROBE: ' + (cases.length - bad) + '/' + cases.length + ' as required ===')
|
|
81
|
+
return bad === 0
|
|
82
|
+
}
|
|
83
|
+
if (process.argv.includes('--self-probe')) process.exit(selfProbe() ? 0 : 1)
|
|
84
|
+
const v2 = read('vibe-math-v2/vibe-math-v2.js')
|
|
85
|
+
const v3 = read('vibe-math-v3/vibe-math-v3.js')
|
|
86
|
+
const v4 = read('vibe-math-v4/vibe-math-v4.js')
|
|
87
|
+
const v5 = read('vibe-math-v5/vibe-math-v5.js')
|
|
88
|
+
|
|
89
|
+
console.log('-- participant-set parity across the four presets --')
|
|
90
|
+
|
|
91
|
+
// ---- 1. v2: one producer, set + expectation + the field, and it is CONSUMED ----
|
|
92
|
+
{
|
|
93
|
+
const b = body(v2, 'voteCount')
|
|
94
|
+
ok(occurrences(v2, /function voteCount\(t, round\)/g) === 1, 'v2: exactly ONE participant-set producer (voteCount)')
|
|
95
|
+
ok(/participants: participants/.test(b) && /expected: expected/.test(b) && /round: r/.test(b),
|
|
96
|
+
'v2: voteCount returns the SET together with its expectation + round')
|
|
97
|
+
ok(/allParticipantsReported: participants\.length > 0 && thisRoundIds\.length === participants\.length/.test(b),
|
|
98
|
+
'v2: voteCount produces allParticipantsReported (its single predicate)')
|
|
99
|
+
ok(occurrences(v2, /voteCount\(t, (?:t|meta)\.round\)\.allParticipantsReported/g) >= 2,
|
|
100
|
+
'v2: the field is CONSUMED (both readers go through voteCount, not a re-derivation)')
|
|
101
|
+
const expectedLine = /const expected = Math\.max\(MIN_REVIEWERS, Number\(tt\.expectedCount\) \|\| 0\)/.test(b)
|
|
102
|
+
ok(expectedLine, 'v2: expected comes from the CREATION-TIME snapshot with the MIN_REVIEWERS floor (deliberately kept)')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---- 2. v3: one producer, NO allParticipantsReported (the documented difference), snapshot-only quorum ----
|
|
106
|
+
{
|
|
107
|
+
const b = body(v3, 'voteCount')
|
|
108
|
+
ok(occurrences(v3, /function voteCount\(t, round\)/g) === 1, 'v3: exactly ONE participant-set producer (voteCount)')
|
|
109
|
+
ok(/participants: participants/.test(b) && /expected: expected/.test(b), 'v3: voteCount returns the SET together with its expectation')
|
|
110
|
+
ok(!/allParticipantsReported/.test(v3), 'v3: deliberately has NO allParticipantsReported (readers compose their own predicate - do not "unify" it away)')
|
|
111
|
+
const mv = body(v3, 'minVotes')
|
|
112
|
+
ok(mv.length > 0 && !/params\.verifierCount/.test(mv), 'v3: minVotes reads the SNAPSHOT only (live params.verifierCount must not leak back in)')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- 3. v4: one producer, ATOMIC set+version, deprecated alias, removal prunes ----
|
|
116
|
+
{
|
|
117
|
+
ok(occurrences(v4, /function activeRosterSnapshot\(/g) === 1, 'v4: exactly ONE participant-set producer (activeRosterSnapshot)')
|
|
118
|
+
ok(/frozen:\s*\(function\(sn\)\{\s*return \{version: sn\?sn\.rosterVersion:null, participants: sn\?sn\.rosterSnapshot:null, kind:/.test(v4),
|
|
119
|
+
'v4: `frozen` is the ATOMIC triple {version, participants, kind}')
|
|
120
|
+
ok(/frozenParticipants:\s*\(activeRosterSnapshot\(\)\|\|\{\}\)\.rosterSnapshot\|\|null/.test(v4) && /deprecated/.test(v4),
|
|
121
|
+
'v4: `frozenParticipants` stays only as a deprecated alias of frozen.participants')
|
|
122
|
+
const rm = body(v4, 'removeMember')
|
|
123
|
+
ok(occurrences(rm, /rosterSnapshot=(\w+)\.rosterSnapshot\.filter\(x=>x!==id\)|rosterSnapshot\.filter\(x=>x!==id\)/g) >= 2,
|
|
124
|
+
'v4: removal prunes BOTH frozen sets (that is what keeps "removal releases the wait" true)')
|
|
125
|
+
ok(/function bumpRoster\(/.test(v4) && /rosterVersion\+\+/.test(body(v4, 'bumpRoster')), 'v4: exactly one roster-version bump helper')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---- 4. v5: one producer, quorum view surfaces the set + version together ----
|
|
129
|
+
{
|
|
130
|
+
ok(occurrences(v5, /function rosterSnapshot\(\)/g) === 1, 'v5: exactly ONE participant-set producer (rosterSnapshot)')
|
|
131
|
+
const qv = body(v5, 'quorumView')
|
|
132
|
+
const keys = ['m', 'mode', 'voters', 'voterCount', 'started', 'phase', 'rosterVersion']
|
|
133
|
+
const missing = keys.filter((k) => !new RegExp('\\b' + k + ':').test(qv))
|
|
134
|
+
ok(qv.length > 0 && missing.length === 0, 'v5: quorumView surfaces the set (voters/voterCount) TOGETHER with m + rosterVersion', 'missing: ' + JSON.stringify(missing))
|
|
135
|
+
ok(/return \{[\s\S]*?version: rosterVersion\(\)[\s\S]*?voters:/.test(body(v5, 'rosterSnapshot')),
|
|
136
|
+
'v5: the snapshot captures version + voters in one object (no re-derivation between them)')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---- 5. the two cross-preset invariants, stated once (so a future preset cannot skip them) ----
|
|
140
|
+
{
|
|
141
|
+
const producers = [
|
|
142
|
+
['v2', occurrences(v2, /function voteCount\(t, round\)/g)],
|
|
143
|
+
['v3', occurrences(v3, /function voteCount\(t, round\)/g)],
|
|
144
|
+
['v4', occurrences(v4, /function activeRosterSnapshot\(/g)],
|
|
145
|
+
['v5', occurrences(v5, /function rosterSnapshot\(\)/g)],
|
|
146
|
+
]
|
|
147
|
+
ok(producers.every(([, n]) => n === 1), 'every preset has EXACTLY ONE participant-set producer ("produced only once")', JSON.stringify(producers))
|
|
148
|
+
const setAndVersion = [
|
|
149
|
+
['v2', /participants: participants/.test(body(v2, 'voteCount')) && /expected: expected/.test(body(v2, 'voteCount'))],
|
|
150
|
+
['v3', /participants: participants/.test(body(v3, 'voteCount')) && /expected: expected/.test(body(v3, 'voteCount'))],
|
|
151
|
+
['v4', /participants: sn\?sn\.rosterSnapshot:null/.test(v4) && /version: sn\?sn\.rosterVersion:null/.test(v4)],
|
|
152
|
+
['v5', /voterCount: snap\.voterCount/.test(body(v5, 'quorumView')) && /rosterVersion: snap\.version/.test(body(v5, 'quorumView'))],
|
|
153
|
+
]
|
|
154
|
+
ok(setAndVersion.every(([, b]) => b), 'every preset surfaces the set AND its roster version/expectation together', JSON.stringify(setAndVersion.filter(([, b]) => !b).map(([n]) => n)))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log('')
|
|
158
|
+
console.log('=== PARTICIPANT SET PARITY: ' + passed + ' passed, ' + failed + ' failed ===')
|
|
159
|
+
if (failures.length) for (const f of failures) console.error(' - ' + f)
|
|
160
|
+
process.exit(failed === 0 ? 0 : 1)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Dedicated cross-preset path-discipline audit (migrated out of formal-verify-v4 so that running ONE
|
|
2
|
+
// preset's suite cannot miss this class).
|
|
3
|
+
//
|
|
4
|
+
// Covers:
|
|
5
|
+
// * v4 / v5 : every member-visible library declaration carries the member root (Members/<x>/...).
|
|
6
|
+
// * v3 : the writer `applyAgentWrites` accepts ONLY Problems|Progress|Propos|Methods|Notes at the
|
|
7
|
+
// PROJECT ROOT, so v3's declarations must stay root-relative - a Members/<x>/... declaration
|
|
8
|
+
// is a write the writer SILENTLY DISCARDS.
|
|
9
|
+
// * v2 / v3 : PAPER_PATH_NOTE structure, by DELIVERED MATERIAL (not by function site):
|
|
10
|
+
// - the evidence-index DATA LIST must not embed the prose rule;
|
|
11
|
+
// - every delivered member-facing material that carries copyable paths must append it -
|
|
12
|
+
// for v2 that is the digest, which INLINES the evidence index; for v3 also the journal.
|
|
13
|
+
// Function-body windows are STRICTLY bounded (no fixed-length fallback): a window that can
|
|
14
|
+
// swallow the next function produces false passes.
|
|
15
|
+
import { readFileSync } from 'node:fs'
|
|
16
|
+
|
|
17
|
+
const R = new URL('../', import.meta.url)
|
|
18
|
+
const ENV_OF = {
|
|
19
|
+
'vibe-math-v2/vibe-math-v2.js': 'V2_PLUGIN',
|
|
20
|
+
'vibe-math-v3/vibe-math-v3.js': 'V3_PLUGIN',
|
|
21
|
+
'vibe-math-v4/vibe-math-v4.js': 'V4_PLUGIN',
|
|
22
|
+
'vibe-math-v5/vibe-math-v5.js': 'V5_PLUGIN',
|
|
23
|
+
}
|
|
24
|
+
// a mutant harness (tests/audit-path-discipline.mutants.mjs) points these at scratch copies
|
|
25
|
+
const rd = (p) => {
|
|
26
|
+
const e = ENV_OF[p] && process.env[ENV_OF[p]]
|
|
27
|
+
return e ? readFileSync(e, 'utf8') : readFileSync(new URL(p, R), 'utf8')
|
|
28
|
+
}
|
|
29
|
+
let passed = 0
|
|
30
|
+
let failed = 0
|
|
31
|
+
const fails = []
|
|
32
|
+
const assert = (c, m) => { if (c) { passed++; console.log(' ok - ' + m) } else { failed++; fails.push(m); console.error(' FAIL - ' + m) } }
|
|
33
|
+
|
|
34
|
+
const v2 = rd('vibe-math-v2/vibe-math-v2.js')
|
|
35
|
+
const v3 = rd('vibe-math-v3/vibe-math-v3.js')
|
|
36
|
+
const v4 = rd('vibe-math-v4/vibe-math-v4.js')
|
|
37
|
+
const v5 = rd('vibe-math-v5/vibe-math-v5.js')
|
|
38
|
+
|
|
39
|
+
const ROOT = /Members\/<[^>]+>\/(?:Progress|Propos|Methods|Subproblems)\//g
|
|
40
|
+
const LEGACY = /(?:^|[^/>])(?:Progress|Propos|Methods|Subproblems)\/<[^>]+>\//g
|
|
41
|
+
|
|
42
|
+
for (const [k, src] of [['v5', v5]]) { // v5 pending vm-v5's re-verification of its bases
|
|
43
|
+
assert((src.match(ROOT) || []).length >= 4, k + ': member-visible declarations carry the member root')
|
|
44
|
+
assert((src.match(LEGACY) || []).length === 0, k + ': no legacy project-root-relative member-library declaration')
|
|
45
|
+
}
|
|
46
|
+
// v4 (critical correction): writer/reader live at the PROJECT ROOT - a Members/<x>/... path is never scanned
|
|
47
|
+
assert(!/Members\//.test(v4), 'v4: no Members/ occurrence at all (widened predicate: literal ids escape the placeholder form) (the framework never scans it)')
|
|
48
|
+
assert(/(?:Progress|Propos|Methods|Subproblems)\/<[^>]+>\//.test(v4), 'v4: declarations use the project-root bases the writer/reader use')
|
|
49
|
+
assert(/progress\.md/.test(v4), 'class guard: v4 documents/writes the Progress base it reads')
|
|
50
|
+
|
|
51
|
+
assert(!/Members\//.test(v3), 'v3: no Members/ occurrence at all (widened predicate: literal ids escape the placeholder form) (applyAgentWrites would DISCARD such a write)')
|
|
52
|
+
assert(/applyAgentWrites[\s\S]{0,600}?(Problems|Progress|Propos|Methods|Notes)/.test(v3), 'class guard: v3 writer whitelists the sections its prompts name')
|
|
53
|
+
|
|
54
|
+
// strictly bounded function-body window: never fall back to a fixed length
|
|
55
|
+
const bodyOf = (src, fn) => {
|
|
56
|
+
const a = src.indexOf('function ' + fn)
|
|
57
|
+
if (a < 0) return null
|
|
58
|
+
const b = src.slice(a + 10).search(/\n {2}(?:async )?function /)
|
|
59
|
+
if (b < 0) return null
|
|
60
|
+
return src.slice(a, a + 10 + b)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const [k, src] of [['v2', v2], ['v3', v3]]) {
|
|
64
|
+
const def = (src.match(/const PAPER_PATH_NOTE/g) || []).length
|
|
65
|
+
assert(def === 1, k + ': PAPER_PATH_NOTE defined exactly once (found ' + def + ')')
|
|
66
|
+
const idxBody = bodyOf(src, 'paperEvidenceIndex')
|
|
67
|
+
const digBody = bodyOf(src, 'buildPaperDigest')
|
|
68
|
+
assert(idxBody !== null && digBody !== null, k + ': both function windows are bounded strictly (no fixed-length fallback)')
|
|
69
|
+
// the data list is a pure path list: no prose, no note
|
|
70
|
+
assert(!/PAPER_PATH_NOTE/.test(idxBody || ''), k + ': the evidence-index DATA LIST embeds no prose rule')
|
|
71
|
+
// the delivered material inlines that list and therefore carries the paths AND must carry the note
|
|
72
|
+
assert(/paperEvidenceIndex\(\)/.test(digBody || ''), k + ': the delivered digest INLINES the evidence index (so it carries the copyable paths)')
|
|
73
|
+
assert(/PAPER_PATH_NOTE/.test(digBody || ''), k + ': the delivered digest material appends PAPER_PATH_NOTE')
|
|
74
|
+
if (k === 'v3') {
|
|
75
|
+
const jr = bodyOf(src, 'writeJournal')
|
|
76
|
+
assert(jr !== null && /PAPER_PATH_NOTE/.test(jr), 'v3: the member-facing journal (writeJournal) also appends PAPER_PATH_NOTE')
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('')
|
|
81
|
+
console.log('passed=' + passed + ' failed=' + failed)
|
|
82
|
+
if (failed) { console.error('FAILURES:'); for (const f of fails) console.error(' - ' + f); process.exit(1) }
|
|
83
|
+
console.log('ALL GREEN')
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Registered mutant harness for tests/audit-path-discipline.mjs - the audit carries its own proof.
|
|
2
|
+
// One mutant per assertion family; each MUST make the audit fail, otherwise the guard does not bite.
|
|
3
|
+
// Usage: node tests/audit-path-discipline.mutants.mjs (exit 0 = every family reddened)
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, rmSync, mkdtempSync } from 'node:fs'
|
|
5
|
+
import { execFileSync } from 'node:child_process'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { dirname, join } from 'node:path'
|
|
8
|
+
import { tmpdir } from 'node:os'
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const repo = join(here, '..')
|
|
11
|
+
const audit = join(here, 'audit-path-discipline.mjs')
|
|
12
|
+
// Unique per run (audit C: a fixed repo-local dir could collide with a concurrent run and dirtied the tree)
|
|
13
|
+
const tmp = mkdtempSync(join(tmpdir(), 'audit-mutants-'))
|
|
14
|
+
const families = [
|
|
15
|
+
{ name: 'v4-text-shape (delivered declaration re-prefixed)', preset: 'vibe-math-v4', env: 'V4_PLUGIN',
|
|
16
|
+
from: "+' Propos/<" + String.fromCharCode(20320) + ">/<id>.md",
|
|
17
|
+
to: "+' Members/<" + String.fromCharCode(20320) + ">/Propos/<" + String.fromCharCode(20320) + ">/<id>.md",
|
|
18
|
+
expect: /no Members\/ occurrence at all/ },
|
|
19
|
+
{ name: 'v4-literal-members (widened Members/ predicate)', preset: 'vibe-math-v4', env: 'V4_PLUGIN',
|
|
20
|
+
from: "/Propos/r-1/p-1.md", to: "/Members/r-1/Propos/p-1.md",
|
|
21
|
+
expect: /no Members\/ occurrence at all/ },
|
|
22
|
+
{ name: 'v3-writer-whitelist class guard (Members/ reintroduced)', preset: 'vibe-math-v3', env: 'V3_PLUGIN',
|
|
23
|
+
from: '(^|[^/>])(Propos)\\/<([^>]+)>\\/', to: '$1Members/<$3>/$2/<$3>/', regex: true,
|
|
24
|
+
expect: /v3: no Members\// },
|
|
25
|
+
{ name: 'v2 PAPER_PATH_NOTE structure (digest append dropped)', preset: 'vibe-math-v2', env: 'V2_PLUGIN',
|
|
26
|
+
from: ' L.push(PAPER_PATH_NOTE)', to: '', expect: /delivered digest material appends PAPER_PATH_NOTE/ },
|
|
27
|
+
]
|
|
28
|
+
mkdirSync(tmp, { recursive: true })
|
|
29
|
+
let ok = 0, bad = 0
|
|
30
|
+
try {
|
|
31
|
+
for (const f of families) {
|
|
32
|
+
const src = join(repo, f.preset, f.preset + '.js')
|
|
33
|
+
let t = readFileSync(src, 'utf8')
|
|
34
|
+
const hits = f.regex ? (t.match(new RegExp(f.from, 'm')) || []).length : t.split(f.from).length - 1
|
|
35
|
+
if (hits < 1) { console.error(' NO-OP - ' + f.name + ' (predicate did not match)'); bad++; continue }
|
|
36
|
+
t = f.regex ? t.replace(new RegExp(f.from, 'm'), f.to) : t.split(f.from).join(f.to)
|
|
37
|
+
const out = join(tmp, f.preset + '.js')
|
|
38
|
+
writeFileSync(out, t, 'utf8')
|
|
39
|
+
let red = false, msg = ''
|
|
40
|
+
try {
|
|
41
|
+
execFileSync(process.execPath, [audit], { env: Object.assign({}, process.env, { [f.env]: out }), encoding: 'utf8' })
|
|
42
|
+
} catch (e) { red = true; msg = String((e && (e.stdout || '')) + (e && (e.stderr || ''))) }
|
|
43
|
+
const named = f.expect.test(msg)
|
|
44
|
+
if (red && named) { ok++; console.log(' ok - ' + f.name + ' reddened the audit by name') }
|
|
45
|
+
else { bad++; console.error(' FAIL - ' + f.name + ' red=' + red + ' named=' + named + ' :: ' + msg.split('\n').filter((l) => l.includes('FAIL')).slice(0, 1).join('')) }
|
|
46
|
+
}
|
|
47
|
+
} finally { rmSync(tmp, { recursive: true, force: true }) }
|
|
48
|
+
console.log('')
|
|
49
|
+
console.log('mutant families reddening the audit: ' + ok + '/' + families.length)
|
|
50
|
+
if (bad) process.exit(1)
|
|
51
|
+
console.log('ALL FAMILIES BITE')
|