@hviana/sema 0.4.7 → 0.5.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 +290 -77
- package/HOW_IT_WORKS.md +2170 -735
- package/dist/example/train_base.d.ts +9 -3
- package/dist/example/train_base.js +21 -4
- package/dist/src/canon.d.ts +19 -0
- package/dist/src/canon.js +28 -0
- package/dist/src/geometry.d.ts +52 -0
- package/dist/src/geometry.js +87 -1
- package/dist/src/mind/bridge.js +27 -1
- package/dist/src/mind/frame-filler.d.ts +15 -0
- package/dist/src/mind/frame-filler.js +535 -0
- package/dist/src/mind/learning.js +6 -11
- package/dist/src/mind/mechanisms/cast.js +72 -2
- package/dist/src/mind/mechanisms/cover.js +6 -1
- package/dist/src/mind/mechanisms/extraction.js +27 -0
- package/dist/src/mind/mechanisms/recall.js +214 -34
- package/dist/src/mind/mind.d.ts +49 -1
- package/dist/src/mind/mind.js +137 -10
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +29 -1
- package/dist/src/mind/prefix-completion.d.ts +59 -0
- package/dist/src/mind/prefix-completion.js +270 -0
- package/dist/src/mind/primitives.d.ts +29 -10
- package/dist/src/mind/primitives.js +52 -61
- package/dist/src/mind/recognition.js +119 -9
- package/dist/src/mind/traverse.d.ts +32 -0
- package/dist/src/mind/traverse.js +52 -0
- package/dist/src/mind/types.d.ts +55 -16
- package/dist/src/mind/types.js +68 -19
- package/dist/src/store.d.ts +21 -0
- package/dist/src/store.js +21 -0
- package/example/train_base.ts +21 -4
- package/package.json +1 -1
- package/src/canon.ts +28 -0
- package/src/geometry.ts +100 -1
- package/src/mind/bridge.ts +34 -0
- package/src/mind/frame-filler.ts +604 -0
- package/src/mind/learning.ts +5 -9
- package/src/mind/mechanisms/cast.ts +70 -2
- package/src/mind/mechanisms/cover.ts +6 -1
- package/src/mind/mechanisms/extraction.ts +27 -0
- package/src/mind/mechanisms/recall.ts +236 -37
- package/src/mind/mind.ts +154 -14
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +33 -1
- package/src/mind/prefix-completion.ts +314 -0
- package/src/mind/primitives.ts +59 -70
- package/src/mind/recognition.ts +117 -6
- package/src/mind/traverse.ts +52 -0
- package/src/mind/types.ts +98 -42
- package/src/store.ts +25 -0
- package/test/13-conversation.test.mjs +13 -0
- package/test/57-fusion-order.test.mjs +65 -0
- package/test/66-query-edge-whitespace.test.mjs +99 -0
- package/test/67-climb-anchor-breadth.test.mjs +113 -0
- package/test/68-extraction-unanchored.test.mjs +79 -0
- package/test/69-frame-filler.test.mjs +115 -0
- package/test/70-prefix-completion.test.mjs +170 -0
- package/test/71-embedded-canon-equivalence.test.mjs +121 -0
- package/test/72-prefix-candidate-supply.test.mjs +114 -0
- package/test/73-scaffolding-only-bridge-abstains.test.mjs +178 -0
- package/test/74-prefix-trap-not-sprung-early.test.mjs +114 -0
- package/test/75-multiturn-context-optimisation.test.mjs +1082 -0
|
@@ -82,6 +82,71 @@ test("2. reversing the question reverses the fused answer", async () => {
|
|
|
82
82
|
await mind.store.close();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
test("2b. a topic is never ECHOED back instead of answered", async () => {
|
|
86
|
+
// The failure this pins: "What is the capital of France? And what is the
|
|
87
|
+
// largest planet?" answered "The capital of France is Paris.What is the
|
|
88
|
+
// largest planet?" — one topic answered, the other repeated verbatim.
|
|
89
|
+
//
|
|
90
|
+
// It hinged on CASE. The comparison schema seats a directly-aligned analog
|
|
91
|
+
// by its own bytes rather than chasing a forward edge, which is correct when
|
|
92
|
+
// those bytes are an answer (test/43 pins that) and an echo when they are
|
|
93
|
+
// the question the asker just asked. The guard against that is a restatement
|
|
94
|
+
// check, and a BYTE-EXACT one missed here: the trained node is "What is the
|
|
95
|
+
// largest planet?" while the query says "And what is the largest planet?" —
|
|
96
|
+
// the same words, one capital apart. The check now reads the response's own
|
|
97
|
+
// injected canon, so it sees what the rest of the mind sees.
|
|
98
|
+
//
|
|
99
|
+
// SCOPE: this asserts only that nothing is echoed. Whether BOTH topics get
|
|
100
|
+
// fused is a separate, corpus- and seed-dependent property of the consensus
|
|
101
|
+
// climb — at this file's seed the second point is sometimes not committed at
|
|
102
|
+
// all, which is why test 1 above guards its ordering assertion on both names
|
|
103
|
+
// being present. Answering one topic and staying silent about the other is a
|
|
104
|
+
// coverage limit; answering one and parroting the other is a defect.
|
|
105
|
+
//
|
|
106
|
+
// Asserted in BOTH orders because the echo appeared in only one: which topic
|
|
107
|
+
// got echoed depended on whether the climb landed on the question node or
|
|
108
|
+
// the answer node, so a single-order test passes while the bug is live.
|
|
109
|
+
// ITS OWN CORPUS, DELIBERATELY. The file's shared `trained()` fixture cannot
|
|
110
|
+
// reproduce this: with five same-frame facts the climb often commits only
|
|
111
|
+
// ONE point, so there is no second topic to echo and the test would pass
|
|
112
|
+
// against the unfixed code (verified — it did). The echo needs exactly two
|
|
113
|
+
// topics, each a bare question node whose answer hangs off a forward edge.
|
|
114
|
+
const mind = new Mind({
|
|
115
|
+
seed: 7,
|
|
116
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
117
|
+
});
|
|
118
|
+
await mind.ingest([
|
|
119
|
+
["What is the capital of France?", "The capital of France is Paris."],
|
|
120
|
+
["What is the largest planet?", "The largest planet is Jupiter."],
|
|
121
|
+
]);
|
|
122
|
+
for (
|
|
123
|
+
const q of [
|
|
124
|
+
"What is the capital of France? And what is the largest planet?",
|
|
125
|
+
"What is the largest planet? And what is the capital of France?",
|
|
126
|
+
]
|
|
127
|
+
) {
|
|
128
|
+
const a = await mind.respondText(q);
|
|
129
|
+
assert.ok(
|
|
130
|
+
!/And what is/i.test(a),
|
|
131
|
+
`the query was echoed rather than answered: ${JSON.stringify(a)} for ${
|
|
132
|
+
JSON.stringify(q)
|
|
133
|
+
}`,
|
|
134
|
+
);
|
|
135
|
+
assert.ok(
|
|
136
|
+
a.includes("Paris") && a.includes("Jupiter"),
|
|
137
|
+
`both topics must be ANSWERED, got ${JSON.stringify(a)} for ${
|
|
138
|
+
JSON.stringify(q)
|
|
139
|
+
}`,
|
|
140
|
+
);
|
|
141
|
+
// Nor may an answer be a bare restatement of one of the asked questions.
|
|
142
|
+
assert.ok(
|
|
143
|
+
!/^\s*What is the (largest planet|capital of France)\?\s*$/i.test(a),
|
|
144
|
+
`the answer is just the question restated: ${JSON.stringify(a)}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
await mind.store.close();
|
|
148
|
+
});
|
|
149
|
+
|
|
85
150
|
test("3. a single-topic answer is unchanged by the ordering rule", async () => {
|
|
86
151
|
const mind = await trained();
|
|
87
152
|
assert.match(
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// 66-query-edge-whitespace.test.mjs — a query's leading/trailing whitespace is
|
|
2
|
+
// presentation, not part of the question, and must not decide whether a trained
|
|
3
|
+
// fact is reachable.
|
|
4
|
+
//
|
|
5
|
+
// canon.ts's contract: "a span's leading or trailing separator belongs BETWEEN
|
|
6
|
+
// forms, not to the form". canon itself PRESERVES edge whitespace, and must,
|
|
7
|
+
// because the hazard it cites is a recognised SUB-span swallowing the boundary
|
|
8
|
+
// byte that separates it from its neighbour ("ice " matching the stored "ice").
|
|
9
|
+
// At the outer edges of a WHOLE input there is no neighbour, so that hazard
|
|
10
|
+
// cannot arise — which is why respond() may trim there and canon may not.
|
|
11
|
+
// test/44 already relies on the same reading for recognise()'s miss path.
|
|
12
|
+
//
|
|
13
|
+
// THE GAP THIS CLOSES (measured on the 15.7M-node trained store): ONE leading
|
|
14
|
+
// space took `Who wrote Romeo and Juliet?` and `What is the chemical symbol for
|
|
15
|
+
// water?` from answered to silent, because a shift re-seats every fold boundary
|
|
16
|
+
// (cos(query, query shifted 1 byte) = 0.68 against a 0.875 reach bar). That was
|
|
17
|
+
// the whole of analyze_training.ts's K2 phase-robustness gap: 15/18 → 18/18.
|
|
18
|
+
//
|
|
19
|
+
// WHY A RETRY AND NOT A PRE-FILTER — the regression this file pins. Trimming
|
|
20
|
+
// the query up front is ASYMMETRIC: it normalises the query but not the stored
|
|
21
|
+
// forms, so it breaks byte-exact identity for a form trained WITH edge
|
|
22
|
+
// whitespace. Verified: pre-filtering broke test/04's [" ice ", "cold"] case.
|
|
23
|
+
// The exact bytes are therefore tried FIRST and the trim is reached only when
|
|
24
|
+
// they grounded nothing — which also means the retry costs nothing on any
|
|
25
|
+
// answering path.
|
|
26
|
+
//
|
|
27
|
+
// NOTE ON WHAT IS AND IS NOT TESTABLE HERE. The padded-query WIN cannot be
|
|
28
|
+
// reproduced in a miniature fixture: a small store answers a padded query on the
|
|
29
|
+
// first pass anyway (an earlier tier catches it), so the retry never fires and
|
|
30
|
+
// an end-to-end assertion passes with or without the fix — verified, an earlier
|
|
31
|
+
// version of this file did exactly that and guarded nothing. What a fixture CAN
|
|
32
|
+
// pin is the trim's own contract and the asymmetry regression, which is what
|
|
33
|
+
// these tests do; the win itself is evidenced on the real store.
|
|
34
|
+
|
|
35
|
+
import { test } from "node:test";
|
|
36
|
+
import assert from "node:assert/strict";
|
|
37
|
+
import { Mind } from "../dist/src/index.js";
|
|
38
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
39
|
+
import { textEdgeTrim } from "../dist/src/canon.js";
|
|
40
|
+
|
|
41
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
42
|
+
const dec = new TextDecoder();
|
|
43
|
+
const trim = (s) => dec.decode(textEdgeTrim(enc(s)));
|
|
44
|
+
|
|
45
|
+
test("1. textEdgeTrim drops only the outer spacing run", () => {
|
|
46
|
+
assert.equal(trim(" ice "), "ice");
|
|
47
|
+
assert.equal(trim("\tice\n"), "ice");
|
|
48
|
+
assert.equal(trim("ice"), "ice");
|
|
49
|
+
// INTERIOR whitespace is content and is never touched.
|
|
50
|
+
assert.equal(trim(" a b "), "a b");
|
|
51
|
+
// All-separator and empty inputs collapse to empty rather than throwing.
|
|
52
|
+
assert.equal(trim(" "), "");
|
|
53
|
+
assert.equal(trim(""), "");
|
|
54
|
+
// The untouched case must return the SAME object (no copy on the hot path).
|
|
55
|
+
const b = enc("ice");
|
|
56
|
+
assert.equal(textEdgeTrim(b), b);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("2. a form trained WITH edge whitespace still answers when asked exactly", async () => {
|
|
60
|
+
// The asymmetry regression: trimming the query but not the store would make
|
|
61
|
+
// this query miss its own deposited form.
|
|
62
|
+
const m = new Mind({ seed: 7 });
|
|
63
|
+
await m.ingest([[" ice ", "cold"]]);
|
|
64
|
+
assert.equal(await m.respondText(" ice "), "cold");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("3. whitespace-only and empty queries are silent, not errors", async () => {
|
|
68
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
69
|
+
await m.ingest([["what is ice?", "ice is frozen water"]]);
|
|
70
|
+
for (const q of ["", " ", " ", "\t\n"]) {
|
|
71
|
+
assert.equal(
|
|
72
|
+
await m.respondText(q),
|
|
73
|
+
"",
|
|
74
|
+
`expected silence for ${JSON.stringify(q)}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
await m.store.close();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("4. a padded query never answers something the unpadded one would not", async () => {
|
|
81
|
+
// The retry may add REACH, never licence: whatever padding does, it must not
|
|
82
|
+
// ground a fact for a question the store cannot answer.
|
|
83
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
84
|
+
await m.ingest([
|
|
85
|
+
["what is the capital of France?", "The capital of France is Paris."],
|
|
86
|
+
["what is the capital of Spain?", "Madrid is the capital of Spain."],
|
|
87
|
+
]);
|
|
88
|
+
for (const q of [" Who wrote the Iliad? ", " xyzzy plugh quux "]) {
|
|
89
|
+
const a = await m.respondText(q);
|
|
90
|
+
assert.doesNotMatch(
|
|
91
|
+
a,
|
|
92
|
+
/Paris|Madrid/,
|
|
93
|
+
`padding manufactured an answer for ${JSON.stringify(q)}: ${
|
|
94
|
+
JSON.stringify(a)
|
|
95
|
+
}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
await m.store.close();
|
|
99
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// 67-climb-anchor-breadth.test.mjs — recall's scaffolding-dominated tier
|
|
2
|
+
// trusts a consensus-climb anchor on its SCALE-INVARIANT breadth as well as on
|
|
3
|
+
// its absolute IDF vote, and a breadth-qualified anchor must also be
|
|
4
|
+
// DISCRIMINATIVE.
|
|
5
|
+
//
|
|
6
|
+
// WHY THE ABSOLUTE VOTE IS NOT ENOUGH. Attention.breadth's own contract
|
|
7
|
+
// (types.ts) already says it: the IDF vote is "an absolute, ln(N)-scaled
|
|
8
|
+
// quantity that means 'strong' on a small store and 'weak' on a large one for
|
|
9
|
+
// the SAME degree of genuine consensus", while breadth is "the fraction of the
|
|
10
|
+
// query's OWN regions whose evidence this point accounts for" and "a point
|
|
11
|
+
// whose breadth clears `dominates` … is real consensus". Attention.peak's
|
|
12
|
+
// contract makes the same point from the other side: a floor that prices ONE
|
|
13
|
+
// region's evidence may not be compared against a POOLED SUM.
|
|
14
|
+
//
|
|
15
|
+
// Measured on the 15.7M-node trained store (N=325,615, floor = ln N + ½ =
|
|
16
|
+
// 13.19). The climb picked the RIGHT context and the floor discarded it, while
|
|
17
|
+
// a junk attractor for a query that must stay SILENT outvoted every correct
|
|
18
|
+
// anchor:
|
|
19
|
+
//
|
|
20
|
+
// anchor the climb picked vote breadth correct?
|
|
21
|
+
// "What is the chemical formula …" 10.60 0.556 RIGHT
|
|
22
|
+
// "Qual é a capital de França?" 8.19 0.667 RIGHT
|
|
23
|
+
// "Who wrote the play Romeo …?" 8.25 0.833 RIGHT
|
|
24
|
+
// "How do you say "good morning" …" 10.77 0.800 RIGHT
|
|
25
|
+
// "What is the commercial capital …" 12.69 0.333 Zamunda — MUST be silent
|
|
26
|
+
// "Menene sunan ginin mafi tsayi …" 12.79 0.214 wrong (Hausa)
|
|
27
|
+
//
|
|
28
|
+
// No vote threshold separates those; breadth > ½ separates them exactly. On
|
|
29
|
+
// that store the old floor was never cleared at all, so the tier was dead code
|
|
30
|
+
// and 12 probes fell through to silence.
|
|
31
|
+
//
|
|
32
|
+
// WHAT MUST NOT REGRESS, and why the gate is an OR of two guarded readings:
|
|
33
|
+
//
|
|
34
|
+
// • REPLACING the vote test with the breadth test broke 7 tests. On a small
|
|
35
|
+
// store ln(N) is low, so the vote bar is the reading that legitimately
|
|
36
|
+
// fires there; and Attention.clusters' contract warns that "breadth starves
|
|
37
|
+
// a genuine, evenly-split multi-topic query, since no root in a real N-way
|
|
38
|
+
// split can exceed half the vote" — the two-topic fusion tests are exactly
|
|
39
|
+
// that shape. Each reading is sufficient on its own evidence.
|
|
40
|
+
// • BREADTH ALONE fabricates. On a one-context store every region trivially
|
|
41
|
+
// corroborates the only anchor there is, so breadth is 1 while the anchor's
|
|
42
|
+
// IDF is 0 — test/31 A2 answered a lone cat fact for "explain quantum
|
|
43
|
+
// chromodynamics". Hence the companion condition: a region's IDF for an
|
|
44
|
+
// anchor reached through c of N contexts is ln(N/c), so requiring it past
|
|
45
|
+
// ln 2 requires c·2 < N — the same half-dominance reading in IDF units.
|
|
46
|
+
|
|
47
|
+
import { test } from "node:test";
|
|
48
|
+
import assert from "node:assert/strict";
|
|
49
|
+
import { Mind } from "../dist/src/index.js";
|
|
50
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
51
|
+
|
|
52
|
+
const mk = () =>
|
|
53
|
+
new Mind({ seed: 1, store: new SQliteStore({ path: ":memory:" }) });
|
|
54
|
+
|
|
55
|
+
test("1. a one-context store never grounds an unrelated query (breadth alone must not decide)", async () => {
|
|
56
|
+
// Breadth is trivially 1 when there is only one anchor to corroborate, but
|
|
57
|
+
// that anchor's IDF is 0 — it says nothing. VERIFIED to bite: dropping the
|
|
58
|
+
// `peak > ln 2` companion makes this fail (and test/31 A2 with it). The
|
|
59
|
+
// fixture matches A2's exactly — `new Mind({ seed: 7 })`, the default store —
|
|
60
|
+
// because the same shape over a SQliteStore did NOT reproduce it.
|
|
61
|
+
const m = new Mind({ seed: 7 });
|
|
62
|
+
await m.ingest([["what is a cat?", "a cat is a small feline"]]);
|
|
63
|
+
const r = await m.respond("explain quantum chromodynamics");
|
|
64
|
+
assert.equal(
|
|
65
|
+
r.v,
|
|
66
|
+
null,
|
|
67
|
+
"a lone low-IDF anchor must not ground a foreign query",
|
|
68
|
+
);
|
|
69
|
+
assert.equal(r.provenance, undefined);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("2. an evenly-split multi-topic query still fuses (breadth must not be required)", async () => {
|
|
73
|
+
// The shape Attention.clusters' contract says breadth starves: no root in a
|
|
74
|
+
// real N-way split can hold more than half the query's regions, so a
|
|
75
|
+
// breadth-only gate would refuse both topics.
|
|
76
|
+
const m = mk();
|
|
77
|
+
await m.ingest([
|
|
78
|
+
["ice", "cold"],
|
|
79
|
+
["fire", "hot"],
|
|
80
|
+
["what is ice?", "ice is frozen water"],
|
|
81
|
+
["what is fire?", "fire is rapid oxidation"],
|
|
82
|
+
]);
|
|
83
|
+
const a = await m.respondText("ice fire");
|
|
84
|
+
assert.ok(a.length > 0, "a two-topic query must still ground something");
|
|
85
|
+
await m.store.close();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("3. honest silence survives on an unrelated corpus", async () => {
|
|
89
|
+
const m = mk();
|
|
90
|
+
await m.ingest([
|
|
91
|
+
["what is the capital of France?", "The capital of France is Paris."],
|
|
92
|
+
["what is the capital of Spain?", "Madrid is the capital of Spain."],
|
|
93
|
+
["what is the capital of Italy?", "Rome is the capital of Italy."],
|
|
94
|
+
]);
|
|
95
|
+
for (const q of ["xyzzy plugh quux baz?", "qq8f3kz9 vv2m1x7w?"]) {
|
|
96
|
+
const a = await m.respondText(q);
|
|
97
|
+
assert.equal(a, "", `gibberish must stay silent, got ${JSON.stringify(a)}`);
|
|
98
|
+
}
|
|
99
|
+
await m.store.close();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("4. a trained fact still answers (the tier did not displace an earlier one)", async () => {
|
|
103
|
+
const m = mk();
|
|
104
|
+
await m.ingest([
|
|
105
|
+
["what is the capital of France?", "The capital of France is Paris."],
|
|
106
|
+
["what is the capital of Spain?", "Madrid is the capital of Spain."],
|
|
107
|
+
]);
|
|
108
|
+
assert.match(
|
|
109
|
+
await m.respondText("what is the capital of France?"),
|
|
110
|
+
/Paris/,
|
|
111
|
+
);
|
|
112
|
+
await m.store.close();
|
|
113
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// 68-extraction-unanchored.test.mjs — an extraction that located NO frame of its
|
|
2
|
+
// exemplar in the query is not an extraction, and must not answer.
|
|
3
|
+
//
|
|
4
|
+
// extractBySkill's own contract says `accounted` carries "the located frames AND
|
|
5
|
+
// any read span BOUNDED by located frames on both sides", while an open-ended
|
|
6
|
+
// read "remains a guess about where the span stops — it stays unaccounted".
|
|
7
|
+
// EMPTY accounted is the degenerate case: no frame was located at all, so
|
|
8
|
+
// nothing ties the bytes just read to this question. isSpanShaped is
|
|
9
|
+
// deliberately permissive (a sparse-subsequence check) and will accept an
|
|
10
|
+
// exemplar whose relation to the query is coincidental gap-matching; requiring
|
|
11
|
+
// at least one located frame is the structural evidence it leaves out.
|
|
12
|
+
//
|
|
13
|
+
// THE CASE (analyze_training.ts F, the battery's ONLY wrong non-silent answer,
|
|
14
|
+
// on the 15.7M-node store): "Which city is France's seat of government?"
|
|
15
|
+
// answered "Which ci" — a fragment of the query itself — from the exemplar
|
|
16
|
+
// "What is dll", with accounted=[] and pieces=1. A/B verified: without the gate
|
|
17
|
+
// the answer is "Which ci"; with it, silence. The battery went from
|
|
18
|
+
// 31✓/1 weak/10 empty to 31✓/0 weak/11 empty, and `extract` left the provenance
|
|
19
|
+
// census entirely — no wrong answers remain anywhere in it.
|
|
20
|
+
//
|
|
21
|
+
// WHY THE GATE LIVES HERE AND NOT IN THE PIPELINE. The same test at the
|
|
22
|
+
// pipeline's post-grounding density check was tried and REVERTED: `accounted` is
|
|
23
|
+
// passed empty BY CONVENTION on recall's own tiers (recall.ts ground(…, [], …)),
|
|
24
|
+
// so a density veto there refused six legitimate reverse-recall groundings
|
|
25
|
+
// (seat symmetry, bidirectional chain, E9 turn parity, C1 reverse-recall …).
|
|
26
|
+
// Inside extraction the field is this mechanism's own output and carries its
|
|
27
|
+
// documented meaning, so the test is sound exactly where the convention cannot
|
|
28
|
+
// reach it.
|
|
29
|
+
//
|
|
30
|
+
// NOT REPRODUCIBLE IN A FIXTURE: a miniature corpus yields ANCHORED extractions
|
|
31
|
+
// (a frame IS located, accounted non-empty), which this gate correctly permits —
|
|
32
|
+
// verified across seeds 1/7/42. So what this file pins is the other side: the
|
|
33
|
+
// gate must not block a located-frame extraction. The wrong-answer fix itself is
|
|
34
|
+
// evidenced by the real-store A/B above.
|
|
35
|
+
|
|
36
|
+
import { test } from "node:test";
|
|
37
|
+
import assert from "node:assert/strict";
|
|
38
|
+
import { Mind } from "../dist/src/index.js";
|
|
39
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
40
|
+
|
|
41
|
+
/** Span-shaped exemplars: the answer is a subsequence of its context, so the
|
|
42
|
+
* learnt skill is "read the thing the frame wraps". */
|
|
43
|
+
const TRAIN = [
|
|
44
|
+
["What is dll?", "dll"],
|
|
45
|
+
["What is api?", "api"],
|
|
46
|
+
["What is ram?", "ram"],
|
|
47
|
+
["What is cpu?", "cpu"],
|
|
48
|
+
["What is gpu?", "gpu"],
|
|
49
|
+
["What is ssd?", "ssd"],
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
test("1. an extraction whose frame IS located still answers (gate must not over-block)", async () => {
|
|
53
|
+
for (const seed of [1, 7, 42]) {
|
|
54
|
+
const m = new Mind({ seed, store: new SQliteStore({ path: ":memory:" }) });
|
|
55
|
+
await m.ingest(TRAIN);
|
|
56
|
+
const a = await m.respondText("Which colour is the deepest ocean?");
|
|
57
|
+
assert.ok(
|
|
58
|
+
a.length > 0,
|
|
59
|
+
`seed ${seed}: a located-frame extraction must survive the unanchored gate`,
|
|
60
|
+
);
|
|
61
|
+
await m.store.close();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("2. the learnt skill still reads its own trained frame", async () => {
|
|
66
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
67
|
+
await m.ingest(TRAIN);
|
|
68
|
+
assert.match(await m.respondText("What is dll?"), /dll/);
|
|
69
|
+
await m.store.close();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("3. gibberish stays silent — the gate adds refusal, never licence", async () => {
|
|
73
|
+
const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
|
|
74
|
+
await m.ingest(TRAIN);
|
|
75
|
+
for (const q of ["qq8f3kz9 vv2m1x7w?", "xyzzy plugh quux baz?"]) {
|
|
76
|
+
assert.equal(await m.respondText(q), "", `expected silence for ${q}`);
|
|
77
|
+
}
|
|
78
|
+
await m.store.close();
|
|
79
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// 69-frame-filler.test.mjs — the frame-filler tier must not fire without its
|
|
2
|
+
// evidence, and must refuse an ambiguous subject.
|
|
3
|
+
//
|
|
4
|
+
// WHAT THE MECHANISM DOES (src/mind/frame-filler.ts): when every other tier has
|
|
5
|
+
// declined, take the query's described span, put a corroborated filler from a
|
|
6
|
+
// trained context in its place, and require the STORE to already hold that key
|
|
7
|
+
// byte-exactly. "We invent a lookup KEY, never an answer" — the answer is the
|
|
8
|
+
// trained continuation of a form the store verifiably has.
|
|
9
|
+
//
|
|
10
|
+
// THE WIN IS REAL-STORE EVIDENCE, NOT A FIXTURE. On the 15.7M-node trained
|
|
11
|
+
// store, `What is the capital of the country where the Eiffel Tower is?` goes
|
|
12
|
+
// from silence to "The capital of France is Paris." (provenance `recall`), taking
|
|
13
|
+
// analyze_training.ts's section G from 33.3% to 66.7% and the battery from 73.8%
|
|
14
|
+
// to 76.2% with `0 weak` intact. A/B on warm caches: +50 ms on that query,
|
|
15
|
+
// +16 ms on a refusing query that runs 480 probes, and +0 ms on
|
|
16
|
+
// `What is the capital of Zamunda?` (guard 1 exits before probing) and on every
|
|
17
|
+
// query that answers earlier.
|
|
18
|
+
//
|
|
19
|
+
// WHY NO POSITIVE FIXTURE TEST EXISTS — measured, not assumed. The mechanism
|
|
20
|
+
// keys on corpus RARITY (container counts, the same reading the bridge's anchor
|
|
21
|
+
// picking uses). At fixture scale that signal is absent and even inverted: in a
|
|
22
|
+
// 21-deposit store `capital` reports 1 container and `landmark` reports 9, while
|
|
23
|
+
// on the trained store `Eiffel` is 54 against `What` at 1,586. So a fixture
|
|
24
|
+
// cannot make the query's rarest word land inside the description, which guard 1
|
|
25
|
+
// requires — three fixture shapes were tried (bare, 21-deposit, and one with an
|
|
26
|
+
// attested long word in the description) and none reached the positive path.
|
|
27
|
+
// What a fixture CAN pin is the refusal side, which is what this file does.
|
|
28
|
+
|
|
29
|
+
import { test } from "node:test";
|
|
30
|
+
import assert from "node:assert/strict";
|
|
31
|
+
import { Mind } from "../dist/src/index.js";
|
|
32
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
33
|
+
import { frameFillerSubstitution } from "../dist/src/mind/frame-filler.js";
|
|
34
|
+
|
|
35
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
36
|
+
const FRAME = [
|
|
37
|
+
["What is the capital of France?", "Paris is the capital of France."],
|
|
38
|
+
["What is the capital of Spain?", "Madrid is the capital of Spain."],
|
|
39
|
+
["What is the capital of Italy?", "Rome is the capital of Italy."],
|
|
40
|
+
["What is the capital of Japan?", "Tokyo is the capital of Japan."],
|
|
41
|
+
];
|
|
42
|
+
const LINK = [
|
|
43
|
+
"What is the most famous landmark in France?",
|
|
44
|
+
"The most famous landmark in France is the Eiffel Tower.",
|
|
45
|
+
];
|
|
46
|
+
const LINK2 = [
|
|
47
|
+
"What is the tallest structure in Spain?",
|
|
48
|
+
"The tallest structure in Spain is the Eiffel Tower.",
|
|
49
|
+
];
|
|
50
|
+
const Q = "What is the capital of the country where the Eiffel Tower is?";
|
|
51
|
+
|
|
52
|
+
async function fixture(train) {
|
|
53
|
+
const mind = new Mind({
|
|
54
|
+
seed: 1,
|
|
55
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
56
|
+
});
|
|
57
|
+
await mind.ingest(train);
|
|
58
|
+
return mind;
|
|
59
|
+
}
|
|
60
|
+
/** Call the tier the way recall does: the query plus recall's ranked hit ids. */
|
|
61
|
+
async function tier(mind, q) {
|
|
62
|
+
const hits = await mind.store.resonate(mind.perceive(q).v, 24);
|
|
63
|
+
return frameFillerSubstitution(mind, enc(q), hits.map((h) => h.id));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
test("1. no linking evidence — the tier does not fire", async () => {
|
|
67
|
+
// The frame is attested, but nothing in the store ties the description's
|
|
68
|
+
// content to any filler. Guard 1 has nothing to qualify.
|
|
69
|
+
const m = await fixture(FRAME);
|
|
70
|
+
assert.equal(await tier(m, Q), null);
|
|
71
|
+
await m.store.close();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("2. an AMBIGUOUS subject is refused", async () => {
|
|
75
|
+
// Two trained contexts hold the description's content and each proposes a
|
|
76
|
+
// different filler. Guard 4: neither is licensed.
|
|
77
|
+
const m = await fixture([...FRAME, LINK, LINK2]);
|
|
78
|
+
assert.equal(await tier(m, Q), null);
|
|
79
|
+
await m.store.close();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("3. an unrelated query never grounds a frame neighbour", async () => {
|
|
83
|
+
// The fabrication shape: a fictional filler in an attested frame. On the real
|
|
84
|
+
// store this query resolves 24 keys (Chile, India, Japan, Italy …) once the
|
|
85
|
+
// guards are weakened, so it is the case that most needs pinning.
|
|
86
|
+
const m = await fixture([...FRAME, LINK]);
|
|
87
|
+
for (
|
|
88
|
+
const q of [
|
|
89
|
+
"What is the capital of Zamunda?",
|
|
90
|
+
"xyzzy plugh quux baz?",
|
|
91
|
+
"qq8f3kz9 vv2m1x7w?",
|
|
92
|
+
]
|
|
93
|
+
) {
|
|
94
|
+
assert.equal(await tier(m, q), null, `expected refusal for ${q}`);
|
|
95
|
+
}
|
|
96
|
+
await m.store.close();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("4. the tier is deterministic and side-effect free", async () => {
|
|
100
|
+
const m = await fixture([...FRAME, LINK]);
|
|
101
|
+
const a = await tier(m, Q);
|
|
102
|
+
const b = await tier(m, Q);
|
|
103
|
+
assert.deepEqual(a, b);
|
|
104
|
+
// Running it must not disturb the answers of forms that ground normally.
|
|
105
|
+
assert.match(await m.respondText("What is the capital of France?"), /Paris/);
|
|
106
|
+
await m.store.close();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("5. end to end, the fixture's own trained forms still answer", async () => {
|
|
110
|
+
const m = await fixture([...FRAME, LINK]);
|
|
111
|
+
assert.match(await m.respondText("What is the capital of Spain?"), /Madrid/);
|
|
112
|
+
assert.match(await m.respondText("What is the capital of Japan?"), /Tokyo/);
|
|
113
|
+
assert.equal(await m.respondText("qq8f3kz9 vv2m1x7w?"), "");
|
|
114
|
+
await m.store.close();
|
|
115
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// 70-prefix-completion.test.mjs — a query that IS the opening of one trained
|
|
2
|
+
// form is completed by that form's remainder; anything less is refused.
|
|
3
|
+
//
|
|
4
|
+
// WHAT THE MECHANISM DOES (src/mind/prefix-completion.ts): when every other
|
|
5
|
+
// tier has declined, scan the candidate list recall's refusal path has ALREADY
|
|
6
|
+
// fetched and look for a trained form whose bytes literally BEGIN with the whole
|
|
7
|
+
// query. The answer is that form's own remainder — never an invention.
|
|
8
|
+
//
|
|
9
|
+
// WHY IT IS NEEDED, measured on the 15.7M-node trained store:
|
|
10
|
+
// `The capital of France is` grounded nothing, while
|
|
11
|
+
// `The capital of France is Paris.` is trained and reads back byte-exact. Two
|
|
12
|
+
// independent reasons the earlier tiers cannot reach it:
|
|
13
|
+
// * `resolve(prefix)` is null — a proper prefix has no branch of its own.
|
|
14
|
+
// * the form is absent from `resonate(k)` at k = 24, 256 AND 2048, while forms
|
|
15
|
+
// scoring LOWER are returned (cos 0.5752 for the target against Germany
|
|
16
|
+
// 0.5670, Yemen 0.5591). `k` only reorders within the IVF clusters already
|
|
17
|
+
// probed, so no k recovers it; with `exhaustive` it ranks 8.
|
|
18
|
+
// It is a RETRIEVABILITY gap, not a semantic one.
|
|
19
|
+
//
|
|
20
|
+
// COST — A/B on the trained store, counting resonate calls directly: the tier
|
|
21
|
+
// adds ZERO exhaustive calls. F-prefix already made exactly one (for the
|
|
22
|
+
// substitution bridge) and returned silence; with the tier it makes the same one
|
|
23
|
+
// and answers. Battery: F 0% → 25%, overall 76.2% → 78.6%, `0 weak`, all three
|
|
24
|
+
// honest-silence probes still silent, median latency 0.63s → 0.57s.
|
|
25
|
+
//
|
|
26
|
+
// Unlike test/69, a POSITIVE fixture IS constructible here: the mechanism keys
|
|
27
|
+
// on literal byte containment, not on corpus rarity, and rarity is the signal
|
|
28
|
+
// that collapses at fixture scale.
|
|
29
|
+
//
|
|
30
|
+
// The mechanism has NO notion of text: no separator, no character class, no
|
|
31
|
+
// "word". Its only structural quantity is W, the river's grouping window. So
|
|
32
|
+
// these fixtures are readable prose only for the reader's benefit -- every
|
|
33
|
+
// assertion below is about bytes and geometry.
|
|
34
|
+
|
|
35
|
+
import { test } from "node:test";
|
|
36
|
+
import assert from "node:assert/strict";
|
|
37
|
+
import { Mind } from "../dist/src/index.js";
|
|
38
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
39
|
+
import { prefixCompletion } from "../dist/src/mind/prefix-completion.js";
|
|
40
|
+
|
|
41
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
42
|
+
const dec = new TextDecoder();
|
|
43
|
+
|
|
44
|
+
async function fixture(train) {
|
|
45
|
+
const mind = new Mind({
|
|
46
|
+
seed: 1,
|
|
47
|
+
store: new SQliteStore({ path: ":memory:" }),
|
|
48
|
+
});
|
|
49
|
+
await mind.ingest(train);
|
|
50
|
+
return mind;
|
|
51
|
+
}
|
|
52
|
+
/** Call the tier the way recall does: the query plus a ranked hit list. */
|
|
53
|
+
async function tier(mind, q, k = 64) {
|
|
54
|
+
const hits = await mind.store.resonate(mind.perceive(q).v, k);
|
|
55
|
+
return prefixCompletion(mind, enc(q), hits.map((h) => h.id));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const FACTS = [
|
|
59
|
+
"The capital of France is Paris.",
|
|
60
|
+
"The capital of Japan is Tokyo.",
|
|
61
|
+
"The capital of Germany is Berlin.",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
test("1. the sole form opening with the query is completed by its remainder", async () => {
|
|
65
|
+
const m = await fixture(FACTS);
|
|
66
|
+
const hit = await tier(m, "The capital of France is");
|
|
67
|
+
assert.notEqual(hit, null, "the trained form opens with the query");
|
|
68
|
+
assert.equal(dec.decode(hit.form), "The capital of France is Paris.");
|
|
69
|
+
await m.store.close();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("2. AMBIGUITY is refused — the prefix trap", async () => {
|
|
73
|
+
// Two trained forms open with the same words and continue differently. The
|
|
74
|
+
// corpus does not say which completion the asker means, so neither is
|
|
75
|
+
// licensed. This is the documented prefix trap, and it is real — it simply
|
|
76
|
+
// does not hold for EVERY prefix, which is what test 1 pins.
|
|
77
|
+
const m = await fixture([
|
|
78
|
+
"The capital city is Paris.",
|
|
79
|
+
"The capital city is Berlin.",
|
|
80
|
+
]);
|
|
81
|
+
assert.equal(await tier(m, "The capital city is"), null);
|
|
82
|
+
await m.store.close();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("3. the same completion via two forms is ONE answer, not an ambiguity", async () => {
|
|
86
|
+
// Uniqueness is judged on the remainder BYTES, not on the candidate id.
|
|
87
|
+
const m = await fixture([
|
|
88
|
+
"The capital of France is Paris.",
|
|
89
|
+
"The capital of France is Paris.",
|
|
90
|
+
]);
|
|
91
|
+
const hit = await tier(m, "The capital of France is");
|
|
92
|
+
assert.notEqual(hit, null);
|
|
93
|
+
assert.equal(dec.decode(hit.form), "The capital of France is Paris.");
|
|
94
|
+
await m.store.close();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("4. a SUB-QUANTUM continuation is refused", async () => {
|
|
98
|
+
// Observed on the trained store: `What is the capital of France?` opens a
|
|
99
|
+
// trained `What is the capital of France??`, which continues by ONE byte.
|
|
100
|
+
// Below W the continuation is sub-quantum -- the fold groups nothing from it
|
|
101
|
+
// -- and voicing it is the degenerate reply of the battery's section M.
|
|
102
|
+
// The bar is the grouping window, not a punctuation class.
|
|
103
|
+
const m = await fixture(["What is the capital of France??"]);
|
|
104
|
+
assert.equal(await tier(m, "What is the capital of France?"), null);
|
|
105
|
+
await m.store.close();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("5. a query no trained form opens with is refused", async () => {
|
|
109
|
+
const m = await fixture(FACTS);
|
|
110
|
+
for (
|
|
111
|
+
const q of [
|
|
112
|
+
"The capital of Zamunda is",
|
|
113
|
+
"xyzzy plugh quux",
|
|
114
|
+
"Paris is the capital of",
|
|
115
|
+
]
|
|
116
|
+
) {
|
|
117
|
+
assert.equal(await tier(m, q), null, q);
|
|
118
|
+
}
|
|
119
|
+
await m.store.close();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("6. a query that is a whole trained form is not 'completed' by itself", async () => {
|
|
123
|
+
// An exact form has an EMPTY remainder, which no guard should let through —
|
|
124
|
+
// and the exact tiers own that query anyway.
|
|
125
|
+
const m = await fixture(FACTS);
|
|
126
|
+
assert.equal(await tier(m, "The capital of France is Paris."), null);
|
|
127
|
+
await m.store.close();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("7. the tier is deterministic and side-effect free", async () => {
|
|
131
|
+
const m = await fixture(FACTS);
|
|
132
|
+
const q = "The capital of France is";
|
|
133
|
+
const before = m.store.nodeCount();
|
|
134
|
+
const a = await tier(m, q);
|
|
135
|
+
const b = await tier(m, q);
|
|
136
|
+
assert.equal(dec.decode(a.form), dec.decode(b.form));
|
|
137
|
+
assert.equal(a.id, b.id);
|
|
138
|
+
assert.equal(
|
|
139
|
+
m.store.nodeCount(),
|
|
140
|
+
before,
|
|
141
|
+
"the tier must not intern anything",
|
|
142
|
+
);
|
|
143
|
+
await m.store.close();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("9. a form that continues past the read bound vetoes", async () => {
|
|
147
|
+
// Reads are bounded (query.length * W). A candidate that opens with the
|
|
148
|
+
// query but SATURATES the read continues out of sight, so it is a standing
|
|
149
|
+
// disagreement -- NOT something to skip. Skipping it is what manufactures a
|
|
150
|
+
// fragment: it removes the only evidence contradicting an interior fold node
|
|
151
|
+
// that happens to fit under the cap.
|
|
152
|
+
const q = "The capital of France is";
|
|
153
|
+
const long = q + " Paris, and the country's largest city by a wide margin, " +
|
|
154
|
+
"a global centre for art, fashion, gastronomy and culture.";
|
|
155
|
+
assert.ok(long.length > q.length * 4, "the fixture must exceed the cap");
|
|
156
|
+
const m = await fixture([long]);
|
|
157
|
+
assert.equal(await tier(m, q), null);
|
|
158
|
+
await m.store.close();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("8. end to end, the fixture's own trained forms still answer", async () => {
|
|
162
|
+
// The tier sits on the refusal path; it must not disturb normal grounding.
|
|
163
|
+
const m = await fixture([
|
|
164
|
+
"What is the capital of France?",
|
|
165
|
+
"The capital of France is Paris.",
|
|
166
|
+
]);
|
|
167
|
+
const r = await m.respond("What is the capital of France?");
|
|
168
|
+
assert.ok(r.bytes.length > 0, "a trained question still answers");
|
|
169
|
+
await m.store.close();
|
|
170
|
+
});
|