@hviana/sema 0.5.2 → 0.5.3

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 (41) hide show
  1. package/AGENTS.md +114 -52
  2. package/HOW_IT_WORKS.md +275 -184
  3. package/dist/src/mind/bridge.d.ts +5 -7
  4. package/dist/src/mind/bridge.js +6 -97
  5. package/dist/src/mind/match.d.ts +159 -0
  6. package/dist/src/mind/match.js +300 -7
  7. package/dist/src/mind/mechanisms/prefix-completion.d.ts +22 -0
  8. package/dist/src/mind/{prefix-completion.js → mechanisms/prefix-completion.js} +64 -91
  9. package/dist/src/mind/mechanisms/recall.js +10 -108
  10. package/dist/src/mind/mechanisms/reference.d.ts +6 -0
  11. package/dist/src/mind/mechanisms/reference.js +296 -0
  12. package/dist/src/mind/mind.d.ts +1 -1
  13. package/dist/src/mind/pipeline-mechanism.d.ts +56 -1
  14. package/dist/src/mind/pipeline-mechanism.js +104 -3
  15. package/dist/src/mind/pipeline.d.ts +1 -1
  16. package/dist/src/mind/pipeline.js +13 -1
  17. package/dist/src/mind/traverse.d.ts +38 -0
  18. package/dist/src/mind/traverse.js +91 -1
  19. package/dist/src/store.d.ts +4 -4
  20. package/jsr.json +6 -0
  21. package/package.json +1 -1
  22. package/src/mind/bridge.ts +10 -104
  23. package/src/mind/match.ts +416 -7
  24. package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
  25. package/src/mind/mechanisms/recall.ts +9 -126
  26. package/src/mind/mechanisms/reference.ts +343 -0
  27. package/src/mind/mind.ts +12 -8
  28. package/src/mind/pipeline-mechanism.ts +120 -3
  29. package/src/mind/pipeline.ts +16 -2
  30. package/src/mind/traverse.ts +92 -1
  31. package/src/store.ts +13 -4
  32. package/test/33-multi-candidate.test.mjs +21 -11
  33. package/test/70-prefix-completion.test.mjs +1 -1
  34. package/test/72-prefix-candidate-supply.test.mjs +7 -9
  35. package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
  36. package/test/76-reference-binding.test.mjs +471 -0
  37. package/dist/src/mind/frame-filler.d.ts +0 -15
  38. package/dist/src/mind/frame-filler.js +0 -535
  39. package/dist/src/mind/prefix-completion.d.ts +0 -59
  40. package/src/mind/frame-filler.ts +0 -604
  41. package/test/69-frame-filler.test.mjs +0 -115
@@ -16,14 +16,21 @@
16
16
  import type { AncestorReach, MindContext, Recognition } from "./types.js";
17
17
  import type { AttentionRead } from "./types.js";
18
18
  import type { ComputedSpan } from "../extension.js";
19
+ import type { Hit } from "../store.js";
19
20
  import type { Vec } from "../vec.js";
20
21
  import { indexOf } from "../bytes.js";
21
- import { dominates } from "../geometry.js";
22
+ import { conceptThreshold, dominates } from "../geometry.js";
22
23
  import { windowIds } from "./canonical.js";
23
24
  import { read, resolve } from "./primitives.js";
24
- import { alignGraded, type GradedRun, skillExemplar } from "./match.js";
25
+ import {
26
+ alignGraded,
27
+ type FrameInstance,
28
+ frameSlots,
29
+ type GradedRun,
30
+ skillExemplar,
31
+ } from "./match.js";
25
32
  import { climbAttentionAll } from "./attention.js";
26
- import { sharedReachMemo } from "./traverse.js";
33
+ import { hubBound, sharedReachMemo } from "./traverse.js";
27
34
 
28
35
  // ── Precomputed ──────────────────────────────────────────────────────────────
29
36
  //
@@ -125,6 +132,116 @@ export class Precomputed {
125
132
  return meter ? meter.time(phase, fn) : fn();
126
133
  }
127
134
 
135
+ private _resonance?: Promise<ReadonlyArray<Hit>>;
136
+ /** The response's ONE top-k content-index read: the k learnt forms nearest
137
+ * the whole-query gist, ranked. Recall's every gist tier is built on it,
138
+ * and {@link frames} assembles the frame inventory from it.
139
+ *
140
+ * An ANN query is the single most expensive read in the engine, and two
141
+ * mechanisms asking the same question of the same gist is the one
142
+ * duplication a profile shows as doubled `annVectorReads` with nothing to
143
+ * account for it. Cached BY PROMISE, so a second caller awaits the first. */
144
+ resonance(): Promise<ReadonlyArray<Hit>> {
145
+ return this._resonance ??= this.shared(
146
+ "resonance",
147
+ () => this.ctx.store.resonate(this.guide, this.k),
148
+ );
149
+ }
150
+
151
+ private _wide?: Promise<ReadonlyArray<number>>;
152
+ /** The response's WIDE candidate list — the top-k when the query's gist has
153
+ * no concept-level match anywhere, and an exhaustive √N read when it does.
154
+ *
155
+ * Every mechanism that has to look PAST the top-k reads this one list: the
156
+ * substitution bridge, prefix completion and the frame filler all did, and
157
+ * it was memoised inside recall for exactly that reason (measured: 490 ms
158
+ * median re-issued against 13 ms non-exhaustive, 36x). A memo inside one
159
+ * mechanism only serves that mechanism's own tiers, so it lives here now —
160
+ * the same move `resonance` made for the top-k.
161
+ *
162
+ * THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
163
+ * ranks at concept level, an exhaustive ANN only scores more vectors below
164
+ * the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
165
+ * context store); the structural channels — junction walks, anchor climbs,
166
+ * the write side's window index — are the correct proposal source there,
167
+ * because the ANN cannot propose what the gist cannot rank. This was once
168
+ * spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
169
+ * answers it wrongly at exactly the scale it was written from: at N =
170
+ * 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
171
+ * exhaustive branch — the very branch measured above. Measured cost of the
172
+ * mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
173
+ * 1,248 ms and 14,218 ms without it, every answer byte-identical. */
174
+ wideResonance(): Promise<ReadonlyArray<number>> {
175
+ return this._wide ??= this.shared("wideResonance", async () => {
176
+ const hits = await this.resonance();
177
+ if (
178
+ hits.length > 0 &&
179
+ hits[0].score >= conceptThreshold(this.ctx.store.D)
180
+ ) {
181
+ const exhaustive = await this.ctx.store.resonate(
182
+ this.guide,
183
+ hubBound(this.ctx),
184
+ true,
185
+ );
186
+ return exhaustive.map((h) => h.id);
187
+ }
188
+ return hits.map((h) => h.id);
189
+ });
190
+ }
191
+
192
+ private _frames?: Promise<ReadonlyArray<FrameInstance>>;
193
+ /** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
194
+ * the same frame as the query, each with the query spans it leaves VARIABLE
195
+ * ({@link FrameInstance}). The one place the engine represents "a position
196
+ * whose occupant comes from the context rather than the corpus".
197
+ *
198
+ * AN INVENTORY, NOT AN ELECTION. It reports every pairing and elects no
199
+ * frame, deliberately: a slot is a property of a PAIRING, not of the query,
200
+ * and different candidates put slots in different places. Committing to one
201
+ * reading here would push whichever consumer asked first onto everyone else
202
+ * — the market's decoupling (§2.6) broken from inside the shared container,
203
+ * and the population error §2.7 names. Each consumer groups and commits
204
+ * for its own question; reference elects the modal slot signature, and a
205
+ * consumer wanting a different reading is not fighting this one.
206
+ *
207
+ * NO LICENCE EITHER. Knowing a span is variable is safe for every consumer
208
+ * — it can only improve an alignment. Knowing one may be VOICED through is
209
+ * a different and much stronger claim, gated separately by
210
+ * {@link carriesFillers}, which needs projections this must not perform. */
211
+ frames(): Promise<ReadonlyArray<FrameInstance>> {
212
+ return this._frames ??= this.shared("frames", async () => {
213
+ const ctx = this.ctx;
214
+ const W = ctx.space.maxGroup;
215
+ // PHRASE SCALE, the same bound the bridge and the frame filler put on a
216
+ // candidate's bytes: a form an order of magnitude longer than the query
217
+ // is not a candidate for BEING it with a span replaced.
218
+ const capBytes = this.query.length * W;
219
+ const out: FrameInstance[] = [];
220
+ for (const h of await this.resonance()) {
221
+ // REJECT BY LENGTH BEFORE RECONSTRUCTING (§2.8): `contentLen` is an
222
+ // indexed read, `bytesPrefix` rebuilds a subtree. ONLY the phrase-scale
223
+ // cap is applied — it is a bounded-read discipline, not a judgement.
224
+ //
225
+ // A LOWER bound was here too (`dominates(len, query.length)`, on the
226
+ // reasoning that a candidate shorter than half the query cannot supply
227
+ // a frame that dominates it). That is reference's gate wearing a cost
228
+ // argument's clothes, and it hid the very pairings another consumer
229
+ // needs: `What is the capital of France?` (30 B) against `What is the
230
+ // capital of the country where the Eiffel Tower is?` (61 B) was
231
+ // rejected before it was ever read — a definite description standing
232
+ // where a noun stands, which is exactly the shape the frame filler
233
+ // exists for.
234
+ const len = ctx.store.contentLen(h.id, capBytes + 1);
235
+ if (len === 0 || len > capBytes) continue;
236
+ const cand = ctx.store.bytesPrefix(h.id, capBytes + 1);
237
+ if (cand.length === 0 || cand.length > capBytes) continue;
238
+ const inst = frameSlots(ctx, this.query, cand, h.id);
239
+ if (inst !== null) out.push(inst);
240
+ }
241
+ return out;
242
+ });
243
+ }
244
+
128
245
  private _attention?: Promise<AttentionRead>;
129
246
  /** The full consensus climb (roots + ranked anchors) — the query-level
130
247
  * evidence CAST, confluence, extraction, recall's scaffolding tier, and
@@ -23,6 +23,8 @@ import { coverMechanism } from "./mechanisms/cover.js";
23
23
  import { castMechanism } from "./mechanisms/cast.js";
24
24
  import { confluenceMechanism } from "./mechanisms/confluence.js";
25
25
  import { extractionMechanism } from "./mechanisms/extraction.js";
26
+ import { referenceMechanism } from "./mechanisms/reference.js";
27
+ import { prefixMechanism } from "./mechanisms/prefix-completion.js";
26
28
  import { recallMechanism } from "./mechanisms/recall.js";
27
29
 
28
30
  // Re-exports: cover's pre-resolution helpers and the ALU adapter kept
@@ -60,13 +62,23 @@ async function collectComputed(
60
62
  // floor pruning every mechanism is already subject to — not by asking
61
63
  // "is this an extension?". Grade TIES keep the earlier candidate, so this
62
64
  // order is also the tie-break priority: cover, cast, confluence, extraction,
63
- // recall.
65
+ // reference, recall.
66
+ //
67
+ // REFERENCE sits after extraction and before recall because that is what its
68
+ // claim is worth: extraction READS a span out of the query (no synthesis),
69
+ // reference voices one through a learnt slot, and recall's tiers degrade
70
+ // toward echo and silence. It does not PRUNE recall — its floor is two
71
+ // projections, so recall's one-STEP floor still clears `worthRunning` — and it
72
+ // is not meant to: both run, share one resonance read
73
+ // (Precomputed.resonance), and the ladder decides.
64
74
  export const defaultMechanisms: PipelineMechanism[] = [
65
75
  coverMechanism,
66
76
  castMechanism,
67
77
  confluenceMechanism,
68
78
  extractionMechanism,
79
+ referenceMechanism,
69
80
  recallMechanism,
81
+ prefixMechanism,
70
82
  ];
71
83
 
72
84
  // ── think — the main inference pipeline ─────────────────────────────────────
@@ -76,8 +88,10 @@ export type Provenance =
76
88
  | "join"
77
89
  | "cover"
78
90
  | "extract"
91
+ | "reference"
79
92
  | "recall"
80
- | "recall-echo";
93
+ | "recall-echo"
94
+ | "prefix";
81
95
 
82
96
  export interface Thought {
83
97
  bytes: Uint8Array;
@@ -9,7 +9,7 @@
9
9
  import { cosine, Vec } from "../vec.js";
10
10
  import type { AncestorReach, MindContext, SaturationStop } from "./types.js";
11
11
  import { gistOf, read } from "./primitives.js";
12
- import { leafIdRun } from "./canonical.js";
12
+ import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
13
13
 
14
14
  // ── Session structural memo ─────────────────────────────────────────────
15
15
  //
@@ -836,3 +836,94 @@ export function allWindowsAreScaffolding(
836
836
  }
837
837
  return sawOne;
838
838
  }
839
+
840
+ // ── THE PREFIX SUPPLY ───────────────────────────────────────────────────────
841
+ //
842
+ // A RETRIEVAL capability, not a grounding one: "which trained forms does this
843
+ // byte run OPEN?" It lived inside a recall tier, which is the wrong altitude
844
+ // — it reads the write side's own leaf-id window index and answers a question
845
+ // about the STORE, so any mechanism may ask it.
846
+
847
+ /** Trained forms the query may OPEN, proposed from the write side's own
848
+ * leaf-id window index — the supply of last resort for prefix completion.
849
+ *
850
+ * WHY A SECOND SUPPLY EXISTS. The ranked list prefix completion normally reads
851
+ * is a resonance list, and resonance cannot rank a proper prefix: measured on
852
+ * the trained store, cos(prefix, form) falls from 0.9629 at a one-byte
853
+ * truncation to 0.6206 at three bytes, against a reachThreshold of 0.8750.
854
+ * Three bytes of truncation put the answer out of reach on GEOMETRY, not on a
855
+ * bug, so no k and no re-ranking recovers it.
856
+ *
857
+ * WHY THIS ROUTE WORKS WHERE THE FOLD DOES NOT. A query's own fold is
858
+ * useless here: content addressing is not phrase-position-invariant, so a
859
+ * standalone prefix folds to a DIFFERENT node than the same bytes sitting
860
+ * inside a longer deposit, and neither the prefix's own node nor its
861
+ * ancestors lead to the deposit (measured: the 22-byte prefix of the
862
+ * photosynthesis form resolves, is shared by 6 contexts, and does not have
863
+ * the form among its ancestors). Leaf ids ARE position-invariant — they are
864
+ * content-addressed on single bytes — and `indexSubSpans` already interns a
865
+ * flat branch over every canonical WINDOW of a deposit's leaf-id stream, with
866
+ * containment edges to the chunks that window spans. A query that is a
867
+ * prefix therefore shares those window nodes exactly, and reaches the deposit
868
+ * by climbing containment then parents. Nothing is added to the write side;
869
+ * this reads an index training already built.
870
+ *
871
+ * BOUNDED (§2.8), AND WITH NO NEW THRESHOLD. The window whose containment is
872
+ * SMALLEST carries the most evidence, and one saturated at `hubBound` carries
873
+ * none — that is the same √N reading of "hub" the rest of the mind uses, not
874
+ * a tuned knob. The upward walk spends a budget of `hubBound` nodes and
875
+ * fans out by W, so a hub query enumerates nothing and the caller stays
876
+ * silent rather than guessing (§2.13). Measured on the trained store: the
877
+ * photosynthesis form at a one-byte truncation picks a window with 52
878
+ * containers, visits 446 nodes, and yields exactly ONE candidate that
879
+ * survives the caller's byte compare — the form itself.
880
+ *
881
+ * These are PROPOSALS only. Every candidate still faces the byte-exact
882
+ * prefix compare and all three guards below, so a wrong proposal costs one
883
+ * bounded read and can never be voiced (§2.3). */
884
+ export function formsOpenedBy(
885
+ ctx: MindContext,
886
+ query: Uint8Array,
887
+ ): number[] {
888
+ const store = ctx.store;
889
+ const W = ctx.space.maxGroup;
890
+ const run = leafIdPrefix(ctx, query);
891
+ // The widest canonical window is the most discriminative one the write side
892
+ // ever interned; a query too short to spell one carries no window evidence.
893
+ const len = canonicalWindows(W)[1];
894
+ if (run.length < len) return [];
895
+ const bound = hubBound(ctx);
896
+
897
+ let best: number | null = null;
898
+ let bestN = 0;
899
+ for (let off = 0; off + len <= run.length; off++) {
900
+ const wid = store.findBranch(run.slice(off, off + len));
901
+ if (wid === null) continue;
902
+ const n = store.containersSlice(wid, 0, bound).length;
903
+ // Empty says the window spans no chunk; saturated says it is a hub, whose
904
+ // containment discriminates nothing. Neither is evidence.
905
+ if (n === 0 || n >= bound) continue;
906
+ if (best === null || n < bestN) {
907
+ best = wid;
908
+ bestN = n;
909
+ }
910
+ }
911
+ if (best === null) return [];
912
+
913
+ let frontier = store.containersSlice(best, 0, bound);
914
+ const seen = new Set<number>(frontier);
915
+ let budget = bound;
916
+ while (frontier.length > 0 && budget > 0) {
917
+ const next: number[] = [];
918
+ for (const f of frontier) {
919
+ if (budget-- <= 0) break;
920
+ for (const p of store.parentsFirst(f, W)) {
921
+ if (seen.has(p)) continue;
922
+ seen.add(p);
923
+ next.push(p);
924
+ }
925
+ }
926
+ frontier = next;
927
+ }
928
+ return [...seen];
929
+ }
package/src/store.ts CHANGED
@@ -970,16 +970,25 @@ export abstract class AbstractStore implements Store {
970
970
  /** Content (gist) index write buffer. */
971
971
  protected _contentBuffer: Array<{ id: NodeId; vector: Float32Array }> = [];
972
972
  /** Halo index write buffer — keyed by id so repeats within a batch coalesce. */
973
- protected _haloBuffer = new Map<NodeId, Float32Array>();
973
+ protected _haloBuffer: Map<NodeId, Float32Array> = new Map<
974
+ NodeId,
975
+ Float32Array
976
+ >();
974
977
  /** Containment write buffer: child → new parents, merged on flush cadence. */
975
- protected _containBuf = new Map<NodeId, Set<NodeId>>();
978
+ protected _containBuf: Map<NodeId, Set<NodeId>> = new Map<
979
+ NodeId,
980
+ Set<NodeId>
981
+ >();
976
982
 
977
983
  /** Dedup-target candidates still in the write buffer (keyed by id). Only
978
984
  * roots that have gained an edge/halo are targets; a fresh intermediate
979
985
  * branch is never folded onto. */
980
- protected _nearDedupBuf = new Map<NodeId, Float32Array>();
986
+ protected _nearDedupBuf: Map<NodeId, Float32Array> = new Map<
987
+ NodeId,
988
+ Float32Array
989
+ >();
981
990
  /** Ids currently in `_contentBuffer` (not yet flushed) — O(1) membership. */
982
- protected _bufferedIds = new Set<NodeId>();
991
+ protected _bufferedIds: Set<NodeId> = new Set<NodeId>();
983
992
 
984
993
  // ── Transparent-chain cache ────────────────────────────────────────────
985
994
 
@@ -171,18 +171,28 @@ test("3 — a near-tie between the winner and runner-up emits narrowDecision", a
171
171
  ["water is frigid so water is freezing", "freezing"],
172
172
  ]);
173
173
  // The near-tie has to be REAL: two candidates whose accounted spans are the
174
- // same, separated by a single move. `steel is hard so steel is` is that
175
- // extraction and recall both explain the whole prefix (grades 21011 and
176
- // 21010, margin 1).
174
+ // same, separated by a single move. `water is hard so water is ???` is that
175
+ // extraction and recall both explain the same span (grades 25011 and
176
+ // 25010, margin 1).
177
177
  //
178
- // The old fixture, `steel is frigid so steel is ???`, was a near-tie only
179
- // while CAST could not fire on it: the counterfactual subject and the seat
180
- // it fills sit on opposite sides of one recurring structure, and CAST's
181
- // subject gate used to reject any point whose alignment continued PAST the
182
- // seat which that very recurrence guarantees. With CAST firing, it leaves
183
- // only "???" unexplained and wins by 22008; a decisive win is the opposite
184
- // of what this test pins, and test 3b already covers it.
185
- const { steps } = await trace(m, "steel is hard so steel is");
178
+ // THIS FIXTURE HAS NOW BEEN REPLACED TWICE, BOTH TIMES FOR THE SAME REASON:
179
+ // a mechanism that genuinely explains the query started firing, and a
180
+ // decisive win is the opposite of what this test pins.
181
+ //
182
+ // * `steel is frigid so steel is ???` was a near-tie only while CAST could
183
+ // not fire on it CAST's subject gate used to reject any point whose
184
+ // alignment continued PAST the seat, which that fixture's recurrence
185
+ // guarantees. With CAST firing it wins by 22008.
186
+ // * `steel is hard so steel is` was a near-tie only while prefix
187
+ // completion was a buried recall TIER. As a market mechanism it grounds
188
+ // that query for what it is — the literal opening of exactly one trained
189
+ // form — at grade 1 against 21010, and answers `steel is hard so steel
190
+ // is strong`. That is a better answer, not a regression.
191
+ //
192
+ // The lesson for whoever re-fixtures this next: pick a query no mechanism can
193
+ // explain OUTRIGHT. A trailing `???` is what keeps this one honest — it is
194
+ // not the prefix of anything trained.
195
+ const { steps } = await trace(m, "water is hard so water is ???");
186
196
  const narrow = stepsNamed(steps, "narrowDecision");
187
197
  assert.equal(narrow.length, 1, "expected exactly one narrowDecision step");
188
198
  assert.ok(/margin \d+ grade-unit/.test(narrow[0].note), narrow[0].note);
@@ -36,7 +36,7 @@ import { test } from "node:test";
36
36
  import assert from "node:assert/strict";
37
37
  import { Mind } from "../dist/src/index.js";
38
38
  import { SQliteStore } from "../dist/src/store-sqlite.js";
39
- import { prefixCompletion } from "../dist/src/mind/prefix-completion.js";
39
+ import { prefixCompletion } from "../dist/src/mind/mechanisms/prefix-completion.js";
40
40
 
41
41
  const enc = (s) => new TextEncoder().encode(s);
42
42
  const dec = new TextDecoder();
@@ -8,7 +8,7 @@
8
8
  // 0.9629 at a one-byte truncation to 0.6206 at three bytes, against a
9
9
  // reachThreshold of 0.8750. The mechanism's guards were therefore never
10
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
11
+ // nothing. `formsOpenedBy` is the second SUPPLY that closes it, reading the
12
12
  // leaf-id WINDOW index `indexSubSpans` already writes at deposit time. No
13
13
  // ingestion, storage or fold change is involved: this test would pass on a
14
14
  // store trained before the supply existed.
@@ -16,7 +16,7 @@
16
16
  // WHY THE ASSERTIONS ARE SHAPED THIS WAY. On a small fixture resonance may
17
17
  // well return the form by luck, and then an end-to-end "does it answer?" test
18
18
  // would pass with the supply deleted — pinning nothing. So the contract is
19
- // asserted on `prefixCandidates` DIRECTLY, and the resonance list is asserted
19
+ // asserted on `formsOpenedBy` DIRECTLY, and the resonance list is asserted
20
20
  // to lack the form, which is what makes the supply load-bearing rather than
21
21
  // redundant.
22
22
  //
@@ -27,10 +27,8 @@ import { test } from "node:test";
27
27
  import assert from "node:assert/strict";
28
28
  import { Mind } from "../dist/src/index.js";
29
29
  import { SQliteStore } from "../dist/src/store-sqlite.js";
30
- import {
31
- prefixCandidates,
32
- prefixCompletion,
33
- } from "../dist/src/mind/prefix-completion.js";
30
+ import { prefixCompletion } from "../dist/src/mind/mechanisms/prefix-completion.js";
31
+ import { formsOpenedBy } from "../dist/src/mind/traverse.js";
34
32
  import { gistOf, resolve } from "../dist/src/mind/primitives.js";
35
33
 
36
34
  const enc = (s) => new TextEncoder().encode(s);
@@ -71,7 +69,7 @@ test("a proper prefix reaches its trained form through the window supply", async
71
69
  // prefix is a LARGE-CORPUS property — cos falls to 0.6206 at a three-byte
72
70
  // truncation against a 0.8750 bar, on a 15.7M-node store — and cannot be
73
71
  // reproduced at this scale. That is why the contract below is asserted on
74
- // `prefixCandidates` DIRECTLY: deleting or emptying the supply fails this
72
+ // `formsOpenedBy` DIRECTLY: deleting or emptying the supply fails this
75
73
  // test regardless of what resonance happens to return.
76
74
  const ranked = (await m.store.resonate(gistOf(m, query), 64)).map((h) =>
77
75
  h.id
@@ -84,7 +82,7 @@ test("a proper prefix reaches its trained form through the window supply", async
84
82
  );
85
83
 
86
84
  // THE CONTRACT — the write side's own window index proposes the form.
87
- const proposed = prefixCandidates(m, query);
85
+ const proposed = formsOpenedBy(m, query);
88
86
  assert.ok(
89
87
  proposed.includes(formId),
90
88
  `the window supply must propose the trained form the query opens ` +
@@ -105,7 +103,7 @@ test("a proper prefix reaches its trained form through the window supply", async
105
103
  // supply that widened until it found something would be the real defect.
106
104
  const hub = enc("The ");
107
105
  assert.equal(
108
- prefixCompletion(m, hub, prefixCandidates(m, hub)),
106
+ prefixCompletion(m, hub, formsOpenedBy(m, hub)),
109
107
  null,
110
108
  "a query carrying no discriminative window must stay silent",
111
109
  );
@@ -31,7 +31,7 @@ import { test } from "node:test";
31
31
  import assert from "node:assert/strict";
32
32
  import { Mind } from "../dist/src/index.js";
33
33
  import { SQliteStore } from "../dist/src/store-sqlite.js";
34
- import { prefixCompletion } from "../dist/src/mind/prefix-completion.js";
34
+ import { prefixCompletion } from "../dist/src/mind/mechanisms/prefix-completion.js";
35
35
  import { resolve } from "../dist/src/mind/primitives.js";
36
36
 
37
37
  const enc = (s) => new TextEncoder().encode(s);