@hviana/sema 0.2.3 → 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 +246 -7
- package/dist/src/mind/attention.js +771 -173
- package/dist/src/mind/bridge.d.ts +30 -0
- package/dist/src/mind/bridge.js +569 -0
- 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/match.d.ts +15 -2
- package/dist/src/mind/match.js +3 -8
- package/dist/src/mind/mechanisms/cast.d.ts +54 -0
- package/dist/src/mind/mechanisms/cast.js +268 -37
- package/dist/src/mind/mechanisms/cover.js +16 -31
- package/dist/src/mind/mechanisms/recall.js +66 -0
- 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/reasoning.d.ts +11 -0
- package/dist/src/mind/reasoning.js +58 -2
- package/dist/src/mind/recognition.js +127 -7
- package/dist/src/mind/traverse.js +90 -25
- package/dist/src/mind/types.d.ts +57 -2
- package/dist/src/mind/types.js +38 -7
- package/dist/src/store.d.ts +12 -3
- package/dist/src/store.js +9 -3
- package/package.json +1 -1
- package/src/geometry.ts +52 -3
- package/src/mind/attention.ts +1199 -128
- package/src/mind/bridge.ts +596 -0
- package/src/mind/index.ts +16 -0
- package/src/mind/junction.ts +125 -16
- package/src/mind/match.ts +19 -5
- package/src/mind/mechanisms/cast.ts +290 -38
- package/src/mind/mechanisms/cover.ts +23 -36
- package/src/mind/mechanisms/recall.ts +79 -0
- package/src/mind/mind.ts +15 -0
- package/src/mind/rationale.ts +12 -4
- package/src/mind/reasoning.ts +71 -2
- package/src/mind/recognition.ts +132 -7
- package/src/mind/traverse.ts +91 -24
- package/src/mind/types.ts +95 -6
- package/src/store.ts +19 -5
- package/test/36-already-answered-fusion.test.mjs +128 -0
- package/test/37-cluster-dispersion-fusion.test.mjs +190 -0
- package/test/38-reason-restate-guard.test.mjs +94 -0
- package/test/39-cast-restate-guard.test.mjs +102 -0
- package/test/40-choosenext-scale-guard.test.mjs +75 -0
- package/test/41-seatofnode-direction.test.mjs +85 -0
- package/test/42-recognise-trace-idempotence.test.mjs +106 -0
- package/test/43-cast-analog-seat.test.mjs +244 -0
- package/test/44-recognise-edge-whitespace.test.mjs +63 -0
- package/test/45-liftanswer-restated-trim.test.mjs +60 -0
- package/test/46-recognise-multibyte-edge.test.mjs +85 -0
- package/test/47-cast-comparison-coverage.test.mjs +134 -0
- package/test/48-recognise-turn-connective.test.mjs +125 -0
- package/test/49-natural-units-synonym-bridge.test.mjs +175 -0
- package/test/50-cast-analog-consensus-floor.test.mjs +179 -0
- package/test/51-structural-resonance-ladder.test.mjs +552 -0
- package/test/52-climb-consensus-instrumentation.test.mjs +324 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// 48-recognise-turn-connective.test.mjs — recognise() must find a trained
|
|
2
|
+
// form even when the asker prepends a real discourse connective ("And ")
|
|
3
|
+
// to a follow-up turn — not boundary noise, a genuine extra word.
|
|
4
|
+
//
|
|
5
|
+
// Traced live (analyze_training.ts, dialogue D geography thread):
|
|
6
|
+
// "What is the capital of France?" then "And what is the capital of
|
|
7
|
+
// Spain?" answered with an unrelated CAST analog (Japan/Moon) instead of
|
|
8
|
+
// "Madrid is the capital of Spain." — root-caused directly against the
|
|
9
|
+
// live store: the turn "And what is the capital of Spain?" canon-misses as
|
|
10
|
+
// ONE 33-byte node (turn/segment scale); canonResolve on the WHOLE turn
|
|
11
|
+
// fails because "and " is real content, not an equivalence the injected
|
|
12
|
+
// canonicalizer folds away. The shipped ±1/k-trim fallbacks are bounded to
|
|
13
|
+
// chunk-scale (<= W²) specifically to avoid rediscovering a smaller
|
|
14
|
+
// subtree's own content as a duplicate, overlapping site at root scale
|
|
15
|
+
// (see test/46's regression case) — but that bound also blocked recovering
|
|
16
|
+
// this turn, which is naturally larger than one chunk.
|
|
17
|
+
//
|
|
18
|
+
// Fix: a canon-miss composite ALSO tries canonResolve from every position
|
|
19
|
+
// UP TO W chunk-widths from its own left edge that the query's OWN
|
|
20
|
+
// structural fold already treats as a chunk boundary (`starts` — the same
|
|
21
|
+
// set the canonical pass privileges with full chain reach). This is NOT a
|
|
22
|
+
// blind offset guess (the size-bounded k-trim loop above it already covers
|
|
23
|
+
// that): `starts.has(p)` is fold EVIDENCE the query itself produced —
|
|
24
|
+
// "And " is a genuine W=4-byte leaf-parent chunk, so its own end (byte 65)
|
|
25
|
+
// is already a `starts` boundary before this code ever runs (foldTree
|
|
26
|
+
// visits children before parents). Unbounded in the NODE's size (turn/
|
|
27
|
+
// segment scale is fine), bounded in candidate COUNT (at most W probes,
|
|
28
|
+
// each an O(1) starts.has() before paying for the real canonResolve fold).
|
|
29
|
+
|
|
30
|
+
import { test } from "node:test";
|
|
31
|
+
import assert from "node:assert/strict";
|
|
32
|
+
import { Mind, SQliteStore } from "../dist/src/index.js";
|
|
33
|
+
import { textCanon } from "../dist/src/canon.js";
|
|
34
|
+
import { recognise } from "../dist/src/mind/recognition.js";
|
|
35
|
+
import { latin1Key, perceive, resolve } from "../dist/src/mind/primitives.js";
|
|
36
|
+
|
|
37
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
38
|
+
const dec = (b) => new TextDecoder().decode(b).replace(/\0+$/, "");
|
|
39
|
+
|
|
40
|
+
test("recognise(): a real leading connective word ('And ') still finds the trained follow-up form", async () => {
|
|
41
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
42
|
+
await m.ingest([
|
|
43
|
+
["What is the capital of France?", "The capital of France is Paris."],
|
|
44
|
+
["What is the capital of Spain?", "Madrid is the capital of Spain."],
|
|
45
|
+
]);
|
|
46
|
+
await m.buildCanonIndex(textCanon);
|
|
47
|
+
m.canon = m._canonFor(textCanon);
|
|
48
|
+
m.canonMemo = new Map();
|
|
49
|
+
|
|
50
|
+
const query = enc(
|
|
51
|
+
"What is the capital of France?The capital of France is Paris." +
|
|
52
|
+
"And what is the capital of Spain?",
|
|
53
|
+
);
|
|
54
|
+
const expected = resolve(m, enc("What is the capital of Spain?"));
|
|
55
|
+
assert.ok(
|
|
56
|
+
expected !== null,
|
|
57
|
+
"sanity: the trained fact must resolve standalone",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// The live shape is STABLE-PREFIX folded (each turn folds independently
|
|
61
|
+
// from its own local offset 0 — see mind.ts's _growContext) so "And "
|
|
62
|
+
// lands on a genuine chunk boundary; a plain fold of the whole query (no
|
|
63
|
+
// boundaries) radix-aligns from byte 0 instead, which happens to reduce
|
|
64
|
+
// this particular query to the already-shipped ±1 case and would not
|
|
65
|
+
// exercise this fix at all. Priming perceiveMemo with the boundary-aware
|
|
66
|
+
// tree (content-keyed, exactly what _growContext itself does) makes this
|
|
67
|
+
// recognise() call see what the real multi-turn pipeline sees, without
|
|
68
|
+
// going through respondTurn's own memo (a second recognise() call on the
|
|
69
|
+
// same bytes is deliberately non-idempotent — see recognise()'s own
|
|
70
|
+
// doc comment — so this must be the FIRST call on this content).
|
|
71
|
+
const boundaries = [30, 61];
|
|
72
|
+
m.perceiveMemo = new Map();
|
|
73
|
+
m.perceiveMemo.set(
|
|
74
|
+
latin1Key(query),
|
|
75
|
+
perceive(m, query, undefined, undefined, boundaries),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const rec = recognise(m, query);
|
|
79
|
+
const hit = rec.sites.find((s) => s.payload === expected);
|
|
80
|
+
assert.ok(
|
|
81
|
+
hit,
|
|
82
|
+
`expected a recognised site for the trained Spain fact despite the ` +
|
|
83
|
+
`leading "And " connective, got sites: ` +
|
|
84
|
+
JSON.stringify(rec.sites.map((s) => [s.start, s.end, s.payload])),
|
|
85
|
+
);
|
|
86
|
+
await m.store.close();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("multi-turn: a follow-up turn prefixed with a real connective word grounds the NEW fact, not a CAST analog", async () => {
|
|
90
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
91
|
+
await m.ingest([
|
|
92
|
+
["What is the capital of France?", "The capital of France is Paris."],
|
|
93
|
+
["What is the capital of Spain?", "Madrid is the capital of Spain."],
|
|
94
|
+
[
|
|
95
|
+
"some other prompt",
|
|
96
|
+
"What is the capital of Japan? Tokyo is the capital of Japan.",
|
|
97
|
+
],
|
|
98
|
+
]);
|
|
99
|
+
// The live store builds this at training time; respondTurn's own
|
|
100
|
+
// automatic canon wiring (_canonFor) only wraps the canonicalizer
|
|
101
|
+
// function — the INDEX canonResolve searches is a separate, opt-in
|
|
102
|
+
// build step, same as test 44/46.
|
|
103
|
+
await m.buildCanonIndex(textCanon);
|
|
104
|
+
|
|
105
|
+
const conv = m.beginConversation();
|
|
106
|
+
await m.respondTurn(conv, "What is the capital of France?");
|
|
107
|
+
const r2 = await m.respondTurn(conv, "And what is the capital of Spain?");
|
|
108
|
+
const got = dec(r2.response.bytes);
|
|
109
|
+
|
|
110
|
+
assert.ok(
|
|
111
|
+
got.includes("Madrid") && got.includes("Spain"),
|
|
112
|
+
`expected the NEW Spain fact to ground the answer, got ${
|
|
113
|
+
JSON.stringify(got)
|
|
114
|
+
} (provenance: ${r2.response.provenance})`,
|
|
115
|
+
);
|
|
116
|
+
assert.ok(
|
|
117
|
+
!got.includes("Japan") && !got.includes("Tokyo"),
|
|
118
|
+
`must not fall back to an unrelated CAST analog, got ${
|
|
119
|
+
JSON.stringify(got)
|
|
120
|
+
}`,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
m.endConversation(conv);
|
|
124
|
+
await m.store.close();
|
|
125
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// 49-natural-units-synonym-bridge.test.mjs — irrefutable demonstration of
|
|
2
|
+
// the architectural gap investigated this session: a query using a
|
|
3
|
+
// near-synonym ("biggest") of a word that was only ever trained in a
|
|
4
|
+
// DIFFERENT phrasing ("largest") finds nothing, even though the fact
|
|
5
|
+
// itself is trained and the synonym relationship is corroborated many
|
|
6
|
+
// times over in the corpus.
|
|
7
|
+
//
|
|
8
|
+
// Root-caused directly (prior turn): "biggest"/"largest" NEVER resolve as
|
|
9
|
+
// independent nodes at all — deposit() only interns whole sentences (as
|
|
10
|
+
// one flat branch) plus tiny W-1/W leaf-id windows (canonical.ts); a
|
|
11
|
+
// 7-byte word floating mid-sentence falls in the gap between those two
|
|
12
|
+
// scales and is never independently addressable, so the EXISTING
|
|
13
|
+
// halo/synonym machinery (conceptHop, already used by CAST) never gets an
|
|
14
|
+
// entry point to it — the halo system is real and works, it is simply
|
|
15
|
+
// never asked about a node that was never minted.
|
|
16
|
+
//
|
|
17
|
+
// Fix (units.ts): a derived, corpus-statistics-only notion of a "natural
|
|
18
|
+
// unit" — a run of adjacent chunks whose pairing recurs at least as often
|
|
19
|
+
// as either chunk alone (the same principle behind BPE/content-defined
|
|
20
|
+
// chunking, computed from this store's own reuse/containment counts, no
|
|
21
|
+
// injected modality-specific segmenter). Interned at deposit time,
|
|
22
|
+
// pairwise halo-poured as each other's company; read at query time via the
|
|
23
|
+
// same derived merge (existingUnits) plus a halo-corroborated substitution
|
|
24
|
+
// fallback in recognise().
|
|
25
|
+
|
|
26
|
+
import { test } from "node:test";
|
|
27
|
+
import assert from "node:assert/strict";
|
|
28
|
+
import { Mind } from "../dist/src/index.js";
|
|
29
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
30
|
+
import { resolve } from "../dist/src/mind/primitives.js";
|
|
31
|
+
|
|
32
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
33
|
+
const dec = (b) => new TextDecoder().decode(b).replace(/\0+$/, "");
|
|
34
|
+
|
|
35
|
+
// Parallel "X is the biggest/largest Y" constructions across several
|
|
36
|
+
// different nouns — real cross-sentence corroboration for the
|
|
37
|
+
// biggest~largest collocation pattern, not a single coincidental pairing.
|
|
38
|
+
const TRAIN = [
|
|
39
|
+
[
|
|
40
|
+
"What is the largest planet in our solar system?",
|
|
41
|
+
"The largest planet is Jupiter.",
|
|
42
|
+
],
|
|
43
|
+
["What is the biggest ocean on Earth?", "The biggest ocean is the Pacific."],
|
|
44
|
+
["What is the largest country by area?", "The largest country is Russia."],
|
|
45
|
+
[
|
|
46
|
+
"What is the biggest mammal on Earth?",
|
|
47
|
+
"The biggest mammal is the blue whale.",
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"What is the largest desert in the world?",
|
|
51
|
+
"The largest desert is the Sahara.",
|
|
52
|
+
],
|
|
53
|
+
["What is the biggest city in Japan?", "The biggest city is Tokyo."],
|
|
54
|
+
["What is the largest lake in Africa?", "The largest lake is Lake Victoria."],
|
|
55
|
+
["What is the biggest island on Earth?", "The biggest island is Greenland."],
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
test("baseline: 'biggest'/'largest' never resolve as independent nodes without natural units", async () => {
|
|
59
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
60
|
+
await m.ingest(TRAIN);
|
|
61
|
+
// This assertion documents the ROOT CAUSE (still true even after the
|
|
62
|
+
// fix — units.ts does not change what resolve() itself returns for a
|
|
63
|
+
// bare word; it changes what recognise() can bridge to via halos).
|
|
64
|
+
assert.equal(resolve(m, enc("biggest")), null);
|
|
65
|
+
assert.equal(resolve(m, enc("largest")), null);
|
|
66
|
+
await m.store.close();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("irrefutable failure: a trained fact is unreachable through an untrained near-synonym", async () => {
|
|
70
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
71
|
+
await m.ingest(TRAIN);
|
|
72
|
+
|
|
73
|
+
// Sanity: the TRAINED phrasing works.
|
|
74
|
+
const trained = await m.respond(
|
|
75
|
+
"What is the largest planet in our solar system?",
|
|
76
|
+
);
|
|
77
|
+
assert.ok(
|
|
78
|
+
dec(trained.bytes).includes("Jupiter"),
|
|
79
|
+
`sanity: trained phrasing must answer, got ${
|
|
80
|
+
JSON.stringify(dec(trained.bytes))
|
|
81
|
+
}`,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// The untrained near-synonym phrasing. NOTE: a query that keeps the
|
|
85
|
+
// trained sentence's own long, near-identical scaffolding around the one
|
|
86
|
+
// swapped word ("What is the biggest planet in our solar system?")
|
|
87
|
+
// already succeeds WITHOUT this fix — recallByResonance's WHOLE-QUERY
|
|
88
|
+
// gist is similar enough (one word out of a long, highly-repetitive
|
|
89
|
+
// sentence) to clear the reach threshold on its own, an existing
|
|
90
|
+
// capability this test must not accidentally exercise instead. Deliberately
|
|
91
|
+
// SHORT and restructured ("Name the biggest planet.") so the whole-query
|
|
92
|
+
// gist has no such shortcut — confirmed directly (see this session's
|
|
93
|
+
// investigation) to return silence at baseline: recognise() finds 0
|
|
94
|
+
// sites, and the whole-query resonance score is nowhere near the reach
|
|
95
|
+
// bar. Only a WORD-level bridge from "biggest" to its corroborated
|
|
96
|
+
// synonym "largest" can recover the trained fact here.
|
|
97
|
+
const untrained = await m.respond("Name the biggest planet.");
|
|
98
|
+
assert.ok(
|
|
99
|
+
dec(untrained.bytes).includes("Jupiter"),
|
|
100
|
+
`expected the trained fact to be reachable through the corroborated ` +
|
|
101
|
+
`synonym "biggest" ~ "largest", got ${
|
|
102
|
+
JSON.stringify(dec(untrained.bytes))
|
|
103
|
+
}`,
|
|
104
|
+
);
|
|
105
|
+
await m.store.close();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("resonance-proposed bridge: casing/punctuation paraphrase reaches the trained fact", async () => {
|
|
109
|
+
// Root-caused live on the trained store (2026-07-20): the query
|
|
110
|
+
// "what is the capital of france" resonates STRAIGHT to the trained
|
|
111
|
+
// "What is the capital of France?" (nearest whole-query hit) yet stayed
|
|
112
|
+
// silent — the case-changed bytes fall below the reach bar, the canon
|
|
113
|
+
// twin misses on the absent "?", and the bridge's own W-window climb
|
|
114
|
+
// cannot single the right context out of hundreds sharing common
|
|
115
|
+
// windows. Recall now hands its resonance ranking to the bridge as
|
|
116
|
+
// PROPOSED candidates (rank-only, byte-verified there), which recovers
|
|
117
|
+
// the fact through two corroborated one-byte case substitutions.
|
|
118
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
119
|
+
await m.ingest([
|
|
120
|
+
["What is the capital of France?", "The capital of France is Paris."],
|
|
121
|
+
["What is the capital of Spain?", "The capital of Spain is Madrid."],
|
|
122
|
+
["What is the capital of Italy?", "The capital of Italy is Rome."],
|
|
123
|
+
// Lowercase mid-sentence occurrences attest the case-folded windows
|
|
124
|
+
// the substitution's corroboration gate requires — including the
|
|
125
|
+
// frame-bearing " of france" the expansion absorbs into.
|
|
126
|
+
["He wrote of france and of spain.", "Then he flew home to italy."],
|
|
127
|
+
["She spoke of france in her diary.", "Her diary told of france."],
|
|
128
|
+
]);
|
|
129
|
+
const r = await m.respond("what is the capital of france");
|
|
130
|
+
assert.ok(
|
|
131
|
+
dec(r.bytes).includes("Paris"),
|
|
132
|
+
`expected the trained fact through the resonance-proposed bridge, got ${
|
|
133
|
+
JSON.stringify(dec(r.bytes))
|
|
134
|
+
}`,
|
|
135
|
+
);
|
|
136
|
+
await m.store.close();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test(
|
|
140
|
+
"FIXED 2026-07-20: a proper-noun substitution must not voice a wrong " +
|
|
141
|
+
"fact when the true answer has no outgoing edge to compete as a " +
|
|
142
|
+
"bridge candidate (RAW BALANCE gate, see bridge.ts)",
|
|
143
|
+
async () => {
|
|
144
|
+
// This miniature corpus does NOT reproduce the live bug (recall's
|
|
145
|
+
// whole-query resonance already answers correctly here — verified: it
|
|
146
|
+
// takes the real 17.9M-node store's specific candidate ranking to
|
|
147
|
+
// surface the failure mode). Kept anyway as a standing regression
|
|
148
|
+
// check on the shape of the bug (terminal fact vs. a same-shaped fact
|
|
149
|
+
// WITH a continuation); the authoritative fix verification was run
|
|
150
|
+
// directly against the trained store: "The capital of France is"
|
|
151
|
+
// used to bridge through a substitution reading "of Fra[nce]" as
|
|
152
|
+
// "of Spain si[nce]" (raw mismatch (3,8) bytes, badly imbalanced);
|
|
153
|
+
// the RAW BALANCE gate (dominates(min(uLen,cLen), max(uLen,cLen)))
|
|
154
|
+
// now refuses it, and recall correctly falls through to an honest
|
|
155
|
+
// echo of the true trained fact instead.
|
|
156
|
+
const m = new Mind({
|
|
157
|
+
seed: 7,
|
|
158
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
159
|
+
});
|
|
160
|
+
await m.ingest([
|
|
161
|
+
["What is the capital of France?", "The capital of France is Paris."],
|
|
162
|
+
[
|
|
163
|
+
"Madrid has been the capital of Spain since 1561.",
|
|
164
|
+
"It was established as such by Philip III.",
|
|
165
|
+
],
|
|
166
|
+
]);
|
|
167
|
+
const r = await m.respond("The capital of France is");
|
|
168
|
+
assert.ok(
|
|
169
|
+
!dec(r.bytes).includes("Spain"),
|
|
170
|
+
`expected no wrong-entity substitution, got ${
|
|
171
|
+
JSON.stringify(dec(r.bytes))
|
|
172
|
+
}`,
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// 50-cast-analog-consensus-floor.test.mjs — CAST's analogical comparison
|
|
2
|
+
// must not voice content the query never named through a climb root whose
|
|
3
|
+
// consensus vote is below consensusFloor(N) = ln(N) + 1/2 — the SAME trust
|
|
4
|
+
// bar recallByResonance applies before grounding through a climb root.
|
|
5
|
+
//
|
|
6
|
+
// The live bug (traced on the real trained store, 325k edge sources, floor
|
|
7
|
+
// 13.2): "Tell me the name of the biggest planet orbiting our sun." shares
|
|
8
|
+
// only stopword scraps ("the", "our", generic bigrams) with an unrelated
|
|
9
|
+
// haiku exemplar; those scraps pooled a climb vote of just 1.92, yet the
|
|
10
|
+
// climb still committed the exemplar as FIRST root (the first root is
|
|
11
|
+
// deliberately floor-free in attention.ts — "the dominant one always
|
|
12
|
+
// grounds"). CAST's comparison schema then cited an analog reached through
|
|
13
|
+
// a continuation hop (never named by the query), and voiced the haiku's
|
|
14
|
+
// continuation ("Winds of change blow free…") as the answer — a wrong
|
|
15
|
+
// answer where recall, extraction, and the ALU all honestly refused.
|
|
16
|
+
//
|
|
17
|
+
// The gate (cast.ts): comparison may cite a hop-reached (unnamed) analog
|
|
18
|
+
// only when some committed root's vote clears consensusFloor(N); a
|
|
19
|
+
// DIRECTLY aligned analog (the query's own bytes evidence it, e.g. test/29
|
|
20
|
+
// C1's "Steel is hard") needs no floor. Derived, never tuned — the floor
|
|
21
|
+
// is the one recall.ts already gates the same trust decision with.
|
|
22
|
+
//
|
|
23
|
+
// This miniature reproduces the exact shape: ~200 filler pairs whose LONG
|
|
24
|
+
// contexts (longer than the probe query) can never be direct analogs, so
|
|
25
|
+
// their short continuations arrive only as continuation-hop descendants;
|
|
26
|
+
// the exemplar's distinctive words also occur across fillers, diluting its
|
|
27
|
+
// root vote below the floor (measured: 4.24 and 5.51 against floor 5.81)
|
|
28
|
+
// while it still ranks first and commits floor-free. Confirmed red at
|
|
29
|
+
// baseline: both probes echoed the haiku continuation with provenance
|
|
30
|
+
// "cast"; with the gate both are honest silence.
|
|
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 dec = (b) => new TextDecoder().decode(b).replace(/\0+$/, "");
|
|
38
|
+
|
|
39
|
+
const subjects = [
|
|
40
|
+
"cat",
|
|
41
|
+
"dog",
|
|
42
|
+
"bird",
|
|
43
|
+
"fish",
|
|
44
|
+
"horse",
|
|
45
|
+
"sheep",
|
|
46
|
+
"goat",
|
|
47
|
+
"duck",
|
|
48
|
+
"frog",
|
|
49
|
+
"deer",
|
|
50
|
+
"wolf",
|
|
51
|
+
"bear",
|
|
52
|
+
"lion",
|
|
53
|
+
"tiger",
|
|
54
|
+
"mouse",
|
|
55
|
+
"rabbit",
|
|
56
|
+
"otter",
|
|
57
|
+
"eagle",
|
|
58
|
+
"shark",
|
|
59
|
+
"whale",
|
|
60
|
+
];
|
|
61
|
+
const verbs = [
|
|
62
|
+
"describes",
|
|
63
|
+
"praises",
|
|
64
|
+
"watches",
|
|
65
|
+
"follows",
|
|
66
|
+
"studies",
|
|
67
|
+
"admires",
|
|
68
|
+
"draws",
|
|
69
|
+
"paints",
|
|
70
|
+
"names",
|
|
71
|
+
"sings",
|
|
72
|
+
];
|
|
73
|
+
const objects = [
|
|
74
|
+
"freedom",
|
|
75
|
+
"feeling",
|
|
76
|
+
"titles",
|
|
77
|
+
"songs",
|
|
78
|
+
"times",
|
|
79
|
+
"stories",
|
|
80
|
+
"poems",
|
|
81
|
+
"rivers",
|
|
82
|
+
"shadows",
|
|
83
|
+
"seasons",
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const TRAIN = [];
|
|
87
|
+
for (const s of subjects) {
|
|
88
|
+
for (const v of verbs) {
|
|
89
|
+
const o =
|
|
90
|
+
objects[(subjects.indexOf(s) + verbs.indexOf(v)) % objects.length];
|
|
91
|
+
TRAIN.push([
|
|
92
|
+
`The ${s} quietly ${v} the many ${o} it meets today in the wide green garden.`,
|
|
93
|
+
`Indeed the ${s} ${v} ${o}.`,
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
TRAIN.push([
|
|
98
|
+
"Create a haiku that describes the feeling of freedom.",
|
|
99
|
+
"Winds of change blow free\nFeeling of lightness fills my soul",
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
// SHORT filler contexts: continuations become directly ALIGNED points, so
|
|
103
|
+
// the junk analog arrives with frame-tier similarity as an aligned point —
|
|
104
|
+
// the configuration the consensusFloor/naming gates alone cannot separate
|
|
105
|
+
// from test/29 C1's legitimate small-corpus comparison. The separator is
|
|
106
|
+
// the IGNORED-KNOWN principle (dismissedKnownContent): under an untrusted
|
|
107
|
+
// root, a frame-tier comparison must account for every STORED window of
|
|
108
|
+
// the query — here "songs"/"times"/"planet"-class trained content is left
|
|
109
|
+
// in gaps, so comparison refuses; C1's gaps ("How ", " like ") are
|
|
110
|
+
// untrained and stay tolerable. Confirmed red at baseline: both probes
|
|
111
|
+
// echoed the haiku + a filler continuation with provenance "cast".
|
|
112
|
+
const SHORT_TRAIN = [];
|
|
113
|
+
for (const s of subjects) {
|
|
114
|
+
for (const v of verbs) {
|
|
115
|
+
const o =
|
|
116
|
+
objects[(subjects.indexOf(s) + verbs.indexOf(v)) % objects.length];
|
|
117
|
+
SHORT_TRAIN.push([
|
|
118
|
+
`The ${s} ${v} the ${o} today.`,
|
|
119
|
+
`Indeed the ${s} ${v} ${o}.`,
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
SHORT_TRAIN.push([
|
|
124
|
+
"Create a haiku that describes the feeling of freedom.",
|
|
125
|
+
"Winds of change blow free\nFeeling of lightness fills my soul",
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
test("comparison refuses an aligned frame-tier analog that dismisses stored query content", async () => {
|
|
129
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
130
|
+
await m.ingest(SHORT_TRAIN);
|
|
131
|
+
for (
|
|
132
|
+
const q of [
|
|
133
|
+
"Give me the title of the finest song describing our times.",
|
|
134
|
+
"Tell me the name of the biggest planet orbiting our sun.",
|
|
135
|
+
]
|
|
136
|
+
) {
|
|
137
|
+
const r = await m.respond(q);
|
|
138
|
+
const t = dec(r.bytes);
|
|
139
|
+
assert.equal(
|
|
140
|
+
t,
|
|
141
|
+
"",
|
|
142
|
+
`expected honest silence for ${JSON.stringify(q)}, got ${
|
|
143
|
+
JSON.stringify(t)
|
|
144
|
+
}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
await m.store.close();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("comparison refuses a hop-reached analog under a below-floor root", async () => {
|
|
151
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
152
|
+
await m.ingest(TRAIN);
|
|
153
|
+
|
|
154
|
+
// Neither query names any trained fact — the corpus knows nothing about
|
|
155
|
+
// songs, titles, or planets. The only honest answer is silence; at
|
|
156
|
+
// baseline both echoed the haiku exemplar's continuation via CAST.
|
|
157
|
+
for (
|
|
158
|
+
const q of [
|
|
159
|
+
"Give me the title of the finest song describing our times.",
|
|
160
|
+
"Tell me the name of the biggest planet orbiting our sun.",
|
|
161
|
+
]
|
|
162
|
+
) {
|
|
163
|
+
const r = await m.respond(q);
|
|
164
|
+
const t = dec(r.bytes);
|
|
165
|
+
assert.ok(
|
|
166
|
+
!t.includes("Winds of change"),
|
|
167
|
+
`CAST voiced an unrelated exemplar's continuation off a below-floor ` +
|
|
168
|
+
`root for ${JSON.stringify(q)}, got ${JSON.stringify(t)}`,
|
|
169
|
+
);
|
|
170
|
+
assert.equal(
|
|
171
|
+
t,
|
|
172
|
+
"",
|
|
173
|
+
`expected honest silence for ${JSON.stringify(q)}, got ${
|
|
174
|
+
JSON.stringify(t)
|
|
175
|
+
}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
await m.store.close();
|
|
179
|
+
});
|