@hviana/sema 0.1.5 → 0.1.6

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 (160) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1687 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +17 -42
  51. package/dist/src/geometry.js +235 -266
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +749 -871
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +126 -185
  82. package/dist/src/mind/mind.d.ts +129 -154
  83. package/dist/src/mind/mind.js +258 -288
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +8 -32
  89. package/dist/src/mind/primitives.js +99 -117
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +183 -208
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +16 -58
  101. package/dist/src/mind/traverse.js +304 -357
  102. package/dist/src/mind/types.d.ts +120 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +169 -166
  117. package/dist/src/store-sqlite.js +762 -662
  118. package/dist/src/store.d.ts +586 -630
  119. package/dist/src/store.js +1423 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +13 -10
  123. package/package.json +1 -1
  124. package/src/alu/README.md +1 -1
  125. package/src/alu/src/index.ts +1 -1
  126. package/src/config.ts +19 -27
  127. package/src/index.ts +6 -11
  128. package/src/rabitq-ivf/README.md +56 -0
  129. package/src/rabitq-ivf/src/database.ts +276 -0
  130. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  131. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  132. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  133. package/src/store-sqlite.ts +196 -9
  134. package/src/store.ts +8 -32
  135. package/test/08-storage.test.mjs +3 -3
  136. package/test/14-scaling.test.mjs +2 -2
  137. package/test/35-ivf.test.mjs +263 -0
  138. package/test/36-bloom.test.mjs +123 -0
  139. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  140. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  141. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  142. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  143. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  144. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  145. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  146. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  147. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  148. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  149. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  150. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  151. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  152. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  153. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  154. package/src/rabitq-hnsw/README.md +0 -303
  155. package/src/rabitq-hnsw/src/database.ts +0 -492
  156. package/src/rabitq-hnsw/src/heap.ts +0 -90
  157. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  158. package/src/rabitq-hnsw/src/store.ts +0 -994
  159. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  160. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -22,41 +22,37 @@
22
22
  /** Decode bytes to text for display, dropping the NUL padding the encoder uses
23
23
  * (the same cleanup {@link Mind.respondText} does for its result). */
24
24
  export function decodeText(bytes) {
25
- return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
25
+ return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
26
26
  }
27
27
  /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
28
28
  * the same union-of-spans reading think's grounding decider prices at PASS
29
29
  * per byte, exposed here so a mechanism can turn it into a human label. */
30
30
  export function unexplainedSpans(queryLen, accounted) {
31
- const sorted = accounted
32
- .map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
33
- .filter(([s, e]) => e > s)
34
- .sort((a, b) => a[0] - b[0]);
35
- const gaps = [];
36
- let reach = 0;
37
- for (const [s, e] of sorted) {
38
- if (s > reach) {
39
- gaps.push([reach, s]);
31
+ const sorted = accounted
32
+ .map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
33
+ .filter(([s, e]) => e > s)
34
+ .sort((a, b) => a[0] - b[0]);
35
+ const gaps = [];
36
+ let reach = 0;
37
+ for (const [s, e] of sorted) {
38
+ if (s > reach)
39
+ gaps.push([reach, s]);
40
+ if (e > reach)
41
+ reach = e;
40
42
  }
41
- if (e > reach) {
42
- reach = e;
43
- }
44
- }
45
- if (reach < queryLen) {
46
- gaps.push([reach, queryLen]);
47
- }
48
- return gaps;
43
+ if (reach < queryLen)
44
+ gaps.push([reach, queryLen]);
45
+ return gaps;
49
46
  }
50
47
  /** A human-readable label for the query bytes a mechanism's `accounted`
51
48
  * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
52
49
  * it never changes a candidate's weight, only what the rationale trace
53
50
  * says the mechanism left on the table. `""` when nothing is unexplained. */
54
51
  export function unexplainedLabel(query, accounted) {
55
- const gaps = unexplainedSpans(query.length, accounted);
56
- if (gaps.length === 0) {
57
- return "";
58
- }
59
- return gaps.map(([s, e]) => decodeText(query.subarray(s, e))).join(" … ");
52
+ const gaps = unexplainedSpans(query.length, accounted);
53
+ if (gaps.length === 0)
54
+ return "";
55
+ return gaps.map(([s, e]) => decodeText(query.subarray(s, e))).join(" … ");
60
56
  }
61
57
  /** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
62
58
  *
@@ -67,104 +63,100 @@ export function unexplainedLabel(query, accounted) {
67
63
  * sub-steps) is the two fused. The tracer never branches the control flow; it
68
64
  * only records it. */
69
65
  export class Rationale {
70
- sink;
71
- next = 0;
72
- /** Open mechanisms, outermost first. Each frame remembers the last child it
73
- * has spawned so the next sibling can default its data-flow edge to it. */
74
- stack = [];
75
- /** The most recent step index emitted under each mechanism name — the handle
76
- * a later step uses to name an EARLIER mechanism as its data-flow producer
77
- * (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
78
- * built per response and inference is sequential, so "most recent" is exactly
79
- * "the one that produced the inputs I am about to consume". */
80
- lastByName = new Map();
81
- constructor(sink) {
82
- this.sink = sink;
83
- }
84
- /** The index of the most recent step with this mechanism name, or undefined if
85
- * none has run. Used to wire an explicit producer edge into {@link
86
- * Scope.done} / {@link step}'s `deps`. */
87
- lastIndex(name) {
88
- return this.lastByName.get(name);
89
- }
90
- /** The mechanism names currently open, outermost → innermost. */
91
- path(leaf) {
92
- const p = this.stack.map((f) => f.name);
93
- p.push(leaf);
94
- return p;
95
- }
96
- /** The default data-flow edge for a step entering now: the previous sibling
97
- * inside the current mechanism, else the enclosing mechanism, else nothing
98
- * (the root). An explicit `deps` overrides this. */
99
- defaultDeps() {
100
- const top = this.stack[this.stack.length - 1];
101
- if (!top) {
102
- return [];
66
+ sink;
67
+ next = 0;
68
+ /** Open mechanisms, outermost first. Each frame remembers the last child it
69
+ * has spawned so the next sibling can default its data-flow edge to it. */
70
+ stack = [];
71
+ /** The most recent step index emitted under each mechanism name — the handle
72
+ * a later step uses to name an EARLIER mechanism as its data-flow producer
73
+ * (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
74
+ * built per response and inference is sequential, so "most recent" is exactly
75
+ * "the one that produced the inputs I am about to consume". */
76
+ lastByName = new Map();
77
+ constructor(sink) {
78
+ this.sink = sink;
79
+ }
80
+ /** The index of the most recent step with this mechanism name, or undefined if
81
+ * none has run. Used to wire an explicit producer edge into {@link
82
+ * Scope.done} / {@link step}'s `deps`. */
83
+ lastIndex(name) {
84
+ return this.lastByName.get(name);
85
+ }
86
+ /** The mechanism names currently open, outermost → innermost. */
87
+ path(leaf) {
88
+ const p = this.stack.map((f) => f.name);
89
+ p.push(leaf);
90
+ return p;
91
+ }
92
+ /** The default data-flow edge for a step entering now: the previous sibling
93
+ * inside the current mechanism, else the enclosing mechanism, else nothing
94
+ * (the root). An explicit `deps` overrides this. */
95
+ defaultDeps() {
96
+ const top = this.stack[this.stack.length - 1];
97
+ if (!top)
98
+ return [];
99
+ return [top.lastChild ?? top.index];
100
+ }
101
+ /** Reserve this step's index and register it as the current mechanism's most
102
+ * recent child (so the NEXT sibling chains to it) and as the most recent step
103
+ * of its own NAME (so a later mechanism can name it as a producer). */
104
+ reserve(name) {
105
+ const index = this.next++;
106
+ const top = this.stack[this.stack.length - 1];
107
+ if (top)
108
+ top.lastChild = index;
109
+ this.lastByName.set(name, index);
110
+ return index;
111
+ }
112
+ emit(index, mechanism, inputs, outputs, deps, note) {
113
+ this.sink({
114
+ index,
115
+ mechanism,
116
+ parent: this.stack.length > 0
117
+ ? this.stack[this.stack.length - 1].index
118
+ : -1,
119
+ dependsOn: deps ?? this.defaultDeps(),
120
+ inputs,
121
+ outputs,
122
+ note,
123
+ });
124
+ }
125
+ /** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
126
+ * now; the matching {@link Scope.done} supplies the outputs when it finishes.
127
+ * `deps` overrides the default data-flow edge (previous sibling / parent). */
128
+ enter(name, inputs, deps) {
129
+ const mechanism = this.path(name);
130
+ const resolvedDeps = deps ?? this.defaultDeps();
131
+ const index = this.reserve(name);
132
+ this.stack.push({ index, name, lastChild: null });
133
+ let closed = false;
134
+ const emit = this.emit.bind(this);
135
+ const pop = () => {
136
+ // Pop down to and including this frame — tolerant of a sub-mechanism that
137
+ // forgot to close, so one missed `done` cannot desync the whole stack.
138
+ const at = this.stack.findIndex((f) => f.index === index);
139
+ if (at >= 0)
140
+ this.stack.length = at;
141
+ };
142
+ return {
143
+ index,
144
+ done: (outputs, note) => {
145
+ if (closed)
146
+ return;
147
+ closed = true;
148
+ pop();
149
+ emit(index, mechanism, inputs, outputs, resolvedDeps, note);
150
+ },
151
+ };
103
152
  }
104
- return [top.lastChild ?? top.index];
105
- }
106
- /** Reserve this step's index and register it as the current mechanism's most
107
- * recent child (so the NEXT sibling chains to it) and as the most recent step
108
- * of its own NAME (so a later mechanism can name it as a producer). */
109
- reserve(name) {
110
- const index = this.next++;
111
- const top = this.stack[this.stack.length - 1];
112
- if (top) {
113
- top.lastChild = index;
153
+ /** Record a mechanism that has no sub-steps — its inputs and outputs are both
154
+ * known at the call site. Returns its index, for a later step to depend on. */
155
+ step(name, inputs, outputs, note, deps) {
156
+ const mechanism = this.path(name);
157
+ const resolvedDeps = deps ?? this.defaultDeps();
158
+ const index = this.reserve(name);
159
+ this.emit(index, mechanism, inputs, outputs, resolvedDeps, note);
160
+ return index;
114
161
  }
115
- this.lastByName.set(name, index);
116
- return index;
117
- }
118
- emit(index, mechanism, inputs, outputs, deps, note) {
119
- this.sink({
120
- index,
121
- mechanism,
122
- parent: this.stack.length > 0
123
- ? this.stack[this.stack.length - 1].index
124
- : -1,
125
- dependsOn: deps ?? this.defaultDeps(),
126
- inputs,
127
- outputs,
128
- note,
129
- });
130
- }
131
- /** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
132
- * now; the matching {@link Scope.done} supplies the outputs when it finishes.
133
- * `deps` overrides the default data-flow edge (previous sibling / parent). */
134
- enter(name, inputs, deps) {
135
- const mechanism = this.path(name);
136
- const resolvedDeps = deps ?? this.defaultDeps();
137
- const index = this.reserve(name);
138
- this.stack.push({ index, name, lastChild: null });
139
- let closed = false;
140
- const emit = this.emit.bind(this);
141
- const pop = () => {
142
- // Pop down to and including this frame — tolerant of a sub-mechanism that
143
- // forgot to close, so one missed `done` cannot desync the whole stack.
144
- const at = this.stack.findIndex((f) => f.index === index);
145
- if (at >= 0) {
146
- this.stack.length = at;
147
- }
148
- };
149
- return {
150
- index,
151
- done: (outputs, note) => {
152
- if (closed) {
153
- return;
154
- }
155
- closed = true;
156
- pop();
157
- emit(index, mechanism, inputs, outputs, resolvedDeps, note);
158
- },
159
- };
160
- }
161
- /** Record a mechanism that has no sub-steps — its inputs and outputs are both
162
- * known at the call site. Returns its index, for a later step to depend on. */
163
- step(name, inputs, outputs, note, deps) {
164
- const mechanism = this.path(name);
165
- const resolvedDeps = deps ?? this.defaultDeps();
166
- const index = this.reserve(name);
167
- this.emit(index, mechanism, inputs, outputs, resolvedDeps, note);
168
- return index;
169
- }
170
162
  }
@@ -7,20 +7,9 @@ import type { Precomputed } from "./pipeline-mechanism.js";
7
7
  * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
8
8
  * response's shared pre-computation — the post-grounding stages read the
9
9
  * same container the mechanisms did. */
10
- export declare function reason(
11
- ctx: MindContext,
12
- query: Uint8Array,
13
- answer: Uint8Array,
14
- preConsumed: ReadonlySet<number>,
15
- pre: Precomputed,
16
- ): Promise<Uint8Array>;
10
+ export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed): Promise<Uint8Array>;
17
11
  /** Fuse independent points of attention into one answer (multi-topic).
18
12
  * When the consensus climb finds more than one dominant point, each
19
13
  * independent point grounds its own answer; they are bridged together
20
14
  * by any learnt connector the graph holds between them. */
21
- export declare function fuseAttention(
22
- ctx: MindContext,
23
- query: Uint8Array,
24
- primary: Uint8Array,
25
- pre: Precomputed,
26
- ): Promise<Uint8Array>;
15
+ export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed): Promise<Uint8Array>;
@@ -16,183 +16,146 @@ import { joinWithBridge, pivotInto } from "./resonance.js";
16
16
  * response's shared pre-computation — the post-grounding stages read the
17
17
  * same container the mechanisms did. */
18
18
  export async function reason(ctx, query, answer, preConsumed, pre) {
19
- // Echo guard: a query that is ITSELF a learnt continuation (some context's
20
- // answer) is being asked back at the system — hopping forward from it would
21
- // chain through the very fact that produced it and echo the conversation
22
- // back. The grounded answer alone is the honest read-out. Deliberately a
23
- // broad structural gate; pinned by test/31-audit.
24
- const qId = pre.queryResolved;
25
- if (qId !== null && ctx.store.prevCount(qId) > 0) {
26
- return answer;
27
- }
28
- const consumed = new Set();
29
- // Consume a node and its neighbours for pivot-cycle prevention CAPPED at
30
- // the hub bound, via the store's LIMITed edge reads: a common continuation's
31
- // reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
32
- // no per-hop operation may grow with the corpus. The cap follows the one
33
- // convention every fan-out decision uses (first √N in the relation's own
34
- // read order); a pivot suppressed only by a beyond-cap neighbour may now
35
- // fire the same visibility trade chooseNext documents.
36
- const hubBound = Math.ceil(Math.sqrt(corpusN(ctx)));
37
- const consumeNode = (id) => {
38
- if (id === null) {
39
- return;
19
+ // Echo guard: a query that is ITSELF a learnt continuation (some context's
20
+ // answer) is being asked back at the system — hopping forward from it would
21
+ // chain through the very fact that produced it and echo the conversation
22
+ // back. The grounded answer alone is the honest read-out. Deliberately a
23
+ // broad structural gate; pinned by test/31-audit.
24
+ const qId = pre.queryResolved;
25
+ if (qId !== null && ctx.store.prevCount(qId) > 0)
26
+ return answer;
27
+ const consumed = new Set();
28
+ // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
29
+ // the hub bound, via the store's LIMITed edge reads: a common continuation's
30
+ // reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
31
+ // no per-hop operation may grow with the corpus. The cap follows the one
32
+ // convention every fan-out decision uses (first √N in the relation's own
33
+ // read order); a pivot suppressed only by a beyond-cap neighbour may now
34
+ // fire the same visibility trade chooseNext documents.
35
+ const hubBound = Math.ceil(Math.sqrt(corpusN(ctx)));
36
+ const consumeNode = (id) => {
37
+ if (id === null)
38
+ return;
39
+ consumed.add(id);
40
+ for (const p of ctx.store.prevFirst(id, hubBound))
41
+ consumed.add(p);
42
+ };
43
+ const consumeAll = (id) => {
44
+ if (id === null)
45
+ return;
46
+ consumeNode(id);
47
+ for (const n of ctx.store.nextFirst(id, hubBound))
48
+ consumed.add(n);
49
+ };
50
+ // Pre-consume whatever the grounding stage already spoke for. The halo
51
+ // sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
52
+ // grounding can pre-consume one node per recognised site, O(query length));
53
+ // nodes past the cap are still consumed directly, they just skip the
54
+ // synonym expansion.
55
+ let haloSweeps = 0;
56
+ for (const id of preConsumed) {
57
+ consumeNode(id);
58
+ if (haloSweeps >= ctx.cfg.haloQueryK)
59
+ continue;
60
+ const h = ctx.store.halo(id);
61
+ if (!h)
62
+ continue;
63
+ haloSweeps++;
64
+ for (const sib of await haloSiblings(ctx, id, h))
65
+ consumeNode(sib.id);
40
66
  }
41
- consumed.add(id);
42
- for (const p of ctx.store.prevFirst(id, hubBound)) {
43
- consumed.add(p);
44
- }
45
- };
46
- const consumeAll = (id) => {
47
- if (id === null) {
48
- return;
49
- }
50
- consumeNode(id);
51
- for (const n of ctx.store.nextFirst(id, hubBound)) {
52
- consumed.add(n);
53
- }
54
- };
55
- // Pre-consume whatever the grounding stage already spoke for. The halo
56
- // sweep is one ANN query per node cap it at haloQueryK sweeps (cover
57
- // grounding can pre-consume one node per recognised site, O(query length));
58
- // nodes past the cap are still consumed directly, they just skip the
59
- // synonym expansion.
60
- let haloSweeps = 0;
61
- for (const id of preConsumed) {
62
- consumeNode(id);
63
- if (haloSweeps >= ctx.cfg.haloQueryK) {
64
- continue;
65
- }
66
- const h = ctx.store.halo(id);
67
- if (!h) {
68
- continue;
69
- }
70
- haloSweeps++;
71
- for (const sib of await haloSiblings(ctx, id, h)) {
72
- consumeNode(sib.id);
73
- }
74
- }
75
- let cur = answer;
76
- const qv = pre.guide; // the response-wide guide IS the query's gist
77
- let t;
78
- const startedFrom = answer;
79
- for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
80
- const curId = resolve(ctx, cur);
81
- consumeNode(curId);
82
- // Forward-absorb: follow only UNCONSUMED continuations. The gate below
83
- // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
84
- // nothing of `consumed` and may still walk to a consumed fixpoint —
85
- // absorbing it would repeat content the grounding stage already spoke
86
- // for, so a consumed fixpoint falls through to the pivot step instead.
87
- if (
88
- curId !== null &&
89
- ctx.store.nextFirst(curId, hubBound).some((n) => !consumed.has(n))
90
- ) {
91
- const fwd = await follow(ctx, curId, qv);
92
- const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
93
- if (
94
- fwd !== null && !bytesEqual(fwd, cur) &&
95
- (fwdId === null || !consumed.has(fwdId))
96
- ) {
67
+ let cur = answer;
68
+ const qv = pre.guide; // the response-wide guide IS the query's gist
69
+ let t;
70
+ const startedFrom = answer;
71
+ for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
72
+ const curId = resolve(ctx, cur);
73
+ consumeNode(curId);
74
+ // Forward-absorb: follow only UNCONSUMED continuations. The gate below
75
+ // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
76
+ // nothing of `consumed` and may still walk to a consumed fixpoint —
77
+ // absorbing it would repeat content the grounding stage already spoke
78
+ // for, so a consumed fixpoint falls through to the pivot step instead.
79
+ if (curId !== null &&
80
+ ctx.store.nextFirst(curId, hubBound).some((n) => !consumed.has(n))) {
81
+ const fwd = await follow(ctx, curId, qv);
82
+ const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
83
+ if (fwd !== null && !bytesEqual(fwd, cur) &&
84
+ (fwdId === null || !consumed.has(fwdId))) {
85
+ consumeAll(curId);
86
+ t ??= ctx.trace?.enter("reason", [
87
+ rItem(startedFrom, "grounded"),
88
+ ]);
89
+ ctx.trace?.step("absorbForward", [rItem(cur, "answer", curId)], [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
90
+ cur = fwd;
91
+ continue;
92
+ }
93
+ }
94
+ // Pivot: find the longest unconsumed learnt context the answer contains.
97
95
  consumeAll(curId);
98
- t ??= ctx.trace?.enter("reason", [
99
- rItem(startedFrom, "grounded"),
100
- ]);
101
- ctx.trace?.step(
102
- "absorbForward",
103
- [rItem(cur, "answer", curId)],
104
- [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)],
105
- "the answer is itself a learnt fact — follow its continuation to the fixpoint",
106
- );
107
- cur = fwd;
108
- continue;
109
- }
110
- }
111
- // Pivot: find the longest unconsumed learnt context the answer contains.
112
- consumeAll(curId);
113
- const pivot = await pivotInto(ctx, cur, consumed);
114
- if (pivot === null) {
115
- break;
96
+ const pivot = await pivotInto(ctx, cur, consumed);
97
+ if (pivot === null)
98
+ break;
99
+ const fc = await follow(ctx, pivot, qv);
100
+ consumeAll(pivot);
101
+ if (fc === null || bytesEqual(fc, cur))
102
+ break;
103
+ t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
104
+ ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
105
+ cur = fc;
116
106
  }
117
- const fc = await follow(ctx, pivot, qv);
118
- consumeAll(pivot);
119
- if (fc === null || bytesEqual(fc, cur)) {
120
- break;
121
- }
122
- t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
123
- ctx.trace?.step(
124
- "pivotStep",
125
- [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")],
126
- [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)],
127
- "pivot on the shared span this answer contains, then step forward across that fact",
128
- );
129
- cur = fc;
130
- }
131
- t?.done(
132
- [rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
133
- "the multi-hop chain's fixpoint",
134
- );
135
- return cur;
107
+ t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)], "the multi-hop chain's fixpoint");
108
+ return cur;
136
109
  }
137
110
  /** Fuse independent points of attention into one answer (multi-topic).
138
111
  * When the consensus climb finds more than one dominant point, each
139
112
  * independent point grounds its own answer; they are bridged together
140
113
  * by any learnt connector the graph holds between them. */
141
114
  export async function fuseAttention(ctx, query, primary, pre) {
142
- // When the answer is structurally drawn from the query itself
143
- // (extraction), it already spans all the query's pieces — fusion
144
- // would only add noise from unrelated stored contexts. The gate is
145
- // STRICT containment (resolved node in the query's tree, or a contiguous
146
- // byte run): the old sparse-subsequence test was trivially satisfied by
147
- // short answers over long queries, silently starving multi-topic queries
148
- // of fusion.
149
- if (containsSpan(ctx, query, primary)) {
150
- return primary;
151
- }
152
- // The committed points of attention ARE the shared climb's roots (same
153
- // query, same k, same DF mode) read them from Precomputed instead of
154
- // re-climbing, so even a traced response pays for the climb once.
155
- const forest = (await pre.attention()).roots;
156
- if (forest.length <= 1) {
157
- return primary;
158
- }
159
- const pieces = [
160
- { start: forest[0].start, bytes: primary },
161
- ];
162
- const qv = pre.guide; // once, not per root
163
- const t = ctx.trace?.enter("fuseAttention", [
164
- rItem(primary, "primary"),
165
- ...forest.slice(1).map((r) => rNode(ctx, r.anchor, "point", r.vote)),
166
- ]);
167
- for (const root of forest.slice(1)) {
168
- const g = await project(ctx, root.anchor, qv);
169
- if (g === null || g.length === 0) {
170
- continue;
115
+ // When the answer is structurally drawn from the query itself
116
+ // (extraction), it already spans all the query's pieces — fusion
117
+ // would only add noise from unrelated stored contexts. The gate is
118
+ // STRICT containment (resolved node in the query's tree, or a contiguous
119
+ // byte run): the old sparse-subsequence test was trivially satisfied by
120
+ // short answers over long queries, silently starving multi-topic queries
121
+ // of fusion.
122
+ if (containsSpan(ctx, query, primary))
123
+ return primary;
124
+ // The committed points of attention ARE the shared climb's roots (same
125
+ // query, same k, same DF mode) read them from Precomputed instead of
126
+ // re-climbing, so even a traced response pays for the climb once.
127
+ const forest = (await pre.attention()).roots;
128
+ if (forest.length <= 1)
129
+ return primary;
130
+ const pieces = [
131
+ { start: forest[0].start, bytes: primary },
132
+ ];
133
+ const qv = pre.guide; // once, not per root
134
+ const t = ctx.trace?.enter("fuseAttention", [
135
+ rItem(primary, "primary"),
136
+ ...forest.slice(1).map((r) => rNode(ctx, r.anchor, "point", r.vote)),
137
+ ]);
138
+ for (const root of forest.slice(1)) {
139
+ const g = await project(ctx, root.anchor, qv);
140
+ if (g === null || g.length === 0)
141
+ continue;
142
+ if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0))
143
+ continue;
144
+ pieces.push({ start: root.start, bytes: g });
145
+ }
146
+ if (pieces.length === 1) {
147
+ t?.done([rItem(primary, "answer")], "no further independent point grounded");
148
+ return primary;
171
149
  }
172
- if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0)) {
173
- continue;
150
+ pieces.sort((a, b) => a.start - b.start);
151
+ let out = pieces[0].bytes;
152
+ for (let i = 1; i < pieces.length; i++) {
153
+ // An approximate-resonance miss (or a genuinely unlearnt junction) joins
154
+ // the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
155
+ out = await joinWithBridge(ctx, out, pieces[i].bytes);
174
156
  }
175
- pieces.push({ start: root.start, bytes: g });
176
- }
177
- if (pieces.length === 1) {
178
- t?.done(
179
- [rItem(primary, "answer")],
180
- "no further independent point grounded",
181
- );
182
- return primary;
183
- }
184
- pieces.sort((a, b) => a.start - b.start);
185
- let out = pieces[0].bytes;
186
- for (let i = 1; i < pieces.length; i++) {
187
- // An approximate-resonance miss (or a genuinely unlearnt junction) joins
188
- // the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
189
- out = await joinWithBridge(ctx, out, pieces[i].bytes);
190
- }
191
- t?.done(
192
- [rItem(out, "answer", resolve(ctx, out) ?? undefined)],
193
- `fused ${pieces.length} independent points of attention into one answer`,
194
- );
195
- return out;
157
+ t?.done([rItem(out, "answer", resolve(ctx, out) ?? undefined)], `fused ${pieces.length} independent points of attention into one answer`);
158
+ return out;
196
159
  }
197
160
  // (resonance.js is already a static dependency above — `bridge` — so the old
198
161
  // dynamic import of pivotInto guarded against a cycle that does not exist.)
@@ -11,10 +11,7 @@ import type { MindContext, Recognition, Segment } from "./types.js";
11
11
  * query's own cut cannot, and records sub-leaf boundaries as `splits`.
12
12
  *
13
13
  * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
14
- export declare function recognise(
15
- ctx: MindContext,
16
- bytes: Uint8Array,
17
- ): Recognition;
14
+ export declare function recognise(ctx: MindContext, bytes: Uint8Array): Recognition;
18
15
  /** Segment bytes using the geometry's own groupings — leaf-parent
19
16
  * nodes from the perceived tree, with consecutive bare leaves merged
20
17
  * into one segment. Each segment's gist is perceived from its bytes