dsh-vibe-math 2.7.3 → 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 -682
- package/README.md +683 -683
- package/docs/AUDIT-CHECKLIST.md +755 -744
- package/docs/COMPAT-AUDIT-ROUND2.md +351 -351
- 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 -194
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -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.8.0.en.md +65 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
- package/docs/release-playbook.md +103 -0
- package/docs/slv-playbook.md +117 -0
- package/docs/test-timing.md +158 -157
- package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
- package/installer.js +792 -792
- package/package.json +13 -3
- package/prompt-corpus-v2/formal-verify-v2.json +2 -2
- package/prompt-corpus-v2/formal-verify-v2.md +4 -0
- package/prompt-corpus-v3/formal-verify-v3.json +7 -7
- package/prompt-corpus-v3/formal-verify-v3.md +14 -0
- package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
- package/prompt-corpus-v5/prompt-corpus-v5.md +122 -0
- package/scripts/clean-temp.mjs +234 -0
- package/tests/audit-engine-faces.mjs +298 -0
- package/tests/audit-formal-sensitivity.mjs +345 -342
- package/tests/audit-installer-compat.test.mjs +256 -256
- package/tests/audit-math-computation-sensitivity.mjs +1 -1
- package/tests/audit-persona-sensitivity.mjs +323 -323
- package/tests/audit-persona-surface.test.mjs +431 -431
- package/tests/audit-prompt-duplication.mutants.mjs +3 -2
- package/tests/audit-prompt-invariants.mjs +589 -546
- package/tests/audit-registration.mjs +78 -78
- package/tests/audit-spec-traceability.mjs +193 -193
- package/tests/audit-status-report-fields.mjs +172 -172
- package/tests/audit-v5-integrity.mjs +795 -795
- package/tests/audit-v5-sensitivity.mjs +433 -432
- package/tests/e2e-v5-round2.test.mjs +2453 -2426
- package/tests/formal-verify-v2.test.mjs +1806 -1800
- package/tests/formal-verify-v3.test.mjs +1757 -1750
- package/tests/formal-verify-v4.test.mjs +2114 -2106
- package/tests/formal-verify-v5.test.mjs +774 -767
- package/tests/math-computation-discovery.mutants.mjs +3 -3
- package/tests/math-computation-shared.test.mjs +122 -5
- package/tests/math-computation-v2.test.mjs +14 -2
- package/tests/math-computation-v3.test.mjs +14 -2
- package/tests/math-computation-v4.test.mjs +739 -739
- package/tests/prompt-v5-integrity.test.mjs +1291 -1291
- package/tests/run-tests.mjs +393 -332
- package/tests/selfdrive-v5.mjs +560 -560
- package/tests/temp-hygiene.mutants.mjs +80 -0
- package/tests/v2-fix-probes.mutants.mjs +4 -0
- package/tests/v2-fix-probes.test.mjs +1034 -1033
- package/tests/v2v3-interrupt.mutants.mjs +111 -0
- package/tests/v3-fix-probes.mutants.mjs +99 -95
- package/tests/v3-fix-probes.test.mjs +891 -890
- package/tests/v4-final-paper.mutants.mjs +108 -0
- package/tests/v5-institute-fixes.mutants.mjs +218 -190
- package/vibe-math-v2/agent.cordis.yml +376 -376
- package/vibe-math-v2/math-computation.js +17 -15
- 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 +5078 -5011
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +486 -486
- package/vibe-math-v3/agent.cordis.yml +420 -420
- package/vibe-math-v3/math-computation.js +17 -15
- package/vibe-math-v3/preset.yml +2 -2
- package/vibe-math-v3/vibe-math-v3.js +6215 -6149
- 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 -438
- package/vibe-math-v4/math-computation.js +17 -15
- package/vibe-math-v4/preset.yml +2 -2
- package/vibe-math-v4/vibe-math-v4.js +4490 -4422
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1015
- package/vibe-math-v5/math-computation.js +17 -15
- package/vibe-math-v5/vibe-math-v5.js +93 -11
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1728
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -1,432 +1,433 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// V5 SENSITIVITY PROBES — prove the suites are not vacuous.
|
|
3
|
-
//
|
|
4
|
-
// Each probe copies vibe-math-v5.js, applies ONE targeted mutation that breaks a
|
|
5
|
-
// specific guarantee, and runs the referencing suite against the mutated copy. A probe
|
|
6
|
-
// PASSES when the suite goes RED (non-zero exit) — i.e. the assertions really do detect
|
|
7
|
-
// that break. A probe that stays green means the suite has a blind spot.
|
|
8
|
-
//
|
|
9
|
-
// The prompt-integrity block below exists because the 2026-09 field test found a bug
|
|
10
|
-
// that 123 tool-level assertions could not see: every member's brief named the WRONG
|
|
11
|
-
// member. Any guarantee about the TEXT a member reads must have a probe that proves
|
|
12
|
-
// prompt-v5-integrity.test.mjs detects its violation.
|
|
13
|
-
//
|
|
14
|
-
// Run: node tests/audit-v5-sensitivity.mjs
|
|
15
|
-
// ============================================================
|
|
16
|
-
import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'
|
|
17
|
-
import { tmpdir } from 'node:os'
|
|
18
|
-
import { join } from 'node:path'
|
|
19
|
-
import { spawnSync } from 'node:child_process'
|
|
20
|
-
import { fileURLToPath } from 'node:url'
|
|
21
|
-
|
|
22
|
-
const SRC = new URL('../vibe-math-v5/vibe-math-v5.js', import.meta.url)
|
|
23
|
-
// Keyed by the EXACT suite filename a probe names in its `ref`. A ternary chain here once
|
|
24
|
-
// silently routed every new probe to the wrong suite (which then stayed green because the
|
|
25
|
-
// mutation did not touch what it tests) — that reports a blind spot that does not exist.
|
|
26
|
-
const TESTS = {}
|
|
27
|
-
for (const f of ['selfdrive-v5.mjs', 'e2e-v5-round2.test.mjs', 'prompt-v5-integrity.test.mjs', 'formal-verify-v5.test.mjs']) {
|
|
28
|
-
TESTS[f] = fileURLToPath(new URL('./' + f, import.meta.url))
|
|
29
|
-
}
|
|
30
|
-
const original = readFileSync(SRC, 'utf8')
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'
|
|
42
|
-
' //
|
|
43
|
-
' //
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
' //
|
|
51
|
-
' //
|
|
52
|
-
' //
|
|
53
|
-
' //
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
' if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
// · `
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
let
|
|
388
|
-
|
|
389
|
-
console.log('
|
|
390
|
-
console.log('')
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
console.log('
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
1
|
+
// ============================================================
|
|
2
|
+
// V5 SENSITIVITY PROBES — prove the suites are not vacuous.
|
|
3
|
+
//
|
|
4
|
+
// Each probe copies vibe-math-v5.js, applies ONE targeted mutation that breaks a
|
|
5
|
+
// specific guarantee, and runs the referencing suite against the mutated copy. A probe
|
|
6
|
+
// PASSES when the suite goes RED (non-zero exit) — i.e. the assertions really do detect
|
|
7
|
+
// that break. A probe that stays green means the suite has a blind spot.
|
|
8
|
+
//
|
|
9
|
+
// The prompt-integrity block below exists because the 2026-09 field test found a bug
|
|
10
|
+
// that 123 tool-level assertions could not see: every member's brief named the WRONG
|
|
11
|
+
// member. Any guarantee about the TEXT a member reads must have a probe that proves
|
|
12
|
+
// prompt-v5-integrity.test.mjs detects its violation.
|
|
13
|
+
//
|
|
14
|
+
// Run: node tests/audit-v5-sensitivity.mjs
|
|
15
|
+
// ============================================================
|
|
16
|
+
import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'
|
|
17
|
+
import { tmpdir } from 'node:os'
|
|
18
|
+
import { join } from 'node:path'
|
|
19
|
+
import { spawnSync } from 'node:child_process'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
const SRC = new URL('../vibe-math-v5/vibe-math-v5.js', import.meta.url)
|
|
23
|
+
// Keyed by the EXACT suite filename a probe names in its `ref`. A ternary chain here once
|
|
24
|
+
// silently routed every new probe to the wrong suite (which then stayed green because the
|
|
25
|
+
// mutation did not touch what it tests) — that reports a blind spot that does not exist.
|
|
26
|
+
const TESTS = {}
|
|
27
|
+
for (const f of ['selfdrive-v5.mjs', 'e2e-v5-round2.test.mjs', 'prompt-v5-integrity.test.mjs', 'formal-verify-v5.test.mjs']) {
|
|
28
|
+
TESTS[f] = fileURLToPath(new URL('./' + f, import.meta.url))
|
|
29
|
+
}
|
|
30
|
+
const original = readFileSync(SRC, 'utf8').replace(/\r\n?/g, '\n') // task-7: anchors are LF; a CRLF checkout made them match 0 times
|
|
31
|
+
if (/\r/.test(original)) { console.error('SETUP-FAIL - the anchor source was not EOL-normalised (CRLF leaked into anchor matching)'); process.exit(1) }
|
|
32
|
+
const REPO = fileURLToPath(new URL('..', import.meta.url))
|
|
33
|
+
const dir = mkdtempSync(join(tmpdir(), 'v5-sens-'))
|
|
34
|
+
|
|
35
|
+
// `armNextVerify`'s two exclusion gates (a live MEETING and a live VERIFICATION), each written
|
|
36
|
+
// at the function head AND as a re-check inside the `while` loop with no `await` in between.
|
|
37
|
+
// `armGuards(true, true)` is the pristine source text; the probes switch ONE gate off in BOTH
|
|
38
|
+
// copies, which is the minimal edit that actually breaks the guarantee. Keeping the block in
|
|
39
|
+
// one place means the two mutations cannot drift apart from the source they mutate.
|
|
40
|
+
const armGuards = (meetingOn, verifyOn) => [
|
|
41
|
+
...(meetingOn ? [' if (meeting) return'] : []),
|
|
42
|
+
' // Only one begin may be in flight. Without this, two callers (a scheduling pass',
|
|
43
|
+
' // and a fresh proposal) could both pass the `currentVerify()` check before either',
|
|
44
|
+
' // has published its verdict record and would start the SAME object twice.',
|
|
45
|
+
' if (beginLock) return',
|
|
46
|
+
...(verifyOn ? [' if (currentVerify()) return'] : []),
|
|
47
|
+
' beginLock = true',
|
|
48
|
+
' try {',
|
|
49
|
+
' while (true) {',
|
|
50
|
+
' // NEVER remove a proposal we are not about to run: the entry leaves the durable',
|
|
51
|
+
' // queue only in the same step that starts it. The old code shifted it out and then',
|
|
52
|
+
' // returned because a meeting was live, so the proposal existed in neither the queue',
|
|
53
|
+
' // nor `verdicts` until some later pass happened to re-arm it (audit M2). Re-check',
|
|
54
|
+
' // the exclusion guards here too: `await putQueue` can let a meeting open.',
|
|
55
|
+
' const q = inst().queue.slice()',
|
|
56
|
+
' if (!q.length) return',
|
|
57
|
+
' if (' + (meetingOn && verifyOn ? 'meeting || currentVerify()' : meetingOn ? 'meeting' : 'currentVerify()') + ') return',
|
|
58
|
+
].join('\n')
|
|
59
|
+
|
|
60
|
+
// Each probe: { name, ref, guarantee, from, to }
|
|
61
|
+
// `from` must occur exactly once, so a mutation can never quietly hit the wrong site.
|
|
62
|
+
const probes = [
|
|
63
|
+
{
|
|
64
|
+
// The m floor is enforced by THREE cooperating checks (an early `bTrue + bFalse < m`
|
|
65
|
+
// return, the conflict check, and a final `bTrue >= m`), so a single weakened check is
|
|
66
|
+
// masked by the others and is a SEMANTICALLY INERT mutation — it must never be used as
|
|
67
|
+
// a probe (it would look like a "blind spot"). Forcing the quorum itself to 1 breaks
|
|
68
|
+
// the rule on every path at once, which is exactly the guarantee under test.
|
|
69
|
+
name: 'quorum-forced-to-one',
|
|
70
|
+
ref: 'selfdrive-v5.mjs',
|
|
71
|
+
guarantee: '④ only >= m boolean votes may verify (m must be min(quorumCap, |voters|))',
|
|
72
|
+
// The floor moved into `quorumMFrom(vc)` when the quorum became single-sourced (defect B of
|
|
73
|
+
// the 3rd self-test); the anchor follows the implementation, it is not relaxed.
|
|
74
|
+
from: " return Math.max(1, Math.min(cap, vc))",
|
|
75
|
+
to: " return 1",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'abstention-counts-as-true',
|
|
79
|
+
ref: 'selfdrive-v5.mjs',
|
|
80
|
+
guarantee: '⑤ an abstention must NOT count toward the quorum',
|
|
81
|
+
from: " else if (p === 0) bFalse += 1\n else abstain += 1",
|
|
82
|
+
to: " else if (p === 0) bFalse += 1\n else { abstain += 1; bTrue += 1 }",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
// `judgeVerdict` checks the m floor, then the conflict, then the floor again, and each
|
|
86
|
+
// guard masks the next — so deleting a guard is inert. The behaviour-changing break is
|
|
87
|
+
// letting a CONFLICT pass as a verdict, which is what this mutation does.
|
|
88
|
+
name: 'conflict-allowed',
|
|
89
|
+
ref: 'selfdrive-v5.mjs',
|
|
90
|
+
guarantee: '④ a conflicting 1 vs 0 must BLOCK the verdict',
|
|
91
|
+
from: " return Object.assign(base, { outcome: 'undecided', reason: 'conflicting assertions (true=' + bTrue + ', false=' + bFalse + ')' })",
|
|
92
|
+
to: " return Object.assign(base, { outcome: 'true', reason: 'conflicting assertions (true=' + bTrue + ', false=' + bFalse + ')' })",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'temp-worker-can-vote',
|
|
96
|
+
ref: 'selfdrive-v5.mjs',
|
|
97
|
+
guarantee: '⑦ a temp worker must NOT be able to vote',
|
|
98
|
+
from: " if (member.kind === 'temp') {\n // Temp workers have no vote",
|
|
99
|
+
to: " if (false) {\n // Temp workers have no vote",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'academician-gate-removed',
|
|
103
|
+
ref: 'selfdrive-v5.mjs',
|
|
104
|
+
guarantee: '⑬ only the academician may assign tasks',
|
|
105
|
+
from: " return { ok: false, code: 'V5_NOT_ACADEMICIAN', message: 'only the academician (or the office) can assign tasks' }",
|
|
106
|
+
to: " return { ok: false, code: 'NO_GATE', message: 'x' }",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'spawn-not-registered-inflight',
|
|
110
|
+
ref: 'selfdrive-v5.mjs',
|
|
111
|
+
guarantee: '⑯ the founding turn must be registered in-flight or its end is dropped',
|
|
112
|
+
from: " inflight.set(started.childId, shortId())",
|
|
113
|
+
to: "",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'group-chat-not-fanned-out',
|
|
117
|
+
ref: 'selfdrive-v5.mjs',
|
|
118
|
+
guarantee: '③ group chat must reach every other member',
|
|
119
|
+
from: " if (to === 'all' || to === '') targets = activeMembers().filter((m) => m.id !== from)",
|
|
120
|
+
to: " if (to === 'all' || to === '') targets = activeMembers().filter((m) => m.id !== from).slice(0, 1)",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'verify-round-advances-anyway',
|
|
124
|
+
ref: 'selfdrive-v5.mjs',
|
|
125
|
+
guarantee: '④ a round must not advance without every voter answering',
|
|
126
|
+
from: " const missing = need.filter((id) => !vs.votes[id])\n if (missing.length) {",
|
|
127
|
+
to: " const missing = need.filter((id) => !vs.votes[id])\n if (false) {",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'fire-does-not-release',
|
|
131
|
+
ref: 'selfdrive-v5.mjs',
|
|
132
|
+
guarantee: '⑨ firing must really release the resident child',
|
|
133
|
+
from: " if (typeof subagents.drainContinuableChildren === 'function') await subagents.drainContinuableChildren(rootAgent, [target.childId])",
|
|
134
|
+
to: " if (false) await subagents.drainContinuableChildren(rootAgent, [target.childId])",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'solve-vote-not-unanimous',
|
|
138
|
+
ref: 'selfdrive-v5.mjs',
|
|
139
|
+
guarantee: '⑥ the institute must not stop unless EVERY voter agrees',
|
|
140
|
+
// Anchor follows the HIGH-3 fix (the tally moved from an in-memory Map to durable state); the
|
|
141
|
+
// guarantee is unchanged — the unanimity test itself is what the mutation removes.
|
|
142
|
+
from: " const sv = solveVotesOf()\n if (!vs.every((id) => sv[id] === true)) return false",
|
|
143
|
+
to: " if (false) return false",
|
|
144
|
+
},
|
|
145
|
+
// ── probes added after the round-2 audit found these defects ──────────────
|
|
146
|
+
{
|
|
147
|
+
name: 'fallback-state-never-loaded',
|
|
148
|
+
ref: 'e2e-v5-round2.test.mjs',
|
|
149
|
+
guarantee: '⑰ the file fallback must LOAD persisted state before any read (restart safety)',
|
|
150
|
+
from: " if (backend.kind === 'file' && typeof backend.load === 'function') await backend.load()",
|
|
151
|
+
to: " if (false) await backend.load()",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'solve-vote-never-re-evaluated',
|
|
155
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
156
|
+
guarantee: '⑥ a solve vote landing OUTSIDE a meeting must still stop the institute',
|
|
157
|
+
// The anchor must include the CALL. An `if (false)` inserted before the comment would
|
|
158
|
+
// leave the real `await checkSolved()` below it untouched — an inert mutation that
|
|
159
|
+
// would masquerade as a blind spot.
|
|
160
|
+
from: " await putSolve({ member: memberId, value: val === true })\n // Evaluate the stop condition on EVERY solve vote, not only when a meeting\n // finalizes. A vote that lands after the meeting closed — a late reply, or an\n // ordinary round carrying vote_solved — would otherwise be recorded and never\n // read, leaving a unanimously-concluded institute running forever.\n await checkSolved()",
|
|
161
|
+
to: " await putSolve({ member: memberId, value: val === true })",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'proposal-only-kicks-scheduler',
|
|
165
|
+
ref: 'selfdrive-v5.mjs',
|
|
166
|
+
guarantee: '④ a proposal must actually START, not just ask the scheduler to try',
|
|
167
|
+
from: " await armNextVerify()\n if (!hasVerifyInFlight()) await scheduleNext()",
|
|
168
|
+
to: " await scheduleNext()",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'begin-not-exclusive',
|
|
172
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
173
|
+
guarantee: '⑱ a second proposal must QUEUE, never start a concurrent ballot',
|
|
174
|
+
from: armGuards(true, true),
|
|
175
|
+
to: armGuards(true, false),
|
|
176
|
+
},
|
|
177
|
+
// NOTE — the mutual-exclusion gates of `armNextVerify` are written TWICE: once at the
|
|
178
|
+
// function head and once as a re-check inside the `while (true)` loop, and there is NO
|
|
179
|
+
// `await` between them. The head copies are therefore redundant mirrors of the in-loop
|
|
180
|
+
// ones, and a probe that deletes only a head copy is SEMANTICALLY INERT — it can never go
|
|
181
|
+
// red, which would masquerade as a "blind spot" forever (this was the state of both of the
|
|
182
|
+
// ⑱/㉝ probes: the in-loop re-check kept the invariant alive and
|
|
183
|
+
// `prompt-v5-integrity.test.mjs` stayed green). The probes for ⑱ and ㉝ therefore remove
|
|
184
|
+
// BOTH copies of the ONE gate they test, which is the minimal edit that actually lets a
|
|
185
|
+
// second ballot start — and the suite's case 9b ("a second proposal QUEUES") and case 10b
|
|
186
|
+
// ("a verification proposed DURING a meeting must queue") do detect exactly that.
|
|
187
|
+
// NOTE — a probe for `continueMeetingRound`'s `if (finalizeLock) { armHeartbeat(); return }`
|
|
188
|
+
// re-arm was REMOVED, not because the re-arm is unnecessary but because the state it
|
|
189
|
+
// guards is UNREACHABLE, so no black-box probe can detect its removal:
|
|
190
|
+
// · `schedulePass` handles a live meeting BEFORE it looks at a verification, and
|
|
191
|
+
// · `startMeeting` parks any meeting while `hasVerifyInFlight()`,
|
|
192
|
+
// so a meeting and a running verify settlement can never overlap. The re-arm stays in
|
|
193
|
+
// the code as cheap insurance against a future ordering change; leaving a probe that can
|
|
194
|
+
// never go red would be a false "detection" and is worse than no probe.
|
|
195
|
+
{
|
|
196
|
+
name: 'meeting-never-finalized',
|
|
197
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
198
|
+
guarantee: '⑲ a meeting that collected every input must actually finalize and write its minutes',
|
|
199
|
+
from: " finalizeLock = 'meeting'\n try {\n await finalizeMeeting(meeting)",
|
|
200
|
+
to: " finalizeLock = 'meeting'\n try {\n meeting = null",
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// ── PROMPT / INTERACTION INTEGRITY PROBES ────────────────────────────────
|
|
204
|
+
// These are the probes that would have caught the 2026-09 field-test bug. Every one
|
|
205
|
+
// of them breaks something a member READS (identity, roster, framing, persona), and
|
|
206
|
+
// prompt-v5-integrity.test.mjs must go RED for each.
|
|
207
|
+
{
|
|
208
|
+
name: 'brief-names-the-last-woken-member',
|
|
209
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
210
|
+
guarantee: '⑳ a prompt\'s [状态] block must name the member it is sent to (the exact field bug: briefs named the previous member)',
|
|
211
|
+
from: " function stateBlock(member, roundNo) {\n return briefBlock(member, roundNo)\n }",
|
|
212
|
+
to: " function stateBlock(member, roundNo) {\n return briefBlock(memberById(currentMember) || activeMembers()[0] || member, roundNo)\n }",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'joiner-absent-from-own-roster',
|
|
216
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
217
|
+
guarantee: '⑳ a founding brief must show the roster INCLUDING its reader (roster committed before the prompt is built)',
|
|
218
|
+
from: " member.phase = 'active'\n member.childId = ''\n await putMember(member)",
|
|
219
|
+
to: " member.childId = ''\n await putMember(member)",
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'inbox-message-delivered-twice',
|
|
223
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
224
|
+
guarantee: '㉑ one prompt must not deliver the same message twice',
|
|
225
|
+
// G1 moved the ack AFTER the wake, so the duplicate can no longer be produced by the ack order:
|
|
226
|
+
// it now comes from prepending the inbox while `briefBlock` ALSO embeds the same pending mail.
|
|
227
|
+
// Suppressing the base block while a block IS prepended is what keeps exactly one inbox section.
|
|
228
|
+
from: " const pending = inboxSuppressed.has(member.id) ? [] : pendingFor(member.id)",
|
|
229
|
+
to: " const pending = pendingFor(member.id)",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: 'charter-rewritten-on-resume',
|
|
233
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
234
|
+
guarantee: '㉒ the charter is frozen at hire; a resume must not rewrite the induction snapshot',
|
|
235
|
+
from: " const persona = member.persona || memberPersona(member)",
|
|
236
|
+
to: " const persona = memberPersona(member)",
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'resume-framed-as-induction',
|
|
240
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
241
|
+
guarantee: '㉓ a rebuilt session must not be told it just joined the institute',
|
|
242
|
+
from: " const resume = mode === 'resume'",
|
|
243
|
+
to: " const resume = false",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'meeting-proposal-misattributed',
|
|
247
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
248
|
+
guarantee: '㉔ a relayed message must carry its TRUE sender, never the last-woken member',
|
|
249
|
+
from: " await say(callerId, { to: 'voters', kind: 'voters', text: '提议开会:「' + agenda + '」(' + kind + ')' })",
|
|
250
|
+
to: " await say(academicianId() || callerId, { to: 'voters', kind: 'voters', text: '提议开会:「' + agenda + '」(' + kind + ')' })",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'office-impersonates-a-member',
|
|
254
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
255
|
+
guarantee: '㉕ the office/host caller must resolve to the office, not to a guessed member',
|
|
256
|
+
// The FAITHFUL regression mutation: restore the old "answer with whoever this session
|
|
257
|
+
// woke last" fallback, which made the office's own assignments resolve to a random
|
|
258
|
+
// researcher and be refused as V5_NOT_ACADEMICIAN.
|
|
259
|
+
from: " try { if (rootOf(agent) === agent) return 'office' } catch (e) { /* fall through */ }",
|
|
260
|
+
to: " if (currentMember && memberById(currentMember)) return currentMember",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'office-assignment-framed-as-academician',
|
|
264
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
265
|
+
guarantee: '㉖ an assignment must be framed by its true origin (office vs academician)',
|
|
266
|
+
from: " if (m.kind === 'assign') return (m.from === 'office' ? '【所办分派】' : '【院士分派】') + m.text",
|
|
267
|
+
to: " if (m.kind === 'assign') return '【院士分派】' + m.text",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'nudge-mislabelled-as-assignment',
|
|
271
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
272
|
+
guarantee: '㉗ a nudge is supervision, not an assignment, and must say so',
|
|
273
|
+
from: " to, kind: 'nudge',",
|
|
274
|
+
to: " to, kind: 'assign',",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'framework-notice-sent-as-self-message',
|
|
278
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
279
|
+
guarantee: '㉘ framework feedback must actually reach the member (not be refused as a self-message)',
|
|
280
|
+
from: " return await say('framework', { to: memberId, kind: 'notice', text: String(text) })",
|
|
281
|
+
to: " return await say(memberId, { to: memberId, kind: 'notice', text: String(text) })",
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'failed-member-hidden',
|
|
285
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
286
|
+
guarantee: '㉙ a member that failed to provision must be visible in [未就位]',
|
|
287
|
+
from: " if (absent.length) b.push('[未就位] ' + absent.map((m) => m.id + '(' + m.phase + ')').join('、'))",
|
|
288
|
+
to: " if (false) b.push('[未就位] ' + absent.map((m) => m.id + '(' + m.phase + ')').join('、'))",
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'leaderless-charter-invents-a-leader',
|
|
292
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
293
|
+
guarantee: '㉚ with academician:false no charter may name a leader who does not exist',
|
|
294
|
+
from: " const a = academicianId()\n const L = [",
|
|
295
|
+
to: " const a = 'acad'\n const L = [",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'reply-spec-hides-the-objection-channel',
|
|
299
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
300
|
+
guarantee: '㉛ every field the framework honours must be documented in the reply spec',
|
|
301
|
+
from: " L.push(' \"reject_assign\": {\"task_id\":\"t-3\",\"why\":\"你对这项分派的异议理由\"}",
|
|
302
|
+
to: " if (false) L.push(' \"reject_assign\": {\"task_id\":\"t-3\",\"why\":\"你对这项分派的异议理由\"}",
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'task-owner-rewoken-unpaced',
|
|
306
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
307
|
+
guarantee: '㉜ a task owner must be pushed on a paced cadence, not in an unbounded tight loop',
|
|
308
|
+
from: " if ((now() - (lastActiveAt.get(m.id) || 0)) < idleMs) continue",
|
|
309
|
+
to: " if (false) continue",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'verify-preempts-a-live-meeting',
|
|
313
|
+
ref: 'prompt-v5-integrity.test.mjs',
|
|
314
|
+
guarantee: '㉝ a verification proposed during a meeting must QUEUE, never start concurrently (the two are mutually exclusive)',
|
|
315
|
+
// Removes BOTH `meeting` gates (see the note above `meeting-never-finalized`): deleting
|
|
316
|
+
// only the head copy is inert because the in-loop re-check runs before any `await`.
|
|
317
|
+
from: armGuards(true, true),
|
|
318
|
+
to: armGuards(false, true),
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
// ── LEAN FORMAL VERIFICATION PROBES (docs/formal-verification.md) ─────────
|
|
322
|
+
{
|
|
323
|
+
name: 'formal-off-is-not-a-no-op',
|
|
324
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
325
|
+
guarantee: '㉞ the default mode must be a TRUE no-op (no Lean text, no gate)',
|
|
326
|
+
from: " const formalOn = () => formalMode() !== 'off'",
|
|
327
|
+
to: " const formalOn = () => true",
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: 'unknown-formal-mode-upgrades',
|
|
331
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
332
|
+
guarantee: '㉟ an unknown mode must degrade to off, never to a STRONGER mode (a typo must not force formalization)',
|
|
333
|
+
from: " out.formalVerify = ['off', 'encourage', 'require'].indexOf(out.formalVerify) !== -1 ? out.formalVerify : 'off'",
|
|
334
|
+
to: " out.formalVerify = ['off', 'encourage', 'require'].indexOf(out.formalVerify) !== -1 ? out.formalVerify : 'require'",
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'fidelity-switch-removed',
|
|
338
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
339
|
+
guarantee: '㊱ a passing Lean run must switch the voting prompt to a FIDELITY review (the whole point of the feature)',
|
|
340
|
+
from: " if (rec.status === 'passed') {\n // The whole point of the feature: the review subject CHANGES.",
|
|
341
|
+
to: " if (false) {\n // The whole point of the feature: the review subject CHANGES.",
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: 'require-gate-removed',
|
|
345
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
346
|
+
guarantee: '㊲ require mode must withhold a true/false verdict until the object is Lean-passed or explicitly blocked',
|
|
347
|
+
from: " if (formalMode() === 'require' && !formalGateOk(rec)) {",
|
|
348
|
+
to: " if (false) {",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'blocked-note-not-required',
|
|
352
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
353
|
+
guarantee: '㊳ a "we judged it infeasible" record must carry a reason (the difficulty decision must be auditable, not silent)',
|
|
354
|
+
from: " if (!note) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: '阻塞记录必须写明原因(note)",
|
|
355
|
+
to: " if (false) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: '阻塞记录必须写明原因(note)",
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'proof-not-archived-under-verified',
|
|
359
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
360
|
+
guarantee: '㊴ a passing proof must be archived as that object\'s proof (Verified/Lean/<id>.lean)',
|
|
361
|
+
from: " if (passed) await writeTextRel('Verified/Lean/' + target + '.lean', body)",
|
|
362
|
+
to: " if (false) await writeTextRel('Verified/Lean/' + target + '.lean', body)",
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'reusable-lib-written-inside-the-institute',
|
|
366
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
367
|
+
guarantee: '㊵ reuse must be CROSS-PROJECT: reusable definitions go to the global Formal/Lib, not inside one institute',
|
|
368
|
+
from: " const okWrite = await writeTextAbs(instRootless(rel), body)",
|
|
369
|
+
to: " const okWrite = await writeTextRel(rel, body)",
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'lean-path-guard-naive',
|
|
373
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
374
|
+
guarantee: '㊶ the Lean path guard must normalise .. (a string prefix check lets a traversal through)',
|
|
375
|
+
from: " const abs = leanAbsPath(rel)\n if (abs === null) {",
|
|
376
|
+
to: " const abs = (rel.charAt(0) === '/' || /^[a-z]:/i.test(rel)) ? rel.replace(/\\\\/g, '/') : instRoot() + '/' + rel\n if (abs.indexOf(vibeRoot() + '/') !== 0) {",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: 'lean-tools-not-registered',
|
|
380
|
+
ref: 'formal-verify-v5.test.mjs',
|
|
381
|
+
guarantee: '㊷ the three Lean tools must be registered (agents can only formalize if the tools exist)',
|
|
382
|
+
from: " registerTool('vibe_v5_lean_run',",
|
|
383
|
+
to: " if (false) registerTool('vibe_v5_lean_run',",
|
|
384
|
+
},
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
let probesPassed = 0
|
|
388
|
+
let probesFailed = 0
|
|
389
|
+
console.log('-- V5 sensitivity probes --')
|
|
390
|
+
console.log('(a probe passes when breaking the guarantee turns the suite RED)')
|
|
391
|
+
console.log('')
|
|
392
|
+
|
|
393
|
+
for (const p of probes) {
|
|
394
|
+
const occurrences = original.split(p.from).length - 1
|
|
395
|
+
if (occurrences !== 1) {
|
|
396
|
+
console.error(' SETUP-FAIL - ' + p.name + ': anchor matched ' + occurrences + ' times (need exactly 1)')
|
|
397
|
+
probesFailed++
|
|
398
|
+
continue
|
|
399
|
+
}
|
|
400
|
+
const mutated = original.replace(p.from, p.to)
|
|
401
|
+
const file = join(dir, p.name + '.js')
|
|
402
|
+
writeFileSync(file, mutated, 'utf8')
|
|
403
|
+
const testPath = TESTS[p.ref]
|
|
404
|
+
if (testPath === undefined) {
|
|
405
|
+
console.error(' SETUP-FAIL - ' + p.name + ': ref "' + p.ref + '" is not a known suite')
|
|
406
|
+
probesFailed++
|
|
407
|
+
continue
|
|
408
|
+
}
|
|
409
|
+
const r = spawnSync(process.execPath, [testPath], {
|
|
410
|
+
// V5_CORPUS_DIR: a MUTATED run must never rewrite the shipped `prompt-corpus-v5/`. The
|
|
411
|
+
// corpus is the mandatory human-review artifact (AUDIT-CHECKLIST §0.3/§2.4); letting a
|
|
412
|
+
// deliberately broken copy dump its prompts there would leave the working tree holding
|
|
413
|
+
// mutant text until someone re-ran the healthy suite.
|
|
414
|
+
env: Object.assign({}, process.env, { V5_PLUGIN: file, V5_CORPUS_DIR: join(dir, 'corpus') }),
|
|
415
|
+
encoding: 'utf8',
|
|
416
|
+
cwd: REPO,
|
|
417
|
+
})
|
|
418
|
+
const red = r.status !== 0
|
|
419
|
+
if (red) {
|
|
420
|
+
probesPassed++
|
|
421
|
+
console.log(' ok - ' + p.name + ' [' + p.ref + '] => suite went RED as required [' + p.guarantee + ']')
|
|
422
|
+
} else {
|
|
423
|
+
probesFailed++
|
|
424
|
+
console.error(' BLIND SPOT - ' + p.name + ' [' + p.ref + '] => suite stayed GREEN, so it does NOT detect: ' + p.guarantee)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
rmSync(dir, { recursive: true, force: true })
|
|
429
|
+
console.log('')
|
|
430
|
+
console.log('sensitivity: ' + probesPassed + ' probes detected the break, ' + probesFailed + ' blind spots')
|
|
431
|
+
if (probesFailed) process.exit(1)
|
|
432
|
+
console.log('ALL PROBES RED AS REQUIRED')
|
|
433
|
+
process.exit(0)
|