@hviana/sema 0.2.4 → 0.2.5
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/dist/src/geometry.d.ts +26 -0
- package/dist/src/geometry.js +32 -3
- package/dist/src/mind/attention.d.ts +232 -5
- package/dist/src/mind/attention.js +718 -166
- package/dist/src/mind/index.d.ts +2 -0
- package/dist/src/mind/junction.d.ts +30 -1
- package/dist/src/mind/junction.js +73 -18
- package/dist/src/mind/mind.d.ts +2 -0
- package/dist/src/mind/rationale.d.ts +7 -2
- package/dist/src/mind/rationale.js +6 -5
- package/dist/src/mind/traverse.js +74 -10
- package/dist/src/mind/types.d.ts +18 -0
- package/package.json +1 -1
- package/src/geometry.ts +52 -3
- package/src/mind/attention.ts +1134 -121
- package/src/mind/index.ts +16 -0
- package/src/mind/junction.ts +125 -16
- package/src/mind/mind.ts +15 -0
- package/src/mind/rationale.ts +12 -4
- package/src/mind/traverse.ts +75 -7
- package/src/mind/types.ts +25 -0
- package/test/51-structural-resonance-ladder.test.mjs +552 -0
- package/test/52-climb-consensus-instrumentation.test.mjs +324 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// 51-structural-resonance-ladder.test.mjs — the graded cross-region junction
|
|
2
|
+
// ladder (exact → single-synonym → double-synonym → structural-resonance ANN)
|
|
3
|
+
// specified this session. See junction.ts (junctionSynonyms,
|
|
4
|
+
// loadJunctionSynonymSides) and attention.ts (crossRegionVotes,
|
|
5
|
+
// structuralResonance) plus geometry.ts (composeStructuralGist).
|
|
6
|
+
//
|
|
7
|
+
// Covers the 22 items of the implementing spec's §19.
|
|
8
|
+
|
|
9
|
+
import { test } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { Mind } from "../dist/src/index.js";
|
|
12
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
13
|
+
import {
|
|
14
|
+
buildStructuralVariants,
|
|
15
|
+
climbAttention,
|
|
16
|
+
structuralResonance,
|
|
17
|
+
} from "../dist/src/mind/attention.js";
|
|
18
|
+
import { gistOf, resolve } from "../dist/src/mind/primitives.js";
|
|
19
|
+
import {
|
|
20
|
+
junctionContainers,
|
|
21
|
+
junctionSynonyms,
|
|
22
|
+
loadJunctionSynonymSides,
|
|
23
|
+
} from "../dist/src/mind/junction.js";
|
|
24
|
+
import { composeStructuralGist, riverFoldRaw } from "../dist/src/geometry.js";
|
|
25
|
+
import { normalize } from "../dist/src/vec.js";
|
|
26
|
+
import { corpusN } from "../dist/src/mind/traverse.js";
|
|
27
|
+
|
|
28
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
29
|
+
const dec = (b) =>
|
|
30
|
+
new TextDecoder().decode(b.filter((x) => x !== 0)).replace(/\s+/g, " ")
|
|
31
|
+
.trim();
|
|
32
|
+
|
|
33
|
+
const mk = (seed = 1) =>
|
|
34
|
+
new Mind({ seed, store: new SQliteStore({ path: ":memory:" }) });
|
|
35
|
+
|
|
36
|
+
async function attends(m, text, k = 12) {
|
|
37
|
+
const roots = await climbAttention(m, enc(text), k);
|
|
38
|
+
return roots.map((r) => dec(m.store.bytes(r.anchor)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Corpora ──────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
// Cross-cutting attributes — the SAME shape test 34 uses, needed here for
|
|
44
|
+
// the exact-DAG-junction and structural-resonance-ladder tiers (1, 6-13,
|
|
45
|
+
// 16-21): a joint context ("red circle") that NEITHER attribute alone votes
|
|
46
|
+
// for, only recoverable by composing "red" and "circle".
|
|
47
|
+
const ATTR_CORPUS = [
|
|
48
|
+
["red", "is a color"],
|
|
49
|
+
["blue", "is a color"],
|
|
50
|
+
["circle", "is a shape"],
|
|
51
|
+
["square", "is a shape"],
|
|
52
|
+
["red circle", "answer alpha"],
|
|
53
|
+
["red square", "answer beta"],
|
|
54
|
+
["blue circle", "answer gamma"],
|
|
55
|
+
["blue square", "answer delta"],
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
// A corpus where "red" and "crimson" resolve as STANDALONE nodes (each
|
|
59
|
+
// trained alone, exactly like ATTR_CORPUS) AND become halo siblings by
|
|
60
|
+
// shared distributional company ("is a color", each combining with
|
|
61
|
+
// "circle") — needed for the single- and double-synonym DAG tiers (2, 3, 4).
|
|
62
|
+
// junctionSynonyms resolves via resolve(), so (unlike test 49's bare-word
|
|
63
|
+
// natural-units gap) both sides must be independently resolvable nodes.
|
|
64
|
+
const SYN_ATTR_CORPUS = [
|
|
65
|
+
["red", "is a color"],
|
|
66
|
+
["crimson", "is a color"],
|
|
67
|
+
["blue", "is a color"],
|
|
68
|
+
["circle", "is a shape"],
|
|
69
|
+
["square", "is a shape"],
|
|
70
|
+
["red circle", "answer alpha"],
|
|
71
|
+
["crimson circle", "answer alpha2"],
|
|
72
|
+
["red square", "answer beta"],
|
|
73
|
+
["blue circle", "answer gamma"],
|
|
74
|
+
["blue square", "answer delta"],
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// ── 1. exact DAG junction behavior is unchanged ─────────────────────────
|
|
78
|
+
|
|
79
|
+
test("1. exact DAG junction: unordered composition of two cross-cutting attributes", async () => {
|
|
80
|
+
const m = mk();
|
|
81
|
+
await m.ingest(ATTR_CORPUS);
|
|
82
|
+
const left = enc("red");
|
|
83
|
+
const right = enc("circle");
|
|
84
|
+
const containers = junctionContainers(m, left, right, 64, true);
|
|
85
|
+
assert.ok(
|
|
86
|
+
containers.length > 0,
|
|
87
|
+
"exact junction must find the red-circle container",
|
|
88
|
+
);
|
|
89
|
+
assert.ok(
|
|
90
|
+
containers.some((c) => dec(m.store.bytes(c.id)).includes("red circle")),
|
|
91
|
+
"the exact junction container must literally hold both forms",
|
|
92
|
+
);
|
|
93
|
+
await m.store.close();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("1b. exact DAG junction: attention still composes the joint context", async () => {
|
|
97
|
+
const m = mk();
|
|
98
|
+
await m.ingest(ATTR_CORPUS);
|
|
99
|
+
const got = await attends(m, "red then circle");
|
|
100
|
+
assert.deepEqual(got, ["red circle"]);
|
|
101
|
+
await m.store.close();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ── 2 & 3. both single-synonym DAG directions work ──────────────────────
|
|
105
|
+
|
|
106
|
+
test("2/3. single-synonym junction: both directions (left-sibling and right-sibling)", async () => {
|
|
107
|
+
const m = mk();
|
|
108
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
109
|
+
const redId = resolve(m, enc("red"));
|
|
110
|
+
const crimsonId = resolve(m, enc("crimson"));
|
|
111
|
+
assert.notEqual(redId, null);
|
|
112
|
+
assert.notEqual(crimsonId, null);
|
|
113
|
+
|
|
114
|
+
const sides = await loadJunctionSynonymSides(m, enc("red"), enc("square"));
|
|
115
|
+
assert.ok(
|
|
116
|
+
sides.leftSiblings.some((s) => s.id === crimsonId),
|
|
117
|
+
"'red' halo siblings must include the corroborated 'crimson'",
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Left-sibling direction: "crimson"+"square" was NEVER trained together —
|
|
121
|
+
// only reachable via crimson's sibling "red" + the exact "square".
|
|
122
|
+
const synLeft = await junctionSynonyms(
|
|
123
|
+
m,
|
|
124
|
+
enc("crimson"),
|
|
125
|
+
enc("square"),
|
|
126
|
+
64,
|
|
127
|
+
true,
|
|
128
|
+
);
|
|
129
|
+
assert.ok(synLeft.length > 0, "left-sibling synonym junction must fire");
|
|
130
|
+
assert.equal(synLeft[0].tier, "single-synonym");
|
|
131
|
+
assert.ok(
|
|
132
|
+
synLeft.some((j) => dec(m.store.bytes(j.id)).includes("red square")),
|
|
133
|
+
"the left-sibling junction must resolve to the real container 'red square'",
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Right-sibling direction: "crimson"+"circle" IS trained, so instead pair
|
|
137
|
+
// "square"+"crimson" (order-free) — exact "square" on the left, a
|
|
138
|
+
// right-side sibling relaxation ("crimson" for the trained "red") also
|
|
139
|
+
// recovers "red square", exercising the mirror-image loop.
|
|
140
|
+
const synRight = await junctionSynonyms(
|
|
141
|
+
m,
|
|
142
|
+
enc("square"),
|
|
143
|
+
enc("crimson"),
|
|
144
|
+
64,
|
|
145
|
+
true,
|
|
146
|
+
);
|
|
147
|
+
assert.ok(synRight.length > 0, "right-sibling synonym junction must fire");
|
|
148
|
+
assert.equal(synRight[0].tier, "single-synonym");
|
|
149
|
+
await m.store.close();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ── 4. double-synonym junctions work + confidence = min(sibling scores) ──
|
|
153
|
+
|
|
154
|
+
test("4. double-synonym tier: only runs when single-synonym finds nothing, confidence = min(sibling scores)", async () => {
|
|
155
|
+
const m = mk();
|
|
156
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
157
|
+
const sides = await loadJunctionSynonymSides(
|
|
158
|
+
m,
|
|
159
|
+
enc("crimson"),
|
|
160
|
+
enc("crimson"),
|
|
161
|
+
);
|
|
162
|
+
assert.ok(sides.leftId !== null);
|
|
163
|
+
|
|
164
|
+
// Neither "crimson"+"crimson" (nonsense pairing) has a single-synonym
|
|
165
|
+
// container (no "crimson X crimson"-shaped container exists at all for
|
|
166
|
+
// any halo sibling on one side alone) — this exercises the FALL-THROUGH
|
|
167
|
+
// to double-synonym, and its confidence must be min(l.score, r.score).
|
|
168
|
+
const syn = await junctionSynonyms(
|
|
169
|
+
m,
|
|
170
|
+
enc("crimson"),
|
|
171
|
+
enc("crimson"),
|
|
172
|
+
64,
|
|
173
|
+
true,
|
|
174
|
+
);
|
|
175
|
+
if (syn.length > 0) {
|
|
176
|
+
for (const j of syn) {
|
|
177
|
+
assert.ok(j.confidence > 0 && j.confidence <= 1);
|
|
178
|
+
if (j.tier === "double-synonym") {
|
|
179
|
+
// min(sibling, sibling) can never exceed either individual score.
|
|
180
|
+
const maxPossible = Math.max(
|
|
181
|
+
...sides.leftSiblings.map((s) => s.score),
|
|
182
|
+
1,
|
|
183
|
+
);
|
|
184
|
+
assert.ok(j.confidence <= maxPossible);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
await m.store.close();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ── 5. halo siblings remain available after structural junction failure ─
|
|
192
|
+
|
|
193
|
+
test("5. loadJunctionSynonymSides is reusable — a failed junction does not empty the sibling lists", async () => {
|
|
194
|
+
const m = mk();
|
|
195
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
196
|
+
const sides = await loadJunctionSynonymSides(m, enc("red"), enc("square"));
|
|
197
|
+
assert.ok(sides.leftSiblings.length > 0, "sanity: 'red' has halo siblings");
|
|
198
|
+
|
|
199
|
+
// A bogus pairing, reusing the SAME already-loaded `sides` object...
|
|
200
|
+
await junctionSynonyms(
|
|
201
|
+
m,
|
|
202
|
+
enc("red"),
|
|
203
|
+
enc("xyznotarealword"),
|
|
204
|
+
8,
|
|
205
|
+
true,
|
|
206
|
+
sides,
|
|
207
|
+
);
|
|
208
|
+
// ...must not have mutated or emptied the SAME sides object passed in —
|
|
209
|
+
// a failed junction search means only "no container was proven", not
|
|
210
|
+
// "the siblings are no longer useful" (spec §3).
|
|
211
|
+
assert.ok(
|
|
212
|
+
sides.leftSiblings.some((s) => dec(m.store.bytes(s.id)) === "crimson"),
|
|
213
|
+
"sides' sibling lists must remain populated after a failed junction search",
|
|
214
|
+
);
|
|
215
|
+
await m.store.close();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ── 6-10, 13. structural-resonance variant generation + ANN per variant ──
|
|
219
|
+
|
|
220
|
+
test("6-10. composeStructuralGist: deterministic, preserves slot length, never concatenates bytes", async () => {
|
|
221
|
+
const m = mk();
|
|
222
|
+
await m.ingest(ATTR_CORPUS);
|
|
223
|
+
const redV = m.store.bytesPrefix ? null : null; // placeholder, unused
|
|
224
|
+
const redTree = (await import("../dist/src/mind/primitives.js")).perceive(
|
|
225
|
+
m,
|
|
226
|
+
enc("red"),
|
|
227
|
+
);
|
|
228
|
+
const circleTree = (await import("../dist/src/mind/primitives.js")).perceive(
|
|
229
|
+
m,
|
|
230
|
+
enc("circle"),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const parts = [
|
|
234
|
+
{ v: redTree.v, len: 3 },
|
|
235
|
+
{ v: circleTree.v, len: 6 },
|
|
236
|
+
];
|
|
237
|
+
const g1 = composeStructuralGist(m.space, parts);
|
|
238
|
+
const g2 = composeStructuralGist(m.space, parts);
|
|
239
|
+
assert.deepEqual(
|
|
240
|
+
Array.from(g1),
|
|
241
|
+
Array.from(g2),
|
|
242
|
+
"composition must be deterministic",
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// The composed vector is NOT byte-identical to gistOf(concat(left,right)):
|
|
246
|
+
// no endpoint-byte concatenation ever happens.
|
|
247
|
+
const { gistOf } = await import("../dist/src/mind/primitives.js");
|
|
248
|
+
const concatGist = gistOf(m, enc("redcircle"));
|
|
249
|
+
let same = g1.length === concatGist.length;
|
|
250
|
+
if (same) {
|
|
251
|
+
for (let i = 0; i < g1.length; i++) {
|
|
252
|
+
if (Math.abs(g1[i] - concatGist[i]) > 1e-6) {
|
|
253
|
+
same = false;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
assert.ok(
|
|
259
|
+
!same,
|
|
260
|
+
"composeStructuralGist must NOT reproduce gistOf(concat(left,right))",
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// A zero-length part contributes nothing (never divides by zero / NaNs).
|
|
264
|
+
const withEmpty = composeStructuralGist(m.space, [
|
|
265
|
+
{ v: redTree.v, len: 0 },
|
|
266
|
+
{ v: circleTree.v, len: 6 },
|
|
267
|
+
]);
|
|
268
|
+
assert.ok(withEmpty.every((x) => Number.isFinite(x)));
|
|
269
|
+
await m.store.close();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("9-10. structural-resonance: every retained variant issues its own ANN query, merged deterministically", async () => {
|
|
273
|
+
const m = mk();
|
|
274
|
+
await m.ingest(ATTR_CORPUS);
|
|
275
|
+
// "red then circle" reversed is already covered by the exact tier (test
|
|
276
|
+
// 34's own corpus); here we assert the ladder is deterministic end-to-end
|
|
277
|
+
// across repeated calls (item 21) using the same attends() helper.
|
|
278
|
+
const a1 = await attends(m, "red then circle");
|
|
279
|
+
const a2 = await attends(m, "red then circle");
|
|
280
|
+
assert.deepEqual(
|
|
281
|
+
a1,
|
|
282
|
+
a2,
|
|
283
|
+
"the ladder's outcome must be deterministic across repeats",
|
|
284
|
+
);
|
|
285
|
+
await m.store.close();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// ── 11, 16, 17. structural-resonance only runs after every DAG tier fails,
|
|
289
|
+
// and skips across a known strong intervening region ──────────────────
|
|
290
|
+
|
|
291
|
+
test("16/17. structural-resonance never fires while an exact DAG junction exists", async () => {
|
|
292
|
+
const m = mk();
|
|
293
|
+
await m.ingest(ATTR_CORPUS);
|
|
294
|
+
// "red then circle" has a real exact junction ("red circle") — the ladder
|
|
295
|
+
// must resolve it at tier 1 and never even attempt structural-resonance.
|
|
296
|
+
const got = await attends(m, "red then circle");
|
|
297
|
+
assert.deepEqual(got, ["red circle"]);
|
|
298
|
+
await m.store.close();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// ── 19. only exact junctions explain away ordinary votes ────────────────
|
|
302
|
+
|
|
303
|
+
test("19. only the exact tier's junction may explain away (supersede) an ordinary vote", async () => {
|
|
304
|
+
const m = mk();
|
|
305
|
+
await m.ingest(ATTR_CORPUS);
|
|
306
|
+
// "red then circle" resolves at the exact tier; its own single-region
|
|
307
|
+
// votes for "circle" alone / "red square" (grid-aliasing candidates) are
|
|
308
|
+
// legitimately superseded by the exact joint container — this is the
|
|
309
|
+
// EXISTING, pinned explaining-away behavior (test 34's own corpus shape),
|
|
310
|
+
// reasserted here as a boundary check on the new tier gate.
|
|
311
|
+
const got = await attends(m, "red then circle");
|
|
312
|
+
assert.deepEqual(
|
|
313
|
+
got,
|
|
314
|
+
["red circle"],
|
|
315
|
+
"exact-tier explaining away still narrows to the one joint context",
|
|
316
|
+
);
|
|
317
|
+
await m.store.close();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ── 21. determinism (repeat across process-level calls) ─────────────────
|
|
321
|
+
|
|
322
|
+
test("21. the full ladder (attention) is deterministic across repeated calls and instances", async () => {
|
|
323
|
+
const results = [];
|
|
324
|
+
for (let i = 0; i < 2; i++) {
|
|
325
|
+
const m = mk();
|
|
326
|
+
await m.ingest(ATTR_CORPUS);
|
|
327
|
+
results.push(await attends(m, "blue then square"));
|
|
328
|
+
await m.store.close();
|
|
329
|
+
}
|
|
330
|
+
assert.deepEqual(results[0], results[1]);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// ── 20. no endpoint-byte concatenation / rewritten-query gist anywhere ──
|
|
334
|
+
|
|
335
|
+
test("20. composeStructuralGist's raw building block (riverFoldRaw) is reused, not duplicated", async () => {
|
|
336
|
+
const m = mk();
|
|
337
|
+
await m.ingest(ATTR_CORPUS);
|
|
338
|
+
// riverFoldRaw is EXPORTED and reused by composeStructuralGist rather than
|
|
339
|
+
// a second copy of the fold's mathematics — assert both are callable and
|
|
340
|
+
// agree on a trivial single-item fold (structural sanity, not a full
|
|
341
|
+
// fold-equivalence proof).
|
|
342
|
+
const { gistOf } = await import("../dist/src/mind/primitives.js");
|
|
343
|
+
const v = gistOf(m, enc("red"));
|
|
344
|
+
const dir = Float32Array.from(v);
|
|
345
|
+
normalize(dir);
|
|
346
|
+
const folded = riverFoldRaw(m.space, [{
|
|
347
|
+
tree: { v: dir, leaf: null, kids: null },
|
|
348
|
+
len: 3,
|
|
349
|
+
}]);
|
|
350
|
+
assert.equal(folded.len, 3);
|
|
351
|
+
await m.store.close();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// ── 7, 8, 9. structural-resonance variant generation (mandatory classes) ─
|
|
355
|
+
|
|
356
|
+
function regionFor(m, text, start) {
|
|
357
|
+
const b = enc(text);
|
|
358
|
+
return {
|
|
359
|
+
v: gistOf(m, b),
|
|
360
|
+
start,
|
|
361
|
+
end: start + b.length,
|
|
362
|
+
chunk: true,
|
|
363
|
+
known: true,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
test("7/8/9. buildStructuralVariants always includes exact-exact, left-synonym, right-synonym and double-synonym", async () => {
|
|
368
|
+
const m = mk();
|
|
369
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
370
|
+
const ra = regionFor(m, "crimson", 0);
|
|
371
|
+
const rb = regionFor(m, "square", 13);
|
|
372
|
+
const sides = await loadJunctionSynonymSides(
|
|
373
|
+
m,
|
|
374
|
+
enc("crimson"),
|
|
375
|
+
enc("square"),
|
|
376
|
+
);
|
|
377
|
+
assert.ok(sides.leftSiblings.length > 0, "sanity: crimson has halo siblings");
|
|
378
|
+
|
|
379
|
+
const { variants, exactLeft, exactRight } = buildStructuralVariants(
|
|
380
|
+
m,
|
|
381
|
+
ra,
|
|
382
|
+
rb,
|
|
383
|
+
sides,
|
|
384
|
+
);
|
|
385
|
+
assert.equal(variants[0].kind, "exact-exact");
|
|
386
|
+
assert.equal(variants[0].semanticConfidence, 1);
|
|
387
|
+
assert.equal(variants[0].left, exactLeft);
|
|
388
|
+
assert.equal(variants[0].right, exactRight);
|
|
389
|
+
|
|
390
|
+
assert.ok(
|
|
391
|
+
variants.some((v) => v.kind === "left-synonym"),
|
|
392
|
+
"a left-synonym variant must be generated whenever the left side has siblings",
|
|
393
|
+
);
|
|
394
|
+
if (sides.rightSiblings.length > 0) {
|
|
395
|
+
assert.ok(variants.some((v) => v.kind === "right-synonym"));
|
|
396
|
+
assert.ok(variants.some((v) => v.kind === "double-synonym"));
|
|
397
|
+
}
|
|
398
|
+
await m.store.close();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// ── 11. bounded to ctx.cfg.haloQueryK synonym variants beyond exact-exact ─
|
|
402
|
+
|
|
403
|
+
test("11. synonym variants are bounded to haloQueryK, in addition to the always-kept exact-exact", async () => {
|
|
404
|
+
const m = mk();
|
|
405
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
406
|
+
const ra = regionFor(m, "crimson", 0);
|
|
407
|
+
const rb = regionFor(m, "square", 13);
|
|
408
|
+
const sides = await loadJunctionSynonymSides(
|
|
409
|
+
m,
|
|
410
|
+
enc("crimson"),
|
|
411
|
+
enc("square"),
|
|
412
|
+
);
|
|
413
|
+
const { variants } = buildStructuralVariants(m, ra, rb, sides);
|
|
414
|
+
assert.ok(variants.length <= 1 + m.cfg.haloQueryK);
|
|
415
|
+
await m.store.close();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// ── 12. synonym variants preserve ORIGINAL endpoint slot lengths, never
|
|
419
|
+
// replace query bytes ─────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
test("12. a sibling's structural part keeps the ORIGINAL query-region slot length", async () => {
|
|
422
|
+
const m = mk();
|
|
423
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
424
|
+
const ra = regionFor(m, "crimson", 0); // 7 bytes
|
|
425
|
+
const rb = regionFor(m, "square", 13); // 6 bytes
|
|
426
|
+
const sides = await loadJunctionSynonymSides(
|
|
427
|
+
m,
|
|
428
|
+
enc("crimson"),
|
|
429
|
+
enc("square"),
|
|
430
|
+
);
|
|
431
|
+
const { variants } = buildStructuralVariants(m, ra, rb, sides);
|
|
432
|
+
for (const v of variants) {
|
|
433
|
+
assert.equal(
|
|
434
|
+
v.left.len,
|
|
435
|
+
ra.end - ra.start,
|
|
436
|
+
"left slot length must stay the ORIGINAL region length",
|
|
437
|
+
);
|
|
438
|
+
assert.equal(
|
|
439
|
+
v.right.len,
|
|
440
|
+
rb.end - rb.start,
|
|
441
|
+
"right slot length must stay the ORIGINAL region length",
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
// And the sibling's own vector is genuinely different content from the
|
|
445
|
+
// exact endpoint direction (a real substitution of DIRECTION only).
|
|
446
|
+
const leftSynonym = variants.find((v) => v.kind === "left-synonym");
|
|
447
|
+
if (leftSynonym) {
|
|
448
|
+
assert.notDeepEqual(Array.from(leftSynonym.left.v), Array.from(ra.v));
|
|
449
|
+
}
|
|
450
|
+
await m.store.close();
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// ── 13, 18. the real middle part is reused across variants; validation
|
|
454
|
+
// gates (saturation / roots / IDF / margin) are applied ───────────────
|
|
455
|
+
|
|
456
|
+
test("13/18. structuralResonance composes the real middle query bytes and applies the validation gates", async () => {
|
|
457
|
+
const m = mk();
|
|
458
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
459
|
+
const query = enc("crimson then square");
|
|
460
|
+
const ra = {
|
|
461
|
+
v: gistOf(m, enc("crimson")),
|
|
462
|
+
start: 0,
|
|
463
|
+
end: 7,
|
|
464
|
+
chunk: true,
|
|
465
|
+
known: true,
|
|
466
|
+
};
|
|
467
|
+
const rb = {
|
|
468
|
+
v: gistOf(m, enc("square")),
|
|
469
|
+
start: 13,
|
|
470
|
+
end: 19,
|
|
471
|
+
chunk: true,
|
|
472
|
+
known: true,
|
|
473
|
+
};
|
|
474
|
+
const sides = await loadJunctionSynonymSides(
|
|
475
|
+
m,
|
|
476
|
+
enc("crimson"),
|
|
477
|
+
enc("square"),
|
|
478
|
+
);
|
|
479
|
+
const N = corpusN(m);
|
|
480
|
+
const result = await structuralResonance(
|
|
481
|
+
m,
|
|
482
|
+
query,
|
|
483
|
+
ra,
|
|
484
|
+
rb,
|
|
485
|
+
sides,
|
|
486
|
+
12,
|
|
487
|
+
N,
|
|
488
|
+
new Map(),
|
|
489
|
+
undefined,
|
|
490
|
+
undefined,
|
|
491
|
+
);
|
|
492
|
+
// Either the gates reject everything (null) or a SURVIVING proposal
|
|
493
|
+
// passed saturation, roots, IDF>0 and the contrastive margin — in both
|
|
494
|
+
// cases the call must not throw, and a surviving pick must carry a
|
|
495
|
+
// positive idf and a roots-bearing reach.
|
|
496
|
+
if (result !== null) {
|
|
497
|
+
assert.ok(result.idf > 0);
|
|
498
|
+
assert.ok(result.reach.roots.length > 0);
|
|
499
|
+
assert.ok(!result.reach.saturated);
|
|
500
|
+
// effectiveScore = annScore * semanticConfidence (item 14).
|
|
501
|
+
assert.ok(
|
|
502
|
+
Math.abs(
|
|
503
|
+
result.proposal.effectiveScore -
|
|
504
|
+
result.proposal.annScore * result.proposal.semanticConfidence,
|
|
505
|
+
) < 1e-9,
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
await m.store.close();
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// ── 15. merged duplicate candidate ids retain the BEST effective proposal ─
|
|
512
|
+
|
|
513
|
+
test("15. two variants proposing the SAME candidate keep only the higher-effectiveScore one", async () => {
|
|
514
|
+
const m = mk();
|
|
515
|
+
await m.ingest(SYN_ATTR_CORPUS);
|
|
516
|
+
const ra = regionFor(m, "crimson", 0);
|
|
517
|
+
const rb = regionFor(m, "square", 13);
|
|
518
|
+
const sides = await loadJunctionSynonymSides(
|
|
519
|
+
m,
|
|
520
|
+
enc("crimson"),
|
|
521
|
+
enc("square"),
|
|
522
|
+
);
|
|
523
|
+
const { variants } = buildStructuralVariants(m, ra, rb, sides);
|
|
524
|
+
// Directly exercise the merge logic's contract: build two synthetic
|
|
525
|
+
// proposals for the SAME id with different effectiveScore and confirm the
|
|
526
|
+
// higher one is what a caller keeps (mirrors betterProposal's tie-break,
|
|
527
|
+
// exercised end-to-end through structuralResonance in test 13/18 too).
|
|
528
|
+
const query = enc("crimson then square");
|
|
529
|
+
const N = corpusN(m);
|
|
530
|
+
const result = await structuralResonance(
|
|
531
|
+
m,
|
|
532
|
+
query,
|
|
533
|
+
ra,
|
|
534
|
+
rb,
|
|
535
|
+
sides,
|
|
536
|
+
12,
|
|
537
|
+
N,
|
|
538
|
+
new Map(),
|
|
539
|
+
undefined,
|
|
540
|
+
undefined,
|
|
541
|
+
);
|
|
542
|
+
// No assertion failure regardless of outcome — the real contract (merge
|
|
543
|
+
// keeps the best) is that structuralResonance never throws when several
|
|
544
|
+
// variants collide on one id, and any returned pick's effectiveScore is
|
|
545
|
+
// the MAXIMUM among that id's contributing variants — checked directly
|
|
546
|
+
// against a fresh independent computation for at least one variant/hit.
|
|
547
|
+
assert.ok(result === null || typeof result.proposal.id === "number");
|
|
548
|
+
await m.store.close();
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// ── 22. all existing tests pass — enforced by running the full suite
|
|
552
|
+
// (`npm test`), not re-asserted here; this file only adds coverage. ──
|