@hviana/sema 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +71 -5
- package/dist/src/derive/src/deduction.d.ts +12 -1
- package/dist/src/derive/src/deduction.js +5 -1
- package/dist/src/derive/src/index.d.ts +1 -0
- package/dist/src/geometry.d.ts +3 -30
- package/dist/src/geometry.js +330 -82
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/meter.d.ts +171 -0
- package/dist/src/meter.js +269 -0
- package/dist/src/mind/attention.d.ts +0 -4
- package/dist/src/mind/attention.js +251 -23
- package/dist/src/mind/bridge.d.ts +10 -1
- package/dist/src/mind/bridge.js +179 -10
- package/dist/src/mind/canonical.d.ts +6 -1
- package/dist/src/mind/canonical.js +6 -1
- package/dist/src/mind/graph-search.d.ts +9 -0
- package/dist/src/mind/graph-search.js +59 -19
- package/dist/src/mind/junction.d.ts +10 -0
- package/dist/src/mind/junction.js +14 -0
- package/dist/src/mind/match.d.ts +40 -0
- package/dist/src/mind/match.js +125 -1
- package/dist/src/mind/mechanisms/cast.js +63 -6
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -34
- package/dist/src/mind/mechanisms/extraction.js +1 -88
- package/dist/src/mind/mechanisms/recall.d.ts +3 -0
- package/dist/src/mind/mechanisms/recall.js +77 -14
- package/dist/src/mind/mind.d.ts +55 -4
- package/dist/src/mind/mind.js +110 -91
- package/dist/src/mind/pipeline-mechanism.d.ts +33 -3
- package/dist/src/mind/pipeline-mechanism.js +179 -10
- package/dist/src/mind/pipeline.js +52 -10
- package/dist/src/mind/primitives.d.ts +11 -15
- package/dist/src/mind/primitives.js +47 -28
- package/dist/src/mind/reasoning.d.ts +7 -1
- package/dist/src/mind/reasoning.js +40 -8
- package/dist/src/mind/recognition.js +93 -20
- package/dist/src/mind/traverse.d.ts +11 -0
- package/dist/src/mind/traverse.js +58 -5
- package/dist/src/mind/types.d.ts +28 -5
- package/dist/src/store.d.ts +15 -0
- package/dist/src/store.js +91 -6
- package/package.json +1 -1
- package/src/derive/src/deduction.ts +15 -0
- package/src/derive/src/index.ts +1 -0
- package/src/geometry.ts +350 -122
- package/src/index.ts +1 -0
- package/src/meter.ts +333 -0
- package/src/mind/attention.ts +268 -31
- package/src/mind/bridge.ts +187 -10
- package/src/mind/canonical.ts +6 -1
- package/src/mind/graph-search.ts +60 -21
- package/src/mind/junction.ts +12 -0
- package/src/mind/match.ts +146 -1
- package/src/mind/mechanisms/cast.ts +62 -6
- package/src/mind/mechanisms/extraction.ts +2 -103
- package/src/mind/mechanisms/recall.ts +84 -17
- package/src/mind/mind.ts +139 -98
- package/src/mind/pipeline-mechanism.ts +203 -13
- package/src/mind/pipeline.ts +65 -8
- package/src/mind/primitives.ts +49 -33
- package/src/mind/reasoning.ts +39 -7
- package/src/mind/recognition.ts +89 -19
- package/src/mind/traverse.ts +59 -5
- package/src/mind/types.ts +28 -5
- package/src/store.ts +75 -6
- package/test/14-scaling.test.mjs +17 -7
- package/test/31-audit.test.mjs +4 -1
- package/test/33-multi-candidate.test.mjs +13 -1
- package/test/36-already-answered-fusion.test.mjs +10 -3
- package/test/46-recognise-multibyte-edge.test.mjs +3 -3
- package/test/53-cross-region-probe-instrumentation.test.mjs +36 -6
- package/test/55-cost-meter.test.mjs +284 -0
- package/test/56-bridge-identity-admission.test.mjs +209 -0
- package/test/57-fusion-order.test.mjs +104 -0
- package/test/58-subquantum-sites.test.mjs +112 -0
- package/test/59-fold-invariance.test.mjs +226 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// 55-cost-meter.test.mjs — the cross-stack computational-usage meter
|
|
2
|
+
// (src/meter.ts).
|
|
3
|
+
//
|
|
4
|
+
// The meter's four contracts, one test each:
|
|
5
|
+
// 1. OFF BY DEFAULT — an unprofiled Mind never attaches one, and the store
|
|
6
|
+
// is left with no meter after a response either way.
|
|
7
|
+
// 2. NEVER READ BY INFERENCE — profiled and unprofiled answers are
|
|
8
|
+
// byte-identical, and so is the provenance.
|
|
9
|
+
// 3. COUNTS ARE DETERMINISTIC — the same query on the same store meters
|
|
10
|
+
// identically (only the millisecond fields may differ).
|
|
11
|
+
// 4. THE WHOLE STACK REPORTS — store reads, perception, recognition,
|
|
12
|
+
// the mechanism market and the graph search all appear.
|
|
13
|
+
//
|
|
14
|
+
// Plus the two aggregation helpers (sumReports, formatReport) and the
|
|
15
|
+
// multi-turn lifecycle (respondTurn meters through the SAME beginResponse /
|
|
16
|
+
// endResponse pair respond() uses).
|
|
17
|
+
|
|
18
|
+
import { test } from "node:test";
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import { formatReport, Mind, sumReports } from "../dist/src/index.js";
|
|
21
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
22
|
+
|
|
23
|
+
const mk = (opts = {}) =>
|
|
24
|
+
new Mind({ seed: 1, store: new SQliteStore({ path: ":memory:" }), ...opts });
|
|
25
|
+
|
|
26
|
+
/** A small store with enough structure that every layer does some work. */
|
|
27
|
+
async function trained(opts) {
|
|
28
|
+
const mind = mk(opts);
|
|
29
|
+
await mind.ingest([
|
|
30
|
+
["the capital of France is", " Paris"],
|
|
31
|
+
["the capital of Japan is", " Tokyo"],
|
|
32
|
+
["the capital of Italy is", " Rome"],
|
|
33
|
+
["Paris is in", " Europe"],
|
|
34
|
+
["Tokyo is in", " Asia"],
|
|
35
|
+
]);
|
|
36
|
+
return mind;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const QUERY = "the capital of France is";
|
|
40
|
+
|
|
41
|
+
test("1. profiling is off by default and leaves no meter on the store", async () => {
|
|
42
|
+
const mind = await trained();
|
|
43
|
+
assert.equal(mind.lastCost, null);
|
|
44
|
+
await mind.respondText(QUERY);
|
|
45
|
+
assert.equal(mind.lastCost, null, "no report without { profile: true }");
|
|
46
|
+
assert.equal(mind.store.meter, null, "store meter never attached");
|
|
47
|
+
assert.equal(mind.meter, null);
|
|
48
|
+
await mind.store.close();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("2. profiling never changes the answer", async () => {
|
|
52
|
+
const plain = await trained();
|
|
53
|
+
const profiled = await trained({ profile: true });
|
|
54
|
+
|
|
55
|
+
for (const q of [QUERY, "Paris is in", "the capital of Japan is", "zzz"]) {
|
|
56
|
+
const a = await plain.respond(q);
|
|
57
|
+
const b = await profiled.respond(q);
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
Array.from(b.bytes),
|
|
60
|
+
Array.from(a.bytes),
|
|
61
|
+
`answer changed under profiling for "${q}"`,
|
|
62
|
+
);
|
|
63
|
+
assert.equal(b.provenance, a.provenance);
|
|
64
|
+
}
|
|
65
|
+
await plain.store.close();
|
|
66
|
+
await profiled.store.close();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("3. the meter is detached and the report published after each response", async () => {
|
|
70
|
+
const mind = await trained({ profile: true });
|
|
71
|
+
await mind.respondText(QUERY);
|
|
72
|
+
|
|
73
|
+
assert.equal(mind.meter, null, "meter torn down with the response");
|
|
74
|
+
assert.equal(mind.store.meter, null, "store detached — no cross-charging");
|
|
75
|
+
|
|
76
|
+
const r = mind.lastCost;
|
|
77
|
+
assert.ok(r, "a report was published");
|
|
78
|
+
assert.equal(r.version, 1);
|
|
79
|
+
assert.equal(r.queryBytes, QUERY.length);
|
|
80
|
+
assert.ok(r.elapsedMs >= 0);
|
|
81
|
+
await mind.store.close();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("4. counters are deterministic across identical calls on identical stores", async () => {
|
|
85
|
+
const a = await trained({ profile: true });
|
|
86
|
+
const b = await trained({ profile: true });
|
|
87
|
+
await a.respondText(QUERY);
|
|
88
|
+
await b.respondText(QUERY);
|
|
89
|
+
assert.deepEqual(
|
|
90
|
+
a.lastCost.counters,
|
|
91
|
+
b.lastCost.counters,
|
|
92
|
+
"same query + same store ⇒ same work; a diff here is a real regression",
|
|
93
|
+
);
|
|
94
|
+
// Phase CALL counts are deterministic too (their millisecond totals are not).
|
|
95
|
+
const calls = (r) =>
|
|
96
|
+
Object.fromEntries(
|
|
97
|
+
Object.entries(r.phases).map(([k, v]) => [k, v.calls]),
|
|
98
|
+
);
|
|
99
|
+
assert.deepEqual(calls(a.lastCost), calls(b.lastCost));
|
|
100
|
+
await a.store.close();
|
|
101
|
+
await b.store.close();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("5. every layer of the stack reports", async () => {
|
|
105
|
+
const mind = await trained({ profile: true });
|
|
106
|
+
await mind.respondText(QUERY);
|
|
107
|
+
const c = mind.lastCost.counters;
|
|
108
|
+
|
|
109
|
+
// Store layer — identity, content, structure.
|
|
110
|
+
assert.ok(c.leafLookups > 0, "content-addressed leaf lookups counted");
|
|
111
|
+
assert.ok(c.branchLookups > 0, "content-addressed branch lookups counted");
|
|
112
|
+
assert.ok(c.byteReads > 0, "node byte reads counted");
|
|
113
|
+
assert.ok(c.bytesRead > 0, "read VOLUME counted, not just call count");
|
|
114
|
+
|
|
115
|
+
// Mind layer — perception and recognition.
|
|
116
|
+
assert.ok(c.perceptions > 0, "perceptions counted");
|
|
117
|
+
assert.ok(c.perceivedBytes >= c.perceptions, "perceived volume counted");
|
|
118
|
+
assert.ok(c.recognitions > 0, "recognitions counted");
|
|
119
|
+
assert.ok(c.resolves > 0, "identity resolutions counted");
|
|
120
|
+
|
|
121
|
+
// The mechanism market — every mechanism was offered the query.
|
|
122
|
+
assert.ok(
|
|
123
|
+
c.mechanismFloors + c.mechanismSkips >= 5,
|
|
124
|
+
"each built-in mechanism's floor() was accounted",
|
|
125
|
+
);
|
|
126
|
+
assert.ok(c.mechanismRuns > 0, "at least one mechanism ran");
|
|
127
|
+
assert.ok(c.candidates > 0, "candidates weighed");
|
|
128
|
+
|
|
129
|
+
// Phases are per-mechanism and named after the mechanism itself.
|
|
130
|
+
const phases = Object.keys(mind.lastCost.phases);
|
|
131
|
+
assert.ok(phases.includes("think"));
|
|
132
|
+
assert.ok(phases.includes("articulate"));
|
|
133
|
+
assert.ok(
|
|
134
|
+
phases.some((p) => p.endsWith(".floor")),
|
|
135
|
+
"per-mechanism floor phases present",
|
|
136
|
+
);
|
|
137
|
+
await mind.store.close();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("6. zero-valued counters are omitted, and no bookkeeping leaks in", async () => {
|
|
141
|
+
const mind = await trained({ profile: true });
|
|
142
|
+
await mind.respondText(QUERY);
|
|
143
|
+
const c = mind.lastCost.counters;
|
|
144
|
+
for (const [k, v] of Object.entries(c)) {
|
|
145
|
+
assert.notEqual(v, 0, `${k} is zero and should have been dropped`);
|
|
146
|
+
assert.ok(!k.startsWith("_"), `${k} is meter bookkeeping, not work`);
|
|
147
|
+
}
|
|
148
|
+
await mind.store.close();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("7. a bigger query costs more than a smaller one", async () => {
|
|
152
|
+
const mind = await trained({ profile: true });
|
|
153
|
+
await mind.respondText("Paris");
|
|
154
|
+
const small = mind.lastCost.counters;
|
|
155
|
+
await mind.respondText(
|
|
156
|
+
"the capital of France is and the capital of Japan is and Paris is in",
|
|
157
|
+
);
|
|
158
|
+
const big = mind.lastCost.counters;
|
|
159
|
+
assert.ok(
|
|
160
|
+
big.perceivedBytes > small.perceivedBytes,
|
|
161
|
+
"perception cost tracks query length",
|
|
162
|
+
);
|
|
163
|
+
await mind.store.close();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("8. respondTurn meters through the same lifecycle", async () => {
|
|
167
|
+
const mind = await trained({ profile: true });
|
|
168
|
+
const conv = mind.beginConversation();
|
|
169
|
+
await mind.respondTurnText(conv, "the capital of France is");
|
|
170
|
+
const first = mind.lastCost;
|
|
171
|
+
assert.ok(first, "a turn publishes a report");
|
|
172
|
+
assert.ok(first.counters.perceptions > 0);
|
|
173
|
+
assert.equal(mind.store.meter, null, "detached after the turn too");
|
|
174
|
+
|
|
175
|
+
await mind.respondTurnText(conv, "Paris is in");
|
|
176
|
+
const second = mind.lastCost;
|
|
177
|
+
assert.notEqual(second, null);
|
|
178
|
+
assert.ok(
|
|
179
|
+
second.queryBytes > first.queryBytes,
|
|
180
|
+
"a turn is metered against the ACCUMULATED context, which grows",
|
|
181
|
+
);
|
|
182
|
+
mind.endConversation(conv);
|
|
183
|
+
await mind.store.close();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("9. conversation memos survive the shared lifecycle (turn 2 re-uses turn 1)", async () => {
|
|
187
|
+
const mind = await trained({ profile: true });
|
|
188
|
+
const conv = mind.beginConversation();
|
|
189
|
+
await mind.respondTurnText(conv, "the capital of France is");
|
|
190
|
+
await mind.respondTurnText(conv, "the capital of Japan is");
|
|
191
|
+
const c = mind.lastCost.counters;
|
|
192
|
+
// The conversation's perceive/recognise memos are swapped in by
|
|
193
|
+
// beginResponse; the second turn must therefore HIT them for the prefix it
|
|
194
|
+
// shares with the first. A zero here means respondTurn stopped carrying
|
|
195
|
+
// the conversation's persistent state — the exact drift the shared
|
|
196
|
+
// lifecycle exists to prevent.
|
|
197
|
+
assert.ok(
|
|
198
|
+
(c.perceiveHits ?? 0) > 0 || (c.recogniseHits ?? 0) > 0,
|
|
199
|
+
"the prefix's earlier results were re-used across turns",
|
|
200
|
+
);
|
|
201
|
+
mind.endConversation(conv);
|
|
202
|
+
await mind.store.close();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("9b. every phase reports the work done inside it, and phases nest", async () => {
|
|
206
|
+
const mind = await trained({ profile: true });
|
|
207
|
+
await mind.respondText(QUERY);
|
|
208
|
+
const { phases, counters } = mind.lastCost;
|
|
209
|
+
|
|
210
|
+
// Each phase carries counter deltas, not just a duration.
|
|
211
|
+
const withWork = Object.entries(phases).filter(([, p]) =>
|
|
212
|
+
Object.keys(p.counters).length > 0
|
|
213
|
+
);
|
|
214
|
+
assert.ok(withWork.length > 0, "phases attribute work, not only time");
|
|
215
|
+
|
|
216
|
+
// `think` is the outermost inference phase, so its counters must DOMINATE
|
|
217
|
+
// every phase nested inside it — that is what "inclusive" means, and it is
|
|
218
|
+
// the property that makes the deltas readable as attribution.
|
|
219
|
+
const think = phases["think"];
|
|
220
|
+
assert.ok(think, "think is a phase");
|
|
221
|
+
for (const [name, p] of Object.entries(phases)) {
|
|
222
|
+
if (name === "think" || name === "articulate") continue;
|
|
223
|
+
for (const [k, v] of Object.entries(p.counters)) {
|
|
224
|
+
assert.ok(
|
|
225
|
+
(think.counters[k] ?? 0) >= v,
|
|
226
|
+
`${name}.${k}=${v} exceeds think.${k}=${think.counters[k] ?? 0} — ` +
|
|
227
|
+
`phases must nest`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// And no phase may claim more of a counter than the whole response spent.
|
|
232
|
+
for (const [name, p] of Object.entries(phases)) {
|
|
233
|
+
for (const [k, v] of Object.entries(p.counters)) {
|
|
234
|
+
assert.ok(
|
|
235
|
+
(counters[k] ?? 0) >= v,
|
|
236
|
+
`${name}.${k}=${v} exceeds the response total ${counters[k] ?? 0}`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
await mind.store.close();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("9c. a recursive read is one read, not one per node descended", async () => {
|
|
244
|
+
const mind = await trained({ profile: true });
|
|
245
|
+
await mind.respondText(QUERY);
|
|
246
|
+
const c = mind.lastCost.counters;
|
|
247
|
+
// bytesRead is the byte VOLUME, byteReads the number of read REQUESTS.
|
|
248
|
+
// Reconstructing a branch used to charge one read per node descended, which
|
|
249
|
+
// made byteReads track tree size — it read as ~1 byte per read. Real reads
|
|
250
|
+
// return whole forms, so the volume must comfortably exceed the count.
|
|
251
|
+
assert.ok(c.byteReads > 0 && c.bytesRead > 0);
|
|
252
|
+
assert.ok(
|
|
253
|
+
c.bytesRead > c.byteReads,
|
|
254
|
+
`bytesRead ${c.bytesRead} should exceed byteReads ${c.byteReads} — ` +
|
|
255
|
+
`a per-node charge would invert this`,
|
|
256
|
+
);
|
|
257
|
+
await mind.store.close();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("10. sumReports and formatReport aggregate a battery", async () => {
|
|
261
|
+
const mind = await trained({ profile: true });
|
|
262
|
+
const reports = [];
|
|
263
|
+
for (const q of [QUERY, "Paris is in", "the capital of Italy is"]) {
|
|
264
|
+
await mind.respondText(q);
|
|
265
|
+
reports.push(mind.lastCost);
|
|
266
|
+
}
|
|
267
|
+
const total = sumReports(reports);
|
|
268
|
+
assert.equal(total.version, 1);
|
|
269
|
+
assert.equal(
|
|
270
|
+
total.queryBytes,
|
|
271
|
+
reports.reduce((s, r) => s + r.queryBytes, 0),
|
|
272
|
+
);
|
|
273
|
+
for (const key of Object.keys(reports[0].counters)) {
|
|
274
|
+
assert.equal(
|
|
275
|
+
total.counters[key],
|
|
276
|
+
reports.reduce((s, r) => s + (r.counters[key] ?? 0), 0),
|
|
277
|
+
`${key} did not sum`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const text = formatReport(total);
|
|
281
|
+
assert.match(text, /^cost: /);
|
|
282
|
+
assert.match(text, /perceptions/);
|
|
283
|
+
await mind.store.close();
|
|
284
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// 56-bridge-identity-admission.test.mjs — the bridge's ZERO-SUBSTITUTION
|
|
2
|
+
// admission: a candidate the alignment explains END TO END on BOTH sides,
|
|
3
|
+
// separated from the query only by material that does not change what the
|
|
4
|
+
// text SAYS, is the SAME learnt form and grounds through its own edge.
|
|
5
|
+
//
|
|
6
|
+
// "Material that does not change what it says" has ONE definition here, and
|
|
7
|
+
// it is read from the corpus, never tuned (AGENTS §2.7, corpus-global
|
|
8
|
+
// population): a span is EXPLAINED when it is sub-quantum (< W — typographic
|
|
9
|
+
// glue) or every W-window in it is COMMON by the store's own climb (the
|
|
10
|
+
// ascent saturates, or it reaches a majority of contexts). A window that
|
|
11
|
+
// reaches NOTHING is novel content and is never explained — the reading that
|
|
12
|
+
// separates a droppable "the process of " from a load-bearing "heavy ".
|
|
13
|
+
//
|
|
14
|
+
// THE GAP THIS CLOSES (measured on the 17.9M-node trained store). The query
|
|
15
|
+
// `Who wrote Romeo and Juliet?` against the trained `Who wrote "Romeo and
|
|
16
|
+
// Juliet"?` — two inserted quote characters — returned honest silence. The
|
|
17
|
+
// gist is a STRUCTURAL signature, so a mid-string insertion shifts every fold
|
|
18
|
+
// boundary after it: the pair scored cos 0.377, BELOW unrelated neighbours
|
|
19
|
+
// like "Who wrote the opera Carmen??" (0.603). Recall's identity tier gates
|
|
20
|
+
// on identityBar (0.969 at that length) and its reach tiers on 0.875, so no
|
|
21
|
+
// gist-based tier could ever see it. Only byte-exact alignment can — and the
|
|
22
|
+
// bridge, which does exactly that, refused it for producing NO substitution.
|
|
23
|
+
//
|
|
24
|
+
// WHAT MUST NOT REGRESS (the documented prefix trap, bridge.ts): when the
|
|
25
|
+
// query is a strict byte-PREFIX of several candidates that continue
|
|
26
|
+
// differently, the bridge must still refuse — nothing corroborates picking
|
|
27
|
+
// one continuation over another, and the surplus IS the invented answer.
|
|
28
|
+
// The two shapes are separated by the candidate-side test: an identity
|
|
29
|
+
// candidate has ≤ W bytes beyond the alignment, a prefix candidate has its
|
|
30
|
+
// whole completion beyond it.
|
|
31
|
+
|
|
32
|
+
import { test } from "node:test";
|
|
33
|
+
import assert from "node:assert/strict";
|
|
34
|
+
import { Mind } from "../dist/src/index.js";
|
|
35
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
36
|
+
|
|
37
|
+
const mk = () =>
|
|
38
|
+
new Mind({ seed: 1, store: new SQliteStore({ path: ":memory:" }) });
|
|
39
|
+
|
|
40
|
+
/** Corroboration for the query's own windows: the bridge only aligns against
|
|
41
|
+
* content whose W-windows are corpus-attested, so the fixture trains the
|
|
42
|
+
* phrasing family, not one isolated sentence. */
|
|
43
|
+
const TRAIN = [
|
|
44
|
+
['Who wrote "Romeo and Juliet"?', " William Shakespeare wrote it."],
|
|
45
|
+
['Who wrote "Hamlet"?', " William Shakespeare wrote it."],
|
|
46
|
+
['Who wrote "Macbeth"?', " William Shakespeare wrote it."],
|
|
47
|
+
["Who wrote the opera Carmen?", " Georges Bizet wrote it."],
|
|
48
|
+
["Who wrote about Romeo and Juliet in an essay?", " A critic did."],
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
async function trained() {
|
|
52
|
+
const mind = mk();
|
|
53
|
+
await mind.ingest(TRAIN);
|
|
54
|
+
return mind;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test("1. a query differing only by typographic glue reaches the trained form", async () => {
|
|
58
|
+
const mind = await trained();
|
|
59
|
+
// Same form as the trained question, minus the two quote characters.
|
|
60
|
+
const a = await mind.respondText("Who wrote Romeo and Juliet?");
|
|
61
|
+
assert.match(
|
|
62
|
+
a,
|
|
63
|
+
/Shakespeare/,
|
|
64
|
+
`expected the trained continuation, got ${JSON.stringify(a)}`,
|
|
65
|
+
);
|
|
66
|
+
await mind.store.close();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("2. the exact trained form still answers (no path was displaced)", async () => {
|
|
70
|
+
const mind = await trained();
|
|
71
|
+
const a = await mind.respondText('Who wrote "Romeo and Juliet"?');
|
|
72
|
+
assert.match(a, /Shakespeare/);
|
|
73
|
+
await mind.store.close();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("3. a DIFFERENT trained question is not answered by its neighbour", async () => {
|
|
77
|
+
const mind = await trained();
|
|
78
|
+
const a = await mind.respondText("Who wrote the opera Carmen?");
|
|
79
|
+
assert.match(
|
|
80
|
+
a,
|
|
81
|
+
/Bizet/,
|
|
82
|
+
"the identity admission must not let a near neighbour stand in",
|
|
83
|
+
);
|
|
84
|
+
await mind.store.close();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("4. PREFIX TRAP: a strict prefix of several candidates is still refused", async () => {
|
|
88
|
+
const mind = mk();
|
|
89
|
+
// Three facts sharing one prefix, each continuing differently. A query
|
|
90
|
+
// that IS that prefix has no corroboration for any single completion; the
|
|
91
|
+
// candidate-side surplus is exactly the answer that would be invented.
|
|
92
|
+
await mind.ingest([
|
|
93
|
+
["The capital city of France is Paris.", " It sits on the Seine."],
|
|
94
|
+
["The capital city of Spain is Madrid.", " It sits on the Manzanares."],
|
|
95
|
+
["The capital city of Italy is Rome.", " It sits on the Tiber."],
|
|
96
|
+
]);
|
|
97
|
+
const a = await mind.respondText("The capital city of");
|
|
98
|
+
// Whatever the pipeline does with this, the bridge must not manufacture a
|
|
99
|
+
// country: an answer naming one of the three would be the invented
|
|
100
|
+
// completion the trap describes.
|
|
101
|
+
const named = ["Paris", "Madrid", "Rome"].filter((c) => a.includes(c));
|
|
102
|
+
assert.ok(
|
|
103
|
+
named.length !== 1 || a.includes("capital city of"),
|
|
104
|
+
`bridge invented a single completion (${named[0]}) for a bare prefix: ` +
|
|
105
|
+
JSON.stringify(a),
|
|
106
|
+
);
|
|
107
|
+
await mind.store.close();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("5. honest silence survives — an unrelated query still grounds nothing", async () => {
|
|
111
|
+
const mind = await trained();
|
|
112
|
+
const a = await mind.respondText("What is the zorblatt frequency?");
|
|
113
|
+
assert.equal(
|
|
114
|
+
a.length,
|
|
115
|
+
0,
|
|
116
|
+
`expected silence, got ${JSON.stringify(a)} — the identity admission ` +
|
|
117
|
+
`must not lower the bar for unrelated content`,
|
|
118
|
+
);
|
|
119
|
+
await mind.store.close();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("6. determinism: the admission is byte-exact, not scored", async () => {
|
|
123
|
+
const a = await trained();
|
|
124
|
+
const b = await trained();
|
|
125
|
+
const x = await a.respondText("Who wrote Romeo and Juliet?");
|
|
126
|
+
const y = await b.respondText("Who wrote Romeo and Juliet?");
|
|
127
|
+
assert.equal(x, y);
|
|
128
|
+
await a.store.close();
|
|
129
|
+
await b.store.close();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ── The scaffolding reading ────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
test("7. an omitted CORPUS-COMMON span is explained; a RARE one is not", async () => {
|
|
135
|
+
const { leafIdRun } = await import("../dist/src/mind/canonical.js");
|
|
136
|
+
const { corpusN, edgeAncestors } = await import(
|
|
137
|
+
"../dist/src/mind/traverse.js"
|
|
138
|
+
);
|
|
139
|
+
const { dominates } = await import("../dist/src/geometry.js");
|
|
140
|
+
|
|
141
|
+
const mind = mk();
|
|
142
|
+
const T = [];
|
|
143
|
+
for (const x of ["A", "B", "C", "D", "E", "F", "G", "H"]) {
|
|
144
|
+
T.push([`What is the process of ${x}ation?`, ` ${x}ation is a process.`]);
|
|
145
|
+
T.push([`Tell me the process of ${x}ing.`, ` ${x}ing is a process.`]);
|
|
146
|
+
}
|
|
147
|
+
T.push(["Is heavy water wet?", " No, heavy water is not wet."]);
|
|
148
|
+
await mind.ingest(T);
|
|
149
|
+
|
|
150
|
+
const N = corpusN(mind);
|
|
151
|
+
const W = mind.space.maxGroup;
|
|
152
|
+
// The predicate under test, mirrored exactly (bridge.ts explainedSpan).
|
|
153
|
+
const explained = (text) => {
|
|
154
|
+
const b = new TextEncoder().encode(text);
|
|
155
|
+
if (b.length < W) return true;
|
|
156
|
+
for (let o = 0; o + W <= b.length; o++) {
|
|
157
|
+
const ids = leafIdRun(mind, b, o, o + W);
|
|
158
|
+
if (ids === null) return false;
|
|
159
|
+
const wid = mind.store.findBranch(ids);
|
|
160
|
+
if (wid === null) return false;
|
|
161
|
+
const r = edgeAncestors(mind, wid, N);
|
|
162
|
+
if (r.saturated) continue;
|
|
163
|
+
if (r.roots.length === 0) return false;
|
|
164
|
+
if (!dominates(r.contextsReached, N)) return false;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
assert.equal(
|
|
170
|
+
explained("the process of "),
|
|
171
|
+
true,
|
|
172
|
+
"corpus-common scaffolding must be droppable",
|
|
173
|
+
);
|
|
174
|
+
assert.equal(
|
|
175
|
+
explained("heavy "),
|
|
176
|
+
false,
|
|
177
|
+
"rare, discriminative content must NEVER be written off as scaffolding — " +
|
|
178
|
+
"a candidate omitting it answers a different, narrower question",
|
|
179
|
+
);
|
|
180
|
+
// The reading, not just the population: a window reaching NOTHING is novel
|
|
181
|
+
// content. Going through reachOf (which maps both saturated and
|
|
182
|
+
// empty-rooted to Infinity) called "heavy " scaffolding and answered
|
|
183
|
+
// "Is water wet?" with "No, heavy water is not wet.".
|
|
184
|
+
assert.equal(
|
|
185
|
+
explained("zqxjwv "),
|
|
186
|
+
false,
|
|
187
|
+
"untrained content is never explained",
|
|
188
|
+
);
|
|
189
|
+
await mind.store.close();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("8. EXACT query coverage — a sub-quantum query-side difference is NOT glue", async () => {
|
|
193
|
+
const mind = mk();
|
|
194
|
+
// A trained arithmetic fact, and a query differing from it only inside a
|
|
195
|
+
// sub-quantum span — but that span is DIGITS, and digits are content.
|
|
196
|
+
await mind.ingest([
|
|
197
|
+
["what is 2+2?", " 2+2 is 4."],
|
|
198
|
+
["what is 3+3?", " 3+3 is 6."],
|
|
199
|
+
["what is 5+5?", " 5+5 is 10."],
|
|
200
|
+
]);
|
|
201
|
+
const a = await mind.respondText("what is 2^10?");
|
|
202
|
+
assert.ok(
|
|
203
|
+
!a.includes("4."),
|
|
204
|
+
`answered a DIFFERENT arithmetic question: ${JSON.stringify(a)} — the ` +
|
|
205
|
+
`identity admission must require covered === query.length, with no ` +
|
|
206
|
+
`sub-quantum slack on the query side`,
|
|
207
|
+
);
|
|
208
|
+
await mind.store.close();
|
|
209
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// 57-fusion-order.test.mjs — a fused multi-topic answer must READ in the
|
|
2
|
+
// order the question posed its topics.
|
|
3
|
+
//
|
|
4
|
+
// fuseAttention sorts its pieces by `start`, their position in the query.
|
|
5
|
+
// Every attention ROOT carries its own start. `primary` did not: it was
|
|
6
|
+
// given forest[0].start — the FIRST root's position — which is primary's own
|
|
7
|
+
// source only when primary happens to come from that root. When it does
|
|
8
|
+
// not, primary sorts to a position it never occupied.
|
|
9
|
+
//
|
|
10
|
+
// Observed live on the 17.9M-node trained store:
|
|
11
|
+
// "What is the capital of France? And what is 2 + 2?"
|
|
12
|
+
// -> "4The capital city of France is Paris."
|
|
13
|
+
// Both pieces were correct; only the order was wrong. The ALU result, whose
|
|
14
|
+
// evidence is the "2 + 2" span at the END of the query, had inherited the
|
|
15
|
+
// France root's start of 0.
|
|
16
|
+
//
|
|
17
|
+
// primary's position is now the earliest query byte its own grounding stands
|
|
18
|
+
// on: `accounted` when non-empty, else the computed span (a pure computation
|
|
19
|
+
// is priced out of `accounted` by cover — the same cost-ladder-vs-coverage
|
|
20
|
+
// distinction think() already draws for the fusion remainder).
|
|
21
|
+
//
|
|
22
|
+
// NOT under test: the missing separator between the pieces. joinWithBridge
|
|
23
|
+
// splices only a LEARNT connector; when the corpus holds none the pieces join
|
|
24
|
+
// bare. Inventing a space would synthesize bytes the store never saw, which
|
|
25
|
+
// this system does not do — bare joining is the honest degradation.
|
|
26
|
+
|
|
27
|
+
import { test } from "node:test";
|
|
28
|
+
import assert from "node:assert/strict";
|
|
29
|
+
import { Mind } from "../dist/src/index.js";
|
|
30
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
31
|
+
|
|
32
|
+
const mk = () =>
|
|
33
|
+
new Mind({ seed: 1, store: new SQliteStore({ path: ":memory:" }) });
|
|
34
|
+
|
|
35
|
+
/** Two independent topics, each with its own trained fact, plus enough
|
|
36
|
+
* same-frame neighbours that the consensus climb commits both as separate
|
|
37
|
+
* points of attention rather than one. */
|
|
38
|
+
const TRAIN = [
|
|
39
|
+
["What is the capital of France?", " The capital of France is Paris."],
|
|
40
|
+
["What is the capital of Spain?", " The capital of Spain is Madrid."],
|
|
41
|
+
["What is the capital of Italy?", " The capital of Italy is Rome."],
|
|
42
|
+
["What is the largest planet?", " The largest planet is Jupiter."],
|
|
43
|
+
["What is the largest ocean?", " The largest ocean is the Pacific."],
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
async function trained() {
|
|
47
|
+
const mind = mk();
|
|
48
|
+
await mind.ingest(TRAIN);
|
|
49
|
+
return mind;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Index of `needle` in `hay`, or -1. */
|
|
53
|
+
const at = (hay, needle) => hay.indexOf(needle);
|
|
54
|
+
|
|
55
|
+
test("1. two fused topics appear in the query's own order", async () => {
|
|
56
|
+
const mind = await trained();
|
|
57
|
+
const a = await mind.respondText(
|
|
58
|
+
"What is the capital of France? And what is the largest planet?",
|
|
59
|
+
);
|
|
60
|
+
const iParis = at(a, "Paris"), iJup = at(a, "Jupiter");
|
|
61
|
+
if (iParis >= 0 && iJup >= 0) {
|
|
62
|
+
assert.ok(
|
|
63
|
+
iParis < iJup,
|
|
64
|
+
`France was asked FIRST but its answer trails: ${JSON.stringify(a)}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
await mind.store.close();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("2. reversing the question reverses the fused answer", async () => {
|
|
71
|
+
const mind = await trained();
|
|
72
|
+
const a = await mind.respondText(
|
|
73
|
+
"What is the largest planet? And what is the capital of France?",
|
|
74
|
+
);
|
|
75
|
+
const iParis = at(a, "Paris"), iJup = at(a, "Jupiter");
|
|
76
|
+
if (iParis >= 0 && iJup >= 0) {
|
|
77
|
+
assert.ok(
|
|
78
|
+
iJup < iParis,
|
|
79
|
+
`the planet was asked FIRST but its answer trails: ${JSON.stringify(a)}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
await mind.store.close();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("3. a single-topic answer is unchanged by the ordering rule", async () => {
|
|
86
|
+
const mind = await trained();
|
|
87
|
+
assert.match(
|
|
88
|
+
await mind.respondText("What is the capital of France?"),
|
|
89
|
+
/Paris/,
|
|
90
|
+
);
|
|
91
|
+
assert.match(
|
|
92
|
+
await mind.respondText("What is the largest planet?"),
|
|
93
|
+
/Jupiter/,
|
|
94
|
+
);
|
|
95
|
+
await mind.store.close();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("4. determinism", async () => {
|
|
99
|
+
const a = await trained(), b = await trained();
|
|
100
|
+
const q = "What is the capital of France? And what is the largest planet?";
|
|
101
|
+
assert.equal(await a.respondText(q), await b.respondText(q));
|
|
102
|
+
await a.store.close();
|
|
103
|
+
await b.store.close();
|
|
104
|
+
});
|