@hviana/sema 0.5.2 → 0.5.4

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 (157) hide show
  1. package/AGENTS.md +115 -53
  2. package/HOW_IT_WORKS.md +291 -184
  3. package/jsr.json +6 -0
  4. package/package.json +1 -1
  5. package/src/mind/bridge.ts +10 -104
  6. package/src/mind/match.ts +497 -7
  7. package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
  8. package/src/mind/mechanisms/recall.ts +38 -129
  9. package/src/mind/mechanisms/reference.ts +343 -0
  10. package/src/mind/mind.ts +12 -8
  11. package/src/mind/pipeline-mechanism.ts +120 -3
  12. package/src/mind/pipeline.ts +16 -2
  13. package/src/mind/traverse.ts +92 -1
  14. package/src/store.ts +13 -4
  15. package/test/33-multi-candidate.test.mjs +21 -11
  16. package/test/70-prefix-completion.test.mjs +1 -1
  17. package/test/72-prefix-candidate-supply.test.mjs +7 -9
  18. package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
  19. package/test/76-reference-binding.test.mjs +543 -0
  20. package/dist/example/demo.d.ts +0 -1
  21. package/dist/example/demo.js +0 -39
  22. package/dist/example/train_base.d.ts +0 -93
  23. package/dist/example/train_base.js +0 -2269
  24. package/dist/src/alphabet.d.ts +0 -7
  25. package/dist/src/alphabet.js +0 -33
  26. package/dist/src/alu/src/alu.d.ts +0 -185
  27. package/dist/src/alu/src/alu.js +0 -440
  28. package/dist/src/alu/src/expr.d.ts +0 -61
  29. package/dist/src/alu/src/expr.js +0 -318
  30. package/dist/src/alu/src/index.d.ts +0 -11
  31. package/dist/src/alu/src/index.js +0 -19
  32. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  33. package/dist/src/alu/src/kernel-arith.js +0 -264
  34. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  35. package/dist/src/alu/src/kernel-bits.js +0 -152
  36. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  37. package/dist/src/alu/src/kernel-logic.js +0 -60
  38. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  39. package/dist/src/alu/src/kernel-nd.js +0 -208
  40. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  41. package/dist/src/alu/src/kernel-numeric.js +0 -366
  42. package/dist/src/alu/src/operation.d.ts +0 -168
  43. package/dist/src/alu/src/operation.js +0 -189
  44. package/dist/src/alu/src/parser.d.ts +0 -221
  45. package/dist/src/alu/src/parser.js +0 -577
  46. package/dist/src/alu/src/resonance.d.ts +0 -55
  47. package/dist/src/alu/src/resonance.js +0 -126
  48. package/dist/src/alu/src/text.d.ts +0 -31
  49. package/dist/src/alu/src/text.js +0 -73
  50. package/dist/src/alu/src/value.d.ts +0 -109
  51. package/dist/src/alu/src/value.js +0 -300
  52. package/dist/src/alu/test/alu.test.d.ts +0 -1
  53. package/dist/src/alu/test/alu.test.js +0 -764
  54. package/dist/src/bytes.d.ts +0 -14
  55. package/dist/src/bytes.js +0 -59
  56. package/dist/src/canon.d.ts +0 -45
  57. package/dist/src/canon.js +0 -85
  58. package/dist/src/config.d.ts +0 -111
  59. package/dist/src/config.js +0 -91
  60. package/dist/src/derive/src/deduction.d.ts +0 -136
  61. package/dist/src/derive/src/deduction.js +0 -159
  62. package/dist/src/derive/src/index.d.ts +0 -8
  63. package/dist/src/derive/src/index.js +0 -11
  64. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  65. package/dist/src/derive/src/priority-queue.js +0 -73
  66. package/dist/src/derive/src/rewrite.d.ts +0 -56
  67. package/dist/src/derive/src/rewrite.js +0 -100
  68. package/dist/src/derive/src/trie.d.ts +0 -90
  69. package/dist/src/derive/src/trie.js +0 -217
  70. package/dist/src/derive/test/derive.test.d.ts +0 -1
  71. package/dist/src/derive/test/derive.test.js +0 -122
  72. package/dist/src/extension.d.ts +0 -37
  73. package/dist/src/extension.js +0 -7
  74. package/dist/src/geometry.d.ts +0 -233
  75. package/dist/src/geometry.js +0 -1089
  76. package/dist/src/index.d.ts +0 -17
  77. package/dist/src/index.js +0 -19
  78. package/dist/src/ingest-cache.d.ts +0 -41
  79. package/dist/src/ingest-cache.js +0 -165
  80. package/dist/src/meter.d.ts +0 -176
  81. package/dist/src/meter.js +0 -274
  82. package/dist/src/mind/articulation.d.ts +0 -6
  83. package/dist/src/mind/articulation.js +0 -99
  84. package/dist/src/mind/attention.d.ts +0 -430
  85. package/dist/src/mind/attention.js +0 -2418
  86. package/dist/src/mind/bridge.d.ts +0 -39
  87. package/dist/src/mind/bridge.js +0 -998
  88. package/dist/src/mind/canonical.d.ts +0 -34
  89. package/dist/src/mind/canonical.js +0 -93
  90. package/dist/src/mind/frame-filler.d.ts +0 -15
  91. package/dist/src/mind/frame-filler.js +0 -535
  92. package/dist/src/mind/graph-search.d.ts +0 -294
  93. package/dist/src/mind/graph-search.js +0 -996
  94. package/dist/src/mind/index.d.ts +0 -9
  95. package/dist/src/mind/index.js +0 -5
  96. package/dist/src/mind/junction.d.ts +0 -137
  97. package/dist/src/mind/junction.js +0 -358
  98. package/dist/src/mind/learning.d.ts +0 -75
  99. package/dist/src/mind/learning.js +0 -265
  100. package/dist/src/mind/match.d.ts +0 -192
  101. package/dist/src/mind/match.js +0 -666
  102. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  103. package/dist/src/mind/mechanisms/alu.js +0 -36
  104. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  105. package/dist/src/mind/mechanisms/cast.js +0 -1186
  106. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  107. package/dist/src/mind/mechanisms/confluence.js +0 -271
  108. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  109. package/dist/src/mind/mechanisms/cover.js +0 -232
  110. package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
  111. package/dist/src/mind/mechanisms/extraction.js +0 -327
  112. package/dist/src/mind/mechanisms/recall.d.ts +0 -16
  113. package/dist/src/mind/mechanisms/recall.js +0 -558
  114. package/dist/src/mind/mind.d.ts +0 -386
  115. package/dist/src/mind/mind.js +0 -754
  116. package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
  117. package/dist/src/mind/pipeline-mechanism.js +0 -521
  118. package/dist/src/mind/pipeline.d.ts +0 -49
  119. package/dist/src/mind/pipeline.js +0 -331
  120. package/dist/src/mind/prefix-completion.d.ts +0 -59
  121. package/dist/src/mind/prefix-completion.js +0 -270
  122. package/dist/src/mind/primitives.d.ts +0 -85
  123. package/dist/src/mind/primitives.js +0 -333
  124. package/dist/src/mind/rationale.d.ts +0 -139
  125. package/dist/src/mind/rationale.js +0 -163
  126. package/dist/src/mind/reasoning.d.ts +0 -42
  127. package/dist/src/mind/reasoning.js +0 -305
  128. package/dist/src/mind/recognition.d.ts +0 -20
  129. package/dist/src/mind/recognition.js +0 -696
  130. package/dist/src/mind/resonance.d.ts +0 -35
  131. package/dist/src/mind/resonance.js +0 -365
  132. package/dist/src/mind/trace.d.ts +0 -15
  133. package/dist/src/mind/trace.js +0 -73
  134. package/dist/src/mind/traverse.d.ts +0 -158
  135. package/dist/src/mind/traverse.js +0 -716
  136. package/dist/src/mind/types.d.ts +0 -418
  137. package/dist/src/mind/types.js +0 -179
  138. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  139. package/dist/src/rabitq-ivf/src/database.js +0 -201
  140. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  141. package/dist/src/rabitq-ivf/src/index.js +0 -4
  142. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  143. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  144. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  145. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  146. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  147. package/dist/src/rabitq-ivf/src/rabitq.js +0 -313
  148. package/dist/src/sema.d.ts +0 -41
  149. package/dist/src/sema.js +0 -77
  150. package/dist/src/store-sqlite.d.ts +0 -184
  151. package/dist/src/store-sqlite.js +0 -942
  152. package/dist/src/store.d.ts +0 -762
  153. package/dist/src/store.js +0 -1823
  154. package/dist/src/vec.d.ts +0 -31
  155. package/dist/src/vec.js +0 -109
  156. package/src/mind/frame-filler.ts +0 -604
  157. package/test/69-frame-filler.test.mjs +0 -115
@@ -0,0 +1,343 @@
1
+ // mechanisms/reference.ts — Reference: voice a slot with the context's own
2
+ // bytes (Grounding IV).
3
+ //
4
+ // This file is a CONFIGURATION of the shared frame reading in match.ts, not a
5
+ // pipeline of its own. The three parts it configures live where §2.5 puts
6
+ // them and are reachable by any mechanism:
7
+ //
8
+ // matcher Precomputed.frames() — the frame INVENTORY: which ranked
9
+ // candidates read as instances of the query's own frame, and
10
+ // where each leaves the query VARIABLE. Shared, election-free.
11
+ // projection follow() — each instance's own learnt continuation.
12
+ // gate carriesFillers() — the carriage licence.
13
+ //
14
+ // What this file adds is the reading and the price: elect one frame from the
15
+ // inventory, demand the licence of it, splice, and state the cost.
16
+ //
17
+ // WHAT KIND OF ACT THIS IS. bridge.ts refuses this shape and is right to:
18
+ // `attestedQ` demands the query-side span be corpus-attested, because a
19
+ // SUBSTITUTION asserts equivalence ("these two spans mean the same"), and
20
+ // nothing can corroborate an equivalence claim about bytes the corpus has
21
+ // never seen. A REFERENCE asserts no equivalence. It asserts POSITION —
22
+ // "this is the thing you named, where the corpus keeps one" — and the bytes
23
+ // come from the asker, so voicing them cannot fabricate corpus knowledge.
24
+ // What CAN be fabricated is the relation claimed about them, which is exactly
25
+ // what the licence withholds.
26
+ //
27
+ // The same reasoning is why the bridge is NOT rewired to consult the frame
28
+ // inventory, though it is now free to: the bridge grounds through its
29
+ // candidate's continuation UNSUBSTITUTED, so admitting a slot-gap there would
30
+ // voice the corpus's filler for the asker's referent — the misreference
31
+ // measured live on the trained store ("How do you say 'flurbish' in French?"
32
+ // answered "the way to say hello is \"Bonjour\""). Nor is CAST rewired: its
33
+ // frame gate is WEAVE-local while a slot is COHORT-local, and substituting one
34
+ // population for the other is the error §2.7 names. The notion is made
35
+ // AVAILABLE, never imposed.
36
+
37
+ import type { MindContext } from "../types.js";
38
+ import type { FrameInstance } from "../match.js";
39
+ import { carriesFillers, distinct, follow, substituteAll } from "../match.js";
40
+ import { dominates } from "../../geometry.js";
41
+ import { bytesEqual, indexOf } from "../../bytes.js";
42
+ import { unexplainedLabel } from "../rationale.js";
43
+ import { STEP } from "../graph-search.js";
44
+ import type {
45
+ MechanismResult,
46
+ PipelineMechanism,
47
+ Precomputed,
48
+ } from "../pipeline-mechanism.js";
49
+ import { rItem, rNode, traceFail } from "../trace.js";
50
+
51
+ /** The minimum number of instances that can establish a frame. One instance
52
+ * agrees with nothing, so no carriage is attested — the same "two or no
53
+ * constituent" reading frame-filler's contentRuns applies.
54
+ *
55
+ * THIS IS ALSO THE MECHANISM'S REACH. Evidence comes from the shared top-k
56
+ * resonance, so a frame the corpus instantiates only ONCE within k is not
57
+ * reachable here. Measured on the trained store: `How do you say 'flurbish'
58
+ * in French?` finds one instance of its frame in the top 24 — the rest are
59
+ * `How do you make …`, a different frame — so this abstains and recall's
60
+ * scaffolding-dominated tier answers with the CORPUS's filler. That
61
+ * misreference is recall's, and widening the supply is not the fix: the
62
+ * exhaustive √N list recall's refusal path builds costs hundreds of
63
+ * milliseconds and this runs before it. Abstaining on thin evidence is the
64
+ * honest reading (§2.13). */
65
+ const MIN_INSTANCES = 2;
66
+
67
+ /** THE VOICING GATES — this mechanism's own reading of a pairing, applied here
68
+ * and NOT in the shared matcher.
69
+ *
70
+ * Every one of these is a requirement for SUBSTITUTING AND SPEAKING, not for
71
+ * knowing where a pairing varies, so each belongs to the consumer that
72
+ * speaks. They lived in `frameSlots` first and made the shared reading
73
+ * reference-shaped: three of four real pairings were hidden from every other
74
+ * consumer, including a definite description standing where a noun stands —
75
+ * the shape the frame filler exists for. A shared layer with one usable
76
+ * consumer is private code at a public address.
77
+ *
78
+ * 1. EVERY slot must be a SUBSTITUTION. An insertion or deletion means the
79
+ * query says more or less than the frame does, and there is no occupant to
80
+ * carry. (A consumer that WANTS insertions — "what did the asker add?" —
81
+ * now sees them; this one cannot use them.)
82
+ * 2. EVERY slot must reach one river window on BOTH sides. Below one window
83
+ * byte overlap is chance, not evidence — the floor identityBar, the
84
+ * bridge's attestedQ and recognition's site test all draw.
85
+ * 3. The FRAME MUST DOMINATE the query, or this is a different sentence that
86
+ * happens to align somewhere. It is also what bounds the slot count
87
+ * without a constant: every slot costs at least a window, and the frame
88
+ * must still be more than half the query.
89
+ * 4. FILLERS PAIRWISE DISTINCT. Two slots holding the same bytes make the
90
+ * occurrence → referent mapping ambiguous, and no evidence resolves it. */
91
+ function voiceable(
92
+ inst: FrameInstance,
93
+ W: number,
94
+ queryLen: number,
95
+ ): boolean {
96
+ if (inst.slots.length === 0) return false;
97
+ for (const slot of inst.slots) {
98
+ if (slot.kind !== "substitution") return false;
99
+ if (slot.qe - slot.qs < W || slot.ce - slot.cs < W) return false;
100
+ }
101
+ if (!dominates(inst.covered, queryLen)) return false;
102
+ return distinct(inst.slots.map((s) => s.filler));
103
+ }
104
+
105
+ /** Elect ONE frame from the inventory: the voiceable instances that place
106
+ * their slots in the same query spans.
107
+ *
108
+ * THE ELECTION IS THIS MECHANISM'S, not the inventory's (see
109
+ * Precomputed.frames). Demanding that everything which aligned agree would
110
+ * be the wrong population: the top-k is ranked by gist, not by frame, so it
111
+ * mixes them — measured, `How do you say 'flurbish' in French?` returns one
112
+ * instance of its own frame and a dozen of `How do you make …`, which align
113
+ * on the shared opening and put their slot somewhere else entirely. One
114
+ * dissenting frame would then veto every binding.
115
+ *
116
+ * Instances of ONE frame put their slots in ONE place, so grouping by the
117
+ * whole slot signature and keeping the modal group IS the frame. The
118
+ * signature is every slot, not one: two candidates agreeing about a file name
119
+ * but disagreeing about whether a second thing was named are instances of two
120
+ * different frames, and mixing them would let a one-slot instance vouch for a
121
+ * two-slot binding it says nothing about. */
122
+ function electFrame(
123
+ inventory: ReadonlyArray<FrameInstance>,
124
+ W: number,
125
+ queryLen: number,
126
+ ): FrameInstance[] {
127
+ const bySignature = new Map<string, FrameInstance[]>();
128
+ for (const inst of inventory) {
129
+ if (!voiceable(inst, W, queryLen)) continue;
130
+ const key = inst.slots.map((s) => `${s.qs}:${s.qe}`).join(",");
131
+ const group = bySignature.get(key);
132
+ if (group === undefined) bySignature.set(key, [inst]);
133
+ else group.push(inst);
134
+ }
135
+ let best: FrameInstance[] = [];
136
+ for (const group of bySignature.values()) {
137
+ // Ties keep the FIRST group in insertion order, which is resonance rank —
138
+ // corpus-determined, like every other tie-break here (§2.1).
139
+ if (group.length > best.length) best = group;
140
+ }
141
+ return best;
142
+ }
143
+
144
+ /** Voice the query's referents through their frame's own attested carriage, or
145
+ * null when the corpus does not attest one. */
146
+ export async function bindReference(
147
+ ctx: MindContext,
148
+ query: Uint8Array,
149
+ pre: Precomputed,
150
+ ): Promise<MechanismResult | null> {
151
+ const t = ctx.trace?.enter("bindReference", [rItem(query, "query")]);
152
+ const fail = traceFail(t);
153
+
154
+ const frame = electFrame(
155
+ await pre.frames(),
156
+ ctx.space.maxGroup,
157
+ query.length,
158
+ );
159
+ if (frame.length < MIN_INSTANCES) {
160
+ return fail(
161
+ `${frame.length} instance(s) of one frame — agreement needs ` +
162
+ `${MIN_INSTANCES}`,
163
+ );
164
+ }
165
+ const slots = frame[0].slots.map((s): [number, number] => [s.qs, s.qe]);
166
+ const fillersOf = (inst: FrameInstance) => inst.slots.map((s) => s.filler);
167
+ const referents = slots.map(([s, e]) => query.subarray(s, e));
168
+
169
+ // A referent must be the ASKER's, never the engine's own words. A completed
170
+ // reply stays available to recognition and the climb as context, but quoting
171
+ // it back as a referent launders the engine's own output into evidence — the
172
+ // same rule the weave applies when it aligns only the asker's stream. ANY
173
+ // slot falling inside one refuses the whole binding.
174
+ for (const [rs, re] of slots) {
175
+ for (const [as_, ae] of ctx.answeredSpans) {
176
+ if (rs < ae && as_ < re) {
177
+ return fail(
178
+ "a referent lies inside a completed reply — not the asker's",
179
+ );
180
+ }
181
+ }
182
+ }
183
+ // Two slots may not name the same bytes, for the same reason two fillers may
184
+ // not (frameSlots applies it to every instance): the mapping from occurrence
185
+ // to referent would be ambiguous.
186
+ if (!distinct(referents)) {
187
+ return fail("two slots name the same bytes — the binding is ambiguous");
188
+ }
189
+
190
+ // ── THE LICENCE ────────────────────────────────────────────────────────
191
+ // Every instance must agree, against the first, that its continuation is its
192
+ // own fillers carried through one fixed form. Unanimity, exactly as the
193
+ // bridge's `unanimous` demands of a frame before it will substitute.
194
+ //
195
+ // Continuations are followed ONE AT A TIME, inside the test, because the
196
+ // overwhelmingly common outcome is refusal and a refusal usually comes from
197
+ // the first comparison — a filler-dependent frame disagrees on instance 2 of
198
+ // 12. Reading all of them up front pays the whole cohort's projections to
199
+ // discard them. Each goes through the shared projection, so an ambiguous
200
+ // instance is disambiguated exactly as it would be anywhere else.
201
+ const leadsNowhere = "an instance of the frame leads nowhere";
202
+ const first = await follow(ctx, frame[0].id, pre.guide);
203
+ if (first === null || first.length === 0) return fail(leadsNowhere);
204
+ for (let i = 1; i < frame.length; i++) {
205
+ const cont = await follow(ctx, frame[i].id, pre.guide);
206
+ if (cont === null || cont.length === 0) return fail(leadsNowhere);
207
+ if (
208
+ !carriesFillers(first, fillersOf(frame[0]), cont, fillersOf(frame[i]))
209
+ ) {
210
+ ctx.trace?.step(
211
+ "referenceLicence",
212
+ [rItem(first, "instance"), rNode(ctx, frame[i].id, "against")],
213
+ [rItem(cont, "attested")],
214
+ "refused — the frame's answer carries content that depends on WHICH " +
215
+ "filler, so the corpus cannot supply it for a new one",
216
+ );
217
+ return fail("the frame's answer is not a carriage of its fillers");
218
+ }
219
+ }
220
+
221
+ // THE BINDING IS A BYTE CONSTRUCTION, NOT A SEARCH, and deliberately so.
222
+ // Articulation splices through ctx.search.cover because WHICH voicing wins is
223
+ // genuinely searched — several candidate substitutions compete. Here the
224
+ // licence has already determined the substitution byte-exactly; a search that
225
+ // can only confirm a determined result is ceremony, not composition. The
226
+ // precedent is frame-filler, which constructs its lookup key the same way.
227
+ const bytes = substituteAll(
228
+ first,
229
+ fillersOf(frame[0]).map((needle, s) => ({ needle, repl: referents[s] })),
230
+ );
231
+ if (bytes.length === 0) return fail("the binding produced nothing");
232
+ // Answering with the question is not answering — the same restated-fragment
233
+ // guard every recall tier applies.
234
+ if (bytes.length < query.length && indexOf(query, bytes, 0) >= 0) {
235
+ return fail("the binding restates part of the question");
236
+ }
237
+ const carried = !bytesEqual(bytes, first);
238
+
239
+ ctx.trace?.step(
240
+ "bindReferent",
241
+ [
242
+ ...referents.map((r, s) =>
243
+ rItem(r, `referent ${s + 1}`, undefined, slots[s])
244
+ ),
245
+ ...frame.map((inst) => rNode(ctx, inst.id, "instance")),
246
+ ],
247
+ [rItem(bytes, "bound")],
248
+ carried
249
+ ? `carry the asker's ${slots.length} referent(s) through the frame's own ` +
250
+ `answer — ${frame.length} instances attest the carriage byte-exactly`
251
+ : `the frame's answer does not depend on its filler(s) — ` +
252
+ `${frame.length} instances attest the same continuation`,
253
+ );
254
+
255
+ // WHAT THIS EXPLAINS: the frame it matched literally, AND every slot. A slot
256
+ // is not a hole in the explanation — it is an act this mechanism PAID for,
257
+ // one STEP each in `moves` below, and leaving it unaccounted charges the same
258
+ // act twice (once as a move, once at PASS per byte, which is far the larger).
259
+ // The bridge records the same reasoning for its own substitutions.
260
+ const accounted: Array<[number, number]> = [
261
+ ...frame[0].matched.filter(([s, e]) => e > s),
262
+ ...slots,
263
+ ].sort((a, b) => a[0] - b[0]);
264
+
265
+ t?.done(
266
+ [rItem(bytes, "answer")],
267
+ "reference — the asker's referent(s) voiced through a slot the corpus " +
268
+ "attests as a carriage",
269
+ );
270
+ return {
271
+ bytes,
272
+ accounted,
273
+ // The acts: one BINDING per slot plus one edge FOLLOW across the frame's
274
+ // own continuation — the same per-projection price CAST's substitution
275
+ // schema pays for the structurally analogous act. Not CONCEPT: the ladder
276
+ // reserves that for halo-mediated acts, and a reference is decided by byte
277
+ // identity, not distributional company. Per-slot matters: a two-slot
278
+ // binding claims strictly more than a one-slot binding, so where both are
279
+ // licensed the smaller claim wins.
280
+ moves: STEP * slots.length + STEP,
281
+ unexplained: unexplainedLabel(query, accounted),
282
+ // NOT scaffolding. That field counts answer bytes carried through BECAUSE
283
+ // NOTHING EXPLAINED THEM; a referent is carried because the frame's slot
284
+ // explains it, and it is accounted above. Reporting it would make every
285
+ // licensed binding lose its equal-grade tie-breaks by construction.
286
+ //
287
+ // COMPLETE — post-grounding must not extend a binding. The bound answer is
288
+ // a byte string this mechanism CONSTRUCTED; the corpus never said it, so
289
+ // pivoting through it treats the engine's own construction as a trained
290
+ // fact — the same laundering the answeredSpans guard above refuses in the
291
+ // other direction. Measured: with the frame "How do I compile X?" ->
292
+ // "Run gcc X" and a stored "Run gcc main.c" -> "then execute ./a.out", the
293
+ // binding produced "Run gcc main.c" and reason() pivoted straight past it,
294
+ // answering "then execute ./a.out" — the referent gone AND the question
295
+ // unanswered. This is what MechanismResult.complete documents: the query
296
+ // IS a trained context (here, an instance of a trained frame), so its
297
+ // continuation is the whole read-out. Fusion still runs; it is gated on
298
+ // the query REMAINDER, which a binding accounting frame plus slots leaves
299
+ // empty.
300
+ complete: true,
301
+ };
302
+ }
303
+
304
+ // ── Pipeline mechanism ──────────────────────────────────────────────────────
305
+
306
+ export const referenceMechanism: PipelineMechanism = {
307
+ name: "reference",
308
+ provenance: "reference",
309
+ async floor(ctx, query, pre, worthRunning) {
310
+ // The floor is exactly one binding plus one follow — the cheapest shape a
311
+ // result can take. INVESTMENT DISCIPLINE: when that already cannot beat
312
+ // the incumbent, return it UNINVESTED rather than first-touching the shared
313
+ // inventory (cast.ts and extraction.ts are the reference implementations).
314
+ const bound = STEP + STEP;
315
+ if (!worthRunning(bound)) return bound;
316
+ // A frame needs a query long enough to hold one, and a slot needs at least
317
+ // one window of its own beside it.
318
+ if (query.length < 2 * ctx.space.maxGroup) return null;
319
+ // A query the store holds outright is not a reference to anything — its own
320
+ // edges answer it, and binding would re-derive what recall reads directly.
321
+ // O(|query|) probes, already computed for this response.
322
+ if (pre.queryResolved !== null) return null;
323
+ // NO SCAFFOLDING GATE HERE, DELIBERATELY. `allWindowsAreScaffolding` gates
324
+ // recall's scaffolding-dominated tier and looks like the obvious third
325
+ // gate, but it is calibrated for mechanisms grounding THROUGH the query's
326
+ // stored windows, where "every window is a hub" means the query says
327
+ // nothing the corpus can be held to. A reference query's discriminative
328
+ // content is the SLOT — exactly the part the corpus cannot attest — so the
329
+ // predicate reports "all scaffolding" for the purest references there are.
330
+ // Measured on the byte-modality fixture (test/76): the frame's windows sit
331
+ // in all three instances and the referents' in none, so the gate fired and
332
+ // the mechanism abstained on an answer it had already derived correctly.
333
+ //
334
+ // What holds this honest is not window rarity but the licence: instances
335
+ // agreeing on one slot signature, each leading somewhere, and unanimous
336
+ // BYTE-EXACT carriage across all of them.
337
+ return bound;
338
+ },
339
+ async run(ctx, query, pre) {
340
+ const bound = await bindReference(ctx, query, pre);
341
+ return bound === null ? [] : [bound];
342
+ },
343
+ };
package/src/mind/mind.ts CHANGED
@@ -304,7 +304,7 @@ export class Mind implements MindContext {
304
304
  /** Per-response memo of {@link chooseNext} picks — ensures every mechanism
305
305
  * of a single response follows the SAME continuation for each ambiguous
306
306
  * context node. */
307
- _edgeChoice = new Map<number, number>();
307
+ _edgeChoice: Map<number, number> = new Map<number, number>();
308
308
 
309
309
  /** Previous deposit's seen node ids for incremental change detection. */
310
310
  _prevSeen: Set<number> | null = null;
@@ -312,7 +312,7 @@ export class Mind implements MindContext {
312
312
  /** Session cache of node-id → perceived gist for candidate scoring — see
313
313
  * {@link MindContext._gistCache}. 32 MB ≈ 8K gists at D=1024; hub
314
314
  * candidate sets (√N at most) fit comfortably and recur across queries. */
315
- _gistCache = new BoundedMap<number, Vec>(
315
+ _gistCache: BoundedMap<number, Vec> = new BoundedMap<number, Vec>(
316
316
  32_000_000,
317
317
  (v) => v.byteLength,
318
318
  );
@@ -320,12 +320,16 @@ export class Mind implements MindContext {
320
320
  // bounded: a pyramid costs ~KB per content byte (one D-float gist per
321
321
  // interior node), and only the few live conversation chains need to stay
322
322
  // warm, so 8 entries is the honest budget.
323
- _depositTrees = new BoundedMap<
324
- string,
325
- import("./types.js").DepositCacheEntry
326
- >(8);
327
- _depositLens = new Set<number>();
328
- _internIds = new WeakMap<import("../sema.js").Sema, number>();
323
+ _depositTrees: BoundedMap<string, import("./types.js").DepositCacheEntry> =
324
+ new BoundedMap<
325
+ string,
326
+ import("./types.js").DepositCacheEntry
327
+ >(8);
328
+ _depositLens: Set<number> = new Set<number>();
329
+ _internIds: WeakMap<import("../sema.js").Sema, number> = new WeakMap<
330
+ import("../sema.js").Sema,
331
+ number
332
+ >();
329
333
 
330
334
  // ── Conversation state ──────────────────────────────────────────────────
331
335
 
@@ -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;