@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
@@ -11,156 +11,156 @@ export type Input = string | Uint8Array | Grid | Grid[];
11
11
  /** The host capabilities GraphSearch consults during a cover. MindContext
12
12
  * extends this so the Mind can pass itself as the host. */
13
13
  export interface GraphSearchHost {
14
- resolve(bytes: Uint8Array): number | null;
15
- recogniseSpan?(bytes: Uint8Array): {
16
- sites: ReadonlyArray<Site>;
17
- leaves: ReadonlyArray<Leaf>;
18
- splits: ReadonlySet<number>;
19
- };
20
- chooseNext?(node: number): number | undefined;
14
+ resolve(bytes: Uint8Array): number | null;
15
+ recogniseSpan?(bytes: Uint8Array): {
16
+ sites: ReadonlyArray<Site>;
17
+ leaves: ReadonlyArray<Leaf>;
18
+ splits: ReadonlySet<number>;
19
+ };
20
+ chooseNext?(node: number): number | undefined;
21
21
  }
22
22
  export interface Recognition {
23
- /** Forms that can lead somewhere — they have an edge or a halo. */
24
- sites: Site[];
25
- /** The query's perceived leaves (the search's covering axioms). */
26
- leaves: Leaf[];
27
- /** Sub-leaf positions where a form boundary falls between leaf edges. */
28
- splits: Set<number>;
23
+ /** Forms that can lead somewhere — they have an edge or a halo. */
24
+ sites: Site[];
25
+ /** The query's perceived leaves (the search's covering axioms). */
26
+ leaves: Leaf[];
27
+ /** Sub-leaf positions where a form boundary falls between leaf edges. */
28
+ splits: Set<number>;
29
29
  }
30
30
  /** How the consensus climb weights a region's Document-Frequency reach. */
31
31
  export type DFMode = "inverse" | "direct" | "combined";
32
32
  /** One POINT OF ATTENTION the consensus climb resolved. */
33
33
  export interface Attention {
34
- /** The learnt context this point resolves to. */
35
- anchor: number;
36
- /** IDF-weighted consensus vote — the strength that orders points. */
37
- vote: number;
38
- /** The union of the query byte-spans whose evidence supports this point. */
39
- start: number;
40
- end: number;
34
+ /** The learnt context this point resolves to. */
35
+ anchor: number;
36
+ /** IDF-weighted consensus vote — the strength that orders points. */
37
+ vote: number;
38
+ /** The union of the query byte-spans whose evidence supports this point. */
39
+ start: number;
40
+ end: number;
41
41
  }
42
42
  /** Both read-outs of one consensus climb. */
43
43
  export interface AttentionRead {
44
- roots: Attention[];
45
- ranked: Attention[];
44
+ roots: Attention[];
45
+ ranked: Attention[];
46
46
  }
47
47
  /** A positioned region of a byte stream paired with its gist. */
48
48
  export interface Segment {
49
- start: number;
50
- end: number;
51
- v: Vec;
49
+ start: number;
50
+ end: number;
51
+ v: Vec;
52
52
  }
53
53
  /** A region of the query's perceived tree for the consensus climb. */
54
54
  export interface Region {
55
- v: Vec;
56
- start: number;
57
- end: number;
58
- chunk: boolean;
59
- /** Whether the region's bytes resolve to a KNOWN node (content-addressed,
60
- * exact). Exact regions vote with full weight; approximate ones pay the
61
- * contrastive margin (see voteRegions) — under the linear fold a raw
62
- * resonance score is byte-overlap, evidence only in excess of its best
63
- * rival conclusion. */
64
- known: boolean;
55
+ v: Vec;
56
+ start: number;
57
+ end: number;
58
+ chunk: boolean;
59
+ /** Whether the region's bytes resolve to a KNOWN node (content-addressed,
60
+ * exact). Exact regions vote with full weight; approximate ones pay the
61
+ * contrastive margin (see voteRegions) — under the linear fold a raw
62
+ * resonance score is byte-overlap, evidence only in excess of its best
63
+ * rival conclusion. */
64
+ known: boolean;
65
65
  }
66
66
  /** Per-region vote data from the consensus climb's resonance pass. */
67
67
  export interface RegionVote {
68
- start: number;
69
- end: number;
70
- canonicalFailed: boolean;
71
- roots: readonly number[];
72
- w: number;
73
- wFocus: number;
68
+ start: number;
69
+ end: number;
70
+ canonicalFailed: boolean;
71
+ roots: readonly number[];
72
+ w: number;
73
+ wFocus: number;
74
74
  }
75
75
  /** The edge-bearing contexts reached by climbing from a node, plus saturation info. */
76
76
  export interface AncestorReach {
77
- roots: number[];
78
- contextsReached: number;
79
- saturated: boolean;
77
+ roots: number[];
78
+ contextsReached: number;
79
+ saturated: boolean;
80
80
  }
81
81
  /** Saturated-interval information for the noise-drop gate. */
82
82
  export interface SaturationInfo {
83
- leadingEnd: number;
84
- hasLeading: boolean;
85
- intervals: Array<{
86
- start: number;
87
- end: number;
88
- }>;
83
+ leadingEnd: number;
84
+ hasLeading: boolean;
85
+ intervals: Array<{
86
+ start: number;
87
+ end: number;
88
+ }>;
89
89
  }
90
90
  /** The items of poolVotes' deduction system. */
91
91
  export type AItem = {
92
- kind: "region";
93
- ri: number;
92
+ kind: "region";
93
+ ri: number;
94
94
  } | {
95
- kind: "anchor";
96
- id: number;
95
+ kind: "anchor";
96
+ id: number;
97
97
  } | {
98
- kind: "anchorFocus";
99
- id: number;
98
+ kind: "anchorFocus";
99
+ id: number;
100
100
  };
101
101
  export interface MindContext extends GraphSearchHost {
102
- store: Store;
103
- space: Space;
104
- alphabet: Alphabet;
105
- cfg: MindConfig;
106
- search: GraphSearch;
107
- trace: Rationale | null;
108
- climbMemo: WeakMap<Uint8Array, Map<string, AttentionRead>> | null;
109
- /** Per-response memo of {@link recognise} keyed by the byte-array OBJECT
110
- * (think, articulate, and the post-grounding pre-consume all recognise the
111
- * same query/answer objects). Valid because the store is read-only while
112
- * a response is in flight; bypassed when a trace is attached so every
113
- * recognise still emits its rationale step. Null outside respond(). */
114
- recogniseMemo: WeakMap<Uint8Array, Recognition> | null;
115
- /** Per-response memo of {@link perceive} keyed by the byte-array OBJECT —
116
- * the GENERAL memo the result-level ones (recogniseMemo, climbMemo,
117
- * _gistCache) each partially compensate for: resolve(), gistOf(), and
118
- * every mechanism's re-perception of the same query/answer object hit it
119
- * (a reason hop used to fold the same answer three times). Valid because
120
- * the store is read-only while a response is in flight and perception is
121
- * a pure function of bytes; only inference-shaped calls (plain Uint8Array,
122
- * no leafAt/lookup capabilities) are memoised, so the deposit path never
123
- * sees it. Keyed by CONTENT (latin1 of the bytes), not object identity —
124
- * mechanisms materialise the same span in fresh subarrays constantly
125
- * (measured on a trained store: 46% of one response's perceptions were
126
- * byte-identical repeats an identity key missed). NOT bypassed under
127
- * trace — perception emits no rationale steps, so there is nothing a memo
128
- * hit could swallow. Null outside respond(). */
129
- perceiveMemo: Map<string, Sema> | null;
130
- _edgeGuide: Vec | null;
131
- _edgeChoice: Map<number, number>;
132
- _prevSeen: Set<number> | null;
133
- /** Session cache of node-id → perceived gist, for candidate scoring
134
- * ({@link chooseAmong} in the reverse projection's recall path re-gists up to
135
- * √N contexts per pick — the measured bottleneck there). `chooseNext` does
136
- * NOT use this cache; forward-edge disambiguation uses prevOf counts
137
- * (distributional evidence) instead of gist comparison, because for short
138
- * answer candidates the gist is dominated by accidental byte-pattern
139
- * correlations. A node's bytes are immutable and perception is a pure
140
- * function of bytes, so an entry stays valid for the store's lifetime —
141
- * never invalidated. Bounded LRU (byte-sized); a miss only re-perceives,
142
- * never a correctness risk. */
143
- _gistCache: BoundedMap<number, Vec>;
144
- /** DEPOSIT-path stable-prefix tree cache: content key (latin1) of a
145
- * deposited input → its plain-fold level PYRAMID, so the NEXT deposit
146
- * whose prefix it is (a conversation's accumulated context) reuses every
147
- * full aligned block and refolds only the right edge of each level —
148
- * O(turn) per deposit instead of O(context), with the tree BIT-IDENTICAL
149
- * to a from-scratch fold (see FoldPyramid). Purely a performance cache.
150
- * Entry-count bounded (a pyramid is ~KB/byte of content; only the few
151
- * live conversation chains matter). */
152
- _depositTrees: BoundedMap<string, FoldPyramid>;
153
- /** The byte lengths present in {@link _depositTrees} — the candidate
154
- * prefix lengths probed (longest first). Drifts on eviction (a stale
155
- * length only costs a miss); cleared with the map when it outgrows the
156
- * probe budget. */
157
- _depositLens: Set<number>;
158
- /** Mind-lifetime intern memo by NODE IDENTITY: perceived-tree node → its
159
- * content-addressed id. Valid forever (ids are permanent, Sema nodes
160
- * immutable); WeakMap, so entries live exactly as long as the pyramid
161
- * cache keeps the shared subtrees alive. Lets internTreeIds skip whole
162
- * shared subtrees and indexSubSpans keep its seenBefore window skip. */
163
- _internIds: WeakMap<Sema, number>;
102
+ store: Store;
103
+ space: Space;
104
+ alphabet: Alphabet;
105
+ cfg: MindConfig;
106
+ search: GraphSearch;
107
+ trace: Rationale | null;
108
+ climbMemo: WeakMap<Uint8Array, Map<string, AttentionRead>> | null;
109
+ /** Per-response memo of {@link recognise} keyed by the byte-array OBJECT
110
+ * (think, articulate, and the post-grounding pre-consume all recognise the
111
+ * same query/answer objects). Valid because the store is read-only while
112
+ * a response is in flight; bypassed when a trace is attached so every
113
+ * recognise still emits its rationale step. Null outside respond(). */
114
+ recogniseMemo: WeakMap<Uint8Array, Recognition> | null;
115
+ /** Per-response memo of {@link perceive} keyed by the byte-array OBJECT —
116
+ * the GENERAL memo the result-level ones (recogniseMemo, climbMemo,
117
+ * _gistCache) each partially compensate for: resolve(), gistOf(), and
118
+ * every mechanism's re-perception of the same query/answer object hit it
119
+ * (a reason hop used to fold the same answer three times). Valid because
120
+ * the store is read-only while a response is in flight and perception is
121
+ * a pure function of bytes; only inference-shaped calls (plain Uint8Array,
122
+ * no leafAt/lookup capabilities) are memoised, so the deposit path never
123
+ * sees it. Keyed by CONTENT (latin1 of the bytes), not object identity —
124
+ * mechanisms materialise the same span in fresh subarrays constantly
125
+ * (measured on a trained store: 46% of one response's perceptions were
126
+ * byte-identical repeats an identity key missed). NOT bypassed under
127
+ * trace — perception emits no rationale steps, so there is nothing a memo
128
+ * hit could swallow. Null outside respond(). */
129
+ perceiveMemo: Map<string, Sema> | null;
130
+ _edgeGuide: Vec | null;
131
+ _edgeChoice: Map<number, number>;
132
+ _prevSeen: Set<number> | null;
133
+ /** Session cache of node-id → perceived gist, for candidate scoring
134
+ * ({@link chooseAmong} in the reverse projection's recall path re-gists up to
135
+ * √N contexts per pick — the measured bottleneck there). `chooseNext` does
136
+ * NOT use this cache; forward-edge disambiguation uses prevOf counts
137
+ * (distributional evidence) instead of gist comparison, because for short
138
+ * answer candidates the gist is dominated by accidental byte-pattern
139
+ * correlations. A node's bytes are immutable and perception is a pure
140
+ * function of bytes, so an entry stays valid for the store's lifetime —
141
+ * never invalidated. Bounded LRU (byte-sized); a miss only re-perceives,
142
+ * never a correctness risk. */
143
+ _gistCache: BoundedMap<number, Vec>;
144
+ /** DEPOSIT-path stable-prefix tree cache: content key (latin1) of a
145
+ * deposited input → its plain-fold level PYRAMID, so the NEXT deposit
146
+ * whose prefix it is (a conversation's accumulated context) reuses every
147
+ * full aligned block and refolds only the right edge of each level —
148
+ * O(turn) per deposit instead of O(context), with the tree BIT-IDENTICAL
149
+ * to a from-scratch fold (see FoldPyramid). Purely a performance cache.
150
+ * Entry-count bounded (a pyramid is ~KB/byte of content; only the few
151
+ * live conversation chains matter). */
152
+ _depositTrees: BoundedMap<string, FoldPyramid>;
153
+ /** The byte lengths present in {@link _depositTrees} — the candidate
154
+ * prefix lengths probed (longest first). Drifts on eviction (a stale
155
+ * length only costs a miss); cleared with the map when it outgrows the
156
+ * probe budget. */
157
+ _depositLens: Set<number>;
158
+ /** Mind-lifetime intern memo by NODE IDENTITY: perceived-tree node → its
159
+ * content-addressed id. Valid forever (ids are permanent, Sema nodes
160
+ * immutable); WeakMap, so entries live exactly as long as the pyramid
161
+ * cache keeps the shared subtrees alive. Lets internTreeIds skip whole
162
+ * shared subtrees and indexSubSpans keep its seenBefore window skip. */
163
+ _internIds: WeakMap<Sema, number>;
164
164
  }
165
165
  /** Read a whole node's bytes. */
166
166
  export declare const ALL = 2147483647;
@@ -168,14 +168,7 @@ export declare const ALL = 2147483647;
168
168
  export declare function spliceAll(segs: Seg[]): Uint8Array | null;
169
169
  /** Lift the answer out of the cover for think: the recognised region, free of
170
170
  * the asker's surrounding (unrecognised) framing. */
171
- export declare function liftAnswer(
172
- segs: Seg[],
173
- queryLen: number,
174
- ): Uint8Array | null;
171
+ export declare function liftAnswer(segs: Seg[], queryLen: number): Uint8Array | null;
175
172
  /** The CHANGED NODES of a freshly-perceived `tree` against the node ids a previous
176
173
  * tracked deposit interned (`prevSeen`). */
177
- export declare function changedNodes(
178
- tree: Sema,
179
- ids: Map<Sema, number>,
180
- prevSeen: Set<number>,
181
- ): Sema[];
174
+ export declare function changedNodes(tree: Sema, ids: Map<Sema, number>, prevSeen: Set<number>): Sema[];
@@ -11,83 +11,74 @@ import { dominates } from "../geometry.js";
11
11
  export const ALL = 0x7fffffff;
12
12
  /** Splice every chosen span in order — the whole cover as one byte string. */
13
13
  export function spliceAll(segs) {
14
- if (!segs.some((s) => s.rec)) {
15
- return null;
16
- }
17
- return concatBytes(segs.map((s) => s.bytes));
14
+ if (!segs.some((s) => s.rec))
15
+ return null;
16
+ return concatBytes(segs.map((s) => s.bytes));
18
17
  }
19
18
  /** Lift the answer out of the cover for think: the recognised region, free of
20
19
  * the asker's surrounding (unrecognised) framing. */
21
20
  export function liftAnswer(segs, queryLen) {
22
- const recognised = [];
23
- for (let k = 0; k < segs.length; k++) {
24
- if (segs[k].rec) {
25
- recognised.push(k);
21
+ const recognised = [];
22
+ for (let k = 0; k < segs.length; k++)
23
+ if (segs[k].rec)
24
+ recognised.push(k);
25
+ if (recognised.length === 0)
26
+ return null;
27
+ if (recognised.length === 1) {
28
+ const s = segs[recognised[0]];
29
+ if (dominates(s.j - s.i, queryLen)) {
30
+ return concatBytes(segs.map((x) => x.bytes));
31
+ }
32
+ return s.bytes;
26
33
  }
27
- }
28
- if (recognised.length === 0) {
29
- return null;
30
- }
31
- if (recognised.length === 1) {
32
- const s = segs[recognised[0]];
33
- if (dominates(s.j - s.i, queryLen)) {
34
- return concatBytes(segs.map((x) => x.bytes));
35
- }
36
- return s.bytes;
37
- }
38
- const lo = recognised[0];
39
- const hi = recognised[recognised.length - 1];
40
- return concatBytes(segs.slice(lo, hi + 1).map((x) => x.bytes));
34
+ const lo = recognised[0];
35
+ const hi = recognised[recognised.length - 1];
36
+ return concatBytes(segs.slice(lo, hi + 1).map((x) => x.bytes));
41
37
  }
42
38
  /** The CHANGED NODES of a freshly-perceived `tree` against the node ids a previous
43
39
  * tracked deposit interned (`prevSeen`). */
44
40
  export function changedNodes(tree, ids, prevSeen) {
45
- const newCount = new Map();
46
- const count = (n) => {
47
- const memo = newCount.get(n);
48
- if (memo !== undefined) {
49
- return memo;
50
- }
51
- const id = ids.get(n);
52
- // PRUNE: a node whose id the previous deposit already interned is old,
53
- // and content addressing makes that transitive the same id names the
54
- // same content, so every descendant was interned then too. The whole
55
- // subtree counts 0 without walking it; with the pyramid fold sharing a
56
- // conversation's prefix subtree, this is what keeps the changed-nodes
57
- // read O(new nodes) instead of O(context). (A node internTreeIds
58
- // memo-skipped has an id here exactly when it is such a shared root.)
59
- if (id !== undefined && prevSeen.has(id)) {
60
- newCount.set(n, 0);
61
- return 0;
62
- }
63
- let c = 1; // reachable only when NOT pruned above ⇒ this node is new
64
- if (n.kids) {
65
- for (const k of n.kids) {
66
- c += count(k);
67
- }
68
- }
69
- newCount.set(n, c);
70
- return c;
71
- };
72
- const total = count(tree);
73
- if (total === 0) {
74
- return [tree];
75
- }
76
- let n = tree;
77
- for (;;) {
78
- if (n.kids === null) {
79
- return [n];
80
- }
81
- let holder = null;
82
- for (const k of n.kids) {
83
- if (newCount.get(k) === total) {
84
- holder = k;
85
- break;
86
- }
87
- }
88
- if (holder === null) {
89
- return [n];
41
+ const newCount = new Map();
42
+ const count = (n) => {
43
+ const memo = newCount.get(n);
44
+ if (memo !== undefined)
45
+ return memo;
46
+ const id = ids.get(n);
47
+ // PRUNE: a node whose id the previous deposit already interned is old,
48
+ // and content addressing makes that transitive the same id names the
49
+ // same content, so every descendant was interned then too. The whole
50
+ // subtree counts 0 without walking it; with the pyramid fold sharing a
51
+ // conversation's prefix subtree, this is what keeps the changed-nodes
52
+ // read O(new nodes) instead of O(context). (A node internTreeIds
53
+ // memo-skipped has an id here exactly when it is such a shared root.)
54
+ if (id !== undefined && prevSeen.has(id)) {
55
+ newCount.set(n, 0);
56
+ return 0;
57
+ }
58
+ let c = 1; // reachable only when NOT pruned above ⇒ this node is new
59
+ if (n.kids) {
60
+ for (const k of n.kids)
61
+ c += count(k);
62
+ }
63
+ newCount.set(n, c);
64
+ return c;
65
+ };
66
+ const total = count(tree);
67
+ if (total === 0)
68
+ return [tree];
69
+ let n = tree;
70
+ for (;;) {
71
+ if (n.kids === null)
72
+ return [n];
73
+ let holder = null;
74
+ for (const k of n.kids) {
75
+ if (newCount.get(k) === total) {
76
+ holder = k;
77
+ break;
78
+ }
79
+ }
80
+ if (holder === null)
81
+ return [n];
82
+ n = holder;
90
83
  }
91
- n = holder;
92
- }
93
84
  }
@@ -0,0 +1,113 @@
1
+ /** External ids are integers only. */
2
+ export type ExternalId = number;
3
+ export interface DatabaseOptions {
4
+ /** Path to the SQLite database file (use ":memory:" for a transient store). */
5
+ dbPath: string;
6
+ /** Vector dimensionality. Required for a new database; ignored when reopening. */
7
+ dim?: number;
8
+ /** Query breadth: clusters probed per query = ceil(efSearch / 4). Default
9
+ * 64 (16 probes). Tunable at runtime and at reopen. */
10
+ efSearch?: number;
11
+ /** Bits used to scalar-quantise the query for the RaBitQ estimator.
12
+ * Query-side only (stored codes are independent of it), so it is tunable
13
+ * at reopen, like efSearch. Default 8. */
14
+ queryBits?: number;
15
+ /** Number of sign-flip + Hadamard rounds in the random rotation. Default 3. */
16
+ rotationRounds?: number;
17
+ /** Seed for the rotation. Default fixed. */
18
+ seed?: number;
19
+ /** Optional centroid the vectors are centered by before quantisation. */
20
+ centroid?: ArrayLike<number>;
21
+ /** SQLite page-cache budget in MiB. Purely a latency knob: correctness and
22
+ * the per-operation storage-read count are identical with it at 0. */
23
+ cacheSizeMb?: number;
24
+ }
25
+ export interface QueryResult {
26
+ id: ExternalId;
27
+ /** Estimated cosine distance (1 - cosine). */
28
+ distance: number;
29
+ }
30
+ export interface StorageStats {
31
+ float32BytesPerVector: number;
32
+ codeBytesPerVector: number;
33
+ bytesPerVector: number;
34
+ compressionRatio: number;
35
+ }
36
+ /**
37
+ * A persistent vector database: an adaptive PARTITIONED (IVF) index over
38
+ * 1-bit RaBitQ codes (cosine), stored entirely in SQLite at `dbPath`. See
39
+ * ivf.ts for the index design. The original float vectors are never
40
+ * retained — only the sign codes — so a 1024-d vector costs 128 bytes.
41
+ *
42
+ * Inserting is route-and-append: cost is essentially FLAT in collection size
43
+ * (one RAM scan of the pivot table + one chunk append — no per-insert graph
44
+ * walk). Query cost is bounded by nprobe × cluster size — constant in N
45
+ * once the collection is past its first splits.
46
+ */
47
+ export declare class VectorDatabase {
48
+ private readonly index;
49
+ constructor(options: DatabaseOptions);
50
+ get dim(): number;
51
+ /** Number of live (non-deleted) vectors. */
52
+ get size(): number;
53
+ /** Physical slot count including tombstones from deletes/updates. */
54
+ get physicalSize(): number;
55
+ /** Number of clusters (partitions) currently in the index. */
56
+ get clusterCount(): number;
57
+ get efSearch(): number;
58
+ set efSearch(value: number);
59
+ /** Distance computations performed during the most recent query. */
60
+ get lastQueryDistanceComputations(): number;
61
+ /** Storage row reads issued by the most recent query — the honest,
62
+ * cache-independent scalability metric. */
63
+ get lastQueryStorageReads(): number;
64
+ get storage(): StorageStats;
65
+ private checkId;
66
+ /** Convert a value to code bytes, selecting by length: `codeWords`
67
+ * elements → an existing 1-bit code; otherwise a raw `dim`-vector. */
68
+ private toCodeBytes;
69
+ has(id: ExternalId): boolean;
70
+ /** Stream every live external id (bounded memory). */
71
+ keys(): IterableIterator<ExternalId>;
72
+ /** Stream live entries whose INTERNAL id is > `after` — a durable
73
+ * incremental watermark (internal ids are monotone at insert and preserved
74
+ * by update and compact). */
75
+ keysSince(after: number): IterableIterator<{
76
+ ext: ExternalId;
77
+ internal: number;
78
+ }>;
79
+ /** Read the stored 1-bit code for an id (a copy as a Uint32Array), or null. */
80
+ get(id: ExternalId): Uint32Array | null;
81
+ insert(id: ExternalId, value: ArrayLike<number>): void;
82
+ update(id: ExternalId, value: ArrayLike<number>): void;
83
+ upsert(id: ExternalId, value: ArrayLike<number>): void;
84
+ /**
85
+ * Upsert many vectors under ONE transaction — one WAL commit for the whole
86
+ * batch instead of one per vector, with chunk appends to the same cluster
87
+ * coalesced in the index's write-back buffer.
88
+ */
89
+ upsertMany(entries: Array<{
90
+ id: ExternalId;
91
+ vector: ArrayLike<number>;
92
+ }>): void;
93
+ /** Delete many ids under ONE transaction. Absent ids are skipped.
94
+ * Returns the number of vectors actually removed. */
95
+ deleteMany(ids: ExternalId[]): number;
96
+ /** Delete the vector bound to an id. Returns false if absent. */
97
+ delete(id: ExternalId): boolean;
98
+ /** Heat the SQLite page cache with sequential scans (latency only). */
99
+ warmCache(): number;
100
+ /** Reclaim tombstoned slots by rewriting the clusters that carry any, then
101
+ * VACUUM. Internal ids and cluster assignment are preserved. */
102
+ compact(): void;
103
+ /**
104
+ * k-NN search. The argument's length selects the mode:
105
+ * - `dim` elements → a raw vector (accurate estimator)
106
+ * - `codeWords` elements → a 1-bit code, by sign-bit Hamming
107
+ */
108
+ query(query: ArrayLike<number>, k?: number, opts?: {
109
+ ef?: number;
110
+ }): QueryResult[];
111
+ /** Close the underlying database. The instance must not be used afterwards. */
112
+ close(): void;
113
+ }