dsh-vibe-math 2.7.1 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.en.md +682 -690
  2. package/README.md +19 -27
  3. package/cordis.patch.yml +112 -16
  4. package/docs/AUDIT-CHECKLIST.md +744 -518
  5. package/docs/COMPAT-AUDIT-ROUND2.md +35 -9
  6. package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
  7. package/docs/final-paper.md +172 -172
  8. package/docs/formal-verification.md +588 -588
  9. package/docs/math-computation.md +194 -163
  10. package/docs/parameter-schema.md +37 -0
  11. package/docs/release-notes/README.en.md +49 -0
  12. package/docs/release-notes/README.md +48 -0
  13. package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +72 -0
  14. package/docs/release-notes/RELEASE-NOTES-2.7.2.md +72 -0
  15. package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
  16. package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
  17. package/docs/release-notes/TEMPLATE.md +126 -0
  18. package/docs/status-report-fields.md +167 -0
  19. package/docs/test-timing.md +157 -154
  20. package/installer.js +27 -17
  21. package/package.json +55 -3
  22. package/prompt-corpus-persona/persona-corpus.json +9 -9
  23. package/prompt-corpus-persona/persona-corpus.md +225 -33
  24. package/prompt-corpus-v2/formal-verify-v2.json +305 -113
  25. package/prompt-corpus-v2/formal-verify-v2.md +2399 -279
  26. package/prompt-corpus-v3/formal-verify-v3.json +134 -110
  27. package/prompt-corpus-v3/formal-verify-v3.md +409 -141
  28. package/prompt-corpus-v4/formal-verify-v4.json +18 -18
  29. package/prompt-corpus-v4/formal-verify-v4.md +49 -44
  30. package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
  31. package/prompt-corpus-v5/prompt-corpus-v5.md +513 -259
  32. package/scripts/update-doc-counts.mjs +71 -0
  33. package/tests/audit-artifact-docs.mjs +72 -0
  34. package/tests/audit-artifact-docs.mutants.mjs +58 -0
  35. package/tests/audit-installer-assertions.mutants.mjs +63 -0
  36. package/tests/audit-installer-compat.test.mjs +52 -4
  37. package/tests/audit-installer-policy.test.mjs +41 -8
  38. package/tests/audit-math-computation-contract.mjs +107 -9
  39. package/tests/audit-math-computation-contract.mutants.mjs +106 -0
  40. package/tests/audit-math-computation-parity.mjs +164 -3
  41. package/tests/audit-math-computation-sensitivity.mjs +6 -4
  42. package/tests/audit-package-membership.mjs +57 -0
  43. package/tests/audit-package-membership.mutants.mjs +28 -0
  44. package/tests/audit-participant-set-parity.mjs +160 -0
  45. package/tests/audit-path-discipline.mjs +83 -0
  46. package/tests/audit-path-discipline.mutants.mjs +51 -0
  47. package/tests/audit-persona-sensitivity.mjs +14 -0
  48. package/tests/audit-persona-surface.mutants.mjs +69 -0
  49. package/tests/audit-persona-surface.test.mjs +63 -22
  50. package/tests/audit-prompt-duplication.mjs +143 -0
  51. package/tests/audit-prompt-duplication.mutants.mjs +50 -0
  52. package/tests/audit-prompt-invariants.mjs +43 -5
  53. package/tests/audit-readme-bilingual.test.mjs +152 -0
  54. package/tests/audit-readme-counts.mjs +95 -0
  55. package/tests/audit-readme-counts.mutants.mjs +52 -0
  56. package/tests/audit-registration.mjs +78 -0
  57. package/tests/audit-status-report-fields.mjs +172 -0
  58. package/tests/audit-status-report-fields.mutants.mjs +64 -0
  59. package/tests/audit-v3-registration-parity.mjs +156 -0
  60. package/tests/audit-v5-integrity.mjs +300 -13
  61. package/tests/audit-v5-lean-abstention.mjs +392 -0
  62. package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
  63. package/tests/audit-v5-prompt-duplication.mjs +88 -0
  64. package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
  65. package/tests/audit-v5-sensitivity.mjs +15 -8
  66. package/tests/e2e-identity-a6.mutants.mjs +65 -0
  67. package/tests/e2e-v5-round2.test.mjs +566 -12
  68. package/tests/formal-verify-v2.test.mjs +91 -5
  69. package/tests/formal-verify-v3.mutants.mjs +117 -0
  70. package/tests/formal-verify-v3.test.mjs +85 -6
  71. package/tests/formal-verify-v4.mutants.mjs +152 -0
  72. package/tests/formal-verify-v4.test.mjs +628 -9
  73. package/tests/formal-verify-v5.test.mjs +68 -9
  74. package/tests/helpers/doc-counts-format.mjs +41 -0
  75. package/tests/helpers/math-computation-fake-seam.mjs +4 -0
  76. package/tests/math-computation-a1.mutants.mjs +72 -0
  77. package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
  78. package/tests/math-computation-discovery.mutants.mjs +76 -0
  79. package/tests/math-computation-shared.test.mjs +396 -2
  80. package/tests/math-computation-v2.test.mjs +767 -0
  81. package/tests/math-computation-v3.test.mjs +759 -0
  82. package/tests/math-computation-v4.mutants.mjs +133 -0
  83. package/tests/math-computation-v4.test.mjs +739 -0
  84. package/tests/prompt-v5-integrity.test.mjs +13 -2
  85. package/tests/run-tests.mjs +139 -9
  86. package/tests/run-tests.mutants.mjs +70 -0
  87. package/tests/selfdrive-v5.mjs +53 -1
  88. package/tests/selfdrive-v5.mutants.mjs +132 -0
  89. package/tests/v2-fix-probes.mutants.mjs +100 -0
  90. package/tests/v2-fix-probes.test.mjs +1039 -0
  91. package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
  92. package/tests/v3-fix-probes.mutants.mjs +95 -0
  93. package/tests/v3-fix-probes.test.mjs +891 -0
  94. package/tests/v5-institute-fixes.mutants.mjs +190 -0
  95. package/vibe-math-v2/agent.cordis.yml +62 -6
  96. package/vibe-math-v2/math-computation.js +229 -29
  97. package/vibe-math-v2/math-engines.js +12 -12
  98. package/vibe-math-v2/vibe-math-v2.js +458 -119
  99. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +5 -1
  100. package/vibe-math-v3/agent.cordis.yml +72 -6
  101. package/vibe-math-v3/math-computation.js +229 -29
  102. package/vibe-math-v3/math-engines.js +12 -12
  103. package/vibe-math-v3/vibe-math-v3.js +482 -139
  104. package/vibe-math-v4/agent.cordis.yml +44 -8
  105. package/vibe-math-v4/math-computation.js +229 -29
  106. package/vibe-math-v4/math-engines.js +12 -12
  107. package/vibe-math-v4/vibe-math-v4.js +245 -52
  108. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +2 -0
  109. package/vibe-math-v5/agent.cordis.yml +46 -12
  110. package/vibe-math-v5/math-computation.js +229 -29
  111. package/vibe-math-v5/math-engines.js +12 -12
  112. package/vibe-math-v5/vibe-math-v5.js +1307 -243
  113. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1655
  114. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
@@ -15,7 +15,8 @@
15
15
  // Usage: node tests/math-computation-shared.test.mjs
16
16
  // ============================================================================================
17
17
  import { makeFakeHost } from './helpers/math-computation-fake-seam.mjs'
18
- import { pathToFileURL } from 'node:url'
18
+ import { pathToFileURL, fileURLToPath } from 'node:url'
19
+ import { readFileSync } from 'node:fs'
19
20
 
20
21
  // The sensitivity probe (tests/audit-math-computation-sensitivity.mjs) points this at a MUTATED copy
21
22
  // of the module pair; the default is the shipped copy in vibe-math-v2/.
@@ -24,6 +25,97 @@ const MODULE = process.env.MATH_COMPUTATION_MODULE
24
25
  : new URL('../vibe-math-v2/math-computation.js', import.meta.url).href
25
26
  const M = await import(MODULE)
26
27
 
28
+ // §30 predicates — used by the §30 assertions AND by `--self-probe` case 3, so the shipped proof and the
29
+ // audit cannot drift ("same predicate, broken input"). Unit: descriptor entries carrying the reason.
30
+ const carriesVerifyProvenance = (v) => !!v && v.verify === true && typeof v.verifyReason === 'string' && v.verifyReason.length > 0
31
+ const hasNoVerifySlot = (v) => !!v && !('verify' in v) && !('verifyReason' in v)
32
+
33
+ // ── --self-probe (shipped): prove the §32 evidence assertion can REDDEN, from the tarball alone.
34
+ // It copies the module, removes ONE `fail()` whitelist line, and requires the named §32 assertion
35
+ // to fail in the child. Usage: node tests/math-computation-shared.test.mjs --self-probe
36
+ if (process.argv.includes('--self-probe')) {
37
+ const { mkdtempSync, copyFileSync, readFileSync: rf, writeFileSync: wf, rmSync } = await import('node:fs')
38
+ const { tmpdir } = await import('node:os')
39
+ const { join, dirname } = await import('node:path')
40
+ const { spawnSync } = await import('node:child_process')
41
+ const modPath = process.env.MATH_COMPUTATION_MODULE || fileURLToPath(new URL('../vibe-math-v2/math-computation.js', import.meta.url))
42
+ const dir = mkdtempSync(join(tmpdir(), 'mc-shared-selfprobe-'))
43
+ const enginesSrc = join(dirname(modPath), 'math-engines.js')
44
+ try { copyFileSync(enginesSrc, join(dir, 'math-engines.js')) } catch (e) { /* engines may be resolved elsewhere */ }
45
+ const src = rf(modPath, 'utf8')
46
+ const whitelist = ' if (extra && Array.isArray(extra.absent)) out.absent = extra.absent\n'
47
+ if (src.indexOf(whitelist) === -1) {
48
+ console.log('SELF-PROBE FAIL: the fail() whitelist line for absent[] was not found (the probe cannot mutate what it cannot see)')
49
+ rmSync(dir, { recursive: true, force: true })
50
+ process.exit(1)
51
+ }
52
+ wf(join(dir, 'math-computation.js'), src.replace(whitelist, ''))
53
+ const child = spawnSync(process.execPath, [fileURLToPath(import.meta.url)], { encoding: 'utf8', maxBuffer: 1 << 26, env: Object.assign({}, process.env, { MATH_COMPUTATION_MODULE: join(dir, 'math-computation.js') }) })
54
+ const out = String(child.stdout || '') + String(child.stderr || '')
55
+ const failLines = out.split('\n').filter((l) => /^\s*FAIL\b/.test(l))
56
+ const named = failLines.some((l) => l.indexOf('S2 absence evidence: the failure carries an absent[] list') !== -1)
57
+ const sum = (out.split('\n').filter((l) => /passed, \d+ failed/.test(l)).slice(-1)[0] || '').trim()
58
+ console.log((child.status !== 0 && named ? 'SELF-PROBE PASS' : 'SELF-PROBE FAIL') + ': removing ONE fail() whitelist line reddens the named §32 assertion (' + sum + ')')
59
+ if (!named) for (const l of failLines.slice(0, 4)) console.log(' child FAIL line: ' + l.trim().slice(0, 140))
60
+ rmSync(dir, { recursive: true, force: true })
61
+ // case 2 (S4/S5): remove the single `out.installedSoFar = …` assignment and require the NAMED §33
62
+ // assertion in the child's FAIL line (a label found anywhere in the output is NOT a red).
63
+ const s45line = ' out.installedSoFar = results.filter((x) => x.exit === 0).map((x) => x.argv)\n'
64
+ const dir2 = mkdtempSync(join(tmpdir(), 'mc-shared-selfprobe2-'))
65
+ const src2 = rf(modPath, 'utf8')
66
+ let ok2 = false
67
+ if (src2.indexOf(s45line) === -1) {
68
+ console.log('SELF-PROBE FAIL: the installedSoFar assignment was not found (cannot mutate what it cannot see)')
69
+ } else {
70
+ try { copyFileSync(join(dirname(modPath), 'math-engines.js'), join(dir2, 'math-engines.js')) } catch (e) { /* optional */ }
71
+ wf(join(dir2, 'math-computation.js'), src2.replace(s45line, ''))
72
+ const child2 = spawnSync(process.execPath, [fileURLToPath(import.meta.url)], { encoding: 'utf8', maxBuffer: 1 << 26, env: Object.assign({}, process.env, { MATH_COMPUTATION_MODULE: join(dir2, 'math-computation.js') }) })
73
+ const out2 = String(child2.stdout || '') + String(child2.stderr || '')
74
+ const failLines2 = out2.split('\n').filter((l) => /^\s*FAIL\b/.test(l))
75
+ ok2 = child2.status !== 0 && failLines2.some((l) => l.indexOf('reports what already succeeded (installedSoFar)') !== -1)
76
+ const sum2 = (out2.split('\n').filter((l) => /passed, \d+ failed/.test(l)).slice(-1)[0] || '').trim()
77
+ console.log((ok2 ? 'SELF-PROBE PASS' : 'SELF-PROBE FAIL') + ': removing ONE installedSoFar assignment reddens the named §33 assertion (' + sum2 + ')')
78
+ if (!ok2) for (const l of failLines2.slice(0, 4)) console.log(' child FAIL line: ' + l.trim().slice(0, 140))
79
+ }
80
+ rmSync(dir2, { recursive: true, force: true })
81
+ // case 3 (§30, verify provenance): the SAME predicates the §30 assertions use, fed a broken helper.
82
+ // The real `verifyFields(engine)` helper is extracted from the module under test and run against a stub
83
+ // descriptor table; dropping its single reason line (or the no-empty-slot guard) must flip its predicate.
84
+ const src3 = rf(modPath, 'utf8')
85
+ const hStart = src3.indexOf('function verifyFields(engine) {')
86
+ let helper = ''
87
+ if (hStart !== -1) {
88
+ let d = 0, end = hStart
89
+ for (let i = hStart; i < src3.length; i++) { if (src3[i] === '{') d++; else if (src3[i] === '}') { d--; if (d === 0) { end = i + 1; break } } }
90
+ helper = src3.slice(hStart, end)
91
+ }
92
+ const REASON_LINE = " if (typeof d.verifyReason === 'string' && d.verifyReason) out.verifyReason = d.verifyReason\n"
93
+ const GUARD_LINE = ' if (!d.verify) return {}\n'
94
+ let ok3 = false
95
+ if (helper.indexOf(REASON_LINE) === -1 || helper.indexOf(GUARD_LINE) === -1) {
96
+ console.log('SELF-PROBE FAIL: the §30 helper anchors were not found (the probe cannot mutate what it cannot see)')
97
+ } else {
98
+ const stub = { maple: { verify: true, verifyReason: 'CLI spelling varies by Maple version - confirm on a licensed machine' }, python: { license: 'free' } }
99
+ const make = (s) => new Function('MATH_ENGINES', s + '\nreturn verifyFields;')(stub)
100
+ const good = make(helper)
101
+ const broken = make(helper.replace(REASON_LINE, ''))
102
+ const brokenGuard = make(helper.replace(GUARD_LINE, ''))
103
+ const cases3 = [
104
+ ['§30 probe carries WHY (shared predicate carriesVerifyProvenance)', carriesVerifyProvenance(good('maple')), carriesVerifyProvenance(broken('maple'))],
105
+ ['§30 no empty slot without a declared reason (hasNoVerifySlot)', hasNoVerifySlot(good('python')), hasNoVerifySlot(brokenGuard('python'))],
106
+ ]
107
+ let bad3 = 0
108
+ for (const [name, greenNow, redWhenBroken] of cases3) {
109
+ const okc = greenNow === true && redWhenBroken === false
110
+ if (!okc) bad3++
111
+ console.log((okc ? ' ok ' : ' FAIL ') + name + ' :: green-now=' + greenNow + ' broken-goes-red=' + !redWhenBroken)
112
+ }
113
+ ok3 = bad3 === 0
114
+ console.log(' --- §30 verify-provenance predicates: ' + (cases3.length - bad3) + '/' + cases3.length + ' as required')
115
+ }
116
+ process.exit(child.status !== 0 && named && ok2 && ok3 ? 0 : 1)
117
+ }
118
+
27
119
  let passed = 0, failed = 0
28
120
  const failures = []
29
121
  function ok(cond, label, detail) {
@@ -116,7 +208,11 @@ console.log('-- math_computation shared contract --')
116
208
  await withHost({ installed: [] }, async (state) => {
117
209
  const r = await state.call({ op: 'probe' })
118
210
  ok(r.ok === false && r.code === 'MATH_ENGINE_NOT_FOUND', 'no engine -> ENGINE_NOT_FOUND')
119
- ok(r.next && r.next.kind === 'user-install' && r.next.command, 'ENGINE_NOT_FOUND carries the per-OS user-install guide')
211
+ // round-9: the guide is per-OS and always carries the SUGGESTED command; `command` itself is only
212
+ // populated when that OS's package manager is actually resolvable on this machine.
213
+ ok(r.next && r.next.kind === 'user-install' && r.next.perOs && r.next.perOs.windows && r.next.perOs.linux, 'ENGINE_NOT_FOUND carries the per-OS user-install guide')
214
+ ok(r.next.suggestedCommand === r.next.perOs[r.next.platform], 'the guide keeps the suggested per-OS command alongside the executable one')
215
+ ok(r.next.packageManagerAvailable === false ? r.next.command === '' : typeof r.next.command === 'string', 'command ⇔ package-manager availability (never a command that cannot run)')
120
216
  })
121
217
  await withHost({ packages: { numpy: 'present', sympy: 'missing' } }, async (state) => {
122
218
  const r = await state.call({ op: 'probe', packages: ['numpy', 'sympy'] })
@@ -800,7 +896,305 @@ console.log('-- math_computation shared contract --')
800
896
  ok(!(r.warnings || []).some((w) => w.code === 'PACKAGE_PRECHECK_UNKNOWN'), 'the precheck RAN (no PACKAGE_PRECHECK_UNKNOWN on a fresh root)')
801
897
  }
802
898
 
899
+ // ── 22. round-8 (P0/D3): member-facing artifact paths must be OPENABLE by members
900
+ // The receipt's scriptPath is PROJECT-ROOT relative, but members' file tools resolve against the
901
+ // SESSION CWD. The receipt/return therefore carry the absolute `scriptAbs` and the `cwd` to join with.
902
+ {
903
+ const h = makeFakeHost({ installed: ['python3'] })
904
+ M.registerMathComputation(h.host)
905
+ const r = await h.call({ op: 'run', engine: 'python', mode: 'code', code: 'print(1)\n' })
906
+ ok(r.ok === true, 'a run succeeds (P0 context)')
907
+ ok(typeof r.scriptAbs === 'string' && r.scriptAbs.length > 0, '★ the return carries an ABSOLUTE scriptAbs (members can open it directly)')
908
+ ok(typeof r.cwd === 'string' && r.cwd.length > 0, '★ the return carries cwd (the project root to join with)')
909
+ ok(String(r.scriptAbs).replace(/\\/g, '/').endsWith(String(r.scriptPath).replace(/\\/g, '/')), '★ scriptAbs ends with the receipt-relative scriptPath (join consistency)')
910
+ ok(!!h.files && h.files.has(String(r.scriptPath).replace(/\\/g, '/')), '★ the archived script really exists at scriptPath (the path handed to members is real)')
911
+ const rj = JSON.parse(h.files.get(String(r.receipt.json).replace(/\\/g, '/')))
912
+ ok(rj.scriptAbs === r.scriptAbs && rj.cwd === r.cwd, 'the RECEIPT carries scriptAbs + cwd too (a later reader can reconstruct the location)')
913
+ ok(String(M.MATH_ARCHIVE_WORKFLOW_LINE).indexOf('receipt.scriptAbs') !== -1 && String(M.MATH_ARCHIVE_WORKFLOW_LINE_EN).indexOf('receipt.scriptAbs') !== -1, 'the injected archive rule tells members HOW to open the script (zh+en)')
914
+ ok(/会话 cwd/.test(String(M.MATH_ARCHIVE_WORKFLOW_LINE)) && /SESSION CWD/.test(String(M.MATH_ARCHIVE_WORKFLOW_LINE_EN)), 'the rule states the session-cwd resolution fact (zh+en)')
915
+ }
916
+
917
+ // ── 23. round-9 (real-engine): an engine INSTALLED but not on PATH must still be discovered
918
+ // R 4.6.1 went to `C:\Program Files\R\R-4.6.1\bin` without touching PATH, so `probe` used to say R
919
+ // was missing even though it was installed.
920
+ {
921
+ // round-B (F4): the candidate roots are INJECTED through the host seam, so this fixture cannot depend
922
+ // on the ambient machine's ProgramFiles/LOCALAPPDATA (which made it a false red on other layouts).
923
+ const RROOT = 'X:/installs/R'
924
+ const absTree = {
925
+ [RROOT]: [{ name: 'R-4.6.1', type: 'directory' }],
926
+ [RROOT + '/R-4.6.1/bin']: [{ name: 'Rscript.exe', type: 'file' }, { name: 'R.exe', type: 'file' }],
927
+ }
928
+ const h = makeFakeHost({ installed: [], absTree: absTree, runtimeRoots: [], installRoots: { r: [RROOT] } })
929
+ M.registerMathComputation(h.host)
930
+ const p = await h.call({ op: 'probe', engine: 'r' })
931
+ ok(p.ok === true && p.engine === 'r', '★ a Rscript that exists ONLY in the default install dir is discovered (not on PATH)')
932
+ ok(!!p.engineInfo && /installs\/R\/R-4\.6\.1\/bin\/Rscript/.test(String(p.engineInfo.path)), 'the discovered path is the versioned install dir (' + String(p.engineInfo && p.engineInfo.path).slice(0, 60) + ')')
933
+ }
934
+ // ── 23b. round-B (F3): the SAME walker covers Octave and Julia default dirs (both layout shapes)
935
+ {
936
+ const OROOT = 'X:/installs/GNU Octave'
937
+ const JROOT = 'X:/home/.juliaup/bin'
938
+ const absTree = {
939
+ [OROOT]: [{ name: 'Octave-9.2.0', type: 'directory' }],
940
+ [OROOT + '/Octave-9.2.0']: [{ name: 'mingw64', type: 'directory' }],
941
+ [OROOT + '/Octave-9.2.0/mingw64/bin']: [{ name: 'octave-cli.exe', type: 'file' }],
942
+ [JROOT]: [{ name: 'julia.exe', type: 'file' }],
943
+ }
944
+ const h = makeFakeHost({ installed: [], absTree: absTree, runtimeRoots: [], installRoots: { octave: [OROOT], julia: [JROOT] } })
945
+ M.registerMathComputation(h.host)
946
+ const po = await h.call({ op: 'probe', engine: 'octave' })
947
+ ok(po.ok === true && /Octave-9\.2\.0\/mingw64\/bin\/octave-cli/.test(String(po.engineInfo && po.engineInfo.path)), '★ an Octave installed in its default dir (no PATH) is discovered (versioned + mingw64/bin layout)')
948
+ const pj = await h.call({ op: 'probe', engine: 'julia' })
949
+ ok(pj.ok === true && /\.juliaup\/bin\/julia/.test(String(pj.engineInfo && pj.engineInfo.path)), '★ a juliaup-managed Julia (flat bin dir, no PATH) is discovered by the same walker')
950
+ const pr = await h.call({ op: 'probe' })
951
+ ok((pr.absent || []).some((a) => a.engine === 'r' && a.why === 'not-found-on-this-machine'), 'an engine with no injected roots and no PATH entry is still honestly listed as absent')
952
+ // F3: the per-engine default roots are part of the contract (the release notes claim "installed but not
953
+ // on PATH is found"), so their coverage is asserted directly - not only via an injected fixture.
954
+ for (const e of ['r', 'python', 'octave', 'julia', 'matlab']) {
955
+ ok(typeof M.mathInstallRoots === 'function' && M.mathInstallRoots(e, {}, true).length > 0 && M.mathInstallRoots(e, {}, false).length > 0,
956
+ '★ every engine with a well-defined default install dir declares per-OS roots: ' + e)
957
+ }
958
+ }
959
+ // ── 24. round-9 (real-engine): never suggest an install command that cannot run on this machine
960
+ {
961
+ const h = makeFakeHost({ installed: [] }) // `winget` is NOT resolvable here
962
+ M.registerMathComputation(h.host)
963
+ const p = await h.call({ op: 'probe', engine: 'octave' })
964
+ ok(p.ok === false && p.code === 'MATH_ENGINE_NOT_FOUND', 'a missing engine still fails with ENGINE_NOT_FOUND')
965
+ ok(!!p.next && p.next.kind === 'user-install', 'the guidance is still user-install shaped')
966
+ ok(String(p.next.command) === '', '★ no runnable-looking command when its package manager is absent (command is empty)')
967
+ ok(!/winget|brew|apt|choco|scoop/i.test(String(p.next.command) + ' ' + String(p.message)), '★ no winget/brew/apt suggestion anywhere when that manager is absent')
968
+ ok(p.next.packageManager === 'winget' && p.next.packageManagerAvailable === false, 'the response SAYS which manager was assumed and that it is unavailable')
969
+ ok(typeof p.next.note === 'string' && p.next.note.length > 0, 'an explicit note explains that the suggested command cannot run')
970
+ const h2 = makeFakeHost({ installed: [], resolveMap: { winget: 'C:/fake/winget.exe' } })
971
+ M.registerMathComputation(h2.host)
972
+ const p2 = await h2.call({ op: 'probe', engine: 'octave' })
973
+ ok(/winget/i.test(String(p2.next.command)) && p2.next.packageManagerAvailable === true, 'when the manager IS present the command is offered as before')
974
+ }
975
+
976
+ // ── 25. round-9 F3/F5/F7: probe completeness, uniform failure evidence, explicit constraint policy
977
+ {
978
+ // F3: a partial availability line must NAME the configured-but-absent engines.
979
+ const h = makeFakeHost({ installed: ['python3'] })
980
+ M.registerMathComputation(h.host)
981
+ const p = await h.call({ op: 'probe', engine: 'python' })
982
+ ok(p.ok === true && Array.isArray(p.absent) && p.absent.length > 0, '★ probe names the configured-but-absent engines instead of being silently partial')
983
+ ok(p.absent.some((a) => a.engine === 'r' && a.why === 'not-found-on-this-machine'), 'the absent list carries a reason (key `why`) per engine')
984
+ ok(p.absent.some((a) => a.engine === 'cli' && /caller-supplied/.test(a.why)), 'cli is explained as needing a caller-supplied command')
985
+ ok(/未发现/.test(String(p.message)), 'the message states which configured engines were not found')
986
+ // F5: MATH_TIMEOUT carries the SAME evidence fields a non-zero exit does.
987
+ const h2 = makeFakeHost({ installed: ['python3'], hang: true })
988
+ M.registerMathComputation(h2.host)
989
+ const t = await h2.call({ op: 'run', engine: 'python', mode: 'code', code: 'while True: pass\n', timeoutMs: 1000 })
990
+ ok(t.ok === false && t.code === 'MATH_TIMEOUT', 'a hanging run reports MATH_TIMEOUT')
991
+ ok(t.timedOut === true && 'exit' in t && t.exit === null, '★ MATH_TIMEOUT carries timedOut:true and an explicit exit field (uniform with NONZERO_EXIT)')
992
+ ok('stderr' in t && typeof t.stderr === 'string', '★ MATH_TIMEOUT carries the captured stderr like NONZERO_EXIT does')
993
+ // F7: the response says the constraints are existence-only, and lists the ones that were NOT checked.
994
+ const h3 = makeFakeHost({ installed: ['python3'], packages: { numpy: 'present' } })
995
+ M.registerMathComputation(h3.host)
996
+ const c = await h3.call({ op: 'run', engine: 'python', mode: 'code', code: 'print(1)\n', packages: ['numpy>=1'] })
997
+ ok(c.ok === true && /existence-only/.test(String(c.versionPolicy)), '★ the run response states the version constraints are existence-only')
998
+ ok(Array.isArray(c.constraintsNotEnforced) && c.constraintsNotEnforced.indexOf('numpy>=1') !== -1, '★ the response names the constraint it did NOT check (no silent drop)')
999
+ const c2 = await h3.call({ op: 'run', engine: 'python', mode: 'code', code: 'print(1)\n', packages: ['numpy'] })
1000
+ ok(Array.isArray(c2.constraintsNotEnforced) && c2.constraintsNotEnforced.length === 0, 'an unconstrained request reports an empty not-enforced list')
1001
+ const m = await h3.call({ op: 'run', engine: 'python', mode: 'code', code: 'print(1)\n', packages: ['pandas>=2'] })
1002
+ ok(m.ok === false && /existence-only/.test(String(m.versionPolicy)), 'the missing-package failure also states the constraint policy')
1003
+ }
1004
+
1005
+ // ── 26. round-9 (N1): edited-archived-script evidence integrity
1006
+ // The documented P2a flow: edit the ORIGINAL source, re-run mode:'file' ⇒ SAME archive id, attempt>=2,
1007
+ // scriptChanged:true, previousReceipt at the prior attempt, prior attempt untouched. Pointing mode:'file'
1008
+ // at an ARCHIVED script is a NEW archive id by design (the id is keyed by the SOURCE PATH) - which must
1009
+ // be SAID (fileIsArchivedScript + a warning), not look like broken change detection.
1010
+ {
1011
+ const h = makeFakeHost({ installed: ['python3'], files: { 'Problems/x.py': 'print(1)\n' } })
1012
+ M.registerMathComputation(h.host)
1013
+ const r1 = await h.call({ op: 'run', engine: 'python', mode: 'file', file: 'Problems/x.py' })
1014
+ ok(r1.ok === true && r1.attempt === 1 && r1.scriptChanged === false, 'first run of a source file: attempt 1, no change')
1015
+ const rj1 = JSON.parse(h.files.get(String(r1.receipt.json).replace(/\\/g, '/')))
1016
+ ok(typeof r1.runId === 'string' && rj1.runId === r1.runId, '★ the run response exposes its archive id (matches the durable receipt.runId) - needed to check "same archive id"')
1017
+ // round-B (F6): the DURABLE receipt must record the constraint policy too (not just the response).
1018
+ ok(typeof rj1.versionPolicy === 'string' && /existence-only/.test(rj1.versionPolicy) && Array.isArray(rj1.constraintsNotEnforced),
1019
+ '★ the durable receipt records versionPolicy + constraintsNotEnforced (the promise is auditable evidence)')
1020
+ h.files.set('Problems/x.py', 'print(2)\n')
1021
+ const r2 = await h.call({ op: 'run', engine: 'python', mode: 'file', file: 'Problems/x.py' })
1022
+ ok(r2.ok === true && r2.runId === r1.runId, '★ editing the ORIGINAL source and re-running lands on the SAME archive id')
1023
+ ok(r2.attempt >= 2 && /\/attempts\/\d+/.test(String(r2.attemptDir || '')), '★ the re-run is attempt>=2 under attempts/<n>/')
1024
+ ok(r2.scriptChanged === true, '★ the re-run reports scriptChanged:true')
1025
+ ok(!!r2.previousReceipt && r2.previousReceipt.attempt === r1.attempt && r2.previousReceipt.scriptHash === r1.scriptHash, '★ previousReceipt points at the prior attempt (attempt + scriptHash)')
1026
+ ok(h.files.get(String(r1.scriptPath).replace(/\\/g, '/')) === 'print(1)\n', '★ the prior attempt\'s script is NOT overwritten (append-only)')
1027
+ // A DIFFERENT source path (e.g. a copy of the old script, or a brand-new file) is a NEW archive id BY
1028
+ // DESIGN: the id is keyed by the source path, so there is no history for that id and
1029
+ // scriptChanged/previousReceipt are - correctly - empty. This is the case a finder can mistake for
1030
+ // "broken change detection"; it is not, and the docs now say so.
1031
+ h.files.set('Problems/copy_of_old.py', 'print(1)\n')
1032
+ const r3 = await h.call({ op: 'run', engine: 'python', mode: 'file', file: 'Problems/copy_of_old.py' })
1033
+ ok(r3.ok === true, 'a run of a different source path succeeds (' + JSON.stringify({ ok: r3.ok, code: r3.code }) + ')')
1034
+ ok(r3.runId !== r1.runId, '★ a DIFFERENT source path gets a DIFFERENT archive id')
1035
+ ok(r3.attempt === 1, '★ …and therefore starts at attempt 1 (fresh archive, by design)')
1036
+ ok(r3.scriptChanged === false, '★ …and reports scriptChanged:false (no history FOR THIS id)')
1037
+ ok(r3.previousReceipt === null || r3.previousReceipt === undefined, '★ …and has no previousReceipt (expected, not broken change detection)')
1038
+ ok(h.files.get(String(r1.scriptPath).replace(/\\/g, '/')) === 'print(1)\n', 'the earlier attempt is still untouched after that run')
1039
+ // Review round A (F1/F2): the ARCHIVED copy is REACHABLE (projectRel() is purely lexical - it rejects
1040
+ // absolute paths and escaping `..` only). The shipped guidance must therefore say the truth: pointing
1041
+ // mode:'file' at the archived copy yields a NEW archive (new id, attempt 1, no history), flagged by
1042
+ // fileIsArchivedScript + ARCHIVED_SCRIPT_RERUN - it is NOT "a new attempt of the same archive".
1043
+ const archivedPath = String(r1.scriptPath).replace(/\\/g, '/')
1044
+ const r4 = await h.call({ op: 'run', engine: 'python', mode: 'file', file: archivedPath })
1045
+ ok(r4.ok === true, '★ pointing mode:file at the ARCHIVED copy succeeds (the guard is lexical, it does not refuse Computation/)')
1046
+ ok(r4.fileIsArchivedScript === true, '★ the response flags fileIsArchivedScript for an archived source')
1047
+ ok((r4.warnings || []).some((w) => w.code === 'ARCHIVED_SCRIPT_RERUN'), '★ the response warns ARCHIVED_SCRIPT_RERUN (so it cannot read as broken change detection)')
1048
+ ok(r4.runId !== r1.runId && r4.attempt === 1 && r4.scriptChanged === false && (r4.previousReceipt === null || r4.previousReceipt === undefined),
1049
+ '★ …and it really is a NEW archive (new id, attempt 1, no history) - NOT a new attempt of the same archive')
1050
+ ok(h.files.get(archivedPath) === 'print(1)\n', 'the archived original is still byte-identical after that run')
1051
+ }
1052
+ // ── 27. round-9 (N2): a TIMEOUT carries its partial output in BOTH the response and the receipt
1053
+ {
1054
+ const h = makeFakeHost({ installed: ['python3'], hang: true, stdoutBytes: 16 })
1055
+ M.registerMathComputation(h.host)
1056
+ const t = await h.call({ op: 'run', engine: 'python', mode: 'code', code: 'print("before-timeout")\n', timeoutMs: 1000 })
1057
+ ok(t.ok === false && t.code === 'MATH_TIMEOUT', 'the hanging run reports MATH_TIMEOUT')
1058
+ ok('stdout' in t && 'stderr' in t, '★ the TIMEOUT response carries stdout AND stderr (not just the archived file)')
1059
+ const rj = JSON.parse(h.files.get(String(t.receipt.json).replace(/\\/g, '/')))
1060
+ ok(typeof rj.partialStdout === 'string' && typeof rj.partialStderr === 'string', '★ the TIMEOUT receipt JSON carries the capped partial output (as strings)')
1061
+ }
1062
+
1063
+ // ── 28. descriptor sweep: NO spawn may ever carry an unsubstituted argv placeholder
1064
+ // Sweep finding (HIGH, fixed): matlab's scriptArgv EMBEDS the placeholder (`run('<script>')`) while the
1065
+ // module substituted by EXACT element equality, so the literal `run('<script>')` reached the engine and
1066
+ // the archived script never ran. This section walks the whole descriptor table.
1067
+ {
1068
+ const PLACEHOLDER = /<script>|<expr>|<pkgs>|<pkgs\.\.\.>|<probeCode>|<exe>|<pkg>|<cliArgv/
1069
+ for (const name of (M.MATH_PARAM_DEFAULTS.mathEngines || [])) {
1070
+ if (name === 'cli') continue
1071
+ const h = makeFakeHost({ installed: [name], files: {}, licence: true })
1072
+ M.registerMathComputation(h.host)
1073
+ const r = await h.call({ op: 'run', engine: name, mode: 'code', code: 'x=1\n' })
1074
+ const bad = (h.spawns || []).map((s) => (s && s.argv) || []).flat().filter((a) => typeof a === 'string' && PLACEHOLDER.test(a))
1075
+ ok(bad.length === 0, '★ ' + name + ': no spawn carries a literal argv placeholder', JSON.stringify(bad.slice(0, 2)))
1076
+ if (name === 'matlab' && r.ok === true) {
1077
+ const runs = (h.spawns || []).map((s) => s.argv).filter((a) => a.join(' ').indexOf('-batch') !== -1 && a.join(' ').indexOf('disp(version)') === -1)
1078
+ const run = runs.pop() || []
1079
+ ok(run.some((a) => /run\('.*script\.m'\)/.test(a)), '★ matlab: the EMBEDDED placeholder is substituted (run(\'<abs script.m>\'))', JSON.stringify(run))
1080
+ }
1081
+ }
1082
+ }
1083
+
1084
+ // ── 29. cli policy is SINGLE-SOURCED (descriptor), not hard-coded in the module
1085
+ // Sweep ruling: `cli.policy.requiresMathMode` / `requiresEngineInList` were declared but never consumed
1086
+ // (the same rule was hard-coded twice), so descriptor and behaviour could drift. The refusals now carry
1087
+ // the DESCRIPTOR value, which is exactly what makes a hard-coded revert observable.
1088
+ {
1089
+ const h = makeFakeHost({ installed: ['python3'], params: { mathMode: 'typed' }, cliCommands: ['python3'] })
1090
+ M.registerMathComputation(h.host)
1091
+ const r = await h.call({ op: 'run', engine: 'cli', mode: 'code', code: 'print(1)\n', cli: { command: 'python3' } })
1092
+ ok(r.ok === false && r.code === 'MATH_REFUSED', 'cli under mathMode=typed is refused')
1093
+ ok(r.next && r.next.reason === 'policy' && r.next.requiresMathMode === 'typed+shell',
1094
+ '★ the refusal carries the DESCRIPTOR policy value (next.requiresMathMode === typed+shell)', JSON.stringify(r.next))
1095
+ const h2 = makeFakeHost({ installed: ['python3'], params: { mathMode: 'typed+shell', mathEngines: ['python', 'r'] }, cliCommands: ['python3'] })
1096
+ M.registerMathComputation(h2.host)
1097
+ const r2 = await h2.call({ op: 'run', engine: 'cli', mode: 'code', code: 'print(1)\n', cli: { command: 'python3' } })
1098
+ ok(r2.ok === false && r2.next && r2.next.reason === 'engine-not-allowed' && r2.next.requiresEngineInList === true,
1099
+ '★ when cli is not in mathEngines the refusal surfaces requiresEngineInList from the descriptor', JSON.stringify(r2.next))
1100
+ }
1101
+
1102
+ // ── 30. a commercial template's VERIFY provenance REACHES the user (item 2)
1103
+ // `maple.verify`/`verifyReason` were declared but never reached any response, so a user told "commercial
1104
+ // template needs confirmation" could not see WHY. It is surfaced on the probe result (per-engine entry +
1105
+ // engineInfo) and on the vendor-guidance payloads; engines without a declared reason print no empty slot.
1106
+ {
1107
+ const h = makeFakeHost({ installed: ['maple'], licence: true, files: {} })
1108
+ M.registerMathComputation(h.host)
1109
+ const p = await h.call({ op: 'probe', engine: 'maple' })
1110
+ ok(p.ok === true && !!p.engineInfo && p.engineInfo.verify === true, 'probe reports the verify flag for a commercial template')
1111
+ ok(!!p.engineInfo && typeof p.engineInfo.verifyReason === 'string' && p.engineInfo.verifyReason.length > 0 && /version/i.test(p.engineInfo.verifyReason),
1112
+ '★ probe carries WHY the template needs confirmation (engineInfo.verifyReason)', JSON.stringify(p.engineInfo && p.engineInfo.verifyReason))
1113
+ const entry = (p.engines || []).find((e) => e.name === 'maple')
1114
+ ok(carriesVerifyProvenance(entry), 'the per-engine probe entry carries the same provenance')
1115
+ const hp = makeFakeHost({ installed: ['python3'], files: {} })
1116
+ M.registerMathComputation(hp.host)
1117
+ const pp = await hp.call({ op: 'probe', engine: 'python' })
1118
+ ok(hasNoVerifySlot(pp.engineInfo),
1119
+ '★ an engine without a declared reason prints NO empty slot (assemble-only-when-present)')
1120
+ const hm = makeFakeHost({ installed: [], files: {} })
1121
+ M.registerMathComputation(hm.host)
1122
+ const pm = await hm.call({ op: 'probe', engine: 'maple' })
1123
+ ok(pm.ok === false && !!pm.next && typeof pm.next.verifyReason === 'string' && /version/i.test(pm.next.verifyReason),
1124
+ '★ the not-found guidance also says why the commercial template needs confirmation', JSON.stringify(pm.next && pm.next.verifyReason))
1125
+ }
1126
+
1127
+ // ── 31. S3 symmetry: EVERY failure kind surfaces partial stdout AND stderr (like the timeout)
1128
+ {
1129
+ const h1 = makeFakeHost({ installed: ['python3'], exit: 2, stdoutBytes: 16 })
1130
+ M.registerMathComputation(h1.host)
1131
+ const r1 = await h1.call({ op: 'run', engine: 'python', mode: 'code', code: 'x=1\n' })
1132
+ ok(r1.ok === false && r1.code === 'MATH_NONZERO_EXIT', 'a non-zero exit reports MATH_NONZERO_EXIT')
1133
+ ok('stdout' in r1 && 'stderr' in r1, '★ S3 symmetry: MATH_NONZERO_EXIT carries BOTH partial stdout and stderr', JSON.stringify({ so: 'stdout' in r1, se: 'stderr' in r1 }))
1134
+ const h2 = makeFakeHost({ installed: ['python3'], argError: 'unknown option', stdoutBytes: 16 })
1135
+ M.registerMathComputation(h2.host)
1136
+ const r2 = await h2.call({ op: 'run', engine: 'python', mode: 'code', code: 'x=1\n' })
1137
+ ok(r2.ok === false && r2.code === 'MATH_ENGINE_BAD_ARGV', 'a usage error reports MATH_ENGINE_BAD_ARGV')
1138
+ ok('stdout' in r2 && 'stderr' in r2, '★ S3 symmetry: MATH_ENGINE_BAD_ARGV carries BOTH partial stdout and stderr')
1139
+ }
1140
+ // ── 32. S2: the failure a user actually hits carries the SAME absence evidence as the success shape
1141
+ {
1142
+ const h = makeFakeHost({ installed: [] })
1143
+ M.registerMathComputation(h.host)
1144
+ const r = await h.call({ op: 'probe' })
1145
+ ok(r.ok === false && r.code === 'MATH_ENGINE_NOT_FOUND', 'no resolvable engine reports MATH_ENGINE_NOT_FOUND')
1146
+ ok(Array.isArray(r.configured) && r.configured.length > 0, '★ S2 absence evidence: the failure names the CONFIGURED engines', JSON.stringify(r.configured ?? null))
1147
+ const absent = Array.isArray(r.absent) ? r.absent : []
1148
+ ok(absent.length > 0, '★ S2 absence evidence: the failure carries an absent[] list (not a silent partial)', JSON.stringify(r.absent ?? null))
1149
+ ok(absent.every((a) => a && typeof a.why === 'string' && a.why.length > 0), 'every absent[] entry carries a why', JSON.stringify(absent))
1150
+ }
803
1151
  console.log('')
1152
+ // ── 33. S4/S5: install failures are discriminated WITHOUT a new code (frozen interface). Reads the
1153
+ // ENV-RESOLVED module (MATH_COMPUTATION_MODULE), so the --self-probe case 2 above can reach exactly
1154
+ // what these assertions read (the parity-located version could not be reddened by any mutant).
1155
+ {
1156
+ const src = readFileSync(fileURLToPath(new URL(MODULE)), 'utf8')
1157
+ ok(src.indexOf("out.op = 'install'") !== -1, '★ S4/S5 install discrimination: a failed install sets op=install')
1158
+ ok(/out\.timedOut = !!/.test(src), '★ S4/S5 install discrimination: a failed install carries timedOut')
1159
+ ok(/out\.installedSoFar = results\.filter/.test(src), '★ S4/S5 install discrimination: a failed install reports what already succeeded (installedSoFar)')
1160
+ ok(/installedSoFar: commands\.map/.test(src), 'the success path reports installedSoFar too (no special-casing by the caller)')
1161
+ ok(src.indexOf('MATH_INSTALL_FAILED') === -1, 'no 12th failure code was added (MATH_FAILURE_CODES stays frozen)')
1162
+ }
1163
+
1164
+ // ── 34. S6: the install plan, the run path and the missing-package failure state ONE policy the
1165
+ // same way (behavioural: read from the responses, not from the source text).
1166
+ {
1167
+ const PREFIX = 'version-constraints-are-existence-only: '
1168
+ const h = makeFakeHost({ installed: ['python3'] })
1169
+ M.registerMathComputation(h.host)
1170
+ const plan = await h.call({ op: 'install', packages: ['numpy==1.2.3'] })
1171
+ const planPolicy = String((plan && plan.plan && plan.plan.versionPolicy) || '')
1172
+ ok(plan.ok === true && !!plan.plan, 'S6: an install dry-run returns a plan', JSON.stringify(plan && plan.code))
1173
+ ok(planPolicy.indexOf(PREFIX) === 0, '★ S6: the install plan uses the SAME policy wording as the run path', planPolicy.slice(0, 70))
1174
+ ok(planPolicy.indexOf('numpy==1.2.3') !== -1, '★ S6: the plan names the pinned spec it does not enforce', planPolicy.slice(0, 90))
1175
+ const miss = await h.call({ op: 'run', engine: 'python', mode: 'code', packages: ['numpy==1.2.3'], code: 'x=1\n' })
1176
+ ok(miss.ok === false && miss.code === 'MATH_MISSING_PACKAGES', 'S6: a pinned-but-missing package fails as MATH_MISSING_PACKAGES', JSON.stringify(miss.code))
1177
+ ok(String(miss.versionPolicy || '').indexOf(PREFIX) === 0, '★ S6: the failure path uses the same wording', String(miss.versionPolicy || '').slice(0, 70))
1178
+ ok(String(miss.versionPolicy || '') === planPolicy, '★ S6: plan and failure wording are byte-identical for the same spec', JSON.stringify([planPolicy.slice(0, 40), String(miss.versionPolicy || '').slice(0, 40)]))
1179
+ ok(String(miss.message || '').indexOf('只检查是否存在;版本求解交给包管理器') !== -1, '★ S6: the MATH_MISSING_PACKAGES detail derives the same clause', String(miss.message || '').slice(0, 70))
1180
+ const badSpec = M.parsePackageSpec('numpy@1.2')
1181
+ ok(badSpec && badSpec.ok === false && String(badSpec.problem || '').indexOf('只检查是否存在;版本求解交给包管理器') !== -1, '★ S6: the unsupported-version-syntax message derives the same clause', String((badSpec && badSpec.problem) || '').slice(0, 80))
1182
+ ok(String(planPolicy || '').indexOf('只检查是否存在;版本求解交给包管理器') !== -1, '★ S6: the policy FIELD itself contains that clause (one literal, three surfaces)', planPolicy.slice(0, 80))
1183
+ }
1184
+
1185
+ // ── 35. S6 derivation is LOAD-BEARING (found by mutant case 2): "the message contains the clause" also
1186
+ // passes when the message RE-INLINES the same words, so the real checks are (a) the clause exists as
1187
+ // exactly ONE literal in the module and (b) the two human messages are built from the constant.
1188
+ {
1189
+ const src = readFileSync(fileURLToPath(new URL(MODULE)), 'utf8')
1190
+ const clause = '只检查是否存在;版本求解交给包管理器'
1191
+ const occurrences = src.split(clause).length - 1
1192
+ ok(occurrences === 1, '★ S6: the policy clause exists as exactly ONE literal in the module (a re-inlined message is caught)', 'occurrences=' + occurrences)
1193
+ ok(/MATH_MISSING_PACKAGES[\s\S]{0,160}MATH_VERSION_POLICY_CLAUSE/.test(src), '★ S6: the missing-package detail is built from the clause constant', 'MATH_MISSING_PACKAGES … MATH_VERSION_POLICY_CLAUSE')
1194
+ ok(/unsupported version syntax[\s\S]{0,160}MATH_VERSION_POLICY_CLAUSE/.test(src), '★ S6: the unsupported-version-syntax message is built from the clause constant')
1195
+ ok(/const MATH_VERSION_POLICY = .*MATH_VERSION_POLICY_CLAUSE/.test(src), '★ S6: the long policy sentence is derived from the clause')
1196
+ }
1197
+
804
1198
  console.log('=== MATH COMPUTATION SHARED: ' + passed + ' passed, ' + failed + ' failed ===')
805
1199
  if (failures.length) for (const f of failures) console.error(' - ' + f)
806
1200
  process.exit(failed === 0 ? 0 : 1)