@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.
Files changed (63) hide show
  1. package/AGENTS.md +290 -77
  2. package/HOW_IT_WORKS.md +2170 -735
  3. package/dist/example/train_base.d.ts +9 -3
  4. package/dist/example/train_base.js +21 -4
  5. package/dist/src/canon.d.ts +19 -0
  6. package/dist/src/canon.js +28 -0
  7. package/dist/src/geometry.d.ts +52 -0
  8. package/dist/src/geometry.js +87 -1
  9. package/dist/src/mind/bridge.js +27 -1
  10. package/dist/src/mind/frame-filler.d.ts +15 -0
  11. package/dist/src/mind/frame-filler.js +535 -0
  12. package/dist/src/mind/learning.js +6 -11
  13. package/dist/src/mind/mechanisms/cast.js +72 -2
  14. package/dist/src/mind/mechanisms/cover.js +6 -1
  15. package/dist/src/mind/mechanisms/extraction.js +27 -0
  16. package/dist/src/mind/mechanisms/recall.js +214 -34
  17. package/dist/src/mind/mind.d.ts +49 -1
  18. package/dist/src/mind/mind.js +137 -10
  19. package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
  20. package/dist/src/mind/pipeline.js +29 -1
  21. package/dist/src/mind/prefix-completion.d.ts +59 -0
  22. package/dist/src/mind/prefix-completion.js +270 -0
  23. package/dist/src/mind/primitives.d.ts +29 -10
  24. package/dist/src/mind/primitives.js +52 -61
  25. package/dist/src/mind/recognition.js +119 -9
  26. package/dist/src/mind/traverse.d.ts +32 -0
  27. package/dist/src/mind/traverse.js +52 -0
  28. package/dist/src/mind/types.d.ts +55 -16
  29. package/dist/src/mind/types.js +68 -19
  30. package/dist/src/store.d.ts +21 -0
  31. package/dist/src/store.js +21 -0
  32. package/example/train_base.ts +21 -4
  33. package/package.json +1 -1
  34. package/src/canon.ts +28 -0
  35. package/src/geometry.ts +100 -1
  36. package/src/mind/bridge.ts +34 -0
  37. package/src/mind/frame-filler.ts +604 -0
  38. package/src/mind/learning.ts +5 -9
  39. package/src/mind/mechanisms/cast.ts +70 -2
  40. package/src/mind/mechanisms/cover.ts +6 -1
  41. package/src/mind/mechanisms/extraction.ts +27 -0
  42. package/src/mind/mechanisms/recall.ts +236 -37
  43. package/src/mind/mind.ts +154 -14
  44. package/src/mind/pipeline-mechanism.ts +7 -0
  45. package/src/mind/pipeline.ts +33 -1
  46. package/src/mind/prefix-completion.ts +314 -0
  47. package/src/mind/primitives.ts +59 -70
  48. package/src/mind/recognition.ts +117 -6
  49. package/src/mind/traverse.ts +52 -0
  50. package/src/mind/types.ts +98 -42
  51. package/src/store.ts +25 -0
  52. package/test/13-conversation.test.mjs +13 -0
  53. package/test/57-fusion-order.test.mjs +65 -0
  54. package/test/66-query-edge-whitespace.test.mjs +99 -0
  55. package/test/67-climb-anchor-breadth.test.mjs +113 -0
  56. package/test/68-extraction-unanchored.test.mjs +79 -0
  57. package/test/69-frame-filler.test.mjs +115 -0
  58. package/test/70-prefix-completion.test.mjs +170 -0
  59. package/test/71-embedded-canon-equivalence.test.mjs +121 -0
  60. package/test/72-prefix-candidate-supply.test.mjs +114 -0
  61. package/test/73-scaffolding-only-bridge-abstains.test.mjs +178 -0
  62. package/test/74-prefix-trap-not-sprung-early.test.mjs +114 -0
  63. 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
+ });