@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
@@ -105,16 +105,13 @@ import {
105
105
  } from "./traverse.js";
106
106
  import { rItem, rNode } from "./trace.js";
107
107
  import { junctionContainersFrom } from "./junction.js";
108
- import { spanHalo } from "./match.js";
108
+ import { alignAround, type AlignGap, spanHalo } from "./match.js";
109
109
 
110
110
  /** One accepted substitution: query span [qs,qe) stands in for the
111
- * candidate context's span — recorded for the rationale trace. */
112
- interface Substitution {
113
- qs: number;
114
- qe: number;
115
- cs: number;
116
- ce: number;
117
- }
111
+ * candidate context's span — recorded for the rationale trace. The same
112
+ * shape the shared aligner reports a disagreement as ({@link AlignGap}); an
113
+ * accepted substitution is a gap that cleared this file's gates. */
114
+ type Substitution = AlignGap;
118
115
 
119
116
  /** A bridged grounding proposal: the trained context to ground, the query
120
117
  * spans its alignment accounts for, and the substitutions that closed it. */
@@ -151,102 +148,11 @@ export function dismissedKnownContent(
151
148
  return false;
152
149
  }
153
150
 
154
- /** Extend a seed match (query offset qo candidate offset co) to its
155
- * maximal common run, then walk outward in both directions collecting
156
- * further common runs of at least W bytes across bounded mismatch gaps
157
- * (each side chainReach). Returns the matched query spans and the
158
- * mismatch pairs between consecutive runs. */
159
- function align(
160
- ctx: MindContext,
161
- q: Uint8Array,
162
- c: Uint8Array,
163
- qo: number,
164
- co: number,
165
- ): { matched: Array<[number, number]>; gaps: Substitution[] } {
166
- const W = ctx.space.maxGroup;
167
- const reachCap = chainReach(W);
168
- // Maximal run around the seed.
169
- let qs = qo, ss = co;
170
- while (qs > 0 && ss > 0 && q[qs - 1] === c[ss - 1]) {
171
- qs--;
172
- ss--;
173
- }
174
- let qe = qo, se = co;
175
- while (qe < q.length && se < c.length && q[qe] === c[se]) {
176
- qe++;
177
- se++;
178
- }
179
- const matched: Array<[number, number]> = [[qs, qe]];
180
- const gaps: Substitution[] = [];
181
- // The next common run of ≥ W bytes past (qi, si), with each side's gap
182
- // bounded by chainReach; smallest total gap wins (nearest continuation).
183
- const runLenAt = (qi: number, si: number): number => {
184
- let n = 0;
185
- while (qi + n < q.length && si + n < c.length && q[qi + n] === c[si + n]) {
186
- n++;
187
- }
188
- return n;
189
- };
190
- // RIGHT sweep.
191
- let qi = qe, si = se;
192
- for (;;) {
193
- let found = false;
194
- for (let total = 1; total <= 2 * reachCap && !found; total++) {
195
- for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
196
- const gs = total - gq;
197
- if (gs > reachCap) continue;
198
- if (qi + gq >= q.length || si + gs >= c.length) continue;
199
- const n = runLenAt(qi + gq, si + gs);
200
- if (n >= W || qi + gq + n === q.length) {
201
- if (n === 0) continue;
202
- if (gq > 0 || gs > 0) {
203
- gaps.push({ qs: qi, qe: qi + gq, cs: si, ce: si + gs });
204
- }
205
- matched.push([qi + gq, qi + gq + n]);
206
- qi = qi + gq + n;
207
- si = si + gs + n;
208
- found = true;
209
- break;
210
- }
211
- }
212
- }
213
- if (!found) break;
214
- }
215
- // LEFT sweep (mirror).
216
- qi = qs;
217
- si = ss;
218
- for (;;) {
219
- let found = false;
220
- for (let total = 1; total <= 2 * reachCap && !found; total++) {
221
- for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
222
- const gs = total - gq;
223
- if (gs > reachCap) continue;
224
- if (qi - gq <= 0 || si - gs <= 0) continue;
225
- // Run ENDING at (qi - gq, si - gs).
226
- let n = 0;
227
- while (
228
- n < qi - gq && n < si - gs &&
229
- q[qi - gq - 1 - n] === c[si - gs - 1 - n]
230
- ) {
231
- n++;
232
- }
233
- if (n >= W || n === qi - gq) {
234
- if (n === 0) continue;
235
- if (gq > 0 || gs > 0) {
236
- gaps.push({ qs: qi - gq, qe: qi, cs: si - gs, ce: si });
237
- }
238
- matched.push([qi - gq - n, qi - gq]);
239
- qi = qi - gq - n;
240
- si = si - gs - n;
241
- found = true;
242
- break;
243
- }
244
- }
245
- }
246
- if (!found) break;
247
- }
248
- return { matched, gaps };
249
- }
151
+ // The seeded aligner this file used to own now lives in the shared match
152
+ // family as {@link alignAround} the frame reading (match.ts) reads the same
153
+ // gaps and asks the OPPOSITE question of them (see AlignGap's own doc). Two
154
+ // consumers, one definition (AGENTS §2.5); the bridge's reading is unchanged.
155
+ const align = alignAround;
250
156
 
251
157
  /** Recall's corroborated-substitution bridge — see the module comment.
252
158
  * Returns the best bridged grounding proposal, or null. */
package/src/mind/match.ts CHANGED
@@ -18,19 +18,29 @@
18
18
  // direct or mutual-sibling)
19
19
  // multi-hop pivot byte containment forward —
20
20
  // articulation halo sibling substitute conceptThreshold
21
+ // reference frameSlots() (the shared carry into carriesFillers
22
+ // aligner, gaps contracted) the answer
21
23
  //
22
24
  // This module holds the shared vocabulary those configurations are built
23
- // from — the MATCHERS (locate, alignRuns, alignGraded, analogyStrength) and
24
- // the PROJECTIONS (follow, conceptHop, reverseContext, project) — so each
25
- // mechanism file states only its configuration, never its own copy of the
26
- // machinery. The gates all live in geometry.ts (derived, never tuned).
25
+ // from — the MATCHERS (locate, alignRuns, alignGraded, alignAround/frameSlots,
26
+ // analogyStrength) and the PROJECTIONS (follow, conceptHop, reverseContext,
27
+ // project) — so each mechanism file states only its configuration, never its
28
+ // own copy of the machinery. Most gates live in geometry.ts (derived, never
29
+ // tuned); the two STRUCTURAL gates that are byte predicates rather than
30
+ // thresholds — isSpanShaped and carriesFillers — live here beside the matchers
31
+ // they gate.
27
32
 
28
33
  import { addInto, cosine, dot, normalize, Vec, zeros } from "../vec.js";
29
34
  import type { Hit } from "../store.js";
30
- import { conceptThreshold, identityBar, significanceBar } from "../geometry.js";
31
- import { indexOf } from "../bytes.js";
35
+ import {
36
+ conceptThreshold,
37
+ dominates,
38
+ identityBar,
39
+ significanceBar,
40
+ } from "../geometry.js";
41
+ import { bytesEqual, indexOf } from "../bytes.js";
32
42
  import type { MindContext } from "./types.js";
33
- import { leafIdRun } from "./canonical.js";
43
+ import { chainReach, leafIdRun } from "./canonical.js";
34
44
  import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
35
45
  import {
36
46
  argmaxCosine,
@@ -294,6 +304,486 @@ export function alignGraded(
294
304
  return out;
295
305
  }
296
306
 
307
+ // ═══════════════════════════════════════════════════════════════════════════
308
+ // THE FRAME READING — variable positions, and the licence to voice through one
309
+ // ═══════════════════════════════════════════════════════════════════════════
310
+ //
311
+ // Sema is otherwise a fully GROUND system: every item of the deduction system,
312
+ // every matcher and every gate compares ground bytes. Nothing anywhere
313
+ // represents a POSITION whose occupant comes from the context rather than the
314
+ // corpus, and so no mechanism can tell
315
+ //
316
+ // "the corpus does not explain these bytes" (PASS — refuse)
317
+ //
318
+ // apart from
319
+ //
320
+ // "these bytes occupy a place the corpus keeps open" (bind).
321
+ //
322
+ // Both arrive as unaligned residue. That single missing distinction is why
323
+ // the substitution bridge refuses on `attestedQ`, why the cover charges PASS
324
+ // over a slot, and why CAST reads a filler as noise rather than as the
325
+ // variable it is. The family below supplies it, and it lives HERE — not in
326
+ // any mechanism — because it is the ordinary (matcher, projection, gate)
327
+ // triple of §2.5 with its three parts in their proper places:
328
+ //
329
+ // matcher alignAround + contractGap + frameSlots — bytes only, no
330
+ // projection, no licence. SAFE FOR EVERY CONSUMER: knowing a
331
+ // span is variable can only improve an alignment.
332
+ // projection follow / project — unchanged, already here.
333
+ // gate carriesFillers — the licence to VOICE through a slot. Needed
334
+ // only by a mechanism that voices, so it is deliberately NOT
335
+ // folded into the matcher.
336
+ //
337
+ // The split matters. Slot detection is universal; the licence is not, and a
338
+ // consumer that took the matcher's answer as permission to voice would be
339
+ // making exactly the claim the licence exists to withhold.
340
+
341
+ /** One place two byte streams DISAGREE, between runs where they agree: the
342
+ * query span `[qs,qe)` standing where the candidate's `[cs,ce)` stands.
343
+ *
344
+ * Two mechanisms read the same gap and ask OPPOSITE questions of it, which is
345
+ * why the shape lives here rather than in either of them:
346
+ *
347
+ * • the substitution bridge asks whether the two sides MEAN THE SAME, and
348
+ * so EXPANDS the gap (absorbing flanking matched bytes) until the query
349
+ * side is corpus-attested and the pair clears the concept bar;
350
+ * • the frame reading asks WHERE THE SLOT IS, and so CONTRACTS it
351
+ * ({@link contractGap}) until the two sides share nothing at all.
352
+ *
353
+ * Neither reading is derivable from the other, and both need the same gap. */
354
+ export interface AlignGap {
355
+ qs: number;
356
+ qe: number;
357
+ cs: number;
358
+ ce: number;
359
+ }
360
+
361
+ /** Extend a seed match (query offset qo ↔ candidate offset co) to its maximal
362
+ * common run, then walk outward in both directions collecting further common
363
+ * runs of at least W bytes across bounded mismatch gaps (each side ≤
364
+ * chainReach). Returns the matched query spans and the mismatch pairs
365
+ * between consecutive runs.
366
+ *
367
+ * This is the SEEDED aligner, distinct from {@link alignRuns}: that one finds
368
+ * every run two structures share anywhere (a weave), this one reads two
369
+ * streams as ONE structure that diverges in bounded places (a frame with
370
+ * slots).
371
+ *
372
+ * Gaps come back in SWEEP order (right sweep, then left), not query order,
373
+ * and only the INTERIOR ones are reported — a consumer that needs the query's
374
+ * unmatched head or tail derives it from `matched`. Both are the bridge's
375
+ * contract, which prices its edges separately (see its matchStart/matchEnd
376
+ * window test); {@link frameSlots} takes the other reading. */
377
+ export function alignAround(
378
+ ctx: MindContext,
379
+ q: Uint8Array,
380
+ c: Uint8Array,
381
+ qo: number,
382
+ co: number,
383
+ ): { matched: Array<[number, number]>; gaps: AlignGap[] } {
384
+ const W = ctx.space.maxGroup;
385
+ const reachCap = chainReach(W);
386
+ // Maximal run around the seed.
387
+ let qs = qo, ss = co;
388
+ while (qs > 0 && ss > 0 && q[qs - 1] === c[ss - 1]) {
389
+ qs--;
390
+ ss--;
391
+ }
392
+ let qe = qo, se = co;
393
+ while (qe < q.length && se < c.length && q[qe] === c[se]) {
394
+ qe++;
395
+ se++;
396
+ }
397
+ const matched: Array<[number, number]> = [[qs, qe]];
398
+ const gaps: AlignGap[] = [];
399
+ // The next common run of ≥ W bytes past (qi, si), with each side's gap
400
+ // bounded by chainReach; smallest total gap wins (nearest continuation).
401
+ const runLenAt = (qi: number, si: number): number => {
402
+ let n = 0;
403
+ while (qi + n < q.length && si + n < c.length && q[qi + n] === c[si + n]) {
404
+ n++;
405
+ }
406
+ return n;
407
+ };
408
+ // RIGHT sweep.
409
+ let qi = qe, si = se;
410
+ for (;;) {
411
+ let found = false;
412
+ for (let total = 1; total <= 2 * reachCap && !found; total++) {
413
+ for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
414
+ const gs = total - gq;
415
+ if (gs > reachCap) continue;
416
+ if (qi + gq >= q.length || si + gs >= c.length) continue;
417
+ const n = runLenAt(qi + gq, si + gs);
418
+ if (n >= W || qi + gq + n === q.length) {
419
+ if (n === 0) continue;
420
+ if (gq > 0 || gs > 0) {
421
+ gaps.push({ qs: qi, qe: qi + gq, cs: si, ce: si + gs });
422
+ }
423
+ matched.push([qi + gq, qi + gq + n]);
424
+ qi = qi + gq + n;
425
+ si = si + gs + n;
426
+ found = true;
427
+ break;
428
+ }
429
+ }
430
+ }
431
+ if (!found) break;
432
+ }
433
+ // LEFT sweep (mirror).
434
+ qi = qs;
435
+ si = ss;
436
+ for (;;) {
437
+ let found = false;
438
+ for (let total = 1; total <= 2 * reachCap && !found; total++) {
439
+ for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
440
+ const gs = total - gq;
441
+ if (gs > reachCap) continue;
442
+ if (qi - gq <= 0 || si - gs <= 0) continue;
443
+ // Run ENDING at (qi - gq, si - gs).
444
+ let n = 0;
445
+ while (
446
+ n < qi - gq && n < si - gs &&
447
+ q[qi - gq - 1 - n] === c[si - gs - 1 - n]
448
+ ) {
449
+ n++;
450
+ }
451
+ if (n >= W || n === qi - gq) {
452
+ if (n === 0) continue;
453
+ if (gq > 0 || gs > 0) {
454
+ gaps.push({ qs: qi - gq, qe: qi, cs: si - gs, ce: si });
455
+ }
456
+ matched.push([qi - gq - n, qi - gq]);
457
+ qi = qi - gq - n;
458
+ si = si - gs - n;
459
+ found = true;
460
+ break;
461
+ }
462
+ }
463
+ }
464
+ if (!found) break;
465
+ }
466
+ return { matched, gaps };
467
+ }
468
+
469
+ /** Contract a gap to its VARYING CORE: strip the prefix and suffix the two
470
+ * sides share. {@link alignAround} cannot match a shared affix shorter than
471
+ * W, so that affix lands INSIDE the gap — measured, the slot of
472
+ * `How do I compile main.c?` against `…hello.c?` comes back as
473
+ * `main.c?`/`hello.c?`, three bytes of which (`.c?`) both sides hold.
474
+ *
475
+ * Splicing the uncontracted gap carries the query's own punctuation into the
476
+ * answer; worse, it hides what actually VARIES, which is the only thing a
477
+ * cohort can agree about. Returns null when nothing is left on either side —
478
+ * a pure insertion or deletion, which names no slot. */
479
+ export function contractGap(
480
+ q: Uint8Array,
481
+ c: Uint8Array,
482
+ g: AlignGap,
483
+ ): AlignGap | null {
484
+ let { qs, qe, cs, ce } = g;
485
+ while (qs < qe && cs < ce && q[qs] === c[cs]) {
486
+ qs++;
487
+ cs++;
488
+ }
489
+ while (qe > qs && ce > cs && q[qe - 1] === c[ce - 1]) {
490
+ qe--;
491
+ ce--;
492
+ }
493
+ return qe > qs && ce > cs ? { qs, qe, cs, ce } : null;
494
+ }
495
+
496
+ /** What one place two streams disagree IS, once contracted to its varying
497
+ * core. A consumer decides which kinds it can use; the matcher only reports.
498
+ *
499
+ * substitution both sides carry bytes — one thing stands where another does
500
+ * insertion the query carries bytes the candidate does not
501
+ * deletion the candidate carries bytes the query does not */
502
+ export type SlotKind = "substitution" | "insertion" | "deletion";
503
+
504
+ /** One VARIABLE POSITION of a pairing: where the query and a candidate differ,
505
+ * contracted to the bytes that actually vary. */
506
+ export interface FrameSlot {
507
+ /** Query span (empty for a deletion). */
508
+ qs: number;
509
+ qe: number;
510
+ /** Candidate span (empty for an insertion). */
511
+ cs: number;
512
+ ce: number;
513
+ kind: SlotKind;
514
+ /** The candidate's own bytes here — empty for an insertion. */
515
+ filler: Uint8Array;
516
+ }
517
+
518
+ /** One trained context read against the query as ONE structure with variable
519
+ * positions.
520
+ *
521
+ * EVERYTHING THE ALIGNER SAW, NOTHING JUDGED. `slots` holds every place the
522
+ * pairing varies, in query order, whatever its kind or size, and `covered`
523
+ * says how much of the query the two hold in common. No gate is applied
524
+ * here — see {@link frameSlots}. */
525
+ export interface FrameInstance {
526
+ /** The trained context this reading is against. */
527
+ id: number;
528
+ /** Every variable position, in query order. */
529
+ slots: FrameSlot[];
530
+ /** Query spans the pairing literally matched — the frame itself. */
531
+ matched: Array<[number, number]>;
532
+ /** Query bytes the frame accounts for: the size of what is shared. */
533
+ covered: number;
534
+ }
535
+
536
+ /** THE SLOT MATCHER: read one query ↔ context pairing as one structure with
537
+ * variable positions.
538
+ *
539
+ * IT REPORTS; IT DOES NOT JUDGE. This returns every gap the aligner found,
540
+ * contracted to its varying core and tagged with its kind, plus the shared
541
+ * coverage — and rejects nothing. That is the whole point of the split, and
542
+ * it was got WRONG first: four VOICING gates (the frame must dominate the
543
+ * query, each slot must reach one window on both sides, an insertion or
544
+ * deletion disqualifies the pairing, fillers must be pairwise distinct) were
545
+ * applied here, and every one of them is a requirement for SUBSTITUTING AND
546
+ * SPEAKING, not for knowing where a pairing varies. With them in place the
547
+ * shared reading was reference-shaped: measured over four real pairings, three
548
+ * were hidden from every consumer —
549
+ *
550
+ * `What is the capital of the country where the Eiffel Tower is?`
551
+ * against `What is the capital of France?` (covered 23/61) HIDDEN
552
+ * `What is the capital of France, really?` (an insertion) HIDDEN
553
+ * `What is the capital of Fran?` (sub-window) HIDDEN
554
+ *
555
+ * — including the case of the one consumer that most obviously needed it. A
556
+ * shared layer with one usable consumer is private code at a public address.
557
+ * Each gate now lives with the mechanism that needs it (see reference.ts).
558
+ *
559
+ * Seeded at the origin, because a frame is shared structure the query and its
560
+ * instances both OPEN with: the maximal run around (0,0) is the frame's head
561
+ * and the sweeps find the rest.
562
+ *
563
+ * Null only for a degenerate pairing (either side empty). */
564
+ export function frameSlots(
565
+ ctx: MindContext,
566
+ query: Uint8Array,
567
+ cand: Uint8Array,
568
+ id: number,
569
+ ): FrameInstance | null {
570
+ if (query.length === 0 || cand.length === 0) return null;
571
+ const { matched, gaps } = alignAround(ctx, query, cand, 0, 0);
572
+ const spans = [...matched].sort((a, b) => a[0] - b[0]);
573
+ // Where the alignment RAN OUT on each side. Seeded at the origin there is
574
+ // no leading gap, so both cursors are everything consumed so far: the
575
+ // matched runs (equal length on both sides by construction) plus what each
576
+ // interior gap ate of its own side. Counting only the runs reads the
577
+ // candidate cursor short by exactly the fillers already seen, and invents a
578
+ // trailing gap on every well-aligned instance.
579
+ const all: AlignGap[] = [...gaps];
580
+ let qEnd = 0, cEnd = 0;
581
+ for (const [s, e] of spans) {
582
+ cEnd += e - s;
583
+ qEnd = Math.max(qEnd, e);
584
+ }
585
+ for (const g of gaps) cEnd += g.ce - g.cs;
586
+ if (qEnd < query.length || cEnd < cand.length) {
587
+ all.push({ qs: qEnd, qe: query.length, cs: cEnd, ce: cand.length });
588
+ }
589
+ const slots: FrameSlot[] = [];
590
+ for (const gap of all.sort((a, b) => a.qs - b.qs)) {
591
+ if (gap.qe <= gap.qs && gap.ce <= gap.cs) continue;
592
+ // Contract to the varying core. contractGap returns null when one side is
593
+ // wholly shared with the other — a pure insertion or deletion, which is a
594
+ // real variation and is reported AS ONE, not discarded.
595
+ const core = contractGap(query, cand, gap);
596
+ const g = core ?? gap;
597
+ const kind: SlotKind = g.qe > g.qs && g.ce > g.cs
598
+ ? "substitution"
599
+ : g.qe > g.qs
600
+ ? "insertion"
601
+ : "deletion";
602
+ slots.push({
603
+ qs: g.qs,
604
+ qe: g.qe,
605
+ cs: g.cs,
606
+ ce: g.ce,
607
+ kind,
608
+ filler: cand.slice(g.cs, g.ce),
609
+ });
610
+ }
611
+ const covered = spans.reduce((n, [s, e]) => n + e - s, 0);
612
+ return { id, slots, matched: spans, covered };
613
+ }
614
+
615
+ /** THE DISPLACED-FILLER GATE: does `projection` speak the ANCHOR's occupant of
616
+ * a position the query fills differently?
617
+ *
618
+ * A mechanism grounding through an anchor voices that anchor's continuation.
619
+ * When the query is the same structure as the anchor with one position filled
620
+ * differently — a different filename, a different word — the anchor's
621
+ * continuation is ABOUT THE ANCHOR'S occupant, and voicing it answers a
622
+ * question the asker did not ask. It is worse than silence, because it is
623
+ * fluent and specific and wrong:
624
+ *
625
+ * trained `How do I compile hello.c?` -> `Run gcc hello.c`
626
+ * asked `How do I compile main.c?`
627
+ * voiced `Run gcc hello.c` <- the corpus's file, not the asker's
628
+ *
629
+ * The same shape on the trained 15.7M-node store: `How do you say 'flurbish'
630
+ * in French?` answers "the way to say hello is \"Bonjour\"".
631
+ *
632
+ * THIS IS NOT THE RESTATED-FRAGMENT GUARD. That one asks whether the
633
+ * projection is a piece of the QUERY; this asks whether it is a piece of the
634
+ * ANCHOR that the query displaced. Neither implies the other, and the
635
+ * observed failures pass the restatement guard cleanly.
636
+ *
637
+ * Three conditions, all byte-exact and all necessary:
638
+ *
639
+ * 1. the query and the anchor must be ONE STRUCTURE — what they share has to
640
+ * dominate the query, or the query is not a variant of the anchor at all
641
+ * and the anchor's occupant of anything is beside the point;
642
+ * 2. both sides of the position must reach one river window — below it byte
643
+ * overlap is chance, not evidence (the floor identityBar and the bridge's
644
+ * attestedQ both draw);
645
+ * 3. the projection must voice the anchor's filler and NOT the query's
646
+ * referent. Voicing both is a projection that carried the asker's own
647
+ * occupant through, which is exactly what a licensed reference does and
648
+ * must stay allowed;
649
+ * 4. and the projection must share NO perceivable content with the query
650
+ * outside that position — no run of one river window.
651
+ *
652
+ * GATE 4 IS WHAT SEPARATES A DIFFERENT THING FROM A DIFFERENT WORD, and
653
+ * without it this refuses correct answers. A displaced slot alone cannot
654
+ * tell them apart: `symbol` <- `formula` and `main` <- `hello` are the same
655
+ * shape to the matcher — one substitution slot, frame dominating. Measured
656
+ * on the trained store, gates 1-3 alone silenced
657
+ *
658
+ * Q `What is the chemical symbol for water?`
659
+ * A `The chemical formula for water is H2O.`
660
+ *
661
+ * which is right, and merely phrased in the corpus's own words. The answer
662
+ * shares `the chemical ` and ` for water` with the question, so it is plainly
663
+ * about what was asked. `Run gcc hello.c` against `How do I compile main.c?`
664
+ * shares nothing but `.c` — two bytes, below the window where overlap stops
665
+ * being chance — so it is not about what was asked at all. No new constant:
666
+ * W is the same floor identityBar, attestedQ and the site test already draw. */
667
+ export function voicesDisplacedFiller(
668
+ ctx: MindContext,
669
+ query: Uint8Array,
670
+ anchor: Uint8Array,
671
+ projection: Uint8Array,
672
+ ): boolean {
673
+ const W = ctx.space.maxGroup;
674
+ const inst = frameSlots(ctx, query, anchor, 0);
675
+ if (inst === null) return false;
676
+ if (!dominates(inst.covered, query.length)) return false;
677
+ for (const slot of inst.slots) {
678
+ if (slot.kind !== "substitution") continue;
679
+ if (slot.qe - slot.qs < W || slot.filler.length < W) continue;
680
+ if (indexOf(projection, slot.filler, 0) < 0) continue;
681
+ const referent = query.subarray(slot.qs, slot.qe);
682
+ if (indexOf(projection, referent, 0) >= 0) continue;
683
+ // Gate 4: does the projection still speak about the query's FRAME? A run
684
+ // of one window anywhere outside the displaced position is enough — the
685
+ // answer is then about the thing that was asked, in the corpus's own
686
+ // wording. Sharing nothing means it is about something else.
687
+ const shared = alignRuns(ctx, query, projection).some((r) =>
688
+ r.qe - r.qs >= W && (r.qe <= slot.qs || r.qs >= slot.qe)
689
+ );
690
+ if (shared) continue;
691
+ return true;
692
+ }
693
+ return false;
694
+ }
695
+
696
+ /** Whether every member is byte-distinct from the others. */
697
+ export function distinct(items: readonly Uint8Array[]): boolean {
698
+ for (let i = 0; i < items.length; i++) {
699
+ for (let j = i + 1; j < items.length; j++) {
700
+ if (bytesEqual(items[i], items[j])) return false;
701
+ }
702
+ }
703
+ return true;
704
+ }
705
+
706
+ /** Substitute every `needle -> repl` pair SIMULTANEOUSLY: one left-to-right
707
+ * pass, longest needle first at each position, and a replacement is never
708
+ * re-examined.
709
+ *
710
+ * SIMULTANEOUS IS NOT A DETAIL. Applying the pairs in sequence lets one
711
+ * substitution's OUTPUT be another's input: with slots `gcc -> zig` and
712
+ * `hello.c -> zig.c` a sequential pass rewrites bytes it had just written,
713
+ * and the result depends on the order the slots happened to be found in.
714
+ * Longest-first at each position makes the pass independent of pair order,
715
+ * which is what keeps {@link carriesFillers} and the binding it licenses the
716
+ * SAME operation — if they could disagree, the licence would not be testing
717
+ * what is voiced. */
718
+ export function substituteAll(
719
+ hay: Uint8Array,
720
+ pairs: ReadonlyArray<{ needle: Uint8Array; repl: Uint8Array }>,
721
+ ): Uint8Array {
722
+ const usable = pairs.filter((p) => p.needle.length > 0);
723
+ if (usable.length === 0) return hay;
724
+ // Longest needle first, so a needle that is a prefix of another can never
725
+ // pre-empt it. Ties cannot arise: an instance whose fillers are not
726
+ // pairwise distinct is refused by frameSlots.
727
+ const order = [...usable].sort((a, b) => b.needle.length - a.needle.length);
728
+ const out: number[] = [];
729
+ let i = 0;
730
+ let hit = false;
731
+ outer:
732
+ while (i < hay.length) {
733
+ for (const p of order) {
734
+ if (i + p.needle.length > hay.length) continue;
735
+ let k = 0;
736
+ while (k < p.needle.length && hay[i + k] === p.needle[k]) k++;
737
+ if (k < p.needle.length) continue;
738
+ for (const b of p.repl) out.push(b);
739
+ i += p.needle.length;
740
+ hit = true;
741
+ continue outer;
742
+ }
743
+ out.push(hay[i]);
744
+ i++;
745
+ }
746
+ return hit ? Uint8Array.from(out) : hay;
747
+ }
748
+
749
+ /** THE CARRIAGE LICENCE — the gate that decides whether a slot may be VOICED
750
+ * through. Given two instances of one frame and what each one continues to,
751
+ * it asks one byte question:
752
+ *
753
+ * substituteAll(contA, fillersA -> fillersB) == contB
754
+ *
755
+ * When it holds, the corpus attests byte-exactly that the continuation is a
756
+ * function of the fillers and nothing else, so putting a NEW occupant through
757
+ * the same carriage is derivation rather than invention. No threshold, no
758
+ * similarity, no new constant: the store's own instances decide, exactly as
759
+ * the bridge's `unanimous` decides whether a frame is a value slot.
760
+ *
761
+ * Its FAILURE is what this is really for. A frame whose continuation carries
762
+ * filler-DEPENDENT content — `What is the capital of X?` answering a different
763
+ * city per X — fails it, and that failure is the only thing between a slot
764
+ * and an invented fact. Measured on the trained 15.7M-node store (325,615
765
+ * contexts): `What is the capital of Zamunda?` resonates to a PURE cohort,
766
+ * every one of the top 14 hits an instance of that frame, with an unambiguous
767
+ * slot; every structural gate passes and only this one refuses, on
768
+ * `replace("Tokyo", "Japan" -> "France") != "Paris"`.
769
+ *
770
+ * With SEVERAL slots the test is unchanged, which is the point of testing the
771
+ * whole substitution at once: a frame whose answer tracks one slot but
772
+ * invents around another fails exactly as a single-slot value slot does. */
773
+ export function carriesFillers(
774
+ contA: Uint8Array,
775
+ fillersA: readonly Uint8Array[],
776
+ contB: Uint8Array,
777
+ fillersB: readonly Uint8Array[],
778
+ ): boolean {
779
+ if (fillersA.length !== fillersB.length) return false;
780
+ const projected = substituteAll(
781
+ contA,
782
+ fillersA.map((needle, s) => ({ needle, repl: fillersB[s] })),
783
+ );
784
+ return bytesEqual(projected, contB);
785
+ }
786
+
297
787
  /** The IN-LIST halo matcher: the best halo-mate for `halo` among EXPLICIT
298
788
  * candidates, above the concept threshold — the list counterpart of
299
789
  * {@link haloSiblings}, which asks the halo INDEX for candidates instead.