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.
Files changed (153) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +682 -681
  3. package/README.md +683 -682
  4. package/cordis.patch.yml +112 -16
  5. package/docs/AUDIT-CHECKLIST.md +755 -518
  6. package/docs/COMPAT-AUDIT-ROUND2.md +354 -328
  7. package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
  8. package/docs/audit-methodology.md +93 -0
  9. package/docs/final-paper.md +172 -172
  10. package/docs/formal-verification.md +613 -588
  11. package/docs/generate_framework_diagram_v2.py +114 -114
  12. package/docs/generate_framework_diagram_v3.py +127 -127
  13. package/docs/math-computation.md +195 -187
  14. package/docs/parameter-schema.md +37 -0
  15. package/docs/release-notes/README.en.md +2 -0
  16. package/docs/release-notes/README.md +2 -0
  17. package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
  18. package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
  19. package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
  20. package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
  21. package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
  22. package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
  23. package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
  24. package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
  25. package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
  26. package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
  27. package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
  28. package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
  29. package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
  30. package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
  31. package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
  32. package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
  33. package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
  34. package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
  35. package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +2 -2
  36. package/docs/release-notes/RELEASE-NOTES-2.7.2.md +3 -3
  37. package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
  38. package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
  39. package/docs/release-notes/RELEASE-NOTES-2.8.0.en.md +65 -0
  40. package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
  41. package/docs/release-notes/TEMPLATE.md +6 -0
  42. package/docs/release-playbook.md +103 -0
  43. package/docs/slv-playbook.md +117 -0
  44. package/docs/status-report-fields.md +167 -0
  45. package/docs/test-timing.md +158 -154
  46. package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
  47. package/installer.js +793 -783
  48. package/package.json +260 -203
  49. package/prompt-corpus-persona/persona-corpus.json +9 -9
  50. package/prompt-corpus-persona/persona-corpus.md +225 -33
  51. package/prompt-corpus-v2/formal-verify-v2.json +295 -115
  52. package/prompt-corpus-v2/formal-verify-v2.md +2290 -282
  53. package/prompt-corpus-v3/formal-verify-v3.json +112 -112
  54. package/prompt-corpus-v3/formal-verify-v3.md +227 -143
  55. package/prompt-corpus-v4/formal-verify-v4.json +18 -18
  56. package/prompt-corpus-v4/formal-verify-v4.md +49 -44
  57. package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
  58. package/prompt-corpus-v5/prompt-corpus-v5.md +573 -207
  59. package/scripts/clean-temp.mjs +234 -0
  60. package/scripts/update-doc-counts.mjs +71 -0
  61. package/tests/audit-artifact-docs.mjs +72 -0
  62. package/tests/audit-artifact-docs.mutants.mjs +58 -0
  63. package/tests/audit-engine-faces.mjs +298 -0
  64. package/tests/audit-formal-sensitivity.mjs +345 -342
  65. package/tests/audit-installer-assertions.mutants.mjs +63 -0
  66. package/tests/audit-installer-compat.test.mjs +256 -208
  67. package/tests/audit-installer-policy.test.mjs +41 -8
  68. package/tests/audit-math-computation-contract.mjs +64 -8
  69. package/tests/audit-math-computation-contract.mutants.mjs +106 -0
  70. package/tests/audit-math-computation-parity.mjs +164 -3
  71. package/tests/audit-math-computation-sensitivity.mjs +5 -3
  72. package/tests/audit-package-membership.mjs +57 -0
  73. package/tests/audit-package-membership.mutants.mjs +28 -0
  74. package/tests/audit-participant-set-parity.mjs +160 -0
  75. package/tests/audit-path-discipline.mjs +83 -0
  76. package/tests/audit-path-discipline.mutants.mjs +51 -0
  77. package/tests/audit-persona-sensitivity.mjs +323 -309
  78. package/tests/audit-persona-surface.mutants.mjs +69 -0
  79. package/tests/audit-persona-surface.test.mjs +431 -390
  80. package/tests/audit-prompt-duplication.mjs +143 -0
  81. package/tests/audit-prompt-duplication.mutants.mjs +51 -0
  82. package/tests/audit-prompt-invariants.mjs +589 -508
  83. package/tests/audit-readme-bilingual.test.mjs +152 -0
  84. package/tests/audit-readme-counts.mjs +95 -0
  85. package/tests/audit-readme-counts.mutants.mjs +52 -0
  86. package/tests/audit-registration.mjs +78 -0
  87. package/tests/audit-spec-traceability.mjs +193 -193
  88. package/tests/audit-status-report-fields.mjs +172 -0
  89. package/tests/audit-status-report-fields.mutants.mjs +64 -0
  90. package/tests/audit-v3-registration-parity.mjs +156 -0
  91. package/tests/audit-v5-integrity.mjs +795 -508
  92. package/tests/audit-v5-lean-abstention.mjs +392 -0
  93. package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
  94. package/tests/audit-v5-prompt-duplication.mjs +88 -0
  95. package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
  96. package/tests/audit-v5-sensitivity.mjs +433 -427
  97. package/tests/e2e-identity-a6.mutants.mjs +65 -0
  98. package/tests/e2e-v5-round2.test.mjs +2453 -1981
  99. package/tests/formal-verify-v2.test.mjs +1778 -1724
  100. package/tests/formal-verify-v3.mutants.mjs +117 -0
  101. package/tests/formal-verify-v3.test.mjs +1724 -1671
  102. package/tests/formal-verify-v4.mutants.mjs +152 -0
  103. package/tests/formal-verify-v4.test.mjs +2114 -1574
  104. package/tests/formal-verify-v5.test.mjs +790 -724
  105. package/tests/helpers/doc-counts-format.mjs +41 -0
  106. package/tests/helpers/math-computation-fake-seam.mjs +4 -0
  107. package/tests/math-computation-a1.mutants.mjs +72 -0
  108. package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
  109. package/tests/math-computation-discovery.mutants.mjs +76 -0
  110. package/tests/math-computation-shared.test.mjs +392 -6
  111. package/tests/math-computation-v2.test.mjs +779 -0
  112. package/tests/math-computation-v3.test.mjs +771 -0
  113. package/tests/math-computation-v4.mutants.mjs +133 -0
  114. package/tests/math-computation-v4.test.mjs +739 -0
  115. package/tests/prompt-v5-integrity.test.mjs +1291 -1280
  116. package/tests/run-tests.mjs +393 -202
  117. package/tests/run-tests.mutants.mjs +70 -0
  118. package/tests/selfdrive-v5.mjs +560 -508
  119. package/tests/selfdrive-v5.mutants.mjs +132 -0
  120. package/tests/temp-hygiene.mutants.mjs +80 -0
  121. package/tests/v2-fix-probes.mutants.mjs +104 -0
  122. package/tests/v2-fix-probes.test.mjs +1040 -0
  123. package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
  124. package/tests/v2v3-interrupt.mutants.mjs +111 -0
  125. package/tests/v3-fix-probes.mutants.mjs +99 -0
  126. package/tests/v3-fix-probes.test.mjs +892 -0
  127. package/tests/v4-final-paper.mutants.mjs +108 -0
  128. package/tests/v5-institute-fixes.mutants.mjs +218 -0
  129. package/vibe-math-v2/agent.cordis.yml +378 -322
  130. package/vibe-math-v2/math-computation.js +144 -53
  131. package/vibe-math-v2/math-engines.js +12 -12
  132. package/vibe-math-v2/preset.yml +2 -2
  133. package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
  134. package/vibe-math-v2/vibe-math-v2.js +5097 -4755
  135. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +491 -487
  136. package/vibe-math-v3/agent.cordis.yml +422 -356
  137. package/vibe-math-v3/math-computation.js +144 -53
  138. package/vibe-math-v3/math-engines.js +12 -12
  139. package/vibe-math-v3/preset.yml +2 -2
  140. package/vibe-math-v3/vibe-math-v3.js +6235 -5873
  141. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
  142. package/vibe-math-v4/agent.cordis.yml +438 -402
  143. package/vibe-math-v4/math-computation.js +144 -53
  144. package/vibe-math-v4/math-engines.js +12 -12
  145. package/vibe-math-v4/preset.yml +2 -2
  146. package/vibe-math-v4/vibe-math-v4.js +4490 -4269
  147. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1013
  148. package/vibe-math-v5/agent.cordis.yml +46 -12
  149. package/vibe-math-v5/math-computation.js +144 -53
  150. package/vibe-math-v5/math-engines.js +12 -12
  151. package/vibe-math-v5/vibe-math-v5.js +1266 -229
  152. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1660
  153. 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')