@hviana/sema 0.2.4 → 0.2.6

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 (142) hide show
  1. package/package.json +1 -1
  2. package/src/geometry.ts +52 -3
  3. package/src/mind/attention.ts +1469 -120
  4. package/src/mind/index.ts +16 -0
  5. package/src/mind/junction.ts +125 -16
  6. package/src/mind/mind.ts +15 -0
  7. package/src/mind/rationale.ts +12 -4
  8. package/src/mind/traverse.ts +75 -7
  9. package/src/mind/types.ts +25 -0
  10. package/test/51-structural-resonance-ladder.test.mjs +552 -0
  11. package/test/52-climb-consensus-instrumentation.test.mjs +324 -0
  12. package/test/53-cross-region-probe-instrumentation.test.mjs +356 -0
  13. package/dist/example/demo.d.ts +0 -1
  14. package/dist/example/demo.js +0 -39
  15. package/dist/example/train_base.d.ts +0 -87
  16. package/dist/example/train_base.js +0 -2252
  17. package/dist/src/alphabet.d.ts +0 -7
  18. package/dist/src/alphabet.js +0 -33
  19. package/dist/src/alu/src/alu.d.ts +0 -185
  20. package/dist/src/alu/src/alu.js +0 -440
  21. package/dist/src/alu/src/expr.d.ts +0 -61
  22. package/dist/src/alu/src/expr.js +0 -318
  23. package/dist/src/alu/src/index.d.ts +0 -11
  24. package/dist/src/alu/src/index.js +0 -19
  25. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  26. package/dist/src/alu/src/kernel-arith.js +0 -264
  27. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  28. package/dist/src/alu/src/kernel-bits.js +0 -152
  29. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  30. package/dist/src/alu/src/kernel-logic.js +0 -60
  31. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  32. package/dist/src/alu/src/kernel-nd.js +0 -208
  33. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  34. package/dist/src/alu/src/kernel-numeric.js +0 -366
  35. package/dist/src/alu/src/operation.d.ts +0 -168
  36. package/dist/src/alu/src/operation.js +0 -189
  37. package/dist/src/alu/src/parser.d.ts +0 -221
  38. package/dist/src/alu/src/parser.js +0 -577
  39. package/dist/src/alu/src/resonance.d.ts +0 -55
  40. package/dist/src/alu/src/resonance.js +0 -126
  41. package/dist/src/alu/src/text.d.ts +0 -31
  42. package/dist/src/alu/src/text.js +0 -73
  43. package/dist/src/alu/src/value.d.ts +0 -109
  44. package/dist/src/alu/src/value.js +0 -300
  45. package/dist/src/alu/test/alu.test.d.ts +0 -1
  46. package/dist/src/alu/test/alu.test.js +0 -764
  47. package/dist/src/bytes.d.ts +0 -14
  48. package/dist/src/bytes.js +0 -59
  49. package/dist/src/canon.d.ts +0 -26
  50. package/dist/src/canon.js +0 -57
  51. package/dist/src/config.d.ts +0 -111
  52. package/dist/src/config.js +0 -91
  53. package/dist/src/derive/src/deduction.d.ts +0 -125
  54. package/dist/src/derive/src/deduction.js +0 -155
  55. package/dist/src/derive/src/index.d.ts +0 -7
  56. package/dist/src/derive/src/index.js +0 -11
  57. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  58. package/dist/src/derive/src/priority-queue.js +0 -73
  59. package/dist/src/derive/src/rewrite.d.ts +0 -56
  60. package/dist/src/derive/src/rewrite.js +0 -100
  61. package/dist/src/derive/src/trie.d.ts +0 -90
  62. package/dist/src/derive/src/trie.js +0 -217
  63. package/dist/src/derive/test/derive.test.d.ts +0 -1
  64. package/dist/src/derive/test/derive.test.js +0 -122
  65. package/dist/src/extension.d.ts +0 -37
  66. package/dist/src/extension.js +0 -7
  67. package/dist/src/geometry.d.ts +0 -176
  68. package/dist/src/geometry.js +0 -546
  69. package/dist/src/index.d.ts +0 -16
  70. package/dist/src/index.js +0 -18
  71. package/dist/src/ingest-cache.d.ts +0 -41
  72. package/dist/src/ingest-cache.js +0 -161
  73. package/dist/src/mind/articulation.d.ts +0 -6
  74. package/dist/src/mind/articulation.js +0 -99
  75. package/dist/src/mind/attention.d.ts +0 -88
  76. package/dist/src/mind/attention.js +0 -997
  77. package/dist/src/mind/bridge.d.ts +0 -30
  78. package/dist/src/mind/bridge.js +0 -569
  79. package/dist/src/mind/canonical.d.ts +0 -29
  80. package/dist/src/mind/canonical.js +0 -88
  81. package/dist/src/mind/graph-search.d.ts +0 -285
  82. package/dist/src/mind/graph-search.js +0 -956
  83. package/dist/src/mind/index.d.ts +0 -5
  84. package/dist/src/mind/index.js +0 -5
  85. package/dist/src/mind/junction.d.ts +0 -95
  86. package/dist/src/mind/junction.js +0 -262
  87. package/dist/src/mind/learning.d.ts +0 -47
  88. package/dist/src/mind/learning.js +0 -248
  89. package/dist/src/mind/match.d.ts +0 -126
  90. package/dist/src/mind/match.js +0 -441
  91. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  92. package/dist/src/mind/mechanisms/alu.js +0 -36
  93. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  94. package/dist/src/mind/mechanisms/cast.js +0 -716
  95. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  96. package/dist/src/mind/mechanisms/confluence.js +0 -225
  97. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  98. package/dist/src/mind/mechanisms/cover.js +0 -203
  99. package/dist/src/mind/mechanisms/extraction.d.ts +0 -67
  100. package/dist/src/mind/mechanisms/extraction.js +0 -387
  101. package/dist/src/mind/mechanisms/recall.d.ts +0 -13
  102. package/dist/src/mind/mechanisms/recall.js +0 -325
  103. package/dist/src/mind/mind.d.ts +0 -274
  104. package/dist/src/mind/mind.js +0 -572
  105. package/dist/src/mind/pipeline-mechanism.d.ts +0 -142
  106. package/dist/src/mind/pipeline-mechanism.js +0 -213
  107. package/dist/src/mind/pipeline.d.ts +0 -20
  108. package/dist/src/mind/pipeline.js +0 -217
  109. package/dist/src/mind/primitives.d.ts +0 -70
  110. package/dist/src/mind/primitives.js +0 -287
  111. package/dist/src/mind/rationale.d.ts +0 -134
  112. package/dist/src/mind/rationale.js +0 -162
  113. package/dist/src/mind/reasoning.d.ts +0 -34
  114. package/dist/src/mind/reasoning.js +0 -240
  115. package/dist/src/mind/recognition.d.ts +0 -20
  116. package/dist/src/mind/recognition.js +0 -390
  117. package/dist/src/mind/resonance.d.ts +0 -23
  118. package/dist/src/mind/resonance.js +0 -0
  119. package/dist/src/mind/trace.d.ts +0 -15
  120. package/dist/src/mind/trace.js +0 -73
  121. package/dist/src/mind/traverse.d.ts +0 -113
  122. package/dist/src/mind/traverse.js +0 -504
  123. package/dist/src/mind/types.d.ts +0 -271
  124. package/dist/src/mind/types.js +0 -130
  125. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  126. package/dist/src/rabitq-ivf/src/database.js +0 -201
  127. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  128. package/dist/src/rabitq-ivf/src/index.js +0 -4
  129. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  130. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  131. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  132. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  133. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  134. package/dist/src/rabitq-ivf/src/rabitq.js +0 -283
  135. package/dist/src/sema.d.ts +0 -31
  136. package/dist/src/sema.js +0 -63
  137. package/dist/src/store-sqlite.d.ts +0 -184
  138. package/dist/src/store-sqlite.js +0 -942
  139. package/dist/src/store.d.ts +0 -663
  140. package/dist/src/store.js +0 -1618
  141. package/dist/src/vec.d.ts +0 -31
  142. package/dist/src/vec.js +0 -109
@@ -0,0 +1,324 @@
1
+ // 52-climb-consensus-instrumentation.test.mjs — structured instrumentation
2
+ // for the climbConsensus / inspectRationale step (spec §10).
3
+ //
4
+ // Purely additive: every assertion here checks the STRUCTURE of the `data`
5
+ // payload a traced "climbConsensus" RationaleStep now carries, alongside the
6
+ // existing human-readable `note` — never that inference itself changed
7
+ // (item 6 pins that explicitly: roots/ranked must be bit-identical whether
8
+ // or not a trace was requested).
9
+
10
+ import { test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { Mind } from "../dist/src/index.js";
13
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
14
+ import { climbAttentionAll } from "../dist/src/mind/attention.js";
15
+ import { Rationale } from "../dist/src/mind/rationale.js";
16
+
17
+ const enc = (s) => new TextEncoder().encode(s);
18
+ const mk = (seed = 1) =>
19
+ new Mind({ seed, store: new SQliteStore({ path: ":memory:" }) });
20
+
21
+ /** Collect the full step stream for one traced query. */
22
+ async function trace(mind, q) {
23
+ const steps = [];
24
+ const ans = await mind.respondText(q, (s) => steps.push(s));
25
+ return { steps, ans };
26
+ }
27
+
28
+ /** The FIRST "climbConsensus" step — the one whose data reflects the actual
29
+ * per-region pipeline (a later same-response call, if any, would hit the
30
+ * content-keyed climb memo). */
31
+ function climbStep(steps) {
32
+ return steps.find((s) => s.mechanism.at(-1) === "climbConsensus");
33
+ }
34
+
35
+ const SATURATION_REASONS = new Set([
36
+ "byte-atom-commonality",
37
+ "predecessor-fan-in",
38
+ "distinct-context-limit",
39
+ "parent-fan-out",
40
+ "lateral-cone-limit",
41
+ ]);
42
+
43
+ const ATTR_CORPUS = [
44
+ ["red", "is a color"],
45
+ ["blue", "is a color"],
46
+ ["circle", "is a shape"],
47
+ ["square", "is a shape"],
48
+ ["red circle", "answer alpha"],
49
+ ["red square", "answer beta"],
50
+ ["blue circle", "answer gamma"],
51
+ ["blue square", "answer delta"],
52
+ ];
53
+
54
+ // ═══════════════════════════════════════════════════════════════════════════
55
+ // 1. every saturation stop reports one of the five reasons, with sound
56
+ // observed/limit provenance — read off the authoritative `reaches` list.
57
+ // ═══════════════════════════════════════════════════════════════════════════
58
+
59
+ test("1. every reported saturation stop names a valid reason with sound provenance", async () => {
60
+ const m = mk(1);
61
+ await m.ingest(ATTR_CORPUS);
62
+ const { steps } = await trace(m, "red circle blue square");
63
+ await m.store.close();
64
+
65
+ const step = climbStep(steps);
66
+ assert.ok(step, "expected a climbConsensus step");
67
+ assert.ok(step.data, "climbConsensus step must carry a data payload");
68
+ assert.equal(step.data.version, 1);
69
+ assert.ok(Array.isArray(step.data.reaches), "expected a reaches list");
70
+
71
+ for (const r of step.data.reaches) {
72
+ assert.equal(typeof r.node, "number");
73
+ assert.ok(Array.isArray(r.roots));
74
+ assert.equal(typeof r.contextsReached, "number");
75
+ assert.equal(typeof r.saturated, "boolean");
76
+ if (r.saturated && r.saturation) {
77
+ assert.ok(
78
+ SATURATION_REASONS.has(r.saturation.reason),
79
+ `unexpected saturation reason "${r.saturation.reason}"`,
80
+ );
81
+ assert.equal(typeof r.saturation.node, "number");
82
+ assert.ok(r.saturation.observed > r.saturation.limit);
83
+ }
84
+ }
85
+ });
86
+
87
+ // ═══════════════════════════════════════════════════════════════════════════
88
+ // 2. canonical vs ANN/fallback regions report correct examined breadth.
89
+ // ═══════════════════════════════════════════════════════════════════════════
90
+
91
+ test("2. an exact chunk region is selected canonically, without an ANN query", async () => {
92
+ const m = mk(2);
93
+ await m.ingest(ATTR_CORPUS);
94
+ // "red then circle" is not itself a trained fact (only "red circle" is),
95
+ // so recall cannot resolve it directly and the consensus climb runs.
96
+ const { steps } = await trace(m, "red then circle");
97
+ await m.store.close();
98
+
99
+ const step = climbStep(steps);
100
+ assert.ok(step, "expected a climbConsensus step");
101
+ assert.ok(step.data, "climbConsensus step must carry a data payload");
102
+ const regions = step.data.regions ?? [];
103
+ assert.ok(regions.length > 0, "expected per-region traces");
104
+
105
+ const votedRegions = regions.filter((r) => r.outcome === "voted");
106
+ assert.ok(votedRegions.length > 0, "expected at least one voted region");
107
+ for (const r of votedRegions) {
108
+ assert.ok(r.selected, "a voted region must report its selection");
109
+ assert.ok(
110
+ r.selected.source === "canonical" || r.selected.source === "ann",
111
+ "selected.source must be canonical or ann",
112
+ );
113
+ if (r.selected.source === "canonical") {
114
+ assert.equal(r.canonicalUsable, true);
115
+ }
116
+ if (r.selected.source === "ann" && !r.selected.fallback) {
117
+ assert.equal(r.annQueried, true);
118
+ assert.equal(typeof r.selected.rank, "number");
119
+ }
120
+ // annHitsExamined counts distinct CONSULTED hits, never more than what
121
+ // was returned.
122
+ assert.ok(r.annHitsExamined <= r.annHitsReturned || !r.annQueried);
123
+ }
124
+ });
125
+
126
+ // ═══════════════════════════════════════════════════════════════════════════
127
+ // 3/4. candidateBreadth / contributingVotes / contributingEvidence differ
128
+ // correctly under junction absorption, and superseded / saturation-
129
+ // masked votes never inflate contributingEvidence.
130
+ // ═══════════════════════════════════════════════════════════════════════════
131
+
132
+ test("3/4. junction absorption widens contributingEvidence past contributingVotes", async () => {
133
+ const m = mk(3);
134
+ await m.ingest(ATTR_CORPUS);
135
+ const { steps } = await trace(m, "red then circle");
136
+ await m.store.close();
137
+
138
+ const step = climbStep(steps);
139
+ assert.ok(step.data.crossRegion, "expected a crossRegion summary");
140
+ assert.ok(
141
+ step.data.crossRegion.junctionVotes.length > 0,
142
+ "expected the exact junction to fire ('red circle' is a trained joint fact)",
143
+ );
144
+ const jv = step.data.crossRegion.junctionVotes[0];
145
+ assert.equal(jv.tier, "exact");
146
+ assert.ok(Array.isArray(jv.sourceRegionIndices));
147
+ assert.ok(Array.isArray(jv.explainedAwayRegionIndices));
148
+ assert.ok(jv.absorbed >= 1);
149
+
150
+ const anchors = step.data.anchors ?? [];
151
+ assert.ok(anchors.length > 0, "expected ranked anchor traces");
152
+ for (const a of anchors) {
153
+ // contributingEvidence is regionSupport (absorbed-weighted); it must
154
+ // never be LESS than the raw pooled-axiom count.
155
+ assert.ok(a.contributingEvidence >= a.contributingVotes);
156
+ assert.equal(typeof a.candidateBreadth, "number");
157
+ assert.ok(a.candidateBreadth >= a.contributingVotes);
158
+ }
159
+
160
+ // A region the junction explained away must be marked superseded on the
161
+ // per-region trace, and superseded regions never produced an ordinary
162
+ // vote that also counts toward contributingEvidence twice.
163
+ const regions = step.data.regions ?? [];
164
+ const supersededRegions = regions.filter((r) => r.superseded);
165
+ for (const r of supersededRegions) {
166
+ assert.equal(
167
+ r.ordinaryVoteProduced,
168
+ true,
169
+ "only a voted region can be superseded",
170
+ );
171
+ }
172
+ });
173
+
174
+ // ═══════════════════════════════════════════════════════════════════════════
175
+ // 5. commit reasons match the existing commitVotes loop.
176
+ // ═══════════════════════════════════════════════════════════════════════════
177
+
178
+ test("5. commit decisions on the anchor trace match the roots actually returned", async () => {
179
+ const m = mk(4);
180
+ await m.ingest(ATTR_CORPUS);
181
+ const { steps } = await trace(m, "red circle blue square");
182
+ await m.store.close();
183
+
184
+ const step = climbStep(steps);
185
+ const anchors = step.data.anchors ?? [];
186
+ assert.ok(anchors.length > 0);
187
+ const roots = step.data.result.roots;
188
+ const rootAnchors = new Set(roots.map((r) => r.anchor));
189
+
190
+ for (const a of anchors) {
191
+ if (a.commit.status === "root") {
192
+ assert.ok(
193
+ rootAnchors.has(a.anchor),
194
+ `anchor ${a.anchor} marked root but absent from result.roots`,
195
+ );
196
+ assert.equal(a.commit.rejectionReasons.length, 0);
197
+ } else {
198
+ assert.ok(!rootAnchors.has(a.anchor) || a.commit.status === "overlap");
199
+ }
200
+ if (a.commit.status === "rejected") {
201
+ assert.ok(
202
+ a.commit.rejectionReasons.length > 0,
203
+ "a rejected anchor must name at least one reason",
204
+ );
205
+ for (const reason of a.commit.rejectionReasons) {
206
+ assert.ok(
207
+ ["below-natural-break", "below-consensus-floor", "leading-saturation"]
208
+ .includes(reason),
209
+ );
210
+ }
211
+ }
212
+ }
213
+ // The dominant (first) root never carries the two vote-threshold gates.
214
+ const dominant = anchors.find((a) => a.commit.dominant);
215
+ if (dominant) {
216
+ assert.equal(dominant.commit.status, "root");
217
+ assert.equal(dominant.commit.passesNaturalBreak, undefined);
218
+ assert.equal(dominant.commit.passesConsensusFloor, undefined);
219
+ }
220
+ });
221
+
222
+ // ═══════════════════════════════════════════════════════════════════════════
223
+ // 6. roots and ranked results are bit-identical with and without tracing.
224
+ // ═══════════════════════════════════════════════════════════════════════════
225
+
226
+ test("6. inspectRationale never changes the answer or the traced result.roots/ranked", async () => {
227
+ const queries = [
228
+ "red circle",
229
+ "red then circle",
230
+ "blue square",
231
+ "red",
232
+ "circle blue",
233
+ ];
234
+ for (const q of queries) {
235
+ const plainMind = mk(5);
236
+ await plainMind.ingest(ATTR_CORPUS);
237
+ const plainAns = await plainMind.respondText(q);
238
+ await plainMind.store.close();
239
+
240
+ const tracedMind = mk(5);
241
+ await tracedMind.ingest(ATTR_CORPUS);
242
+ const { ans: tracedAns, steps } = await trace(tracedMind, q);
243
+ await tracedMind.store.close();
244
+
245
+ assert.equal(
246
+ tracedAns,
247
+ plainAns,
248
+ `answer differs for query "${q}" — tracing must be purely additive`,
249
+ );
250
+
251
+ // Cross-check the climb itself: the untraced mind-level climbAttention
252
+ // convenience (never touches ctx.trace) must agree with the roots the
253
+ // traced climbConsensus step reported in its `data.result`.
254
+ const untracedMind = mk(5);
255
+ await untracedMind.ingest(ATTR_CORPUS);
256
+ const untracedRoots = await untracedMind.climbAttention(enc(q), 24);
257
+ await untracedMind.store.close();
258
+
259
+ const step = climbStep(steps);
260
+ if (step?.data) {
261
+ assert.deepEqual(
262
+ JSON.parse(JSON.stringify(step.data.result.roots)),
263
+ JSON.parse(JSON.stringify(untracedRoots)),
264
+ `traced result.roots differ from the untraced climb for query "${q}"`,
265
+ );
266
+ }
267
+ }
268
+ });
269
+
270
+ // ═══════════════════════════════════════════════════════════════════════════
271
+ // 7. a repeated query produces the abbreviated cache-hit payload (§9).
272
+ // ═══════════════════════════════════════════════════════════════════════════
273
+
274
+ test("7. a repeated query within one response emits the abbreviated cache trace", async () => {
275
+ const m = mk(7);
276
+ await m.ingest(ATTR_CORPUS);
277
+ // A top-level respond() gets a FRESH climbMemo every call (by design —
278
+ // beginResponse()), so a real cache hit is only observable by driving
279
+ // climbAttentionAll directly against a climbMemo that persists across
280
+ // two calls — exactly what respondTurn() does for a real conversation
281
+ // (see AttentionRead cache doc on climbAttentionAll).
282
+ m.climbMemo = new Map();
283
+ m.recogniseMemo = new Map();
284
+ m.perceiveMemo = new Map();
285
+ m.canon = null;
286
+ m.canonMemo = null;
287
+
288
+ const q = enc("red then circle");
289
+ const steps1 = [];
290
+ m.trace = new Rationale((s) => steps1.push(s));
291
+ await climbAttentionAll(m, q, 12, "inverse");
292
+
293
+ const steps2 = [];
294
+ m.trace = new Rationale((s) => steps2.push(s));
295
+ await climbAttentionAll(m, q, 12, "inverse");
296
+ await m.store.close();
297
+
298
+ const first = climbStep(steps1);
299
+ const second = climbStep(steps2);
300
+ assert.ok(first, "expected a climbConsensus step on the first call");
301
+ assert.ok(second, "expected a climbConsensus step on the repeated call");
302
+
303
+ assert.equal(first.data.cache.hit, false);
304
+ assert.equal(second.data.cache.hit, true);
305
+ assert.equal(second.data.cache.detailAvailable, false);
306
+ assert.equal(second.data.version, 1);
307
+ assert.deepEqual(second.data.config, {
308
+ annK: second.data.config.annK,
309
+ crossRegionProbeLimit: second.data.config.annK,
310
+ mode: second.data.config.mode,
311
+ });
312
+ assert.deepEqual(second.data.candidates, {
313
+ perceived: 0,
314
+ recognised: 0,
315
+ total: 0,
316
+ });
317
+ assert.equal(second.data.regions, undefined);
318
+ assert.equal(second.data.reaches, undefined);
319
+ assert.equal(second.data.crossRegion, undefined);
320
+ assert.equal(second.data.saturation, undefined);
321
+ assert.equal(second.data.pooling, undefined);
322
+ assert.equal(second.data.anchors, undefined);
323
+ assert.ok(second.data.result, "abbreviated payload must still carry result");
324
+ });
@@ -0,0 +1,356 @@
1
+ // 53-cross-region-probe-instrumentation.test.mjs — extends the climbConsensus
2
+ // instrumentation (test/52) to cover the ordinary-region contrastive rival,
3
+ // the cross-region junction probe ladder, and structural-resonance. Purely
4
+ // additive: every assertion checks the STRUCTURE of the `data` payload's new
5
+ // fields — never that inference itself changed.
6
+
7
+ import { test } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { Mind } from "../dist/src/index.js";
10
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
11
+
12
+ const mk = (seed = 1) =>
13
+ new Mind({ seed, store: new SQliteStore({ path: ":memory:" }) });
14
+
15
+ async function trace(mind, q) {
16
+ const steps = [];
17
+ const ans = await mind.respondText(q, (s) => steps.push(s));
18
+ return { steps, ans };
19
+ }
20
+
21
+ function climbStep(steps) {
22
+ return steps.find((s) => s.mechanism.at(-1) === "climbConsensus");
23
+ }
24
+
25
+ // Reused verbatim from test/51's proven fixtures.
26
+ const ATTR_CORPUS = [
27
+ ["red", "is a color"],
28
+ ["blue", "is a color"],
29
+ ["circle", "is a shape"],
30
+ ["square", "is a shape"],
31
+ ["red circle", "answer alpha"],
32
+ ["red square", "answer beta"],
33
+ ["blue circle", "answer gamma"],
34
+ ["blue square", "answer delta"],
35
+ ];
36
+
37
+ const SYN_ATTR_CORPUS = [
38
+ ["red", "is a color"],
39
+ ["crimson", "is a color"],
40
+ ["blue", "is a color"],
41
+ ["circle", "is a shape"],
42
+ ["square", "is a shape"],
43
+ ["red circle", "answer alpha"],
44
+ ["crimson circle", "answer alpha2"],
45
+ ["red square", "answer beta"],
46
+ ["blue circle", "answer gamma"],
47
+ ["blue square", "answer delta"],
48
+ ];
49
+
50
+ // ═══════════════════════════════════════════════════════════════════════════
51
+ // 1. ordinary-region contrastive rival.
52
+ // ═══════════════════════════════════════════════════════════════════════════
53
+
54
+ test("1. an approximate region with a genuine rival reports contrastiveRival, and the margin matches score - rival.score", async () => {
55
+ const m = mk(1);
56
+ await m.ingest(ATTR_CORPUS);
57
+ const { steps } = await trace(m, "blue then circle then square");
58
+ await m.store.close();
59
+
60
+ const step = climbStep(steps);
61
+ assert.ok(step?.data, "expected a traced climbConsensus step");
62
+ const regions = step.data.regions ?? [];
63
+ const withRival = regions.filter((r) => r.contrastiveRival);
64
+ assert.ok(withRival.length > 0, "expected at least one region with a rival");
65
+ for (const r of withRival) {
66
+ assert.equal(typeof r.contrastiveRival.node, "number");
67
+ assert.equal(typeof r.contrastiveRival.rank, "number");
68
+ assert.equal(typeof r.contrastiveRival.score, "number");
69
+ assert.ok(
70
+ Math.abs(
71
+ r.contrastiveMargin - (r.selected.score - r.contrastiveRival.score),
72
+ ) < 1e-9,
73
+ "contrastiveMargin must equal selected.score - contrastiveRival.score",
74
+ );
75
+ }
76
+ });
77
+
78
+ test("1b. a known region never carries a contrastiveRival (the contrast only applies to approximate regions)", async () => {
79
+ const m = mk(1);
80
+ await m.ingest(ATTR_CORPUS);
81
+ const { steps } = await trace(m, "red then circle");
82
+ await m.store.close();
83
+
84
+ const step = climbStep(steps);
85
+ const regions = step.data.regions ?? [];
86
+ for (const r of regions) {
87
+ if (r.known) {
88
+ assert.equal(r.contrastiveRival, undefined);
89
+ assert.equal(r.contrastiveMargin, undefined);
90
+ }
91
+ }
92
+ // And when a voted approximate region has NO rival at all, contrastiveMargin
93
+ // stays exactly selected.score, with no contrastiveRival field.
94
+ for (const r of regions) {
95
+ if (r.outcome === "voted" && !r.known && r.contrastiveRival === undefined) {
96
+ assert.equal(r.contrastiveMargin, r.selected.score);
97
+ }
98
+ }
99
+ });
100
+
101
+ // ═══════════════════════════════════════════════════════════════════════════
102
+ // 2. probe accounting invariant.
103
+ // ═══════════════════════════════════════════════════════════════════════════
104
+
105
+ test("2. crossRegion.probes.length === crossRegion.probesAttempted", async () => {
106
+ const m = mk(1);
107
+ await m.ingest(SYN_ATTR_CORPUS);
108
+ const { steps } = await trace(m, "the crimson near the square");
109
+ await m.store.close();
110
+
111
+ const step = climbStep(steps);
112
+ const cr = step.data.crossRegion;
113
+ assert.ok(cr, "expected a crossRegion summary");
114
+ assert.equal(cr.probes.length, cr.probesAttempted);
115
+ assert.ok(
116
+ ["insufficient-regions", "probe-limit", "pairs-exhausted"].includes(
117
+ cr.stopReason,
118
+ ),
119
+ );
120
+ });
121
+
122
+ // ═══════════════════════════════════════════════════════════════════════════
123
+ // 3. exact junction: attempt count, accepted structural outcome, vote-probe
124
+ // link.
125
+ // ═══════════════════════════════════════════════════════════════════════════
126
+
127
+ test("3. exact junction: probe.exact attempted with candidates, accepted structural outcome, and vote links back to its probe", async () => {
128
+ const m = mk(3);
129
+ await m.ingest(ATTR_CORPUS);
130
+ const { steps } = await trace(m, "red then circle");
131
+ await m.store.close();
132
+
133
+ const step = climbStep(steps);
134
+ const cr = step.data.crossRegion;
135
+ assert.ok(cr.junctionVotes.length > 0);
136
+ const jv = cr.junctionVotes[0];
137
+ assert.equal(jv.tier, "exact");
138
+ assert.equal(typeof jv.probe, "number");
139
+ const probe = cr.probes[jv.probe];
140
+ assert.ok(probe, "vote's probe index must resolve into crossRegion.probes");
141
+ assert.equal(probe.exact.attempted, true);
142
+ assert.ok(probe.exact.candidatesReturned > 0);
143
+ assert.ok(probe.structural, "expected a structural trace on this probe");
144
+ assert.equal(probe.structural.outcome, "accepted");
145
+ assert.equal(probe.outcome, "accepted");
146
+ });
147
+
148
+ // ═══════════════════════════════════════════════════════════════════════════
149
+ // 4. single-synonym junction.
150
+ // ═══════════════════════════════════════════════════════════════════════════
151
+
152
+ test("4. single-synonym junction: exact returns zero, single attempted and returns candidates, selected single-synonym tier", async () => {
153
+ const m = mk(1);
154
+ await m.ingest(SYN_ATTR_CORPUS);
155
+ // "square then crimson" — order-free single-sibling substitution recovers
156
+ // 'red square' via crimson's sibling 'red' (mirrors test/51's synRight).
157
+ const { steps } = await trace(m, "square then crimson");
158
+ await m.store.close();
159
+
160
+ const step = climbStep(steps);
161
+ const cr = step.data.crossRegion;
162
+ assert.ok(cr.junctionVotes.length > 0);
163
+ const jv = cr.junctionVotes.find((v) => v.tier === "single-synonym");
164
+ assert.ok(jv, "expected a single-synonym junction vote");
165
+ const probe = cr.probes[jv.probe];
166
+ assert.equal(probe.exact.candidatesReturned, 0);
167
+ assert.equal(probe.singleSynonym.attempted, true);
168
+ assert.ok(probe.singleSynonym.candidatesReturned > 0);
169
+ assert.equal(probe.structural.tier, "single-synonym");
170
+ assert.equal(probe.structural.outcome, "accepted");
171
+ });
172
+
173
+ // ═══════════════════════════════════════════════════════════════════════════
174
+ // 5. double-synonym junction: verify the ladder position and fields.
175
+ // ═══════════════════════════════════════════════════════════════════════════
176
+
177
+ test("5. double-synonym tier: probe fields report single returned zero and double attempted when only the double tier can fire", async () => {
178
+ const m = mk(1);
179
+ await m.ingest(SYN_ATTR_CORPUS);
180
+ const { steps } = await trace(m, "crimson then crimson");
181
+ await m.store.close();
182
+
183
+ const step = climbStep(steps);
184
+ const cr = step.data.crossRegion ?? { probes: [] };
185
+ // Whether or not a double-synonym container was actually found (fixture-
186
+ // sensitive per test/51's own test 4), every probe that attempted the
187
+ // synonym ladder must report single/double consistently: double is only
188
+ // ever attempted when single returned zero.
189
+ for (const p of cr.probes) {
190
+ if (p.doubleSynonym.attempted) {
191
+ assert.equal(p.singleSynonym.candidatesReturned, 0);
192
+ assert.equal(p.singleSynonym.attempted, true);
193
+ }
194
+ if (p.singleSynonym.candidatesReturned > 0) {
195
+ assert.equal(p.doubleSynonym.attempted, false);
196
+ }
197
+ }
198
+ });
199
+
200
+ // ═══════════════════════════════════════════════════════════════════════════
201
+ // 6. structural resonance: variants, budget, hits, merged proposals,
202
+ // examined sequence, margin, noise floor, outcome.
203
+ // ═══════════════════════════════════════════════════════════════════════════
204
+
205
+ test("6. structural-resonance: eligible probes report variants, merged proposals and an examined sequence with effectiveScore = annScore * semanticConfidence", async () => {
206
+ const m = mk(1);
207
+ await m.ingest(SYN_ATTR_CORPUS);
208
+ const { steps } = await trace(m, "crimson then square");
209
+ await m.store.close();
210
+
211
+ const step = climbStep(steps);
212
+ const cr = step.data.crossRegion;
213
+ const eligible = cr.probes.filter((p) =>
214
+ p.resonance && p.resonance.outcome !== "ineligible"
215
+ );
216
+ assert.ok(
217
+ eligible.length > 0,
218
+ "expected at least one eligible resonance probe",
219
+ );
220
+ for (const p of eligible) {
221
+ const res = p.resonance;
222
+ assert.equal(res.variantBudget, m.cfg.haloQueryK);
223
+ assert.ok(Array.isArray(res.variants));
224
+ assert.ok(res.variants.length > 0);
225
+ for (const v of res.variants) {
226
+ assert.equal(typeof v.semanticConfidence, "number");
227
+ assert.equal(typeof v.annHitsReturned, "number");
228
+ }
229
+ assert.equal(typeof res.mergedProposals, "number");
230
+ assert.ok(Array.isArray(res.examined));
231
+ for (const c of res.examined) {
232
+ assert.ok(
233
+ Math.abs(c.effectiveScore - c.annScore * c.semanticConfidence) < 1e-9,
234
+ );
235
+ assert.ok(
236
+ [
237
+ "saturated",
238
+ "no-roots",
239
+ "nonpositive-idf",
240
+ "same-as-endpoint",
241
+ "same-as-selected",
242
+ "selected",
243
+ "contrastive-rival",
244
+ ].includes(c.outcome),
245
+ );
246
+ }
247
+ assert.equal(typeof res.noiseFloor, "number");
248
+ assert.ok(
249
+ ["empty", "no-valid-proposal", "margin-rejected", "accepted"].includes(
250
+ res.outcome,
251
+ ),
252
+ );
253
+ if (res.outcome === "accepted" || res.outcome === "margin-rejected") {
254
+ assert.equal(typeof res.contrastiveMargin, "number");
255
+ }
256
+ }
257
+ });
258
+
259
+ // ═══════════════════════════════════════════════════════════════════════════
260
+ // 7. resonance rejection outcomes.
261
+ // ═══════════════════════════════════════════════════════════════════════════
262
+
263
+ test("7. resonance outcomes: ineligible reasons are named and a margin-rejected probe reports a sub-noise-floor margin", async () => {
264
+ const mIneligible = mk(1);
265
+ await mIneligible.ingest(SYN_ATTR_CORPUS);
266
+ const { steps: ineligibleSteps } = await trace(
267
+ mIneligible,
268
+ "crimson beside square",
269
+ );
270
+ await mIneligible.store.close();
271
+
272
+ const ineligibleStep = climbStep(ineligibleSteps);
273
+ const ineligible = (ineligibleStep.data.crossRegion?.probes ?? []).filter(
274
+ (p) => p.resonance?.outcome === "ineligible",
275
+ );
276
+ assert.ok(ineligible.length > 0, "expected at least one ineligible probe");
277
+ for (const p of ineligible) {
278
+ assert.ok(Array.isArray(p.resonance.ineligibleReasons));
279
+ assert.ok(p.resonance.ineligibleReasons.length > 0);
280
+ for (const r of p.resonance.ineligibleReasons) {
281
+ assert.ok(
282
+ ["between-region", "not-both-strong", "not-both-known", "gap-too-large"]
283
+ .includes(r),
284
+ );
285
+ }
286
+ assert.equal(p.outcome, "resonance-ineligible");
287
+ }
288
+
289
+ const mMargin = mk(1);
290
+ await mMargin.ingest(SYN_ATTR_CORPUS);
291
+ const { steps: marginSteps } = await trace(mMargin, "crimson then square");
292
+ await mMargin.store.close();
293
+ const marginStep = climbStep(marginSteps);
294
+ const marginRejected = (marginStep.data.crossRegion?.probes ?? []).filter(
295
+ (p) => p.resonance?.outcome === "margin-rejected",
296
+ );
297
+ assert.ok(marginRejected.length > 0, "expected a margin-rejected probe");
298
+ for (const p of marginRejected) {
299
+ assert.ok(p.resonance.contrastiveMargin <= p.resonance.noiseFloor);
300
+ assert.equal(p.outcome, "resonance-rejected");
301
+ }
302
+ });
303
+
304
+ // ═══════════════════════════════════════════════════════════════════════════
305
+ // 8. cross-region vote weight fields.
306
+ // ═══════════════════════════════════════════════════════════════════════════
307
+
308
+ test("8. a linked cross-region vote reports confidence, evidenceBytes, mutualWeight, voteWeightPerRoot as numbers", async () => {
309
+ const m = mk(3);
310
+ await m.ingest(ATTR_CORPUS);
311
+ const { steps } = await trace(m, "red then circle");
312
+ await m.store.close();
313
+
314
+ const step = climbStep(steps);
315
+ const jv = step.data.crossRegion.junctionVotes[0];
316
+ assert.equal(typeof jv.confidence, "number");
317
+ assert.equal(typeof jv.evidenceBytes, "number");
318
+ assert.equal(typeof jv.mutualWeight, "number");
319
+ assert.equal(typeof jv.voteWeightPerRoot, "number");
320
+ assert.equal(jv.confidence, 1); // exact tier: confidence collapses to 1
321
+ });
322
+
323
+ // ═══════════════════════════════════════════════════════════════════════════
324
+ // 9. additive behavior — the new fields never change roots/ranked/answer.
325
+ // ═══════════════════════════════════════════════════════════════════════════
326
+
327
+ test("9. enabling the extended instrumentation never changes roots, ranked, or the answer", async () => {
328
+ const queries = [
329
+ "red then circle",
330
+ "square then crimson",
331
+ "crimson then square",
332
+ "crimson then crimson",
333
+ "the crimson near the square",
334
+ ];
335
+ for (const q of queries) {
336
+ const plain = mk(9);
337
+ await plain.ingest(SYN_ATTR_CORPUS);
338
+ const plainAns = await plain.respondText(q);
339
+ await plain.store.close();
340
+
341
+ const traced = mk(9);
342
+ await traced.ingest(SYN_ATTR_CORPUS);
343
+ const { ans: tracedAns, steps } = await trace(traced, q);
344
+ await traced.store.close();
345
+
346
+ assert.equal(tracedAns, plainAns, `answer differs for "${q}"`);
347
+
348
+ const step = climbStep(steps);
349
+ if (step?.data?.crossRegion) {
350
+ // The probe/resonance instrumentation is present but does not affect
351
+ // the pooled result: roots/ranked come from the SAME vote objects the
352
+ // probes were recorded from.
353
+ assert.ok(Array.isArray(step.data.crossRegion.probes));
354
+ }
355
+ }
356
+ });
@@ -1 +0,0 @@
1
- export {};