@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
@@ -0,0 +1,121 @@
1
+ // 71-embedded-canon-equivalence.test.mjs — an EMBEDDED trained form must be
2
+ // recognised under the response's canonical equivalence, not only byte-exactly.
3
+ //
4
+ // The contract: `recognise` decomposes a query into stored forms that lead
5
+ // somewhere. A trained form sitting at an interior offset of a larger query is
6
+ // found by the scale-gated "exact query-edge forms" tier in recognition.ts.
7
+ // That tier admits candidates with a BYTE-EXACT `store.findBranch` probe over
8
+ // the query's leaf-id run, then calls the canon-capable `resolveSpan`. The
9
+ // prefilter is therefore strictly narrower than its own resolver: a form whose
10
+ // deposit differs from the query only by the response canonicalizer's
11
+ // equivalence (case, width) can never reach `resolveSpan`, because a
12
+ // differently-cased deposit's branch kid-ids are not the query's leaf-id run
13
+ // under ANY canonicalization of the query. Measured on a 15.7M-node store:
14
+ // "Hey, What is the process of photosynthesis?" recognises the trained form,
15
+ // "Hey, what is …" recognises nothing, though the lowercased form resolves
16
+ // exactly at offset 0.
17
+ //
18
+ // WHY THE 4.3k-FACT FIXTURE IS NOT OPTIONAL: the tier runs only when
19
+ // `atomIsHub(ctx, corpusN)` is true, i.e. N > ~4096 contexts at maxGroup=4.
20
+ // Every small-store suite exercises the OTHER branch of recognition, so a
21
+ // conventional fixture would pass while the defect is fully present. The
22
+ // atomIsHub assertion below fails loudly if that crossover ever moves, so this
23
+ // test can never silently stop covering the branch it exists for.
24
+ import { test } from "node:test";
25
+ import assert from "node:assert/strict";
26
+ import { Mind } from "../dist/src/index.js";
27
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
28
+ import { recognise } from "../dist/src/mind/recognition.js";
29
+ import { resolve } from "../dist/src/mind/primitives.js";
30
+ import { atomIsHub, corpusN } from "../dist/src/mind/traverse.js";
31
+ import { textCanon } from "../dist/src/canon.js";
32
+
33
+ const enc = (s) => new TextEncoder().encode(s);
34
+
35
+ // Longer than chainReach(W)=W²=16 bytes, or the tier's own size gate skips it.
36
+ const FORM = "Madam Your Glasses Are Fogged";
37
+ const PREFIX = "Hey, ";
38
+
39
+ /** Recognise with the canonicalizer a TEXT response would carry. `ctx.canon`
40
+ * is per-response state that `respond()` injects; calling `recognise`
41
+ * directly would otherwise run with canon disabled and test nothing. */
42
+ function recogniseAsText(mind, text) {
43
+ mind.canon = textCanon;
44
+ mind.canonMemo = new Map();
45
+ try {
46
+ return recognise(mind, enc(text));
47
+ } finally {
48
+ mind.canon = null;
49
+ mind.canonMemo = null;
50
+ }
51
+ }
52
+
53
+ const sitesFor = (rec, id) => rec.sites.filter((s) => s.payload === id);
54
+
55
+ test("an embedded trained form is recognised under canonical equivalence", async () => {
56
+ const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
57
+
58
+ const filler = [];
59
+ for (let i = 0; i < 4300; i++) filler.push([`filler-${i}`, `f${i}`]);
60
+ await m.ingest(filler);
61
+ await m.ingest([[FORM, "a stored continuation for the form"]]);
62
+ // Canonical resolution reads a store-side index that training builds
63
+ // explicitly (meta `canon.upto`); without it `canonResolve` has no
64
+ // candidates and the equivalence under test does not exist at all.
65
+ await m.buildCanonIndex();
66
+
67
+ const N = corpusN(m);
68
+ assert.ok(
69
+ atomIsHub(m, N),
70
+ `fixture must cross atomIsHub (N=${N}); the tier under test is ` +
71
+ `scale-gated and this suite would otherwise assert nothing`,
72
+ );
73
+
74
+ const formId = resolve(m, enc(FORM));
75
+ assert.ok(formId !== null, "the trained form must resolve exactly");
76
+
77
+ // Control 1 — the form standalone, in its deposited case.
78
+ assert.ok(
79
+ sitesFor(recogniseAsText(m, FORM), formId).length > 0,
80
+ "standalone deposited-case form must be recognised",
81
+ );
82
+
83
+ // Control 2 — the form standalone, lowercased. This is what proves the
84
+ // canonical equivalence is REAL and reachable, so the embedded assertion
85
+ // below is about placement, not about the canonicalizer.
86
+ assert.ok(
87
+ sitesFor(recogniseAsText(m, FORM.toLowerCase()), formId).length > 0,
88
+ "lowercased form must resolve to the same node at offset 0",
89
+ );
90
+
91
+ // Control 3 — embedded, deposited case. The tier's byte-exact route.
92
+ assert.ok(
93
+ sitesFor(recogniseAsText(m, PREFIX + FORM), formId).length > 0,
94
+ "embedded deposited-case form must be recognised",
95
+ );
96
+
97
+ // THE CONTRACT — embedded AND canonically equivalent. Controls 2 and 3 each
98
+ // hold, so anything that fails here is the prefilter being narrower than its
99
+ // resolver, which is the defect this test exists to prevent.
100
+ const rec = recogniseAsText(m, PREFIX + FORM.toLowerCase());
101
+ const hit = sitesFor(rec, formId);
102
+ assert.ok(
103
+ hit.length > 0,
104
+ `embedded lowercased form must be recognised: standalone-lowercased and ` +
105
+ `embedded-exact both are, so the byte-exact admission gate is the only ` +
106
+ `thing rejecting it (sites found: ${
107
+ rec.sites.map((s) => `${s.start}-${s.end}`).join(",") || "none"
108
+ })`,
109
+ );
110
+ // It must be found AT its true offset, not as some other coincidental span.
111
+ assert.ok(
112
+ hit.some((s) =>
113
+ s.start === PREFIX.length && s.end === PREFIX.length + FORM.length
114
+ ),
115
+ `the form must be recognised at its own span [${PREFIX.length},${
116
+ PREFIX.length + FORM.length
117
+ }], got ${hit.map((s) => `${s.start}-${s.end}`).join(",")}`,
118
+ );
119
+
120
+ await m.store.close();
121
+ });
@@ -0,0 +1,114 @@
1
+ // 72-prefix-candidate-supply.test.mjs — a query that is a proper PREFIX of a
2
+ // trained form must be able to reach that form even when resonance cannot rank
3
+ // it.
4
+ //
5
+ // THE GAP THIS PINS. `prefixCompletion` consumes a ranked list the caller
6
+ // already fetched, and that list comes from resonance. Resonance cannot rank a
7
+ // proper prefix: measured on a 15.7M-node store, cos(prefix, form) falls from
8
+ // 0.9629 at a one-byte truncation to 0.6206 at three bytes, against a
9
+ // reachThreshold of 0.8750. The mechanism's guards were therefore never
10
+ // reached — the trace read `candidates: 24, opened: 0` — and the query answered
11
+ // nothing. `prefixCandidates` is the second SUPPLY that closes it, reading the
12
+ // leaf-id WINDOW index `indexSubSpans` already writes at deposit time. No
13
+ // ingestion, storage or fold change is involved: this test would pass on a
14
+ // store trained before the supply existed.
15
+ //
16
+ // WHY THE ASSERTIONS ARE SHAPED THIS WAY. On a small fixture resonance may
17
+ // well return the form by luck, and then an end-to-end "does it answer?" test
18
+ // would pass with the supply deleted — pinning nothing. So the contract is
19
+ // asserted on `prefixCandidates` DIRECTLY, and the resonance list is asserted
20
+ // to lack the form, which is what makes the supply load-bearing rather than
21
+ // redundant.
22
+ //
23
+ // The 4.3k-fact fixture is not decoration: window containment is judged against
24
+ // `hubBound` = √N, so a toy store makes every window look saturated and the
25
+ // supply would correctly return nothing, testing nothing.
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 {
31
+ prefixCandidates,
32
+ prefixCompletion,
33
+ } from "../dist/src/mind/prefix-completion.js";
34
+ import { gistOf, resolve } from "../dist/src/mind/primitives.js";
35
+
36
+ const enc = (s) => new TextEncoder().encode(s);
37
+ const dec = new TextDecoder();
38
+
39
+ const FORM = "The chief export of the northern province is powdered basalt.";
40
+
41
+ test("a proper prefix reaches its trained form through the window supply", async () => {
42
+ const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
43
+
44
+ const filler = [];
45
+ for (let i = 0; i < 4300; i++) filler.push([`filler-${i}`, `f${i}`]);
46
+ await m.ingest(filler);
47
+ await m.ingest([[FORM, "acknowledged"]]);
48
+
49
+ const formId = resolve(m, enc(FORM));
50
+ assert.ok(formId !== null, "the trained form must resolve exactly");
51
+
52
+ // Truncate by more than one grouping window, so the continuation clears
53
+ // prefixCompletion's sub-quantum guard and the tier can actually fire.
54
+ const W = m.space.maxGroup;
55
+ const query = enc(FORM.slice(0, FORM.length - 3 * W));
56
+
57
+ // Premise 1 — the exact tiers genuinely cannot serve this query.
58
+ assert.equal(
59
+ resolve(m, query),
60
+ null,
61
+ "a proper prefix must have no branch of its own, or the gap is not real",
62
+ );
63
+
64
+ // Premise 2 — resonance does not supply the form, so anything that works
65
+ // below is the new supply and not the ranked list in disguise.
66
+ // Premise 2 — the ranked list is NOT what is under test. At fixture scale
67
+ // resonance does rank the form (it is the only content among 4,300 fillers
68
+ // that resembles the query), and that is measured, not assumed: the
69
+ // assertion below records it, so if fixture geometry ever changes the reader
70
+ // is told rather than misled. Resonance's real-world inability to rank a
71
+ // prefix is a LARGE-CORPUS property — cos falls to 0.6206 at a three-byte
72
+ // truncation against a 0.8750 bar, on a 15.7M-node store — and cannot be
73
+ // reproduced at this scale. That is why the contract below is asserted on
74
+ // `prefixCandidates` DIRECTLY: deleting or emptying the supply fails this
75
+ // test regardless of what resonance happens to return.
76
+ const ranked = (await m.store.resonate(gistOf(m, query), 64)).map((h) =>
77
+ h.id
78
+ );
79
+ assert.ok(
80
+ ranked.includes(formId),
81
+ "fixture note: at this scale resonance is expected to rank the form; " +
82
+ "if it no longer does, the end-to-end path below became the load-" +
83
+ "bearing assertion and this comment must be revisited",
84
+ );
85
+
86
+ // THE CONTRACT — the write side's own window index proposes the form.
87
+ const proposed = prefixCandidates(m, query);
88
+ assert.ok(
89
+ proposed.includes(formId),
90
+ `the window supply must propose the trained form the query opens ` +
91
+ `(proposed ${proposed.length} candidate(s))`,
92
+ );
93
+
94
+ // …and the mechanism, unchanged, grounds it whole through that supply.
95
+ const completed = prefixCompletion(m, query, proposed);
96
+ assert.ok(completed !== null, "the supplied form must complete the query");
97
+ assert.equal(
98
+ dec.decode(completed.form),
99
+ FORM,
100
+ "a FORM is grounded whole, never a slice cut at the query's end",
101
+ );
102
+
103
+ // HONEST DEGRADATION (§2.13). A query with no discriminative window must
104
+ // propose nothing rather than guess — silence is the correct answer, and a
105
+ // supply that widened until it found something would be the real defect.
106
+ const hub = enc("The ");
107
+ assert.equal(
108
+ prefixCompletion(m, hub, prefixCandidates(m, hub)),
109
+ null,
110
+ "a query carrying no discriminative window must stay silent",
111
+ );
112
+
113
+ await m.store.close();
114
+ });
@@ -0,0 +1,178 @@
1
+ // 73-scaffolding-only-bridge-abstains.test.mjs — the substitution bridge must
2
+ // ABSTAIN when every literal span it did not substitute is corpus-global
3
+ // scaffolding.
4
+ //
5
+ // THE DEFECT THIS PINS. A bridge grounds through the literal spans it did NOT
6
+ // substitute; those anchors are the whole of its evidence. The anchor scan
7
+ // ranked them by containment but rejected only the ones with ZERO containers,
8
+ // so a query made entirely of scaffolding still bridged — the single
9
+ // substituted span carried the whole semantic load, and the answer was voiced
10
+ // with confidence. Measured on the trained store (hubBound 571): "What is the
11
+ // capital of" has 19 anchors, ALL saturated ("What":572, "hat ":572, "at i":572
12
+ // …), and answered with an unrelated trained context about an integral. That
13
+ // breaks honest silence (§2.13), which is worse than a gap: a gap is visible, a
14
+ // fabrication is not.
15
+ //
16
+ // WHY THIS IS NOT A PROBE-SHAPED PATCH. The gate was falsified against the
17
+ // queries the bridge answers CORRECTLY before it was written, and every one of
18
+ // them has an unsaturated anchor with no near miss: "Who is the author of
19
+ // Hamlet?" → "let?":12, "How do you say 'thank you' in French?" → "y 't":3,
20
+ // "…largest planet…" → "tem?":31, "What is the capital of France?" →
21
+ // "f Fr":114. The honest-silence probes sit on the same side as the correct
22
+ // ones ("Zamu":3), so this gate is not what makes them silent and cannot be
23
+ // credited for them. The separation is categorical, not marginal.
24
+ //
25
+ // WHY A TEST DOUBLE AND NOT A CORPUS. Saturation is a LARGE-CORPUS
26
+ // phenomenon and cannot be manufactured at fixture scale — this was measured,
27
+ // not assumed, across three fixture designs:
28
+ // • one repeated template → content addressing dedups the identical chunks,
29
+ // so its windows have 1–5 containers, not thousands;
30
+ // • template with varied surroundings → the leading windows saturate (67 at
31
+ // bound 66) but interior ones stay at 1, because "is the " is always the
32
+ // same deduped chunk whatever surrounds it;
33
+ // • randomised small vocabulary → most windows saturate, but cross-boundary
34
+ // windows ("fa b":13) cannot, since they touch only the two adjacent chunk
35
+ // types.
36
+ // Every window saturating at once needs the real store's scale AND lexical
37
+ // diversity. So the CONDITION is supplied by a store double and the BEHAVIOUR
38
+ // under it is asserted — the honest way to pin a rule whose trigger a fixture
39
+ // cannot reach. The double inflates containment only; it invents no node, and
40
+ // the padding is a real container id, so nothing downstream reads a fiction.
41
+ import { test } from "node:test";
42
+ import assert from "node:assert/strict";
43
+ import { Mind } from "../dist/src/index.js";
44
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
45
+ import { substitutionBridge } from "../dist/src/mind/bridge.js";
46
+ import { gistOf } from "../dist/src/mind/primitives.js";
47
+ import {
48
+ allWindowsAreScaffolding,
49
+ hubBound,
50
+ } from "../dist/src/mind/traverse.js";
51
+
52
+ const enc = (s) => new TextEncoder().encode(s);
53
+
54
+ /** Report every window as corpus-global: the real containers, padded with an
55
+ * id that is genuinely among them, up to past the hub bound. Only `.length`
56
+ * drives the gate, and padding with a real container keeps every other reader
57
+ * truthful. */
58
+ function saturateContainment(mind) {
59
+ const store = mind.store;
60
+ const real = store.containersSlice.bind(store);
61
+ const bound = hubBound(mind);
62
+ store.containersSlice = (child, off, limit) => {
63
+ const got = real(child, off, limit);
64
+ if (got.length === 0) return got;
65
+ const out = got.slice();
66
+ while (out.length < Math.min(limit, bound + 1)) out.push(got[0]);
67
+ return out;
68
+ };
69
+ return () => {
70
+ store.containersSlice = real;
71
+ };
72
+ }
73
+
74
+ test("a bridge with only scaffolding anchors stays silent", async () => {
75
+ const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
76
+
77
+ const facts = [];
78
+ for (let i = 0; i < 600; i++) {
79
+ facts.push([
80
+ `case ${i}: what is the qty${i} of item${i}?`,
81
+ `it is ${i * 2}`,
82
+ ]);
83
+ }
84
+ await m.ingest(facts);
85
+
86
+ const query = enc("case 7: what is the qty7 of item7?");
87
+ const proposed = async () =>
88
+ (await m.store.resonate(gistOf(m, query), 256)).map((h) => h.id);
89
+
90
+ // CONTROL — with real containment the query HAS discriminating anchors, so
91
+ // the gate must not apply. Establishing this first is what makes the
92
+ // assertion below about saturation rather than about this query being
93
+ // unbridgeable for some unrelated reason.
94
+ const ordinary = await substitutionBridge(m, query, proposed);
95
+
96
+ // THE CONTRACT — the identical query, with every anchor now corpus-global.
97
+ // Nothing corroborates a substitution, so the only honest result is none.
98
+ const restore = saturateContainment(m);
99
+ try {
100
+ const bridged = await substitutionBridge(m, query, proposed);
101
+ assert.equal(
102
+ bridged,
103
+ null,
104
+ "a bridge whose every anchor is corpus-global scaffolding must abstain",
105
+ );
106
+ } finally {
107
+ restore();
108
+ }
109
+
110
+ // …and the gate is SELECTIVE: it changed the outcome only because of
111
+ // saturation. If the bridge declined this query anyway, the assertion above
112
+ // would be vacuous, and this is what says it is not.
113
+ assert.notEqual(
114
+ ordinary,
115
+ null,
116
+ "fixture invalid: the bridge declines this query even with real " +
117
+ "containment, so the saturated assertion above pins nothing",
118
+ );
119
+
120
+ await m.store.close();
121
+ });
122
+
123
+ // The predicate itself, and its SECOND consumer. The scaffolding-dominated
124
+ // tier in mechanisms/recall.ts grounds the consensus-climb anchor, and it
125
+ // fabricated for the same reason the bridge did: measured on the trained
126
+ // store, "What is the capital " answered "Colombo is the commercial capital of
127
+ // Sri Lanka…" on breadth 0.667 / clusters 1, every window a hub ("What":572).
128
+ //
129
+ // DISPERSION WAS TRIED THERE FIRST AND FALSIFIED — recorded here because the
130
+ // falsification is the reason this predicate is shared rather than local: the
131
+ // fabrication and the no-punctuation robustness probe "what is the capital of
132
+ // france" have the IDENTICAL profile (breadth 0.667, clusters 1), so requiring
133
+ // clusters >= 2 silenced the good probe too and cost the battery a probe.
134
+ // Window saturation separates them cleanly where dispersion cannot
135
+ // ("f fr":248 vs all-saturated).
136
+ test("scaffolding-only is a property of the query, not of one mechanism", async () => {
137
+ const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
138
+
139
+ const facts = [];
140
+ for (let i = 0; i < 600; i++) {
141
+ facts.push([
142
+ `case ${i}: what is the qty${i} of item${i}?`,
143
+ `it is ${i * 2}`,
144
+ ]);
145
+ }
146
+ await m.ingest(facts);
147
+
148
+ const query = enc("case 7: what is the qty7 of item7?");
149
+
150
+ // With real containment the query HAS discriminating windows.
151
+ assert.equal(
152
+ allWindowsAreScaffolding(m, query),
153
+ false,
154
+ "a query with a discriminating window must not read as scaffolding-only",
155
+ );
156
+
157
+ // With every window corpus-global it does not.
158
+ const restore = saturateContainment(m);
159
+ try {
160
+ assert.equal(
161
+ allWindowsAreScaffolding(m, query),
162
+ true,
163
+ "a query whose every stored window is a hub must read as scaffolding-only",
164
+ );
165
+ } finally {
166
+ restore();
167
+ }
168
+
169
+ // A query the store has never seen has no evidence EITHER WAY, and must not
170
+ // be mistaken for scaffolding — its callers refuse it on their own terms.
171
+ assert.equal(
172
+ allWindowsAreScaffolding(m, enc("zzqx vvwy jjkl")),
173
+ false,
174
+ "a query with no stored window at all is not scaffolding-only",
175
+ );
176
+
177
+ await m.store.close();
178
+ });
@@ -0,0 +1,114 @@
1
+ // 74-prefix-trap-not-sprung-early.test.mjs — a query that is the OPENING of
2
+ // many trained forms must stay silent, at BOTH points where that decision can
3
+ // be made wrongly.
4
+ //
5
+ // THE SHAPE. A corpus of templated facts ("what is the value of <i>?") holds
6
+ // thousands of forms sharing one long opening. Asked the opening alone, the
7
+ // corpus does not say which one is meant, so the only honest answer is none.
8
+ // Two independent defects each voiced one anyway, and each is pinned here by a
9
+ // fixture the OTHER fix cannot rescue.
10
+ //
11
+ // 1. THE IDENTITY BRIDGE SPRANG THE PREFIX TRAP ITSELF. With zero
12
+ // substitutions the bridge claims "a trained context IS this query, up to
13
+ // filler". When the query is a strict byte PREFIX of that context, the
14
+ // dismissed tail is exactly the discriminating part, and grounding through
15
+ // it asserts a specification the asker never made. It also PREEMPTED
16
+ // prefixCompletion, which runs later and owns this decision. Fixture 1
17
+ // keeps every continuation at or above one grouping window, so guard 2b
18
+ // below cannot fire and only the deferral can produce silence.
19
+ //
20
+ // 2. A SUB-QUANTUM CONTINUATION WAS DROPPED FROM THE UNIQUENESS TALLY.
21
+ // prefixCompletion refuses to VOICE a below-window continuation, but it
22
+ // also removed those candidates before counting, turning "many readings,
23
+ // most unvoiceable" into "exactly one" — guard 3 then passed vacuously.
24
+ // Fixture 2 makes the competing continuations sub-quantum and the survivor
25
+ // voiceable, so ONLY guard 2b can produce silence.
26
+ //
27
+ // Both fixtures were measured before the fixes: fixture 1 answered "the value
28
+ // of 0 is 0", fixture 2 answered "the value of 10 is 20" — each an arbitrary
29
+ // pick from thousands of equally-good readings.
30
+ import { test } from "node:test";
31
+ import assert from "node:assert/strict";
32
+ import { Mind } from "../dist/src/index.js";
33
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
34
+ import { prefixCompletion } from "../dist/src/mind/prefix-completion.js";
35
+ import { resolve } from "../dist/src/mind/primitives.js";
36
+
37
+ const enc = (s) => new TextEncoder().encode(s);
38
+
39
+ const dec = new TextDecoder();
40
+ const say = async (m, q) => {
41
+ const r = await m.respond(q);
42
+ return dec.decode((r?.bytes ?? new Uint8Array()).filter((b) => b !== 0));
43
+ };
44
+
45
+ test("sub-quantum continuations still count as competing readings", async () => {
46
+ const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
47
+
48
+ // Continuations are " 0?", " 4?", " 8?" (3 bytes, sub-quantum at W=4) and
49
+ // " 10?" (4 bytes, voiceable). Dropping the short ones leaves exactly one
50
+ // survivor — the vacuous uniqueness this test exists to prevent.
51
+ const facts = [];
52
+ for (let i = 0; i < 4300; i++) {
53
+ facts.push([`what is the value of ${i}?`, `the value of ${i} is ${i * 2}`]);
54
+ }
55
+ await m.ingest(facts);
56
+
57
+ const answer = await say(m, "what is the value of");
58
+ assert.equal(
59
+ answer,
60
+ "",
61
+ `forms opening with this query continue below one grouping window — ` +
62
+ `they are competing readings, not absent ones; got ${
63
+ JSON.stringify(answer.slice(0, 60))
64
+ }`,
65
+ );
66
+
67
+ await m.store.close();
68
+ });
69
+
70
+ // GUARD 2b IN ISOLATION. The end-to-end test above needs BOTH fixes (with the
71
+ // identity-bridge deferral disabled it fabricates before prefixCompletion is
72
+ // ever consulted), so it cannot attribute the silence to guard 2b alone. This
73
+ // one calls the mechanism directly with a candidate list of exactly the shape
74
+ // that fooled it: several forms continuing below one grouping window and ONE
75
+ // continuing above it. Dropping the short ones leaves a lone survivor and
76
+ // guard 3 passes vacuously — the defect — so a mechanism that answers here is
77
+ // counting evidence it discarded.
78
+ //
79
+ // Isolating the DEFERRAL the same way was attempted and is not achievable
80
+ // end-to-end: in every fixture whose continuations are all voiceable the
81
+ // bridge does not ground at all, so the deferral is unreachable and such a
82
+ // test asserts nothing. It is pinned by the test above instead.
83
+ test("prefixCompletion refuses when dropped candidates were the disagreement", async () => {
84
+ const m = new Mind({ seed: 7, store: new SQliteStore({ path: ":memory:" }) });
85
+
86
+ // W = 4. " 0?"/" 4?"/" 8?" are 3 bytes — sub-quantum; " 10?" is 4.
87
+ await m.ingest([
88
+ ["what is the value of 0?", "zero"],
89
+ ["what is the value of 4?", "four"],
90
+ ["what is the value of 8?", "eight"],
91
+ ["what is the value of 10?", "ten"],
92
+ ]);
93
+
94
+ const query = enc("what is the value of");
95
+ const ranked = ["0?", "4?", "8?", "10?"].map((v) =>
96
+ m.store.findLeaf(enc(`what is the value of ${v}`)) ??
97
+ resolveForm(m, `what is the value of ${v}`)
98
+ );
99
+
100
+ const hit = prefixCompletion(m, query, ranked.filter((x) => x !== null));
101
+ assert.equal(
102
+ hit,
103
+ null,
104
+ `three forms open with this query and continue sub-quantum while one ` +
105
+ `continues perceivably; the corpus offers competing readings, so no ` +
106
+ `completion is licensed`,
107
+ );
108
+
109
+ await m.store.close();
110
+ });
111
+
112
+ function resolveForm(mind, text) {
113
+ return resolve(mind, enc(text));
114
+ }