dsh-vibe-math 2.3.12 → 2.3.13

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 (47) hide show
  1. package/README.md +17 -13
  2. package/{AUDIT-CHECKLIST.md → docs/AUDIT-CHECKLIST.md} +323 -304
  3. package/docs/COMPAT-AUDIT-ROUND2.md +325 -0
  4. package/docs/generate_framework_diagram_v2.py +114 -0
  5. package/docs/generate_framework_diagram_v3.py +127 -0
  6. package/{RELEASE-NOTES-2.1.0.md → docs/release-notes/RELEASE-NOTES-2.1.0.md} +143 -143
  7. package/{RELEASE-NOTES-2.2.0.md → docs/release-notes/RELEASE-NOTES-2.2.0.md} +266 -266
  8. package/{RELEASE-NOTES-2.2.1.md → docs/release-notes/RELEASE-NOTES-2.2.1.md} +43 -43
  9. package/{RELEASE-NOTES-2.2.2.md → docs/release-notes/RELEASE-NOTES-2.2.2.md} +88 -88
  10. package/{RELEASE-NOTES-2.3.0.md → docs/release-notes/RELEASE-NOTES-2.3.0.md} +207 -207
  11. package/{RELEASE-NOTES-2.3.1.md → docs/release-notes/RELEASE-NOTES-2.3.1.md} +134 -134
  12. package/{RELEASE-NOTES-2.3.10.md → docs/release-notes/RELEASE-NOTES-2.3.10.md} +105 -105
  13. package/{RELEASE-NOTES-2.3.11.md → docs/release-notes/RELEASE-NOTES-2.3.11.md} +57 -57
  14. package/{RELEASE-NOTES-2.3.12.md → docs/release-notes/RELEASE-NOTES-2.3.12.md} +80 -80
  15. package/docs/release-notes/RELEASE-NOTES-2.3.13.md +137 -0
  16. package/{RELEASE-NOTES-2.3.2.md → docs/release-notes/RELEASE-NOTES-2.3.2.md} +145 -145
  17. package/{RELEASE-NOTES-2.3.3.md → docs/release-notes/RELEASE-NOTES-2.3.3.md} +115 -115
  18. package/{RELEASE-NOTES-2.3.4.md → docs/release-notes/RELEASE-NOTES-2.3.4.md} +69 -69
  19. package/{RELEASE-NOTES-2.3.5.md → docs/release-notes/RELEASE-NOTES-2.3.5.md} +63 -63
  20. package/{RELEASE-NOTES-2.3.6.md → docs/release-notes/RELEASE-NOTES-2.3.6.md} +66 -66
  21. package/{RELEASE-NOTES-2.3.7.md → docs/release-notes/RELEASE-NOTES-2.3.7.md} +59 -59
  22. package/{RELEASE-NOTES-2.3.8.md → docs/release-notes/RELEASE-NOTES-2.3.8.md} +45 -45
  23. package/{RELEASE-NOTES-2.3.9.md → docs/release-notes/RELEASE-NOTES-2.3.9.md} +70 -70
  24. package/docs/test-timing.md +18 -17
  25. package/installer.js +105 -41
  26. package/package.json +42 -37
  27. package/{audit-formal-sensitivity.mjs → tests/audit-formal-sensitivity.mjs} +342 -342
  28. package/{audit-installer-compat.test.mjs → tests/audit-installer-compat.test.mjs} +136 -136
  29. package/tests/audit-installer-policy.test.mjs +245 -0
  30. package/{audit-persona-sensitivity.mjs → tests/audit-persona-sensitivity.mjs} +249 -249
  31. package/{audit-persona-surface.test.mjs → tests/audit-persona-surface.test.mjs} +349 -349
  32. package/{audit-prompt-invariants.mjs → tests/audit-prompt-invariants.mjs} +508 -508
  33. package/{audit-spec-traceability.mjs → tests/audit-spec-traceability.mjs} +193 -193
  34. package/{audit-v5-integrity.mjs → tests/audit-v5-integrity.mjs} +448 -448
  35. package/{audit-v5-sensitivity.mjs → tests/audit-v5-sensitivity.mjs} +384 -384
  36. package/{e2e-v5-round2.test.mjs → tests/e2e-v5-round2.test.mjs} +521 -521
  37. package/{formal-verify-v2.test.mjs → tests/formal-verify-v2.test.mjs} +1315 -1315
  38. package/{formal-verify-v3.test.mjs → tests/formal-verify-v3.test.mjs} +1257 -1257
  39. package/{formal-verify-v4.test.mjs → tests/formal-verify-v4.test.mjs} +1082 -1082
  40. package/{formal-verify-v5.test.mjs → tests/formal-verify-v5.test.mjs} +708 -708
  41. package/{prompt-v5-integrity.test.mjs → tests/prompt-v5-integrity.test.mjs} +3 -3
  42. package/{run-tests.mjs → tests/run-tests.mjs} +121 -118
  43. package/{selfdrive-v5.mjs → tests/selfdrive-v5.mjs} +470 -470
  44. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  45. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1 -1
  46. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +1 -1
  47. /package/{RELEASE-NOTES-2.0.22.md → docs/release-notes/RELEASE-NOTES-2.0.22.md} +0 -0
@@ -1,249 +1,249 @@
1
- // ============================================================
2
- // PERSONA SURFACE SENSITIVITY PROBES
3
- //
4
- // `audit-persona-surface.test.mjs` compares each preset's persona prompt against the tools
5
- // that preset actually registers. It is the only guard against two failures that no other
6
- // suite can see (they all call apply(ctx) directly and never load the YAML):
7
- //
8
- // · a registered tool that the persona never names → the agent cannot discover it;
9
- // · a persona that names an unregistered tool → the agent is told to call a tool
10
- // that does not exist.
11
- //
12
- // The Lean formal-verification feature shipped with both, in three of four presets. This
13
- // script proves the guard is not vacuous: each probe copies the preset tree, applies ONE
14
- // mutation, and requires the suite to go RED.
15
- //
16
- // False-green traps handled (see AUDIT-CHECKLIST.md §2):
17
- // 1. a probe whose own spawn fails — reported as SETUP-FAIL, never as a detection;
18
- // 2. a suite that ignores its override env var — `PERSONA_ROOT` is exercised in both
19
- // directions (a NON-mutated copy must keep the suite GREEN before any probe runs);
20
- // 3. a semantically inert mutation — the anchor must match an exact, asserted count, and
21
- // for YAML probes the mutated copy must still yield the persona's two literal blocks;
22
- // 4. a mutation that breaks syntax — every mutated `.js` copy is `node --check`ed, and a
23
- // mutated `.yml` whose persona markers are damaged is a SETUP-FAIL, not a detection.
24
- //
25
- // Run: node audit-persona-sensitivity.mjs
26
- // ============================================================
27
- import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync, cpSync } from 'node:fs'
28
- import { tmpdir } from 'node:os'
29
- import { join } from 'node:path'
30
- import { spawnSync } from 'node:child_process'
31
- import { fileURLToPath } from 'node:url'
32
-
33
- const REPO = fileURLToPath(new URL('.', import.meta.url))
34
- const SUITE = join(REPO, 'audit-persona-surface.test.mjs')
35
- const PRESETS = [
36
- { dir: 'vibe-math-v2', js: 'vibe-math-v2.js' },
37
- { dir: 'vibe-math-v3', js: 'vibe-math-v3.js' },
38
- { dir: 'vibe-math-v4', js: 'vibe-math-v4.js' },
39
- { dir: 'vibe-math-v5', js: 'vibe-math-v5.js' },
40
- ]
41
-
42
- /** Prepare a private copy of every preset (persona + plugin), so one probe cannot leak. */
43
- function prepareRoot() {
44
- const root = mkdtempSync(join(tmpdir(), 'persona-sens-'))
45
- for (const P of PRESETS) {
46
- mkdirSync(join(root, P.dir), { recursive: true })
47
- cpSync(join(REPO, P.dir, 'agent.cordis.yml'), join(root, P.dir, 'agent.cordis.yml'))
48
- cpSync(join(REPO, P.dir, P.js), join(root, P.dir, P.js))
49
- }
50
- return root
51
- }
52
-
53
- /** The persona's two literal blocks must survive a YAML mutation, or the probe is unfair. */
54
- function yamlStillHasPersona(yml) {
55
- if (!/^\s*-\s*id:\s*persona\s*$/m.test(yml)) return false
56
- const markers = yml.match(/^\s*(?:prefix|text):\s*\|-?\s*$/gm) || []
57
- return markers.length === 2
58
- }
59
-
60
- function runSuite(root) {
61
- const env = Object.assign({}, process.env, { PERSONA_ROOT: root })
62
- const r = spawnSync(process.execPath, [SUITE], { env, encoding: 'utf8', cwd: REPO })
63
- return { status: r.status, error: r.error, out: String(r.stdout || '') + String(r.stderr || '') }
64
- }
65
-
66
- // A sanity gate first: the UNMUTATED copy must be GREEN through the override, otherwise
67
- // every probe below would "detect" the override itself rather than the mutation (trap 2).
68
- const sanityRoot = prepareRoot()
69
- {
70
- const r = runSuite(sanityRoot)
71
- if (r.status !== 0) {
72
- console.error(' SETUP-FAIL - the suite is RED on an unmutated copy through PERSONA_ROOT:')
73
- console.error(r.out.split('\n').filter((l) => l.includes('FAIL')).slice(0, 8).join('\n'))
74
- rmSync(sanityRoot, { recursive: true, force: true })
75
- process.exit(1)
76
- }
77
- console.log(' ok - control: unmutated copy is GREEN through PERSONA_ROOT (the override works)')
78
- }
79
- rmSync(sanityRoot, { recursive: true, force: true })
80
-
81
- const probes = [
82
- // ── v2 ─────────────────────────────────────────────────────────────────────
83
- {
84
- name: 'v2-lean-tools-line-removed',
85
- preset: 'vibe-math-v2', file: 'agent.cordis.yml', nth: 'all', expect: 2,
86
- guarantee: 'the three Lean tools must be named in the persona the main agent receives',
87
- from: ' - vibe_math_lean_run / vibe_math_lean_archive / vibe_math_lean_lib — Lean formal\n verification (execute / archive / list the reuse library). The scheduler\'s child agents\n use them too; they work in every mode.\n',
88
- to: '',
89
- },
90
- {
91
- name: 'v2-phantom-tool-mentioned',
92
- preset: 'vibe-math-v2', file: 'agent.cordis.yml', nth: 'all', expect: 2,
93
- guarantee: 'the persona must not advertise a tool that is not registered (the agent would call it and fail)',
94
- from: ' - vibe_math_lean_run / vibe_math_lean_archive / vibe_math_lean_lib — Lean formal',
95
- to: ' - vibe_math_lean_exec / vibe_math_lean_run / vibe_math_lean_archive / vibe_math_lean_lib — Lean formal',
96
- },
97
-
98
- // ── v3 ─────────────────────────────────────────────────────────────────────
99
- {
100
- name: 'v3-persona-tool-name-typo',
101
- preset: 'vibe-math-v3', file: 'agent.cordis.yml', nth: 'all', expect: 2,
102
- guarantee: 'a typo in a persona tool name must be caught (an agent copying it calls a tool that does not exist)',
103
- from: 'vibe_math_lean_archive',
104
- to: 'vibe_math_lean_archiv',
105
- },
106
- {
107
- name: 'v3-tool-registered-but-not-documented',
108
- preset: 'vibe-math-v3', file: 'vibe-math-v3.js', nth: 'first', expect: 2,
109
- guarantee: 'a newly registered tool must be documented in the persona (or the snapshot updated on purpose)',
110
- from: " registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain",
111
- to: " registerTool('vibe_math_extra_tool', 'x', objParams({}), async function () { return {} })\n registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain",
112
- },
113
-
114
- // ── v4 ─────────────────────────────────────────────────────────────────────
115
- {
116
- name: 'v4-formal-mode-renamed-in-persona',
117
- preset: 'vibe-math-v4', file: 'agent.cordis.yml', nth: 'all', expect: 2,
118
- guarantee: 'the persona must spell the three mode names exactly as the parameter accepts them',
119
- from: " - 'off' (default, no extra requirement) | 'encourage' (the residents decide by implementation\n difficulty whether to formalize in Lean; once a Lean run passes, their unanimous vote becomes\n a FIDELITY review — do the Lean definitions/objects/conditions/assumptions/conclusion match\n the proposition as stated) | 'require' (same, plus a gate: a unanimous true/false verdict is",
120
- to: " - 'off' (default, no extra requirement) | 'encourage' (the residents decide by implementation\n difficulty whether to formalize in Lean; once a Lean run passes, their unanimous vote becomes\n a FIDELITY review — do the Lean definitions/objects/conditions/assumptions/conclusion match\n the proposition as stated) | 'forced' (same, plus a gate: a unanimous true/false verdict is",
121
- },
122
- {
123
- name: 'v4-formal-report-tool-hidden',
124
- preset: 'vibe-math-v4', file: 'agent.cordis.yml', nth: 'all', expect: 2,
125
- guarantee: 'the formal-report tool must be named in the persona (it is the coordinator-facing Lean view)',
126
- from: ' - vibe_v4_formal_report — human-readable Lean formal-verification mirror (mode, Lean-passed',
127
- to: ' - vibe_v4_formal_reportX — human-readable Lean formal-verification mirror (mode, Lean-passed',
128
- },
129
-
130
- // ── v5 ─────────────────────────────────────────────────────────────────────
131
- {
132
- name: 'v5-prefix-text-drift',
133
- preset: 'vibe-math-v5', file: 'agent.cordis.yml', nth: 'second', expect: 2,
134
- guarantee: 'prefix and text must stay identical apart from line 0 (old and new hosts must see the same surface)',
135
- from: 'TRUST RULE: only Verified/',
136
- to: 'TRUST RULE (amended): only Verified/',
137
- },
138
- {
139
- name: 'v5-tool-renamed-in-plugin',
140
- preset: 'vibe-math-v5', file: 'vibe-math-v5.js', nth: 'first', expect: 1,
141
- guarantee: 'renaming a registered tool must be caught (the persona still advertises the old name)',
142
- from: " registerTool('vibe_v5_meeting',",
143
- to: " registerTool('vibe_v5_meeting2',",
144
- },
145
- {
146
- name: 'v4-usage-advertises-phantom-subcommand',
147
- preset: 'vibe-math-v4', file: 'vibe-math-v4.js', nth: 'first', expect: 1,
148
- guarantee: 'the unknown-subcommand usage string must not advertise a subcommand that no branch implements',
149
- from: "usage:'configure|start|resume|pause|abort|status|report|message <to|all> <content>|meeting|members|add|remove|set'",
150
- to: "usage:'configure|start|resume|pause|abort|status|report|message <to|all> <content>|meeting|members|add|remove|scan|set'",
151
- },
152
- {
153
- name: 'v4-hint-advertises-unimplemented-subcommand',
154
- preset: 'vibe-math-v4', file: 'vibe-math-v4.js', nth: 'first', expect: 1,
155
- guarantee: 'the typing hint must not advertise a subcommand the handler does not implement',
156
- from: "message <to|all> <content>|meeting|members|add|remove|set]'",
157
- to: "message <to|all> <content>|meeting|add|remove|set]'",
158
- },
159
- {
160
- name: 'v5-persona-slash-list-drops-add-remove',
161
- preset: 'vibe-math-v5', file: 'agent.cordis.yml', nth: 'all', expect: 2,
162
- guarantee: "the persona's /v5 list must match the command hint (a human reading the persona must see every subcommand)",
163
- from: 'meeting|hire|fire|add|remove|set)',
164
- to: 'meeting|hire|fire|set)',
165
- },
166
- ]
167
-
168
- let ok = 0
169
- let bad = 0
170
- console.log('')
171
- console.log('-- persona surface sensitivity probes --')
172
- console.log('(a probe passes when breaking the guarantee turns audit-persona-surface.test.mjs RED)')
173
- console.log('')
174
-
175
- for (const p of probes) {
176
- const root = prepareRoot()
177
- const target = join(root, p.preset, p.file)
178
- const original = readFileSync(target, 'utf8')
179
- // The repo's YAML files are CRLF; anchors are written with \n. Normalise both directions so
180
- // a multi-line anchor matches regardless of the checkout's line endings.
181
- const eol = original.includes('\r\n') ? '\r\n' : '\n'
182
- const from = p.from.split('\n').join(eol)
183
- const to = p.to.split('\n').join(eol)
184
- p.from = from
185
- p.to = to
186
- const occurrences = original.split(p.from).length - 1
187
- if (occurrences !== p.expect) {
188
- console.error(` SETUP-FAIL - ${p.name}: anchor matched ${occurrences} times (need ${p.expect})`)
189
- bad++
190
- rmSync(root, { recursive: true, force: true })
191
- continue
192
- }
193
- let mutated
194
- if (p.nth === 'all') mutated = original.split(p.from).join(p.to)
195
- else if (p.nth === 'first') mutated = original.replace(p.from, p.to)
196
- else if (p.nth === 'second') {
197
- const i = original.indexOf(p.from)
198
- const j = original.indexOf(p.from, i + 1)
199
- mutated = original.slice(0, j) + p.to + original.slice(j + p.from.length)
200
- } else throw new Error('unknown nth ' + p.nth)
201
-
202
- if (mutated === original) {
203
- console.error(` SETUP-FAIL - ${p.name}: the mutation is a no-op`)
204
- bad++
205
- rmSync(root, { recursive: true, force: true })
206
- continue
207
- }
208
- if (p.file.endsWith('.yml') && !yamlStillHasPersona(mutated)) {
209
- console.error(` SETUP-FAIL - ${p.name}: the mutated persona no longer has its two literal blocks (unfair mutation)`)
210
- bad++
211
- rmSync(root, { recursive: true, force: true })
212
- continue
213
- }
214
- writeFileSync(target, mutated, 'utf8')
215
-
216
- if (p.file.endsWith('.js')) {
217
- const chk = spawnSync(process.execPath, ['--check', target], { encoding: 'utf8' })
218
- if (chk.status !== 0) {
219
- console.error(` SETUP-FAIL - ${p.name}: the mutated copy has a syntax error:\n` + String(chk.stderr || '').split('\n').slice(0, 4).join('\n'))
220
- bad++
221
- rmSync(root, { recursive: true, force: true })
222
- continue
223
- }
224
- }
225
-
226
- const r = runSuite(root)
227
- if (r.status === null) {
228
- console.error(` SETUP-FAIL - ${p.name}: the suite could not be started (${String(r.error && r.error.message)})`)
229
- bad++
230
- rmSync(root, { recursive: true, force: true })
231
- continue
232
- }
233
- if (r.status !== 0) {
234
- const firstFail = (r.out.split('\n').find((l) => l.trim().startsWith('FAIL')) || '').trim().slice(0, 110)
235
- ok++
236
- console.log(` ok - ${p.name} [${p.preset}] => suite went RED as required [${p.guarantee}]`)
237
- if (firstFail) console.log(` ${firstFail}`)
238
- } else {
239
- bad++
240
- console.error(` BLIND SPOT - ${p.name} [${p.preset}] => suite stayed GREEN, so it does NOT detect: ${p.guarantee}`)
241
- }
242
- rmSync(root, { recursive: true, force: true })
243
- }
244
-
245
- console.log('')
246
- console.log(`persona sensitivity: ${ok} probes detected the break, ${bad} problems`)
247
- if (bad) process.exit(1)
248
- console.log('ALL PERSONA PROBES RED AS REQUIRED')
249
- process.exit(0)
1
+ // ============================================================
2
+ // PERSONA SURFACE SENSITIVITY PROBES
3
+ //
4
+ // `audit-persona-surface.test.mjs` compares each preset's persona prompt against the tools
5
+ // that preset actually registers. It is the only guard against two failures that no other
6
+ // suite can see (they all call apply(ctx) directly and never load the YAML):
7
+ //
8
+ // · a registered tool that the persona never names → the agent cannot discover it;
9
+ // · a persona that names an unregistered tool → the agent is told to call a tool
10
+ // that does not exist.
11
+ //
12
+ // The Lean formal-verification feature shipped with both, in three of four presets. This
13
+ // script proves the guard is not vacuous: each probe copies the preset tree, applies ONE
14
+ // mutation, and requires the suite to go RED.
15
+ //
16
+ // False-green traps handled (see AUDIT-CHECKLIST.md §2):
17
+ // 1. a probe whose own spawn fails — reported as SETUP-FAIL, never as a detection;
18
+ // 2. a suite that ignores its override env var — `PERSONA_ROOT` is exercised in both
19
+ // directions (a NON-mutated copy must keep the suite GREEN before any probe runs);
20
+ // 3. a semantically inert mutation — the anchor must match an exact, asserted count, and
21
+ // for YAML probes the mutated copy must still yield the persona's two literal blocks;
22
+ // 4. a mutation that breaks syntax — every mutated `.js` copy is `node --check`ed, and a
23
+ // mutated `.yml` whose persona markers are damaged is a SETUP-FAIL, not a detection.
24
+ //
25
+ // Run: node tests/audit-persona-sensitivity.mjs
26
+ // ============================================================
27
+ import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync, cpSync } from 'node:fs'
28
+ import { tmpdir } from 'node:os'
29
+ import { join } from 'node:path'
30
+ import { spawnSync } from 'node:child_process'
31
+ import { fileURLToPath } from 'node:url'
32
+
33
+ const REPO = fileURLToPath(new URL('..', import.meta.url))
34
+ const SUITE = join(REPO, 'tests', 'audit-persona-surface.test.mjs')
35
+ const PRESETS = [
36
+ { dir: 'vibe-math-v2', js: 'vibe-math-v2.js' },
37
+ { dir: 'vibe-math-v3', js: 'vibe-math-v3.js' },
38
+ { dir: 'vibe-math-v4', js: 'vibe-math-v4.js' },
39
+ { dir: 'vibe-math-v5', js: 'vibe-math-v5.js' },
40
+ ]
41
+
42
+ /** Prepare a private copy of every preset (persona + plugin), so one probe cannot leak. */
43
+ function prepareRoot() {
44
+ const root = mkdtempSync(join(tmpdir(), 'persona-sens-'))
45
+ for (const P of PRESETS) {
46
+ mkdirSync(join(root, P.dir), { recursive: true })
47
+ cpSync(join(REPO, P.dir, 'agent.cordis.yml'), join(root, P.dir, 'agent.cordis.yml'))
48
+ cpSync(join(REPO, P.dir, P.js), join(root, P.dir, P.js))
49
+ }
50
+ return root
51
+ }
52
+
53
+ /** The persona's two literal blocks must survive a YAML mutation, or the probe is unfair. */
54
+ function yamlStillHasPersona(yml) {
55
+ if (!/^\s*-\s*id:\s*persona\s*$/m.test(yml)) return false
56
+ const markers = yml.match(/^\s*(?:prefix|text):\s*\|-?\s*$/gm) || []
57
+ return markers.length === 2
58
+ }
59
+
60
+ function runSuite(root) {
61
+ const env = Object.assign({}, process.env, { PERSONA_ROOT: root })
62
+ const r = spawnSync(process.execPath, [SUITE], { env, encoding: 'utf8', cwd: REPO })
63
+ return { status: r.status, error: r.error, out: String(r.stdout || '') + String(r.stderr || '') }
64
+ }
65
+
66
+ // A sanity gate first: the UNMUTATED copy must be GREEN through the override, otherwise
67
+ // every probe below would "detect" the override itself rather than the mutation (trap 2).
68
+ const sanityRoot = prepareRoot()
69
+ {
70
+ const r = runSuite(sanityRoot)
71
+ if (r.status !== 0) {
72
+ console.error(' SETUP-FAIL - the suite is RED on an unmutated copy through PERSONA_ROOT:')
73
+ console.error(r.out.split('\n').filter((l) => l.includes('FAIL')).slice(0, 8).join('\n'))
74
+ rmSync(sanityRoot, { recursive: true, force: true })
75
+ process.exit(1)
76
+ }
77
+ console.log(' ok - control: unmutated copy is GREEN through PERSONA_ROOT (the override works)')
78
+ }
79
+ rmSync(sanityRoot, { recursive: true, force: true })
80
+
81
+ const probes = [
82
+ // ── v2 ─────────────────────────────────────────────────────────────────────
83
+ {
84
+ name: 'v2-lean-tools-line-removed',
85
+ preset: 'vibe-math-v2', file: 'agent.cordis.yml', nth: 'all', expect: 2,
86
+ guarantee: 'the three Lean tools must be named in the persona the main agent receives',
87
+ from: ' - vibe_math_lean_run / vibe_math_lean_archive / vibe_math_lean_lib — Lean formal\n verification (execute / archive / list the reuse library). The scheduler\'s child agents\n use them too; they work in every mode.\n',
88
+ to: '',
89
+ },
90
+ {
91
+ name: 'v2-phantom-tool-mentioned',
92
+ preset: 'vibe-math-v2', file: 'agent.cordis.yml', nth: 'all', expect: 2,
93
+ guarantee: 'the persona must not advertise a tool that is not registered (the agent would call it and fail)',
94
+ from: ' - vibe_math_lean_run / vibe_math_lean_archive / vibe_math_lean_lib — Lean formal',
95
+ to: ' - vibe_math_lean_exec / vibe_math_lean_run / vibe_math_lean_archive / vibe_math_lean_lib — Lean formal',
96
+ },
97
+
98
+ // ── v3 ─────────────────────────────────────────────────────────────────────
99
+ {
100
+ name: 'v3-persona-tool-name-typo',
101
+ preset: 'vibe-math-v3', file: 'agent.cordis.yml', nth: 'all', expect: 2,
102
+ guarantee: 'a typo in a persona tool name must be caught (an agent copying it calls a tool that does not exist)',
103
+ from: 'vibe_math_lean_archive',
104
+ to: 'vibe_math_lean_archiv',
105
+ },
106
+ {
107
+ name: 'v3-tool-registered-but-not-documented',
108
+ preset: 'vibe-math-v3', file: 'vibe-math-v3.js', nth: 'first', expect: 2,
109
+ guarantee: 'a newly registered tool must be documented in the persona (or the snapshot updated on purpose)',
110
+ from: " registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain",
111
+ to: " registerTool('vibe_math_extra_tool', 'x', objParams({}), async function () { return {} })\n registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain",
112
+ },
113
+
114
+ // ── v4 ─────────────────────────────────────────────────────────────────────
115
+ {
116
+ name: 'v4-formal-mode-renamed-in-persona',
117
+ preset: 'vibe-math-v4', file: 'agent.cordis.yml', nth: 'all', expect: 2,
118
+ guarantee: 'the persona must spell the three mode names exactly as the parameter accepts them',
119
+ from: " - 'off' (default, no extra requirement) | 'encourage' (the residents decide by implementation\n difficulty whether to formalize in Lean; once a Lean run passes, their unanimous vote becomes\n a FIDELITY review — do the Lean definitions/objects/conditions/assumptions/conclusion match\n the proposition as stated) | 'require' (same, plus a gate: a unanimous true/false verdict is",
120
+ to: " - 'off' (default, no extra requirement) | 'encourage' (the residents decide by implementation\n difficulty whether to formalize in Lean; once a Lean run passes, their unanimous vote becomes\n a FIDELITY review — do the Lean definitions/objects/conditions/assumptions/conclusion match\n the proposition as stated) | 'forced' (same, plus a gate: a unanimous true/false verdict is",
121
+ },
122
+ {
123
+ name: 'v4-formal-report-tool-hidden',
124
+ preset: 'vibe-math-v4', file: 'agent.cordis.yml', nth: 'all', expect: 2,
125
+ guarantee: 'the formal-report tool must be named in the persona (it is the coordinator-facing Lean view)',
126
+ from: ' - vibe_v4_formal_report — human-readable Lean formal-verification mirror (mode, Lean-passed',
127
+ to: ' - vibe_v4_formal_reportX — human-readable Lean formal-verification mirror (mode, Lean-passed',
128
+ },
129
+
130
+ // ── v5 ─────────────────────────────────────────────────────────────────────
131
+ {
132
+ name: 'v5-prefix-text-drift',
133
+ preset: 'vibe-math-v5', file: 'agent.cordis.yml', nth: 'second', expect: 2,
134
+ guarantee: 'prefix and text must stay identical apart from line 0 (old and new hosts must see the same surface)',
135
+ from: 'TRUST RULE: only Verified/',
136
+ to: 'TRUST RULE (amended): only Verified/',
137
+ },
138
+ {
139
+ name: 'v5-tool-renamed-in-plugin',
140
+ preset: 'vibe-math-v5', file: 'vibe-math-v5.js', nth: 'first', expect: 1,
141
+ guarantee: 'renaming a registered tool must be caught (the persona still advertises the old name)',
142
+ from: " registerTool('vibe_v5_meeting',",
143
+ to: " registerTool('vibe_v5_meeting2',",
144
+ },
145
+ {
146
+ name: 'v4-usage-advertises-phantom-subcommand',
147
+ preset: 'vibe-math-v4', file: 'vibe-math-v4.js', nth: 'first', expect: 1,
148
+ guarantee: 'the unknown-subcommand usage string must not advertise a subcommand that no branch implements',
149
+ from: "usage:'configure|start|resume|pause|abort|status|report|message <to|all> <content>|meeting|members|add|remove|set'",
150
+ to: "usage:'configure|start|resume|pause|abort|status|report|message <to|all> <content>|meeting|members|add|remove|scan|set'",
151
+ },
152
+ {
153
+ name: 'v4-hint-advertises-unimplemented-subcommand',
154
+ preset: 'vibe-math-v4', file: 'vibe-math-v4.js', nth: 'first', expect: 1,
155
+ guarantee: 'the typing hint must not advertise a subcommand the handler does not implement',
156
+ from: "message <to|all> <content>|meeting|members|add|remove|set]'",
157
+ to: "message <to|all> <content>|meeting|add|remove|set]'",
158
+ },
159
+ {
160
+ name: 'v5-persona-slash-list-drops-add-remove',
161
+ preset: 'vibe-math-v5', file: 'agent.cordis.yml', nth: 'all', expect: 2,
162
+ guarantee: "the persona's /v5 list must match the command hint (a human reading the persona must see every subcommand)",
163
+ from: 'meeting|hire|fire|add|remove|set)',
164
+ to: 'meeting|hire|fire|set)',
165
+ },
166
+ ]
167
+
168
+ let ok = 0
169
+ let bad = 0
170
+ console.log('')
171
+ console.log('-- persona surface sensitivity probes --')
172
+ console.log('(a probe passes when breaking the guarantee turns audit-persona-surface.test.mjs RED)')
173
+ console.log('')
174
+
175
+ for (const p of probes) {
176
+ const root = prepareRoot()
177
+ const target = join(root, p.preset, p.file)
178
+ const original = readFileSync(target, 'utf8')
179
+ // The repo's YAML files are CRLF; anchors are written with \n. Normalise both directions so
180
+ // a multi-line anchor matches regardless of the checkout's line endings.
181
+ const eol = original.includes('\r\n') ? '\r\n' : '\n'
182
+ const from = p.from.split('\n').join(eol)
183
+ const to = p.to.split('\n').join(eol)
184
+ p.from = from
185
+ p.to = to
186
+ const occurrences = original.split(p.from).length - 1
187
+ if (occurrences !== p.expect) {
188
+ console.error(` SETUP-FAIL - ${p.name}: anchor matched ${occurrences} times (need ${p.expect})`)
189
+ bad++
190
+ rmSync(root, { recursive: true, force: true })
191
+ continue
192
+ }
193
+ let mutated
194
+ if (p.nth === 'all') mutated = original.split(p.from).join(p.to)
195
+ else if (p.nth === 'first') mutated = original.replace(p.from, p.to)
196
+ else if (p.nth === 'second') {
197
+ const i = original.indexOf(p.from)
198
+ const j = original.indexOf(p.from, i + 1)
199
+ mutated = original.slice(0, j) + p.to + original.slice(j + p.from.length)
200
+ } else throw new Error('unknown nth ' + p.nth)
201
+
202
+ if (mutated === original) {
203
+ console.error(` SETUP-FAIL - ${p.name}: the mutation is a no-op`)
204
+ bad++
205
+ rmSync(root, { recursive: true, force: true })
206
+ continue
207
+ }
208
+ if (p.file.endsWith('.yml') && !yamlStillHasPersona(mutated)) {
209
+ console.error(` SETUP-FAIL - ${p.name}: the mutated persona no longer has its two literal blocks (unfair mutation)`)
210
+ bad++
211
+ rmSync(root, { recursive: true, force: true })
212
+ continue
213
+ }
214
+ writeFileSync(target, mutated, 'utf8')
215
+
216
+ if (p.file.endsWith('.js')) {
217
+ const chk = spawnSync(process.execPath, ['--check', target], { encoding: 'utf8' })
218
+ if (chk.status !== 0) {
219
+ console.error(` SETUP-FAIL - ${p.name}: the mutated copy has a syntax error:\n` + String(chk.stderr || '').split('\n').slice(0, 4).join('\n'))
220
+ bad++
221
+ rmSync(root, { recursive: true, force: true })
222
+ continue
223
+ }
224
+ }
225
+
226
+ const r = runSuite(root)
227
+ if (r.status === null) {
228
+ console.error(` SETUP-FAIL - ${p.name}: the suite could not be started (${String(r.error && r.error.message)})`)
229
+ bad++
230
+ rmSync(root, { recursive: true, force: true })
231
+ continue
232
+ }
233
+ if (r.status !== 0) {
234
+ const firstFail = (r.out.split('\n').find((l) => l.trim().startsWith('FAIL')) || '').trim().slice(0, 110)
235
+ ok++
236
+ console.log(` ok - ${p.name} [${p.preset}] => suite went RED as required [${p.guarantee}]`)
237
+ if (firstFail) console.log(` ${firstFail}`)
238
+ } else {
239
+ bad++
240
+ console.error(` BLIND SPOT - ${p.name} [${p.preset}] => suite stayed GREEN, so it does NOT detect: ${p.guarantee}`)
241
+ }
242
+ rmSync(root, { recursive: true, force: true })
243
+ }
244
+
245
+ console.log('')
246
+ console.log(`persona sensitivity: ${ok} probes detected the break, ${bad} problems`)
247
+ if (bad) process.exit(1)
248
+ console.log('ALL PERSONA PROBES RED AS REQUIRED')
249
+ process.exit(0)