@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
@@ -97,7 +97,7 @@ import { chainReach, leafIdRun } from "./canonical.js";
97
97
  import { allWindowsAreScaffolding, corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "./traverse.js";
98
98
  import { rItem, rNode } from "./trace.js";
99
99
  import { junctionContainersFrom } from "./junction.js";
100
- import { spanHalo } from "./match.js";
100
+ import { alignAround, spanHalo } from "./match.js";
101
101
  /** True when some query byte-range left UNACCOUNTED by `spans` contains a
102
102
  * STORED window — content the store has seen that the proposed reading
103
103
  * simply ignores. The IGNORED-KNOWN principle: a span may be dismissed
@@ -121,102 +121,11 @@ export function dismissedKnownContent(ctx, query, spans) {
121
121
  }
122
122
  return false;
123
123
  }
124
- /** Extend a seed match (query offset qo candidate offset co) to its
125
- * maximal common run, then walk outward in both directions collecting
126
- * further common runs of at least W bytes across bounded mismatch gaps
127
- * (each side chainReach). Returns the matched query spans and the
128
- * mismatch pairs between consecutive runs. */
129
- function align(ctx, q, c, qo, co) {
130
- const W = ctx.space.maxGroup;
131
- const reachCap = chainReach(W);
132
- // Maximal run around the seed.
133
- let qs = qo, ss = co;
134
- while (qs > 0 && ss > 0 && q[qs - 1] === c[ss - 1]) {
135
- qs--;
136
- ss--;
137
- }
138
- let qe = qo, se = co;
139
- while (qe < q.length && se < c.length && q[qe] === c[se]) {
140
- qe++;
141
- se++;
142
- }
143
- const matched = [[qs, qe]];
144
- const gaps = [];
145
- // The next common run of ≥ W bytes past (qi, si), with each side's gap
146
- // bounded by chainReach; smallest total gap wins (nearest continuation).
147
- const runLenAt = (qi, si) => {
148
- let n = 0;
149
- while (qi + n < q.length && si + n < c.length && q[qi + n] === c[si + n]) {
150
- n++;
151
- }
152
- return n;
153
- };
154
- // RIGHT sweep.
155
- let qi = qe, si = se;
156
- for (;;) {
157
- let found = false;
158
- for (let total = 1; total <= 2 * reachCap && !found; total++) {
159
- for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
160
- const gs = total - gq;
161
- if (gs > reachCap)
162
- continue;
163
- if (qi + gq >= q.length || si + gs >= c.length)
164
- continue;
165
- const n = runLenAt(qi + gq, si + gs);
166
- if (n >= W || qi + gq + n === q.length) {
167
- if (n === 0)
168
- continue;
169
- if (gq > 0 || gs > 0) {
170
- gaps.push({ qs: qi, qe: qi + gq, cs: si, ce: si + gs });
171
- }
172
- matched.push([qi + gq, qi + gq + n]);
173
- qi = qi + gq + n;
174
- si = si + gs + n;
175
- found = true;
176
- break;
177
- }
178
- }
179
- }
180
- if (!found)
181
- break;
182
- }
183
- // LEFT sweep (mirror).
184
- qi = qs;
185
- si = ss;
186
- for (;;) {
187
- let found = false;
188
- for (let total = 1; total <= 2 * reachCap && !found; total++) {
189
- for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
190
- const gs = total - gq;
191
- if (gs > reachCap)
192
- continue;
193
- if (qi - gq <= 0 || si - gs <= 0)
194
- continue;
195
- // Run ENDING at (qi - gq, si - gs).
196
- let n = 0;
197
- while (n < qi - gq && n < si - gs &&
198
- q[qi - gq - 1 - n] === c[si - gs - 1 - n]) {
199
- n++;
200
- }
201
- if (n >= W || n === qi - gq) {
202
- if (n === 0)
203
- continue;
204
- if (gq > 0 || gs > 0) {
205
- gaps.push({ qs: qi - gq, qe: qi, cs: si - gs, ce: si });
206
- }
207
- matched.push([qi - gq - n, qi - gq]);
208
- qi = qi - gq - n;
209
- si = si - gs - n;
210
- found = true;
211
- break;
212
- }
213
- }
214
- }
215
- if (!found)
216
- break;
217
- }
218
- return { matched, gaps };
219
- }
124
+ // The seeded aligner this file used to own now lives in the shared match
125
+ // family as {@link alignAround} the frame reading (match.ts) reads the same
126
+ // gaps and asks the OPPOSITE question of them (see AlignGap's own doc). Two
127
+ // consumers, one definition (AGENTS §2.5); the bridge's reading is unchanged.
128
+ const align = alignAround;
220
129
  /** Recall's corroborated-substitution bridge — see the module comment.
221
130
  * Returns the best bridged grounding proposal, or null. */
222
131
  /** `proposed` is a THUNK, not a list: the bridge's own cheap gates (the
@@ -57,6 +57,165 @@ export interface GradedRun {
57
57
  * (optional — when absent, only literal alignment fires and graded degrades
58
58
  * to the original behaviour). Context sites are recognised internally. */
59
59
  export declare function alignGraded(ctx: MindContext, query: Uint8Array, contextBytes: Uint8Array, querySites?: ReadonlyArray<Site>): GradedRun[];
60
+ /** One place two byte streams DISAGREE, between runs where they agree: the
61
+ * query span `[qs,qe)` standing where the candidate's `[cs,ce)` stands.
62
+ *
63
+ * Two mechanisms read the same gap and ask OPPOSITE questions of it, which is
64
+ * why the shape lives here rather than in either of them:
65
+ *
66
+ * • the substitution bridge asks whether the two sides MEAN THE SAME, and
67
+ * so EXPANDS the gap (absorbing flanking matched bytes) until the query
68
+ * side is corpus-attested and the pair clears the concept bar;
69
+ * • the frame reading asks WHERE THE SLOT IS, and so CONTRACTS it
70
+ * ({@link contractGap}) until the two sides share nothing at all.
71
+ *
72
+ * Neither reading is derivable from the other, and both need the same gap. */
73
+ export interface AlignGap {
74
+ qs: number;
75
+ qe: number;
76
+ cs: number;
77
+ ce: number;
78
+ }
79
+ /** Extend a seed match (query offset qo ↔ candidate offset co) to its maximal
80
+ * common run, then walk outward in both directions collecting further common
81
+ * runs of at least W bytes across bounded mismatch gaps (each side ≤
82
+ * chainReach). Returns the matched query spans and the mismatch pairs
83
+ * between consecutive runs.
84
+ *
85
+ * This is the SEEDED aligner, distinct from {@link alignRuns}: that one finds
86
+ * every run two structures share anywhere (a weave), this one reads two
87
+ * streams as ONE structure that diverges in bounded places (a frame with
88
+ * slots).
89
+ *
90
+ * Gaps come back in SWEEP order (right sweep, then left), not query order,
91
+ * and only the INTERIOR ones are reported — a consumer that needs the query's
92
+ * unmatched head or tail derives it from `matched`. Both are the bridge's
93
+ * contract, which prices its edges separately (see its matchStart/matchEnd
94
+ * window test); {@link frameSlots} takes the other reading. */
95
+ export declare function alignAround(ctx: MindContext, q: Uint8Array, c: Uint8Array, qo: number, co: number): {
96
+ matched: Array<[number, number]>;
97
+ gaps: AlignGap[];
98
+ };
99
+ /** Contract a gap to its VARYING CORE: strip the prefix and suffix the two
100
+ * sides share. {@link alignAround} cannot match a shared affix shorter than
101
+ * W, so that affix lands INSIDE the gap — measured, the slot of
102
+ * `How do I compile main.c?` against `…hello.c?` comes back as
103
+ * `main.c?`/`hello.c?`, three bytes of which (`.c?`) both sides hold.
104
+ *
105
+ * Splicing the uncontracted gap carries the query's own punctuation into the
106
+ * answer; worse, it hides what actually VARIES, which is the only thing a
107
+ * cohort can agree about. Returns null when nothing is left on either side —
108
+ * a pure insertion or deletion, which names no slot. */
109
+ export declare function contractGap(q: Uint8Array, c: Uint8Array, g: AlignGap): AlignGap | null;
110
+ /** What one place two streams disagree IS, once contracted to its varying
111
+ * core. A consumer decides which kinds it can use; the matcher only reports.
112
+ *
113
+ * substitution both sides carry bytes — one thing stands where another does
114
+ * insertion the query carries bytes the candidate does not
115
+ * deletion the candidate carries bytes the query does not */
116
+ export type SlotKind = "substitution" | "insertion" | "deletion";
117
+ /** One VARIABLE POSITION of a pairing: where the query and a candidate differ,
118
+ * contracted to the bytes that actually vary. */
119
+ export interface FrameSlot {
120
+ /** Query span (empty for a deletion). */
121
+ qs: number;
122
+ qe: number;
123
+ /** Candidate span (empty for an insertion). */
124
+ cs: number;
125
+ ce: number;
126
+ kind: SlotKind;
127
+ /** The candidate's own bytes here — empty for an insertion. */
128
+ filler: Uint8Array;
129
+ }
130
+ /** One trained context read against the query as ONE structure with variable
131
+ * positions.
132
+ *
133
+ * EVERYTHING THE ALIGNER SAW, NOTHING JUDGED. `slots` holds every place the
134
+ * pairing varies, in query order, whatever its kind or size, and `covered`
135
+ * says how much of the query the two hold in common. No gate is applied
136
+ * here — see {@link frameSlots}. */
137
+ export interface FrameInstance {
138
+ /** The trained context this reading is against. */
139
+ id: number;
140
+ /** Every variable position, in query order. */
141
+ slots: FrameSlot[];
142
+ /** Query spans the pairing literally matched — the frame itself. */
143
+ matched: Array<[number, number]>;
144
+ /** Query bytes the frame accounts for: the size of what is shared. */
145
+ covered: number;
146
+ }
147
+ /** THE SLOT MATCHER: read one query ↔ context pairing as one structure with
148
+ * variable positions.
149
+ *
150
+ * IT REPORTS; IT DOES NOT JUDGE. This returns every gap the aligner found,
151
+ * contracted to its varying core and tagged with its kind, plus the shared
152
+ * coverage — and rejects nothing. That is the whole point of the split, and
153
+ * it was got WRONG first: four VOICING gates (the frame must dominate the
154
+ * query, each slot must reach one window on both sides, an insertion or
155
+ * deletion disqualifies the pairing, fillers must be pairwise distinct) were
156
+ * applied here, and every one of them is a requirement for SUBSTITUTING AND
157
+ * SPEAKING, not for knowing where a pairing varies. With them in place the
158
+ * shared reading was reference-shaped: measured over four real pairings, three
159
+ * were hidden from every consumer —
160
+ *
161
+ * `What is the capital of the country where the Eiffel Tower is?`
162
+ * against `What is the capital of France?` (covered 23/61) HIDDEN
163
+ * `What is the capital of France, really?` (an insertion) HIDDEN
164
+ * `What is the capital of Fran?` (sub-window) HIDDEN
165
+ *
166
+ * — including the case of the one consumer that most obviously needed it. A
167
+ * shared layer with one usable consumer is private code at a public address.
168
+ * Each gate now lives with the mechanism that needs it (see reference.ts).
169
+ *
170
+ * Seeded at the origin, because a frame is shared structure the query and its
171
+ * instances both OPEN with: the maximal run around (0,0) is the frame's head
172
+ * and the sweeps find the rest.
173
+ *
174
+ * Null only for a degenerate pairing (either side empty). */
175
+ export declare function frameSlots(ctx: MindContext, query: Uint8Array, cand: Uint8Array, id: number): FrameInstance | null;
176
+ /** Whether every member is byte-distinct from the others. */
177
+ export declare function distinct(items: readonly Uint8Array[]): boolean;
178
+ /** Substitute every `needle -> repl` pair SIMULTANEOUSLY: one left-to-right
179
+ * pass, longest needle first at each position, and a replacement is never
180
+ * re-examined.
181
+ *
182
+ * SIMULTANEOUS IS NOT A DETAIL. Applying the pairs in sequence lets one
183
+ * substitution's OUTPUT be another's input: with slots `gcc -> zig` and
184
+ * `hello.c -> zig.c` a sequential pass rewrites bytes it had just written,
185
+ * and the result depends on the order the slots happened to be found in.
186
+ * Longest-first at each position makes the pass independent of pair order,
187
+ * which is what keeps {@link carriesFillers} and the binding it licenses the
188
+ * SAME operation — if they could disagree, the licence would not be testing
189
+ * what is voiced. */
190
+ export declare function substituteAll(hay: Uint8Array, pairs: ReadonlyArray<{
191
+ needle: Uint8Array;
192
+ repl: Uint8Array;
193
+ }>): Uint8Array;
194
+ /** THE CARRIAGE LICENCE — the gate that decides whether a slot may be VOICED
195
+ * through. Given two instances of one frame and what each one continues to,
196
+ * it asks one byte question:
197
+ *
198
+ * substituteAll(contA, fillersA -> fillersB) == contB
199
+ *
200
+ * When it holds, the corpus attests byte-exactly that the continuation is a
201
+ * function of the fillers and nothing else, so putting a NEW occupant through
202
+ * the same carriage is derivation rather than invention. No threshold, no
203
+ * similarity, no new constant: the store's own instances decide, exactly as
204
+ * the bridge's `unanimous` decides whether a frame is a value slot.
205
+ *
206
+ * Its FAILURE is what this is really for. A frame whose continuation carries
207
+ * filler-DEPENDENT content — `What is the capital of X?` answering a different
208
+ * city per X — fails it, and that failure is the only thing between a slot
209
+ * and an invented fact. Measured on the trained 15.7M-node store (325,615
210
+ * contexts): `What is the capital of Zamunda?` resonates to a PURE cohort,
211
+ * every one of the top 14 hits an instance of that frame, with an unambiguous
212
+ * slot; every structural gate passes and only this one refuses, on
213
+ * `replace("Tokyo", "Japan" -> "France") != "Paris"`.
214
+ *
215
+ * With SEVERAL slots the test is unchanged, which is the point of testing the
216
+ * whole substitution at once: a frame whose answer tracks one slot but
217
+ * invents around another fails exactly as a single-slot value slot does. */
218
+ export declare function carriesFillers(contA: Uint8Array, fillersA: readonly Uint8Array[], contB: Uint8Array, fillersB: readonly Uint8Array[]): boolean;
60
219
  /** The IN-LIST halo matcher: the best halo-mate for `halo` among EXPLICIT
61
220
  * candidates, above the concept threshold — the list counterpart of
62
221
  * {@link haloSiblings}, which asks the halo INDEX for candidates instead.
@@ -18,16 +18,21 @@
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
  import { addInto, cosine, dot, normalize, zeros } from "../vec.js";
28
- import { conceptThreshold, identityBar, significanceBar } from "../geometry.js";
29
- import { indexOf } from "../bytes.js";
30
- import { leafIdRun } from "./canonical.js";
33
+ import { conceptThreshold, identityBar, significanceBar, } from "../geometry.js";
34
+ import { bytesEqual, indexOf } from "../bytes.js";
35
+ import { chainReach, leafIdRun } from "./canonical.js";
31
36
  import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
32
37
  import { argmaxCosine, chooseAmong, chooseNext, corpusN, edgeAncestors, guidedFirst, hubBound, hubCap, sharedReachMemo, } from "./traverse.js";
33
38
  import { recognise, segment } from "./recognition.js";
@@ -230,6 +235,294 @@ export function alignGraded(ctx, query, contextBytes, querySites) {
230
235
  out.sort((a, b) => a.qs - b.qs);
231
236
  return out;
232
237
  }
238
+ /** Extend a seed match (query offset qo ↔ candidate offset co) to its maximal
239
+ * common run, then walk outward in both directions collecting further common
240
+ * runs of at least W bytes across bounded mismatch gaps (each side ≤
241
+ * chainReach). Returns the matched query spans and the mismatch pairs
242
+ * between consecutive runs.
243
+ *
244
+ * This is the SEEDED aligner, distinct from {@link alignRuns}: that one finds
245
+ * every run two structures share anywhere (a weave), this one reads two
246
+ * streams as ONE structure that diverges in bounded places (a frame with
247
+ * slots).
248
+ *
249
+ * Gaps come back in SWEEP order (right sweep, then left), not query order,
250
+ * and only the INTERIOR ones are reported — a consumer that needs the query's
251
+ * unmatched head or tail derives it from `matched`. Both are the bridge's
252
+ * contract, which prices its edges separately (see its matchStart/matchEnd
253
+ * window test); {@link frameSlots} takes the other reading. */
254
+ export function alignAround(ctx, q, c, qo, co) {
255
+ const W = ctx.space.maxGroup;
256
+ const reachCap = chainReach(W);
257
+ // Maximal run around the seed.
258
+ let qs = qo, ss = co;
259
+ while (qs > 0 && ss > 0 && q[qs - 1] === c[ss - 1]) {
260
+ qs--;
261
+ ss--;
262
+ }
263
+ let qe = qo, se = co;
264
+ while (qe < q.length && se < c.length && q[qe] === c[se]) {
265
+ qe++;
266
+ se++;
267
+ }
268
+ const matched = [[qs, qe]];
269
+ const gaps = [];
270
+ // The next common run of ≥ W bytes past (qi, si), with each side's gap
271
+ // bounded by chainReach; smallest total gap wins (nearest continuation).
272
+ const runLenAt = (qi, si) => {
273
+ let n = 0;
274
+ while (qi + n < q.length && si + n < c.length && q[qi + n] === c[si + n]) {
275
+ n++;
276
+ }
277
+ return n;
278
+ };
279
+ // RIGHT sweep.
280
+ let qi = qe, si = se;
281
+ for (;;) {
282
+ let found = false;
283
+ for (let total = 1; total <= 2 * reachCap && !found; total++) {
284
+ for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
285
+ const gs = total - gq;
286
+ if (gs > reachCap)
287
+ continue;
288
+ if (qi + gq >= q.length || si + gs >= c.length)
289
+ continue;
290
+ const n = runLenAt(qi + gq, si + gs);
291
+ if (n >= W || qi + gq + n === q.length) {
292
+ if (n === 0)
293
+ continue;
294
+ if (gq > 0 || gs > 0) {
295
+ gaps.push({ qs: qi, qe: qi + gq, cs: si, ce: si + gs });
296
+ }
297
+ matched.push([qi + gq, qi + gq + n]);
298
+ qi = qi + gq + n;
299
+ si = si + gs + n;
300
+ found = true;
301
+ break;
302
+ }
303
+ }
304
+ }
305
+ if (!found)
306
+ break;
307
+ }
308
+ // LEFT sweep (mirror).
309
+ qi = qs;
310
+ si = ss;
311
+ for (;;) {
312
+ let found = false;
313
+ for (let total = 1; total <= 2 * reachCap && !found; total++) {
314
+ for (let gq = 0; gq <= Math.min(total, reachCap); gq++) {
315
+ const gs = total - gq;
316
+ if (gs > reachCap)
317
+ continue;
318
+ if (qi - gq <= 0 || si - gs <= 0)
319
+ continue;
320
+ // Run ENDING at (qi - gq, si - gs).
321
+ let n = 0;
322
+ while (n < qi - gq && n < si - gs &&
323
+ q[qi - gq - 1 - n] === c[si - gs - 1 - n]) {
324
+ n++;
325
+ }
326
+ if (n >= W || n === qi - gq) {
327
+ if (n === 0)
328
+ continue;
329
+ if (gq > 0 || gs > 0) {
330
+ gaps.push({ qs: qi - gq, qe: qi, cs: si - gs, ce: si });
331
+ }
332
+ matched.push([qi - gq - n, qi - gq]);
333
+ qi = qi - gq - n;
334
+ si = si - gs - n;
335
+ found = true;
336
+ break;
337
+ }
338
+ }
339
+ }
340
+ if (!found)
341
+ break;
342
+ }
343
+ return { matched, gaps };
344
+ }
345
+ /** Contract a gap to its VARYING CORE: strip the prefix and suffix the two
346
+ * sides share. {@link alignAround} cannot match a shared affix shorter than
347
+ * W, so that affix lands INSIDE the gap — measured, the slot of
348
+ * `How do I compile main.c?` against `…hello.c?` comes back as
349
+ * `main.c?`/`hello.c?`, three bytes of which (`.c?`) both sides hold.
350
+ *
351
+ * Splicing the uncontracted gap carries the query's own punctuation into the
352
+ * answer; worse, it hides what actually VARIES, which is the only thing a
353
+ * cohort can agree about. Returns null when nothing is left on either side —
354
+ * a pure insertion or deletion, which names no slot. */
355
+ export function contractGap(q, c, g) {
356
+ let { qs, qe, cs, ce } = g;
357
+ while (qs < qe && cs < ce && q[qs] === c[cs]) {
358
+ qs++;
359
+ cs++;
360
+ }
361
+ while (qe > qs && ce > cs && q[qe - 1] === c[ce - 1]) {
362
+ qe--;
363
+ ce--;
364
+ }
365
+ return qe > qs && ce > cs ? { qs, qe, cs, ce } : null;
366
+ }
367
+ /** THE SLOT MATCHER: read one query ↔ context pairing as one structure with
368
+ * variable positions.
369
+ *
370
+ * IT REPORTS; IT DOES NOT JUDGE. This returns every gap the aligner found,
371
+ * contracted to its varying core and tagged with its kind, plus the shared
372
+ * coverage — and rejects nothing. That is the whole point of the split, and
373
+ * it was got WRONG first: four VOICING gates (the frame must dominate the
374
+ * query, each slot must reach one window on both sides, an insertion or
375
+ * deletion disqualifies the pairing, fillers must be pairwise distinct) were
376
+ * applied here, and every one of them is a requirement for SUBSTITUTING AND
377
+ * SPEAKING, not for knowing where a pairing varies. With them in place the
378
+ * shared reading was reference-shaped: measured over four real pairings, three
379
+ * were hidden from every consumer —
380
+ *
381
+ * `What is the capital of the country where the Eiffel Tower is?`
382
+ * against `What is the capital of France?` (covered 23/61) HIDDEN
383
+ * `What is the capital of France, really?` (an insertion) HIDDEN
384
+ * `What is the capital of Fran?` (sub-window) HIDDEN
385
+ *
386
+ * — including the case of the one consumer that most obviously needed it. A
387
+ * shared layer with one usable consumer is private code at a public address.
388
+ * Each gate now lives with the mechanism that needs it (see reference.ts).
389
+ *
390
+ * Seeded at the origin, because a frame is shared structure the query and its
391
+ * instances both OPEN with: the maximal run around (0,0) is the frame's head
392
+ * and the sweeps find the rest.
393
+ *
394
+ * Null only for a degenerate pairing (either side empty). */
395
+ export function frameSlots(ctx, query, cand, id) {
396
+ if (query.length === 0 || cand.length === 0)
397
+ return null;
398
+ const { matched, gaps } = alignAround(ctx, query, cand, 0, 0);
399
+ const spans = [...matched].sort((a, b) => a[0] - b[0]);
400
+ // Where the alignment RAN OUT on each side. Seeded at the origin there is
401
+ // no leading gap, so both cursors are everything consumed so far: the
402
+ // matched runs (equal length on both sides by construction) plus what each
403
+ // interior gap ate of its own side. Counting only the runs reads the
404
+ // candidate cursor short by exactly the fillers already seen, and invents a
405
+ // trailing gap on every well-aligned instance.
406
+ const all = [...gaps];
407
+ let qEnd = 0, cEnd = 0;
408
+ for (const [s, e] of spans) {
409
+ cEnd += e - s;
410
+ qEnd = Math.max(qEnd, e);
411
+ }
412
+ for (const g of gaps)
413
+ cEnd += g.ce - g.cs;
414
+ if (qEnd < query.length || cEnd < cand.length) {
415
+ all.push({ qs: qEnd, qe: query.length, cs: cEnd, ce: cand.length });
416
+ }
417
+ const slots = [];
418
+ for (const gap of all.sort((a, b) => a.qs - b.qs)) {
419
+ if (gap.qe <= gap.qs && gap.ce <= gap.cs)
420
+ continue;
421
+ // Contract to the varying core. contractGap returns null when one side is
422
+ // wholly shared with the other — a pure insertion or deletion, which is a
423
+ // real variation and is reported AS ONE, not discarded.
424
+ const core = contractGap(query, cand, gap);
425
+ const g = core ?? gap;
426
+ const kind = g.qe > g.qs && g.ce > g.cs
427
+ ? "substitution"
428
+ : g.qe > g.qs
429
+ ? "insertion"
430
+ : "deletion";
431
+ slots.push({
432
+ qs: g.qs,
433
+ qe: g.qe,
434
+ cs: g.cs,
435
+ ce: g.ce,
436
+ kind,
437
+ filler: cand.slice(g.cs, g.ce),
438
+ });
439
+ }
440
+ const covered = spans.reduce((n, [s, e]) => n + e - s, 0);
441
+ return { id, slots, matched: spans, covered };
442
+ }
443
+ /** Whether every member is byte-distinct from the others. */
444
+ export function distinct(items) {
445
+ for (let i = 0; i < items.length; i++) {
446
+ for (let j = i + 1; j < items.length; j++) {
447
+ if (bytesEqual(items[i], items[j]))
448
+ return false;
449
+ }
450
+ }
451
+ return true;
452
+ }
453
+ /** Substitute every `needle -> repl` pair SIMULTANEOUSLY: one left-to-right
454
+ * pass, longest needle first at each position, and a replacement is never
455
+ * re-examined.
456
+ *
457
+ * SIMULTANEOUS IS NOT A DETAIL. Applying the pairs in sequence lets one
458
+ * substitution's OUTPUT be another's input: with slots `gcc -> zig` and
459
+ * `hello.c -> zig.c` a sequential pass rewrites bytes it had just written,
460
+ * and the result depends on the order the slots happened to be found in.
461
+ * Longest-first at each position makes the pass independent of pair order,
462
+ * which is what keeps {@link carriesFillers} and the binding it licenses the
463
+ * SAME operation — if they could disagree, the licence would not be testing
464
+ * what is voiced. */
465
+ export function substituteAll(hay, pairs) {
466
+ const usable = pairs.filter((p) => p.needle.length > 0);
467
+ if (usable.length === 0)
468
+ return hay;
469
+ // Longest needle first, so a needle that is a prefix of another can never
470
+ // pre-empt it. Ties cannot arise: an instance whose fillers are not
471
+ // pairwise distinct is refused by frameSlots.
472
+ const order = [...usable].sort((a, b) => b.needle.length - a.needle.length);
473
+ const out = [];
474
+ let i = 0;
475
+ let hit = false;
476
+ outer: while (i < hay.length) {
477
+ for (const p of order) {
478
+ if (i + p.needle.length > hay.length)
479
+ continue;
480
+ let k = 0;
481
+ while (k < p.needle.length && hay[i + k] === p.needle[k])
482
+ k++;
483
+ if (k < p.needle.length)
484
+ continue;
485
+ for (const b of p.repl)
486
+ out.push(b);
487
+ i += p.needle.length;
488
+ hit = true;
489
+ continue outer;
490
+ }
491
+ out.push(hay[i]);
492
+ i++;
493
+ }
494
+ return hit ? Uint8Array.from(out) : hay;
495
+ }
496
+ /** THE CARRIAGE LICENCE — the gate that decides whether a slot may be VOICED
497
+ * through. Given two instances of one frame and what each one continues to,
498
+ * it asks one byte question:
499
+ *
500
+ * substituteAll(contA, fillersA -> fillersB) == contB
501
+ *
502
+ * When it holds, the corpus attests byte-exactly that the continuation is a
503
+ * function of the fillers and nothing else, so putting a NEW occupant through
504
+ * the same carriage is derivation rather than invention. No threshold, no
505
+ * similarity, no new constant: the store's own instances decide, exactly as
506
+ * the bridge's `unanimous` decides whether a frame is a value slot.
507
+ *
508
+ * Its FAILURE is what this is really for. A frame whose continuation carries
509
+ * filler-DEPENDENT content — `What is the capital of X?` answering a different
510
+ * city per X — fails it, and that failure is the only thing between a slot
511
+ * and an invented fact. Measured on the trained 15.7M-node store (325,615
512
+ * contexts): `What is the capital of Zamunda?` resonates to a PURE cohort,
513
+ * every one of the top 14 hits an instance of that frame, with an unambiguous
514
+ * slot; every structural gate passes and only this one refuses, on
515
+ * `replace("Tokyo", "Japan" -> "France") != "Paris"`.
516
+ *
517
+ * With SEVERAL slots the test is unchanged, which is the point of testing the
518
+ * whole substitution at once: a frame whose answer tracks one slot but
519
+ * invents around another fails exactly as a single-slot value slot does. */
520
+ export function carriesFillers(contA, fillersA, contB, fillersB) {
521
+ if (fillersA.length !== fillersB.length)
522
+ return false;
523
+ const projected = substituteAll(contA, fillersA.map((needle, s) => ({ needle, repl: fillersB[s] })));
524
+ return bytesEqual(projected, contB);
525
+ }
233
526
  /** The IN-LIST halo matcher: the best halo-mate for `halo` among EXPLICIT
234
527
  * candidates, above the concept threshold — the list counterpart of
235
528
  * {@link haloSiblings}, which asks the halo INDEX for candidates instead.
@@ -0,0 +1,22 @@
1
+ import type { MindContext } from "../types.js";
2
+ import type { PipelineMechanism } from "../pipeline-mechanism.js";
3
+ /** A trained form the query opens, and the bytes by which it continues. */
4
+ export interface PrefixCompletion {
5
+ /** The trained form whose opening the query is — the answer, voiced whole. */
6
+ id: number;
7
+ /** The form's own bytes. The mechanism grounds a FORM, never a slice of
8
+ * one: slicing at the query's end would cut at an offset the geometry has
9
+ * no reason to treat as a boundary. */
10
+ form: Uint8Array;
11
+ /** The bytes past the query — carried for the rationale and for the
12
+ * uniqueness comparison, not voiced on its own. */
13
+ continuation: Uint8Array;
14
+ }
15
+ /** The sole trained form the query opens — or null when no candidate opens with
16
+ * it, when the continuation is sub-quantum, when a candidate's continuation
17
+ * cannot be read through, or when the candidates disagree.
18
+ *
19
+ * `ranked` must be a list the caller has ALREADY fetched; this mechanism never
20
+ * resonates on its own (see the header's cost note). */
21
+ export declare function prefixCompletion(ctx: MindContext, query: Uint8Array, ranked: ReadonlyArray<number>): PrefixCompletion | null;
22
+ export declare const prefixMechanism: PipelineMechanism;