@hviana/sema 0.6.0 → 0.7.1
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/.github/workflows/release.yml +80 -0
- package/AGENTS.md +53 -9
- package/dist/src/meter.d.ts +1 -4
- package/dist/src/meter.js +0 -3
- package/dist/src/mind/attention.js +22 -20
- package/dist/src/mind/graph-search.d.ts +43 -9
- package/dist/src/mind/graph-search.js +82 -15
- package/dist/src/mind/junction.d.ts +13 -0
- package/dist/src/mind/junction.js +13 -0
- package/dist/src/mind/mechanisms/cover.js +23 -2
- package/dist/src/mind/mechanisms/prefix-completion.js +13 -11
- package/dist/src/mind/mechanisms/recall.js +8 -4
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -24
- package/dist/src/mind/pipeline-mechanism.js +13 -36
- package/dist/src/mind/pipeline.d.ts +23 -0
- package/dist/src/mind/pipeline.js +51 -3
- package/dist/src/mind/recognition.d.ts +6 -1
- package/dist/src/mind/recognition.js +11 -6
- package/dist/src/mind/resonance.js +48 -13
- package/dist/src/store.js +22 -1
- package/jsr.json +1 -1
- package/package.json +7 -2
- package/src/meter.ts +1 -4
- package/src/mind/attention.ts +22 -19
- package/src/mind/graph-search.ts +93 -16
- package/src/mind/junction.ts +13 -0
- package/src/mind/mechanisms/cover.ts +23 -4
- package/src/mind/mechanisms/prefix-completion.ts +13 -11
- package/src/mind/mechanisms/recall.ts +8 -4
- package/src/mind/pipeline-mechanism.ts +13 -42
- package/src/mind/pipeline.ts +87 -3
- package/src/mind/recognition.ts +19 -6
- package/src/mind/resonance.ts +79 -50
- package/src/store.ts +21 -1
- package/test/89-completion-recursion.test.mjs +230 -0
- package/test/90-connector-read-cap.test.mjs +130 -0
- package/test/91-branch-bytes-cache.test.mjs +152 -0
- package/test/93-regime-prediction.test.mjs +148 -0
- package/test/94-cross-region-budget.test.mjs +67 -0
- package/test/95-wide-resonance-removed.test.mjs +109 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// 91-branch-bytes-cache.test.mjs — reconstructing a node twice must not
|
|
2
|
+
// re-walk it.
|
|
3
|
+
//
|
|
4
|
+
// `bytesPrefix` rebuilds a node's bytes by descending the DAG, one `store.get`
|
|
5
|
+
// per node visited. `_prefix` consults `_bytesCache` for EVERY id but used to
|
|
6
|
+
// populate it only for leaves, so a BRANCH re-walked its entire subtree on every
|
|
7
|
+
// request — and because the DAG is hash-consed, the same children recur under
|
|
8
|
+
// many different parents.
|
|
9
|
+
//
|
|
10
|
+
// MEASURED on the trained store (18,938,834 nodes), ONE 1,314-byte query:
|
|
11
|
+
// _prefix calls (all levels) : 20,021,474
|
|
12
|
+
// distinct ids : 469,083 → 42.7x reuse
|
|
13
|
+
// avoidable by a cache : 19,552,391 → 97.7%
|
|
14
|
+
// top-level calls : 87,789 over 76,849 distinct (1.1x)
|
|
15
|
+
// hottest id : a single-byte leaf, 2,599,984 reconstructions
|
|
16
|
+
//
|
|
17
|
+
// The 1.1x at the top level is why this hid for so long: measured there, reuse
|
|
18
|
+
// looks absent and a cache looks worthless. All of the reuse is one level down.
|
|
19
|
+
//
|
|
20
|
+
// `_bytesCache` was already the right home — a byte-accounted BoundedMap with
|
|
21
|
+
// "smallest"/"clock" eviction, the configuration this codebase reserves for a
|
|
22
|
+
// TRANSPARENT cache (evicting costs a re-read and nothing else). Reconstruction
|
|
23
|
+
// is a pure function of the store, so it qualifies; only the population was
|
|
24
|
+
// missing.
|
|
25
|
+
//
|
|
26
|
+
// WHAT THIS FILE ACTUALLY GUARDS — read this before trusting it.
|
|
27
|
+
//
|
|
28
|
+
// The re-walk assertion routes through `_prefix` (a CAPPED read), because that
|
|
29
|
+
// is where the fix lives. `bytesPrefix(id, ALL)` short-circuits to `bytes()`
|
|
30
|
+
// (store.ts `bytesPrefix`: `maxLen >= 0x7fffffff → this.bytes(id)`), and
|
|
31
|
+
// `bytes()` has its OWN, pre-existing branch cache — so a re-walk assertion
|
|
32
|
+
// built on the ALL sentinel stays green when the `_prefix` fix is reverted and
|
|
33
|
+
// guards nothing (this file once did exactly that, and its header claimed the
|
|
34
|
+
// cause was "the node carries `flat` bytes", which is false: `flat` is
|
|
35
|
+
// STRUCTURAL — a branch stores its bytes flat iff every kid is an implicit
|
|
36
|
+
// single-byte leaf (store.ts `flatKidsBytes`) — and the fixture's node is
|
|
37
|
+
// non-flat, kids of real chunk nodes. There is no size threshold that drops
|
|
38
|
+
// `flat`).
|
|
39
|
+
//
|
|
40
|
+
// With the fix, a capped read that completes the node caches the BRANCH
|
|
41
|
+
// (`got < maxLen` guard), so a second capped read is a full-cache hit and costs
|
|
42
|
+
// 0 node reads; with the fix reverted it re-reads the root (1 node read) because
|
|
43
|
+
// only the LEAVES are cached. The signal is small at fixture scale precisely
|
|
44
|
+
// because the fixture's tree is shallow (root → flat chunks → leaves); the real
|
|
45
|
+
// 5.4–7.7× nodeRecords reduction is verified on the trained store instead.
|
|
46
|
+
//
|
|
47
|
+
// The TRUNCATION assertion at the end IS a real guard, verified red: with the
|
|
48
|
+
// `got < maxLen` condition removed, it fails with "a capped read poisoned the
|
|
49
|
+
// cache: the full read came back 29 bytes instead of 59". That is the
|
|
50
|
+
// dangerous half of this fix — a truncated prefix served as a node's whole
|
|
51
|
+
// content would silently corrupt every later reader — so that is the half worth
|
|
52
|
+
// having a test for.
|
|
53
|
+
//
|
|
54
|
+
// The fix's real effect is verified on the trained store instead: identical
|
|
55
|
+
// answers and identical `bytesRead`/`byteReads`/`junctionPops`, with
|
|
56
|
+
// `nodeRecords` falling 1,182,651 → 218,449 · 2,462,577 → 321,794 ·
|
|
57
|
+
// 2,492,035 → 381,020 (5.4–7.7×), and the 1,314-byte query 14.0 s → 9.4 s.
|
|
58
|
+
|
|
59
|
+
import { test } from "node:test";
|
|
60
|
+
import assert from "node:assert/strict";
|
|
61
|
+
import { Mind } from "../dist/src/index.js";
|
|
62
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
63
|
+
import { Meter } from "../dist/src/meter.js";
|
|
64
|
+
|
|
65
|
+
const ALL = 0x7fffffff;
|
|
66
|
+
|
|
67
|
+
test("a branch's bytes are reconstructed once, not re-walked", async () => {
|
|
68
|
+
const store = new SQliteStore({ path: ":memory:", D: 256 });
|
|
69
|
+
const mind = new Mind({ seed: 7, store });
|
|
70
|
+
// Long deposits, so the nodes have real interior structure to re-walk.
|
|
71
|
+
await mind.ingest([
|
|
72
|
+
["alpha", "the quick brown fox jumps over the lazy dog again and again"],
|
|
73
|
+
["beta", "the quick brown fox jumps over the lazy cat again and again"],
|
|
74
|
+
["gamma", "a quick brown fox once jumped over a lazy dog and then rested"],
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// A branch node with interior structure — the deposit's own root.
|
|
78
|
+
const tree = mind.perceive(
|
|
79
|
+
"the quick brown fox jumps over the lazy dog again and again",
|
|
80
|
+
);
|
|
81
|
+
const id = mind.resolve(new TextEncoder().encode(
|
|
82
|
+
"the quick brown fox jumps over the lazy dog again and again",
|
|
83
|
+
));
|
|
84
|
+
assert.ok(id !== null, "the deposited form resolves to a stored node");
|
|
85
|
+
assert.ok(tree.kids && tree.kids.length > 1, "and it has interior structure");
|
|
86
|
+
|
|
87
|
+
// Route through `_prefix`, not `bytes()`: the ALL sentinel short-circuits to
|
|
88
|
+
// `bytes()`, whose branch cache is pre-existing and would keep the assertion
|
|
89
|
+
// green even with the `_prefix` fix reverted. A capped read past the node's
|
|
90
|
+
// full length completes the walk (so the branch gets cached, `got < maxLen`)
|
|
91
|
+
// while staying off the ALL fast path. `contentLen` warms `_recCache`/`_lenCache`
|
|
92
|
+
// only, never `_bytesCache`, so the first read below starts cold.
|
|
93
|
+
const fullLen = store.contentLen(id);
|
|
94
|
+
store.meter = new Meter();
|
|
95
|
+
const capped = fullLen + 1;
|
|
96
|
+
|
|
97
|
+
// FIRST reconstruction — this one legitimately walks the subtree.
|
|
98
|
+
const first = store.bytesPrefix(id, capped);
|
|
99
|
+
const walked = store.meter.nodeRecords;
|
|
100
|
+
assert.ok(
|
|
101
|
+
first.length > 0,
|
|
102
|
+
"the node reconstructs to bytes",
|
|
103
|
+
);
|
|
104
|
+
// NON-VACUITY: if the first read did no node reads either, the store answered
|
|
105
|
+
// from some other cache and this test proves nothing about re-walking.
|
|
106
|
+
assert.ok(
|
|
107
|
+
walked > 0,
|
|
108
|
+
`the first reconstruction did no node reads at all (nodeRecords=${walked}), ` +
|
|
109
|
+
`so there is no re-walk for this test to detect`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// SECOND reconstruction of the SAME node — must be free.
|
|
113
|
+
const before = store.meter.nodeRecords;
|
|
114
|
+
const second = store.bytesPrefix(id, capped);
|
|
115
|
+
const again = store.meter.nodeRecords - before;
|
|
116
|
+
|
|
117
|
+
console.log(
|
|
118
|
+
` first reconstruction: ${walked} node reads; second: ${again}`,
|
|
119
|
+
);
|
|
120
|
+
assert.deepEqual(second, first, "the cached bytes are the same bytes");
|
|
121
|
+
assert.equal(
|
|
122
|
+
again,
|
|
123
|
+
0,
|
|
124
|
+
`re-reading the same node cost ${again} node reads (the first cost ` +
|
|
125
|
+
`${walked}) — reconstruction is a pure function of the store, so the ` +
|
|
126
|
+
`second request must be served from _bytesCache, not re-walked`,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// AND a truncated read must never be cached AS the whole node: serving a
|
|
130
|
+
// prefix as the full content would silently corrupt every later reader.
|
|
131
|
+
const cut = Math.max(1, first.length >> 1);
|
|
132
|
+
const store2 = new SQliteStore({ path: ":memory:", D: 256 });
|
|
133
|
+
const mind2 = new Mind({ seed: 7, store: store2 });
|
|
134
|
+
await mind2.ingest([[
|
|
135
|
+
"alpha",
|
|
136
|
+
"the quick brown fox jumps over the lazy dog again and again",
|
|
137
|
+
]]);
|
|
138
|
+
const id2 = mind2.resolve(new TextEncoder().encode(
|
|
139
|
+
"the quick brown fox jumps over the lazy dog again and again",
|
|
140
|
+
));
|
|
141
|
+
store2.bytesPrefix(id2, cut); // truncated FIRST, so a bad cache would poison
|
|
142
|
+
const whole = store2.bytesPrefix(id2, ALL);
|
|
143
|
+
assert.equal(
|
|
144
|
+
whole.length,
|
|
145
|
+
first.length,
|
|
146
|
+
`a capped read poisoned the cache: the full read came back ${whole.length} ` +
|
|
147
|
+
`bytes instead of ${first.length}`,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await store.close();
|
|
151
|
+
await store2.close();
|
|
152
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// 93-regime-prediction.test.mjs — the retrieval/composition regime (R8) is
|
|
2
|
+
// exposed as a structured trace step, without changing inference.
|
|
3
|
+
//
|
|
4
|
+
// After the FIRST mechanism runs (cover, which §2.6 places first and floors at
|
|
5
|
+
// 0), the market's whole outcome is already determined by the one cost ladder:
|
|
6
|
+
// the consensus climb runs exactly when `worthRunning(2 * STEP)` is true —
|
|
7
|
+
// CAST (floor 2·STEP) is the cheapest mechanism that first-touches it. An
|
|
8
|
+
// incumbent at or below that floor prunes CAST and, with it, the climb
|
|
9
|
+
// (retrieval); anything above — or no incumbent — runs the full market and the
|
|
10
|
+
// climb (composition). The step is purely observational: it is built only
|
|
11
|
+
// under a trace (optional-chaining short-circuits it otherwise), and it never
|
|
12
|
+
// alters which candidate wins. The assertions here check the payload's
|
|
13
|
+
// STRUCTURE and its consistency with the actual market outcome, never that
|
|
14
|
+
// inference itself changed.
|
|
15
|
+
|
|
16
|
+
import { test } from "node:test";
|
|
17
|
+
import assert from "node:assert/strict";
|
|
18
|
+
import { Mind } from "../dist/src/index.js";
|
|
19
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
20
|
+
|
|
21
|
+
const mk = (seed = 7) =>
|
|
22
|
+
new Mind({ seed, store: new SQliteStore({ path: ":memory:", D: 256 }) });
|
|
23
|
+
|
|
24
|
+
/** Collect the full step stream for one traced query. */
|
|
25
|
+
async function trace(mind, q) {
|
|
26
|
+
const steps = [];
|
|
27
|
+
const ans = await mind.respondText(q, (s) => steps.push(s));
|
|
28
|
+
return { steps, ans };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function regimeStep(steps) {
|
|
32
|
+
return steps.filter((s) => s.mechanism.at(-1) === "regimePrediction");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("1. a fully-grounding query predicts retrieval, and the market prunes", async () => {
|
|
36
|
+
const m = mk();
|
|
37
|
+
await m.ingest([
|
|
38
|
+
[
|
|
39
|
+
"who wrote romeo and juliet",
|
|
40
|
+
"William Shakespeare wrote Romeo and Juliet.",
|
|
41
|
+
],
|
|
42
|
+
]);
|
|
43
|
+
const { steps } = await trace(m, "who wrote romeo and juliet");
|
|
44
|
+
await m.store.close();
|
|
45
|
+
|
|
46
|
+
const r = regimeStep(steps);
|
|
47
|
+
assert.equal(r.length, 1, "exactly one regimePrediction step per response");
|
|
48
|
+
const d = r[0].data;
|
|
49
|
+
assert.equal(d.version, 1);
|
|
50
|
+
assert.equal(d.regime, "retrieval");
|
|
51
|
+
assert.ok(
|
|
52
|
+
d.incumbentGrade <= d.climbFloorGrade,
|
|
53
|
+
"incumbent grade at or under the climb floor",
|
|
54
|
+
);
|
|
55
|
+
assert.equal(
|
|
56
|
+
d.climbFloorGrade,
|
|
57
|
+
2,
|
|
58
|
+
"2·STEP = 2 (CAST's floor), in grade units",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("2. an ungroundable query predicts composition with no incumbent", async () => {
|
|
63
|
+
const m = mk();
|
|
64
|
+
await m.ingest([["alpha", "beta"]]);
|
|
65
|
+
const { steps } = await trace(m, "qzx zzjf vbnm plkj");
|
|
66
|
+
await m.store.close();
|
|
67
|
+
|
|
68
|
+
const r = regimeStep(steps);
|
|
69
|
+
assert.equal(r.length, 1);
|
|
70
|
+
assert.equal(r[0].data.regime, "composition");
|
|
71
|
+
assert.equal(
|
|
72
|
+
r[0].data.incumbentGrade,
|
|
73
|
+
null,
|
|
74
|
+
"nothing grounded — no incumbent",
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("3. a partially-grounding query predicts composition (one unexplained byte outbids every floor)", async () => {
|
|
79
|
+
const m = mk();
|
|
80
|
+
await m.ingest([
|
|
81
|
+
["the capital of france", "The capital of France is Paris."],
|
|
82
|
+
]);
|
|
83
|
+
const { steps } = await trace(m, "what is the capital of france");
|
|
84
|
+
await m.store.close();
|
|
85
|
+
|
|
86
|
+
const r = regimeStep(steps);
|
|
87
|
+
assert.equal(r[0].data.regime, "composition");
|
|
88
|
+
assert.ok(
|
|
89
|
+
r[0].data.incumbentGrade > r[0].data.climbFloorGrade,
|
|
90
|
+
`incumbent grade ${r[0].data.incumbentGrade} must exceed the climb floor ` +
|
|
91
|
+
`${
|
|
92
|
+
r[0].data.climbFloorGrade
|
|
93
|
+
} — PASS prices each unexplained byte at 1000`,
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("5. a fully-covered multi-move query (grade above the climb floor) still predicts composition", async () => {
|
|
98
|
+
// Three contiguous trained forms cover the whole query with three STEP moves
|
|
99
|
+
// and NO unexplained bytes — incumbent grade 3, which the old
|
|
100
|
+
// `worthRunning(CONCEPT + STEP)` boundary (≤ 11) mislabelled "retrieval"
|
|
101
|
+
// even though CAST's 2·STEP floor still runs the consensus climb. The
|
|
102
|
+
// regime must follow the climb, not the market's maximum floor.
|
|
103
|
+
const m = mk();
|
|
104
|
+
await m.ingest([
|
|
105
|
+
["abcdefgh", "ABCDEFGH"],
|
|
106
|
+
["ijklmnop", "IJKLMNOP"],
|
|
107
|
+
["qrstuvwx", "QRSTUVWX"],
|
|
108
|
+
]);
|
|
109
|
+
const { steps } = await trace(m, "abcdefghijklmnopqrstuvwx");
|
|
110
|
+
await m.store.close();
|
|
111
|
+
|
|
112
|
+
const r = regimeStep(steps);
|
|
113
|
+
assert.equal(r.length, 1);
|
|
114
|
+
const d = r[0].data;
|
|
115
|
+
assert.equal(d.regime, "composition", "the climb runs, so it is composition");
|
|
116
|
+
assert.ok(
|
|
117
|
+
d.incumbentGrade > d.climbFloorGrade,
|
|
118
|
+
`incumbent grade ${d.incumbentGrade} must exceed the climb floor ${d.climbFloorGrade}`,
|
|
119
|
+
);
|
|
120
|
+
// The prediction's own premise: the climb DID run (CAST first-touched it).
|
|
121
|
+
const climbed = steps.some((s) => s.mechanism.at(-1) === "climbConsensus");
|
|
122
|
+
assert.ok(climbed, "the consensus climb must actually run in this regime");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("4. the prediction is observational — an untraced response is byte-identical", async () => {
|
|
126
|
+
const mk2 = () =>
|
|
127
|
+
new Mind({
|
|
128
|
+
seed: 7,
|
|
129
|
+
store: new SQliteStore({ path: ":memory:", D: 256 }),
|
|
130
|
+
});
|
|
131
|
+
const q = "who wrote romeo and juliet";
|
|
132
|
+
const corpus = [[
|
|
133
|
+
"who wrote romeo and juliet",
|
|
134
|
+
"William Shakespeare wrote Romeo and Juliet.",
|
|
135
|
+
]];
|
|
136
|
+
|
|
137
|
+
const a = mk2();
|
|
138
|
+
await a.ingest(corpus);
|
|
139
|
+
const plain = await a.respondText(q);
|
|
140
|
+
await a.store.close();
|
|
141
|
+
|
|
142
|
+
const b = mk2();
|
|
143
|
+
await b.ingest(corpus);
|
|
144
|
+
const traced = await b.respondText(q, () => {});
|
|
145
|
+
await b.store.close();
|
|
146
|
+
|
|
147
|
+
assert.equal(traced, plain, "attaching a trace must not change the answer");
|
|
148
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// 94-cross-region-budget.test.mjs — the cross-region junction ladder shares ONE
|
|
2
|
+
// k·W allowance per evidence tier once atoms are hubs, instead of letting each
|
|
3
|
+
// candidate pair spend its own √N·W drift budget (attention.ts crossRegionVotes,
|
|
4
|
+
// §2.17's derived gate = traverse.atomIsHub).
|
|
5
|
+
//
|
|
6
|
+
// This is a PERFORMANCE regression test, not a behaviour test: the shared
|
|
7
|
+
// budget is byte-identical at every scale — a pair whose container is not
|
|
8
|
+
// reached within the allowance falls through to the resonance tier exactly as a
|
|
9
|
+
// per-pair walk that exhausted its own budget would — so the only observable is
|
|
10
|
+
// the meter's junctionPops counter. A ~4.3k-fact fixture is NOT optional:
|
|
11
|
+
// atomIsHub is false in every small-store suite, so a conventional fixture
|
|
12
|
+
// would pass while the per-pair drift (measured: 160k junction pops, 31% of
|
|
13
|
+
// think) is fully present. The atomIsHub assertion below fails loudly if the
|
|
14
|
+
// crossover ever moves, so this suite can never silently stop covering the
|
|
15
|
+
// branch it exists for.
|
|
16
|
+
|
|
17
|
+
import { test } from "node:test";
|
|
18
|
+
import assert from "node:assert/strict";
|
|
19
|
+
import { Mind } from "../dist/src/index.js";
|
|
20
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
21
|
+
import { atomIsHub, corpusN } from "../dist/src/mind/traverse.js";
|
|
22
|
+
|
|
23
|
+
test("crossRegion shares one k·W allowance per tier once atoms are hubs", async () => {
|
|
24
|
+
const m = new Mind({
|
|
25
|
+
seed: 7,
|
|
26
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
27
|
+
profile: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Common windows, each in ≤ √N contexts so the hub guard does NOT abstain —
|
|
31
|
+
// a non-hub cone is exactly the drift the shared budget must bound. Three
|
|
32
|
+
// such windows ("aaaa", "bbbb", "cccc") that never co-occur give the query
|
|
33
|
+
// below three strong regions and three pairs, every one of whose junction
|
|
34
|
+
// walks drifts through both windows' cones and finds no container.
|
|
35
|
+
const per = 60;
|
|
36
|
+
const corpus = [];
|
|
37
|
+
for (const w of ["aaaa", "bbbb", "cccc"]) {
|
|
38
|
+
for (let i = 0; i < per; i++) corpus.push([`${w} ${i}`, `a ${w} ${i}`]);
|
|
39
|
+
}
|
|
40
|
+
// Filler edge sources push N past atomIsHub (N > ~4096 at maxGroup=4).
|
|
41
|
+
for (let i = 0; i < 4300; i++) corpus.push([`filler-${i}`, `f${i}`]);
|
|
42
|
+
await m.ingest(corpus);
|
|
43
|
+
|
|
44
|
+
const N = corpusN(m);
|
|
45
|
+
assert.ok(
|
|
46
|
+
atomIsHub(m, N),
|
|
47
|
+
`fixture must cross atomIsHub (N=${N}); the shared budget is scale-gated ` +
|
|
48
|
+
`and this suite would otherwise assert nothing`,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await m.respondText("aaaa bbbb cccc");
|
|
52
|
+
|
|
53
|
+
const c = m.lastCost;
|
|
54
|
+
const crossPops = c.phases["climb.crossRegion"]?.counters.junctionPops ?? 0;
|
|
55
|
+
const k = m.cfg.recallQueryK * 2; // pre.k — the ladder's pair budget breadth
|
|
56
|
+
const W = m.space.maxGroup;
|
|
57
|
+
|
|
58
|
+
assert.ok(crossPops > 0, "the query must actually run cross-region walks");
|
|
59
|
+
assert.ok(
|
|
60
|
+
crossPops <= 2 * k * W,
|
|
61
|
+
`crossRegion junction pops ${crossPops} must stay within the shared ` +
|
|
62
|
+
`2·k·W = ${2 * k * W} allowance (exact + synonym tiers), not the ` +
|
|
63
|
+
`per-pair √N·W drift`,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await m.store.close();
|
|
67
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// 95-wide-resonance-removed.test.mjs — the substitution bridge and prefix
|
|
2
|
+
// completion must propose from BOUNDED sources, never from the exhaustive-√N
|
|
3
|
+
// ANN scan that `Precomputed.wideResonance()` used to run (removed; see
|
|
4
|
+
// pipeline-mechanism.ts's REMOVED note).
|
|
5
|
+
//
|
|
6
|
+
// This is a PERFORMANCE regression test, not a behaviour test: the wide list
|
|
7
|
+
// was a PROPOSAL source whose consumers byte-verify every candidate (§2.3), so
|
|
8
|
+
// removing it is byte-identical wherever the bounded sources supply the same
|
|
9
|
+
// candidate set — and the meter's phase map is the only observable that says
|
|
10
|
+
// whether the exhaustive machinery still exists. Red-on-revert: re-adding
|
|
11
|
+
// wideResonance re-creates the `wideResonance` phase (and, when the query's
|
|
12
|
+
// top hit clears conceptThreshold, a full-index ANN scan inside it), so the
|
|
13
|
+
// phase-absence assertion below fails.
|
|
14
|
+
|
|
15
|
+
import { test } from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { Mind } from "../dist/src/index.js";
|
|
18
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
19
|
+
|
|
20
|
+
const dec = (b) => new TextDecoder().decode(b).replace(/\0+$/, "");
|
|
21
|
+
|
|
22
|
+
// The test/49 resonance-proposed corpus: the query "what is the capital of
|
|
23
|
+
// france" resonates STRAIGHT to the trained "What is the capital of France?"
|
|
24
|
+
// (nearest whole-query hit, so its top hit clears conceptThreshold — the exact
|
|
25
|
+
// condition that made the old wideResonance go exhaustive) yet falls below the
|
|
26
|
+
// reach bar, so recall refuses and the bridge runs to recover the fact.
|
|
27
|
+
const CORPUS = [
|
|
28
|
+
["What is the capital of France?", "The capital of France is Paris."],
|
|
29
|
+
["What is the capital of Spain?", "The capital of Spain is Madrid."],
|
|
30
|
+
["What is the capital of Italy?", "The capital of Italy is Rome."],
|
|
31
|
+
// Lowercase mid-sentence occurrences attest the case-folded windows the
|
|
32
|
+
// substitution's corroboration gate requires.
|
|
33
|
+
["He wrote of france and of spain.", "Then he flew home to italy."],
|
|
34
|
+
["She spoke of france in her diary.", "Her diary told of france."],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
test("the bridge's refusal path never descends the exhaustive-√N ANN scan", async () => {
|
|
38
|
+
const m = new Mind({
|
|
39
|
+
seed: 7,
|
|
40
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
41
|
+
profile: true,
|
|
42
|
+
});
|
|
43
|
+
await m.ingest(CORPUS);
|
|
44
|
+
|
|
45
|
+
const r = await m.respond("what is the capital of france");
|
|
46
|
+
assert.ok(
|
|
47
|
+
dec(r.bytes).includes("Paris"),
|
|
48
|
+
`the bridge must still ground the fact through its bounded proposals, got ${
|
|
49
|
+
JSON.stringify(dec(r.bytes))
|
|
50
|
+
}`,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const c = m.lastCost;
|
|
54
|
+
// The fixture actually ran the bridge — without this, a silently-skipped
|
|
55
|
+
// fixture would pass the phase-absence assertion while asserting nothing.
|
|
56
|
+
assert.ok(
|
|
57
|
+
c.phases["substitutionBridge"],
|
|
58
|
+
"the fixture must run the substitution bridge",
|
|
59
|
+
);
|
|
60
|
+
// The exhaustive-√N phase is gone. Its removal is the whole point: the
|
|
61
|
+
// bridge's proposal source is the response's ONE top-k read (memoized), and
|
|
62
|
+
// every proposal is byte-verified downstream, so the full-index scan bought
|
|
63
|
+
// recall at O(index) cost for an O(k) need.
|
|
64
|
+
assert.equal(
|
|
65
|
+
c.phases["wideResonance"],
|
|
66
|
+
undefined,
|
|
67
|
+
"wideResonance (the exhaustive √N scan) must be removed from the response",
|
|
68
|
+
);
|
|
69
|
+
// The bridge phase itself accrues no content-index ANN reads: proposals come
|
|
70
|
+
// from the memoized `resonance()`, never a fresh exhaustive descend.
|
|
71
|
+
const bridgeAnn = c.phases["substitutionBridge"].counters.annVectorReads ?? 0;
|
|
72
|
+
assert.equal(
|
|
73
|
+
bridgeAnn,
|
|
74
|
+
0,
|
|
75
|
+
"the substitution bridge must not descend the content ANN on its own",
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
await m.store.close();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("prefix completion proposes from the write-side window index, not the ANN", async () => {
|
|
82
|
+
const m = new Mind({
|
|
83
|
+
seed: 7,
|
|
84
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
85
|
+
profile: true,
|
|
86
|
+
});
|
|
87
|
+
await m.ingest(CORPUS);
|
|
88
|
+
|
|
89
|
+
// A strict byte-prefix of the trained answer form. Its gist cannot rank its
|
|
90
|
+
// own continuation (cos falls below reachThreshold), so the content-addressed
|
|
91
|
+
// window walk is the correct proposal source — and the one prefix-completion
|
|
92
|
+
// now tries FIRST.
|
|
93
|
+
const r = await m.respond("The capital of France is");
|
|
94
|
+
assert.ok(
|
|
95
|
+
dec(r.bytes).includes("Paris"),
|
|
96
|
+
`expected the trained form through the content-addressed prefix supply, got ${
|
|
97
|
+
JSON.stringify(dec(r.bytes))
|
|
98
|
+
}`,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const c = m.lastCost;
|
|
102
|
+
assert.equal(
|
|
103
|
+
c.phases["wideResonance"],
|
|
104
|
+
undefined,
|
|
105
|
+
"wideResonance (the exhaustive √N scan) must be removed from the response",
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
await m.store.close();
|
|
109
|
+
});
|