@hviana/sema 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/AGENTS.md +115 -53
  2. package/HOW_IT_WORKS.md +291 -184
  3. package/jsr.json +6 -0
  4. package/package.json +1 -1
  5. package/src/mind/bridge.ts +10 -104
  6. package/src/mind/match.ts +497 -7
  7. package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
  8. package/src/mind/mechanisms/recall.ts +38 -129
  9. package/src/mind/mechanisms/reference.ts +343 -0
  10. package/src/mind/mind.ts +12 -8
  11. package/src/mind/pipeline-mechanism.ts +120 -3
  12. package/src/mind/pipeline.ts +16 -2
  13. package/src/mind/traverse.ts +92 -1
  14. package/src/store.ts +13 -4
  15. package/test/33-multi-candidate.test.mjs +21 -11
  16. package/test/70-prefix-completion.test.mjs +1 -1
  17. package/test/72-prefix-candidate-supply.test.mjs +7 -9
  18. package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
  19. package/test/76-reference-binding.test.mjs +543 -0
  20. package/dist/example/demo.d.ts +0 -1
  21. package/dist/example/demo.js +0 -39
  22. package/dist/example/train_base.d.ts +0 -93
  23. package/dist/example/train_base.js +0 -2269
  24. package/dist/src/alphabet.d.ts +0 -7
  25. package/dist/src/alphabet.js +0 -33
  26. package/dist/src/alu/src/alu.d.ts +0 -185
  27. package/dist/src/alu/src/alu.js +0 -440
  28. package/dist/src/alu/src/expr.d.ts +0 -61
  29. package/dist/src/alu/src/expr.js +0 -318
  30. package/dist/src/alu/src/index.d.ts +0 -11
  31. package/dist/src/alu/src/index.js +0 -19
  32. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  33. package/dist/src/alu/src/kernel-arith.js +0 -264
  34. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  35. package/dist/src/alu/src/kernel-bits.js +0 -152
  36. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  37. package/dist/src/alu/src/kernel-logic.js +0 -60
  38. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  39. package/dist/src/alu/src/kernel-nd.js +0 -208
  40. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  41. package/dist/src/alu/src/kernel-numeric.js +0 -366
  42. package/dist/src/alu/src/operation.d.ts +0 -168
  43. package/dist/src/alu/src/operation.js +0 -189
  44. package/dist/src/alu/src/parser.d.ts +0 -221
  45. package/dist/src/alu/src/parser.js +0 -577
  46. package/dist/src/alu/src/resonance.d.ts +0 -55
  47. package/dist/src/alu/src/resonance.js +0 -126
  48. package/dist/src/alu/src/text.d.ts +0 -31
  49. package/dist/src/alu/src/text.js +0 -73
  50. package/dist/src/alu/src/value.d.ts +0 -109
  51. package/dist/src/alu/src/value.js +0 -300
  52. package/dist/src/alu/test/alu.test.d.ts +0 -1
  53. package/dist/src/alu/test/alu.test.js +0 -764
  54. package/dist/src/bytes.d.ts +0 -14
  55. package/dist/src/bytes.js +0 -59
  56. package/dist/src/canon.d.ts +0 -45
  57. package/dist/src/canon.js +0 -85
  58. package/dist/src/config.d.ts +0 -111
  59. package/dist/src/config.js +0 -91
  60. package/dist/src/derive/src/deduction.d.ts +0 -136
  61. package/dist/src/derive/src/deduction.js +0 -159
  62. package/dist/src/derive/src/index.d.ts +0 -8
  63. package/dist/src/derive/src/index.js +0 -11
  64. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  65. package/dist/src/derive/src/priority-queue.js +0 -73
  66. package/dist/src/derive/src/rewrite.d.ts +0 -56
  67. package/dist/src/derive/src/rewrite.js +0 -100
  68. package/dist/src/derive/src/trie.d.ts +0 -90
  69. package/dist/src/derive/src/trie.js +0 -217
  70. package/dist/src/derive/test/derive.test.d.ts +0 -1
  71. package/dist/src/derive/test/derive.test.js +0 -122
  72. package/dist/src/extension.d.ts +0 -37
  73. package/dist/src/extension.js +0 -7
  74. package/dist/src/geometry.d.ts +0 -233
  75. package/dist/src/geometry.js +0 -1089
  76. package/dist/src/index.d.ts +0 -17
  77. package/dist/src/index.js +0 -19
  78. package/dist/src/ingest-cache.d.ts +0 -41
  79. package/dist/src/ingest-cache.js +0 -165
  80. package/dist/src/meter.d.ts +0 -176
  81. package/dist/src/meter.js +0 -274
  82. package/dist/src/mind/articulation.d.ts +0 -6
  83. package/dist/src/mind/articulation.js +0 -99
  84. package/dist/src/mind/attention.d.ts +0 -430
  85. package/dist/src/mind/attention.js +0 -2418
  86. package/dist/src/mind/bridge.d.ts +0 -39
  87. package/dist/src/mind/bridge.js +0 -998
  88. package/dist/src/mind/canonical.d.ts +0 -34
  89. package/dist/src/mind/canonical.js +0 -93
  90. package/dist/src/mind/frame-filler.d.ts +0 -15
  91. package/dist/src/mind/frame-filler.js +0 -535
  92. package/dist/src/mind/graph-search.d.ts +0 -294
  93. package/dist/src/mind/graph-search.js +0 -996
  94. package/dist/src/mind/index.d.ts +0 -9
  95. package/dist/src/mind/index.js +0 -5
  96. package/dist/src/mind/junction.d.ts +0 -137
  97. package/dist/src/mind/junction.js +0 -358
  98. package/dist/src/mind/learning.d.ts +0 -75
  99. package/dist/src/mind/learning.js +0 -265
  100. package/dist/src/mind/match.d.ts +0 -192
  101. package/dist/src/mind/match.js +0 -666
  102. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  103. package/dist/src/mind/mechanisms/alu.js +0 -36
  104. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  105. package/dist/src/mind/mechanisms/cast.js +0 -1186
  106. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  107. package/dist/src/mind/mechanisms/confluence.js +0 -271
  108. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  109. package/dist/src/mind/mechanisms/cover.js +0 -232
  110. package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
  111. package/dist/src/mind/mechanisms/extraction.js +0 -327
  112. package/dist/src/mind/mechanisms/recall.d.ts +0 -16
  113. package/dist/src/mind/mechanisms/recall.js +0 -558
  114. package/dist/src/mind/mind.d.ts +0 -386
  115. package/dist/src/mind/mind.js +0 -754
  116. package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
  117. package/dist/src/mind/pipeline-mechanism.js +0 -521
  118. package/dist/src/mind/pipeline.d.ts +0 -49
  119. package/dist/src/mind/pipeline.js +0 -331
  120. package/dist/src/mind/prefix-completion.d.ts +0 -59
  121. package/dist/src/mind/prefix-completion.js +0 -270
  122. package/dist/src/mind/primitives.d.ts +0 -85
  123. package/dist/src/mind/primitives.js +0 -333
  124. package/dist/src/mind/rationale.d.ts +0 -139
  125. package/dist/src/mind/rationale.js +0 -163
  126. package/dist/src/mind/reasoning.d.ts +0 -42
  127. package/dist/src/mind/reasoning.js +0 -305
  128. package/dist/src/mind/recognition.d.ts +0 -20
  129. package/dist/src/mind/recognition.js +0 -696
  130. package/dist/src/mind/resonance.d.ts +0 -35
  131. package/dist/src/mind/resonance.js +0 -365
  132. package/dist/src/mind/trace.d.ts +0 -15
  133. package/dist/src/mind/trace.js +0 -73
  134. package/dist/src/mind/traverse.d.ts +0 -158
  135. package/dist/src/mind/traverse.js +0 -716
  136. package/dist/src/mind/types.d.ts +0 -418
  137. package/dist/src/mind/types.js +0 -179
  138. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  139. package/dist/src/rabitq-ivf/src/database.js +0 -201
  140. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  141. package/dist/src/rabitq-ivf/src/index.js +0 -4
  142. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  143. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  144. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  145. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  146. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  147. package/dist/src/rabitq-ivf/src/rabitq.js +0 -313
  148. package/dist/src/sema.d.ts +0 -41
  149. package/dist/src/sema.js +0 -77
  150. package/dist/src/store-sqlite.d.ts +0 -184
  151. package/dist/src/store-sqlite.js +0 -942
  152. package/dist/src/store.d.ts +0 -762
  153. package/dist/src/store.js +0 -1823
  154. package/dist/src/vec.d.ts +0 -31
  155. package/dist/src/vec.js +0 -109
  156. package/src/mind/frame-filler.ts +0 -604
  157. package/test/69-frame-filler.test.mjs +0 -115
@@ -1,762 +0,0 @@
1
- import { Vec } from "./vec.js";
2
- import { type StoreConfig } from "./config.js";
3
- import type { Meter } from "./meter.js";
4
- /** A node id: a dense, non-negative integer assigned in creation order. */
5
- export type NodeId = number;
6
- /** A node in the graph. Exactly one of `leaf` / `kids` is set. */
7
- export interface NodeRec {
8
- id: NodeId;
9
- leaf: Uint8Array | null;
10
- kids: NodeId[] | null;
11
- }
12
- /** A soft-resonance hit. */
13
- export interface Hit {
14
- id: NodeId;
15
- /** Estimated cosine (1 − RaBitQ estimated distance) — an APPROXIMATION,
16
- * never an exact rerank. Identity decisions must use content-addressed
17
- * resolve(), never `score >= 1`. */
18
- score: number;
19
- /** This node's halo, when a producer chooses to attach it. NOT populated
20
- * by {@link Store.resonate}/{@link Store.resonateHalo}: no consumer read
21
- * it there, and eagerly fetching k halo rows per query was pure waste.
22
- * Call {@link Store.halo} on the hits that actually need it. */
23
- halo?: Vec;
24
- }
25
- /** Eviction strategy for a {@link BoundedMap}.
26
- *
27
- * - `"lru"`: evict the least-recently-used entry. Best when every entry
28
- * saves roughly the same work (dedup, leaf vectors, node records).
29
- * - `"smallest"`: among the oldest few LRU candidates, evict the smallest.
30
- * This protects expensive-to-reconstruct entries (large branches) at the
31
- * expense of cheap ones (small leaves) — useful for reconstruction caches
32
- * where rebuild cost varies by orders of magnitude. */
33
- export type Evict = "lru" | "smallest";
34
- /** Bounded map with LRU eviction and byte accounting. On get the entry
35
- * moves to the most-recent end; on set the least-recently-used entries are
36
- * evicted until total bytes ≤ `maxBytes`. The optional `sizeOf` callback
37
- * measures each value in bytes (defaults to 1, so `maxBytes` = max entries
38
- * for uniform caches). A miss only costs a little extra work later, never
39
- * correctness. */
40
- export declare class BoundedMap<K, V> {
41
- readonly maxBytes: number;
42
- private readonly sizeOf;
43
- private readonly evict;
44
- /** How a HIT records recency.
45
- *
46
- * `"reorder"` (default) promotes the entry to most-recent by
47
- * `m.delete(k); m.set(k, v)` — exact LRU, and the only policy that is
48
- * safe for a cache whose CONTENTS are load-bearing rather than merely
49
- * warm. `_depositTrees` (8 entries, feeds stablePrefixFoldIncremental)
50
- * is exactly that: which of its entries survives changes how the next
51
- * turn FOLDS, so test/13 D1 flips answer when the victim changes.
52
- *
53
- * `"clock"` records recency as a BIT instead of as position, spent by
54
- * the eviction sweep (see `nextOldest`). Correct only for a TRANSPARENT
55
- * cache — one where evicting the wrong entry costs a re-read and nothing
56
- * else. Opt in deliberately, per cache. */
57
- private readonly recency;
58
- private m;
59
- private _bytes;
60
- private _cursor;
61
- private _candidates;
62
- private _used;
63
- constructor(maxBytes: number, sizeOf?: (v: V) => number, evict?: Evict,
64
- /** How a HIT records recency.
65
- *
66
- * `"reorder"` (default) promotes the entry to most-recent by
67
- * `m.delete(k); m.set(k, v)` — exact LRU, and the only policy that is
68
- * safe for a cache whose CONTENTS are load-bearing rather than merely
69
- * warm. `_depositTrees` (8 entries, feeds stablePrefixFoldIncremental)
70
- * is exactly that: which of its entries survives changes how the next
71
- * turn FOLDS, so test/13 D1 flips answer when the victim changes.
72
- *
73
- * `"clock"` records recency as a BIT instead of as position, spent by
74
- * the eviction sweep (see `nextOldest`). Correct only for a TRANSPARENT
75
- * cache — one where evicting the wrong entry costs a re-read and nothing
76
- * else. Opt in deliberately, per cache. */
77
- recency?: "reorder" | "clock");
78
- /** Next key in insertion (≈ LRU) order, resuming where the last call left
79
- * off; wraps to the front when exhausted. Undefined only when empty. */
80
- private nextOldest;
81
- /** RECENCY WITHOUT MUTATING THE MAP (clock policy only).
82
- *
83
- * The default `"reorder"` policy below is the textbook JS LRU
84
- * (`m.delete(k); m.set(k, v)`), and on the read path that idiom was
85
- * measured as the single largest CPU consumer in inference: 55% of
86
- * profiled self time, 14.6s of a 26.4s battery, over 6.4M gets — 5.3M of
87
- * them on `_bytesCache` alone at an 83% hit rate. Every hit deletes and
88
- * reinserts a live key, and each delete leaves a hole in V8's ordered
89
- * backing store that is compacted only on rehash — the same O(size) cliff
90
- * the eviction cursor above already documents, paid here on the path taken
91
- * orders of magnitude more often.
92
- *
93
- * Under `"clock"`, a hit sets a BIT that the eviction sweep spends.
94
- * `Set.add` of a key already present neither inserts nor rehashes, so hot
95
- * keys — the 83% — cost one hash probe and nothing else. Measured on the
96
- * two transparent store caches, with every counter byte-identical
97
- * (nodeRecords 488,468 / byteReads 62,959 in both arms — the SAME entries
98
- * stayed cached): multi-turn think 11,399ms -> 2,022ms, its crossRegion
99
- * 8,833ms -> 771ms; single-turn think 12,977ms -> 6,779ms.
100
- *
101
- * It is NOT the default, because it is only sound where eviction costs a
102
- * re-read. See the `recency` parameter. */
103
- get(k: K): V | undefined;
104
- /** Membership without touching LRU order — a pure peek, for callers that only
105
- * need "is this key present?" and must not promote it to most-recent. */
106
- has(k: K): boolean;
107
- set(k: K, v: V): void;
108
- get size(): number;
109
- get bytes(): number;
110
- /** Remove one entry (point invalidation), with byte accounting. */
111
- delete(k: K): void;
112
- /** Drop every entry (bulk invalidation) — O(1) amortised via fresh maps. */
113
- clear(): void;
114
- }
115
- export interface Store {
116
- readonly D: number;
117
- /** The work accumulator for the inference call in flight, or null. The
118
- * Mind attaches one per profiled response and detaches it after (see
119
- * src/meter.ts). A store MUST only ever write to it — no read may reach
120
- * a decision, or determinism is gone. An adapter that adds reads of its
121
- * own should bump the matching counter; one that does not simply reports
122
- * less, never wrongly. */
123
- meter: Meter | null;
124
- /** Insert a leaf, returning its content id. Idempotent. */
125
- putLeaf(bytes: Uint8Array, gist: Vec): Promise<NodeId>;
126
- /** Insert a branch over child ids, returning its content id. Idempotent. */
127
- putBranch(kids: NodeId[], gist: Vec): Promise<NodeId>;
128
- /** Whether a node with this id already exists. O(1). */
129
- has(id: NodeId): boolean;
130
- /** The node record, or null. */
131
- get(id: NodeId): NodeRec | null;
132
- /** The bytes a node spans, reconstructed by traversal (cached). */
133
- bytes(id: NodeId): Uint8Array;
134
- /** First `maxLen` bytes of a node, stopping early — far cheaper than
135
- * `bytes()` for large branches when only a prefix is needed. */
136
- bytesPrefix(id: NodeId, maxLen: number): Uint8Array;
137
- /** The CONTENT LENGTH of a node in bytes — under the linear river fold
138
- * this IS the node's gist magnitude, squared: seat permutations
139
- * decorrelate siblings, so ‖gist‖ = √(content bytes) up to decorrelation
140
- * noise. The magnitude is therefore never persisted beside the vector
141
- * (that would duplicate what the content already determines; the ANN
142
- * index keeps unit directions), it is READ here in O(1) amortized — the
143
- * store-side half of angle+magnitude semantics. Mechanisms use it to
144
- * convert a scale-free cosine into the absolute quantities the linear
145
- * geometry carries: shared bytes ≈ cos·√(lenA·lenB), fraction of A
146
- * explained ≈ cos·√(lenB/lenA). `cap`, when given, SATURATES the walk:
147
- * the return value is exact below the cap and merely ≥ cap otherwise —
148
- * for decisions that stop caring past a bound (a fraction that caps at 1,
149
- * a weight that is sub-noise beyond a ratio), so one huge conversation
150
- * root never costs a full subtree walk. Only complete walks are
151
- * memoized. */
152
- contentLen(id: NodeId, cap?: number): number;
153
- findLeaf(bytes: Uint8Array): NodeId | null;
154
- findBranch(kids: NodeId[]): NodeId | null;
155
- /** {@link findBranch} for a run of single-byte leaves, addressed by the raw
156
- * bytes — the allocation-free probe span scanners use. Optional: a store
157
- * without it is simply probed through `findBranch`. */
158
- findFlatBranch?(bytes: Uint8Array): NodeId | null;
159
- /** The branch nodes that list `id` among their children — the reverse of
160
- * `get(id).kids`. Lets the structural DAG be climbed upward, from a
161
- * recognised fragment to the larger learned forms that contain it. */
162
- parents(id: NodeId): NodeId[];
163
- /** The first `limit` structural parents of `id` — the CAPPED read for the
164
- * climb. A heavily shared subtree's parent set grows with the corpus;
165
- * reading `bound + 1` decides "hub or not" exactly (a result of length
166
- * bound + 1 means MORE than bound) while the read stays bounded by the
167
- * bound, never by the fan-in. */
168
- parentsFirst(id: NodeId, limit: number): NodeId[];
169
- /** Whether `id` has ANY structural parent — one LIMITed point probe. */
170
- hasParents(id: NodeId): boolean;
171
- /** The TRANSPARENT CHAIN from `id` upward: `run[0] === id`, and while the
172
- * current node is transparent — no continuation edge in or out and exactly
173
- * ONE structural parent — the run steps to that parent. The last element
174
- * is the first NON-transparent ancestor (or a parentless top). Transparent
175
- * nodes are invisible to the edge climb (they contribute no roots, no
176
- * contexts and no lateral branching), so a climber may hop a whole run in
177
- * one read where a node-at-a-time ascent pays three probes per node — the
178
- * dominant cost of climbing deep single-structure scaffolding. Results
179
- * are cached for the store's LIFETIME (reads are pure between writes) and
180
- * the cache is dropped whenever a write could break transparency: a node
181
- * gaining a structural parent (fresh mint) or a continuation edge (link).
182
- * The run may be truncated at an internal safety depth; a truncated run's
183
- * last element is then still transparent, and a climber that treats the
184
- * terminal generically simply continues from it — semantics never depend
185
- * on completeness. */
186
- chainRun(id: NodeId): readonly NodeId[];
187
- /** Record a CONTAINMENT edge: `parent`'s byte content contains `child`'s,
188
- * even though `child` is not among `parent`'s kids (a sub-span flat branch
189
- * inside a leaf-parent chunk). Kept apart from {@link parents} — the
190
- * structural climb is untouched; {@link containers} serves the climbing
191
- * surface of orphan flat branches durably, where a session-local side
192
- * table would be lost on restart or an ingest-cache replay. Idempotent. */
193
- addContainer(child: NodeId, parent: NodeId): void;
194
- /** The chunks recorded by {@link addContainer} as containing `child`. */
195
- containers(child: NodeId): NodeId[];
196
- /** Whether `child` has ANY containment parent — an EXISTS probe that never
197
- * unpacks the (occurrence-proportional) packed parents blob. */
198
- hasContainers(child: NodeId): boolean;
199
- /** A PAGE of containment parents — `limit` entries starting at `offset`
200
- * (the buffered adds follow the stored ones), so a consumer can STREAM a
201
- * common window's corpus-sized parent list and stop the moment its
202
- * question is decided, with per-page work bounded by `limit`. */
203
- containersSlice(child: NodeId, offset: number, limit: number): NodeId[];
204
- nodeCount(): number;
205
- /** The k nodes whose gist resonates most with v. `exhaustive` widens the
206
- * IVF probe to every cluster (see {@link AbstractStore.efFor}'s doc) —
207
- * for refusal-path-only callers where an approximate top-√C-clusters
208
- * search is not the same discriminator as the caller's own byte-exact
209
- * verification (the substitution bridge's proposal channel): a rarer
210
- * paraphrase can score lower than hundreds of unrelated hits by pure
211
- * fold-geometry structural distance (a middle-of-string mismatch
212
- * perturbs the tree hash far more than a tail mismatch of the same
213
- * byte length) and so never even reach a probed cluster, no matter how
214
- * large k is — k only reorders WITHIN the clusters already probed. */
215
- resonate(v: Vec, k: number, exhaustive?: boolean): Promise<Hit[]>;
216
- /** Mark a node as a RESONANCE TARGET — promote its gist into the content
217
- * index so {@link resonate} can find it. A node's gist is captured at intern
218
- * but indexed LAZILY (only targets are indexed; the ~99.5% intermediate DAG
219
- * is not — that is the store's compression). A node becomes a target
220
- * implicitly via {@link link}/{@link pourHalo}; this is the EXPLICIT hook for
221
- * the one target those do not cover — a DEPOSIT ROOT, the node `express`,
222
- * `bridge`, and whole-query recall resonate the input to. Idempotent; a
223
- * no-op once a node is indexed or if its captured gist has been evicted. */
224
- indexTarget(id: NodeId): void;
225
- /** Remove content-index entries for nodes that are structurally isolated
226
- * (fewer than `minParents` structural parents) and are not roots of any
227
- * experience (no edges, no halo). These nodes are unique to one tree and
228
- * bridge nothing between experiences — their index slots are wasted.
229
- *
230
- * This is a POST-HOC batch operation, not on the training hot path. Run it
231
- * at checkpoints or after training to reclaim index space. The underlying
232
- * vector DB is compacted after deletion to physically free the space.
233
- *
234
- * INCREMENTAL: keep decisions are monotone (parents, edges and halos only
235
- * ever grow), so entries examined by a previous pass are settled; each
236
- * pass scans only entries indexed since the last one (a durable watermark
237
- * over the index's monotone internal ids), making the checkpoint-cadence
238
- * call cheap on a large trained store.
239
- *
240
- * @param minParents keep only nodes with ≥ this many structural parents
241
- * (default 2 — keep nodes that bridge ≥2 experiences)
242
- * @returns number of entries removed */
243
- compactContentIndex(minParents?: number): Promise<number>;
244
- /** Re-index structurally-important nodes whose gists were evicted from the
245
- * pending cache before they could be indexed — the inverse of {@link
246
- * compactContentIndex}. Walks the edge/halo-bearing id set (a repairable
247
- * node must carry edges or a halo, so that set IS the candidate set —
248
- * corpus-of-experiences-sized, not node-count-sized); for each candidate
249
- * that (a) has ≥ `minParents` structural parents and (b) is NOT already
250
- * in the content index, regenerates its gist via `regenerateGist` and adds
251
- * it. Intended as a post-training batch operation, not on the hot path.
252
- *
253
- * `regenerateGist` receives a node id and must return its perceived gist
254
- * vector (or `null` to skip). The store has no access to the
255
- * {@link Space}/{@link Alphabet} needed for perception — the caller (the
256
- * {@link Mind}) wires those in.
257
- *
258
- * @param regenerateGist async callback that regenerates a node's gist
259
- * @param minParents only repair nodes with ≥ this many parents
260
- * (default 2 — structural bridges)
261
- * @returns number of nodes added to the index */
262
- repairContentIndex(regenerateGist: (id: NodeId) => Promise<Vec | null>, minParents?: number): Promise<number>;
263
- /** Learn that `to` follows `from`. Idempotent. */
264
- link(from: NodeId, to: NodeId): Promise<void>;
265
- /** What follows `id` (in insertion order). */
266
- next(id: NodeId): NodeId[];
267
- /** Whether ANY edge leaves `id` — the EXISTENCE probe for the forward
268
- * relation. Decision points that only ask "does this lead anywhere?"
269
- * must use this instead of materialising {@link next} (a context's edge
270
- * list is a range read; a hub's is large) — the same
271
- * count-instead-of-materialise principle {@link prevCount} serves for the
272
- * reverse relation. One indexed point probe. */
273
- hasNext(id: NodeId): boolean;
274
- /** What `id` follows (for reverse recall). */
275
- prev(id: NodeId): NodeId[];
276
- /** The first `limit` continuations of `id`, in the SAME order {@link next}
277
- * returns (insertion order) — the CAPPED read for consumers bounded by the
278
- * hub convention. A LIMITed statement, never a full materialisation: no
279
- * read through this may grow with the corpus. */
280
- nextFirst(id: NodeId, limit: number): NodeId[];
281
- /** The first `limit` predecessors of `id`, in the SAME order {@link prev}
282
- * returns (NEWEST-first — prev is seq-descending; see the adapter) — the
283
- * capped read for reverse fan-ins, which are corpus-sized on a common
284
- * continuation. */
285
- prevFirst(id: NodeId, limit: number): NodeId[];
286
- /** How many nodes `id` follows — the reverse-edge SUPPORT count. Decision
287
- * points that only weigh evidence (chooseNext) must use this instead of
288
- * materialising {@link prev}, whose list is corpus-sized for a common
289
- * continuation ("Yes."). One indexed COUNT, never a row materialisation. */
290
- prevCount(id: NodeId): number;
291
- /** How many DISTINCT nodes bear a continuation edge — the number of learnt
292
- * contexts (the document count for inverse-document-frequency weighting in
293
- * the consensus climb). O(1) from an index; a coarse count is fine. */
294
- edgeSourceCount(): number;
295
- /** Pour a partner's signature into a node's halo. */
296
- pourHalo(id: NodeId, add: Vec): Promise<void>;
297
- /** Nodes whose halo resonates with `v` (the concept's siblings). */
298
- resonateHalo(v: Vec, k: number): Promise<Hit[]>;
299
- /** A node's halo, or null if it has none / too little mass. */
300
- halo(id: NodeId): Vec | null;
301
- /** Whether {@link halo} would return non-null — the EXISTENCE probe.
302
- * {@link halo} decodes and normalizes the full D-element quantized row on
303
- * every call; existence-only consumers (recognition's admission predicate,
304
- * the search's fuse guard) must ask this instead: one row read, a mass
305
- * compare, no decode. */
306
- hasHalo(id: NodeId): boolean;
307
- /** How many episode signatures were poured into `id`'s halo — the DIRECT
308
- * measure of distributional evidence (each training pair pours once, so
309
- * repetition counts, unlike {@link prevCount}, which counts DISTINCT
310
- * contexts). Consulted at disambiguation decision points as the tie-break
311
- * behind distinct-context support: diversity of evidence outranks sheer
312
- * repetition, but repetition outranks insertion-order accident. 0 when
313
- * the node has no halo row. */
314
- haloMass(id: NodeId): number;
315
- /** Record that node `id`'s canonical key hashes to `h`. Idempotent. */
316
- canonAdd?(h: number, id: NodeId): void;
317
- /** All candidate node ids whose canonical key hashes to `h` (collisions
318
- * included — the caller verifies). */
319
- canonFind?(h: number): NodeId[];
320
- /** Number of (h, id) rows in the canon index — 0 means never built. */
321
- canonCount?(): number;
322
- /** Visit every content-bearing node (flat branch: `leaf` present, no
323
- * kids) — the population a canonical index is built over. `fromId`
324
- * restricts the scan to ids ≥ fromId, so an index refresh after further
325
- * training only visits the new rows. */
326
- eachContent?(cb: (id: NodeId, bytes: Uint8Array) => void, fromId?: NodeId): void;
327
- size(): Promise<number>;
328
- saveSnapshot(bytes: Uint8Array): Promise<void>;
329
- loadSnapshot(): Promise<Uint8Array | null>;
330
- /** Free-form provenance metadata (e.g. training dataset name). */
331
- setMeta(key: string, val: string): Promise<void>;
332
- getMeta(key: string): Promise<string | null>;
333
- deleteMeta(key: string): Promise<void>;
334
- /** Commit any deferred writes — makes metadata, edges, and node writes
335
- * durable immediately. Safe to call frequently; a no-op if nothing is
336
- * pending. */
337
- commit(): void;
338
- close(): Promise<void>;
339
- }
340
- /** The implicit kid list of a flat branch — the inverse of
341
- * {@link flatKidsBytes}. */
342
- export declare function flatBytesKids(bytes: Uint8Array): NodeId[];
343
- /** Pack a child-id list as little-endian int32s — 4 bytes per child, far more
344
- * compact than a space-joined decimal string and trivial to read back. */
345
- export declare function packKids(kids: NodeId[]): Uint8Array;
346
- export declare function unpackKids(blob: Uint8Array): NodeId[];
347
- /**
348
- * Template-method base class that contains ALL domain logic for the content-
349
- * addressed DAG store — caching, dedup/merge decisions, structural-compaction
350
- * geometric halo scheduling, buffer management. A concrete persistence adapter
351
- * (e.g. {@link SQliteStore}) extends it and implements only the ~35 one-liner
352
- * protected abstract methods that talk to the actual storage backend.
353
- */
354
- export declare abstract class AbstractStore implements Store {
355
- /** Open the storage backend. Must set `this._nextId`, `this._D`, and
356
- * `this._maxGroup` from the stored state (or defaults for a fresh store). */
357
- protected abstract _dbOpen(): Promise<void>;
358
- /** Close the storage backend and release all resources. */
359
- protected abstract _dbClose(): void;
360
- protected abstract _dbBeginTx(): void;
361
- protected abstract _dbCommitTx(): void;
362
- protected abstract _dbInsertNode(id: NodeId, leaf: Uint8Array | null, kids: Uint8Array | null, h: number): void;
363
- protected abstract _dbGetNode(id: NodeId): NodeRec | null;
364
- protected abstract _dbFindLeaf(h: number, bytes: Uint8Array): NodeId | null;
365
- protected abstract _dbFindBranchByLeaf(h: number, bytes: Uint8Array): NodeId | null;
366
- protected abstract _dbFindBranchByKids(h: number, packed: Uint8Array): NodeId | null;
367
- protected abstract _dbInsertKid(child: NodeId, parent: NodeId): void;
368
- protected abstract _dbGetParents(id: NodeId): NodeId[];
369
- /** LIMITed variant of the parent read — same statement, `LIMIT ?`. Must
370
- * NOT be implemented by materialising and slicing. */
371
- protected abstract _dbGetParentsFirst(id: NodeId, limit: number): NodeId[];
372
- protected abstract _dbGetContainParents(child: NodeId): NodeId[];
373
- /** Whether a containment row exists for `child` — no blob unpack. */
374
- protected abstract _dbContainExists(child: NodeId): boolean;
375
- /** A page of stored containment parents — partial blob unpack. */
376
- protected abstract _dbGetContainParentsSlice(child: NodeId, offset: number, limit: number): NodeId[];
377
- /** The stored containment-parent COUNT — blob length / 4, no unpack. */
378
- protected abstract _dbGetContainCount(child: NodeId): number;
379
- /** Append containment parents for `child`. MUST NOT re-write the child's
380
- * whole stored list per call — a hot child's fan-in is corpus-sized, and a
381
- * full read-modify-write per flush is quadratic over training. Durable
382
- * dedup may be DEFERRED (e.g. to a geometric merge schedule); readers
383
- * dedup, so transient duplicates only cost bytes, never correctness. */
384
- protected abstract _dbAppendContain(child: NodeId, parents: NodeId[]): void;
385
- protected abstract _dbInsertEdge(src: NodeId, dst: NodeId): void;
386
- protected abstract _dbGetNextEdges(id: NodeId): NodeId[];
387
- protected abstract _dbGetPrevEdges(id: NodeId): NodeId[];
388
- /** LIMITed variants of the two edge reads — same ORDER BY, `LIMIT ?`.
389
- * These exist so a capped consumer's cost is bounded by the cap, not by
390
- * the fan-out; an adapter must NOT implement them by materialising and
391
- * slicing. */
392
- protected abstract _dbGetNextEdgesFirst(id: NodeId, limit: number): NodeId[];
393
- protected abstract _dbGetPrevEdgesFirst(id: NodeId, limit: number): NodeId[];
394
- /** Whether ANY edge already leaves `src` — one indexed point probe, used to
395
- * maintain the distinct-source count incrementally. */
396
- protected abstract _dbEdgeSrcExists(src: NodeId): boolean;
397
- protected abstract _dbEdgeDistinctSrcCount(): number;
398
- protected abstract _dbGetHalo(id: NodeId): {
399
- vec: Uint8Array;
400
- mass: number;
401
- } | null;
402
- protected abstract _dbUpsertHalo(id: NodeId, encodedVec: Uint8Array, mass: number): void;
403
- protected abstract _dbGetMeta(key: string): string | null;
404
- protected abstract _dbSetMeta(key: string, val: string): void;
405
- protected abstract _dbDeleteMeta(key: string): void;
406
- protected abstract _dbSaveSnapshot(bytes: Uint8Array): void;
407
- protected abstract _dbLoadSnapshot(): Uint8Array | null;
408
- protected abstract _vecContentUpsert(entries: Array<{
409
- id: NodeId;
410
- vector: Float32Array;
411
- }>): void;
412
- protected abstract _vecContentQuery(v: Float32Array, k: number, ef: number): Array<{
413
- id: number;
414
- distance: number;
415
- }>;
416
- /** Whether `id` is already a live entry of the content index — one point
417
- * query, used to recognise durably-indexed nodes across sessions (the
418
- * in-memory `_indexedIds` cache starts empty on every open). */
419
- protected abstract _vecContentHas(id: NodeId): boolean;
420
- protected abstract _vecContentSize(): number;
421
- protected abstract _vecContentLastReads(): number;
422
- protected abstract _vecContentPhysicalSize(): number;
423
- protected abstract _vecContentClusterCount(): number;
424
- protected abstract _vecContentCompact(): void;
425
- /** Live content-index entries whose INTERNAL id is > `after`, as
426
- * {ext, internal} pairs in internal-id order. Internal ids are monotone
427
- * at insert and preserved by the index's tombstone-splice compaction, so
428
- * the largest internal id a scan has seen is a durable watermark for
429
- * incremental maintenance ({@link compactContentIndex}). */
430
- protected abstract _vecContentEntriesSince(after: number): IterableIterator<{
431
- ext: NodeId;
432
- internal: number;
433
- }>;
434
- /** Every node id that carries a continuation edge (as source OR target) or
435
- * a halo row — the RESONANCE-TARGET id set, sorted ascending and deduped.
436
- * This is the exact keep/repair criterion of index maintenance; driving
437
- * the maintenance loops from this (corpus-of-experiences-sized) set
438
- * instead of probing per node turned repair from an every-node walk into
439
- * a candidates-only walk. */
440
- protected abstract _dbEdgeOrHaloIds(): NodeId[];
441
- /** Remove a batch of entries from the content index by external id, under
442
- * ONE storage transaction (a tombstone per implicit transaction is one WAL
443
- * commit per id — the dominant cost of a bulk prune). Idempotent per id —
444
- * already-deleted or non-existent ids are skipped. */
445
- protected abstract _vecContentDeleteMany(ids: NodeId[]): void;
446
- protected abstract _vecHaloUpsert(entries: Array<{
447
- id: NodeId;
448
- vector: Float32Array;
449
- }>): void;
450
- protected abstract _vecHaloQuery(v: Float32Array, k: number, ef: number): Array<{
451
- id: number;
452
- distance: number;
453
- }>;
454
- /** Live (non-tombstoned) entry count of the halo index — the denominator
455
- * the tombstone-ratio compaction trigger compares physical size against. */
456
- protected abstract _vecHaloSize(): number;
457
- protected abstract _vecHaloPhysicalSize(): number;
458
- protected abstract _vecHaloClusterCount(): number;
459
- protected abstract _vecHaloCompact(): void;
460
- /** Derived query breadth for a partitioned index of C clusters: probe √C
461
- * of them (the same √-of-the-population convention as the hub bound √N).
462
- * The IVF maps ef → nprobe as ceil(ef/4), so ef = 4·⌈√C⌉ probes exactly
463
- * ⌈√C⌉ clusters. A FIXED efSearch stops scaling the moment the
464
- * collection outgrows it: at 4,270 clusters the default 64 probed 16
465
- * clusters (0.4%), and an exact stored match of a query routinely sat in
466
- * an unprobed cluster — recall silently degraded as the store grew. The
467
- * configured efSearch remains the floor for small collections. */
468
- protected efFor(clusterCount: number): number;
469
- protected _D: number;
470
- protected _maxGroup: number;
471
- protected readonly minHaloMass: number;
472
- protected readonly efSearch: number;
473
- protected readonly overfetch: number;
474
- protected readonly batchSize: number;
475
- protected readonly compactEveryNWrites: number;
476
- /** Branch node ids are a dense, monotonically-increasing integer sequence
477
- * (0,1,2,…). Single-byte leaves occupy the implicit negative range −256…−1.
478
- * They are NEVER deleted, so the count of minted branch ids IS the next id —
479
- * which doubles as the branch-node count and lets has() be an O(1) check.
480
- * Set by `_dbOpen()` from the stored node count; incremented by `mintId()`. */
481
- protected _nextId: number;
482
- protected _writtenSinceCompact: number;
483
- protected closed: boolean;
484
- /** Lifecycle guard — resolved once `_dbOpen()` completes. */
485
- protected _ready: Promise<void> | null;
486
- /** Exact-content dedup: content-key → node id. Intrinsic compression. */
487
- protected readonly _leafKey: BoundedMap<string, NodeId>;
488
- protected readonly _branchKey: BoundedMap<string, NodeId>;
489
- /** Reconstructed-bytes read cache (regenerable), keyed by node id. */
490
- protected readonly _bytesCache: BoundedMap<NodeId, Uint8Array>;
491
- /** contentLen memo — content is immutable, so entries never invalidate. */
492
- protected readonly _lenCache: BoundedMap<NodeId, number>;
493
- /** Node-record cache — avoids repeated persistence queries for shared DAG
494
- * nodes. Each record is small (a few ints + short leaf buffer). */
495
- protected readonly _recCache: BoundedMap<NodeId, NodeRec>;
496
- /** Captured-but-not-yet-indexed gists. Sized in bytes (each is D·4); a deposit
497
- * links/pours a node right after interning it, so the working set is one
498
- * deposit's nodes — a small budget captures ~all of it, and an eviction only
499
- * means that node is reached by the DAG climb instead of by direct
500
- * resonance. */
501
- protected _pendingGist: BoundedMap<NodeId, Vec>;
502
- /** EXACT halo accumulators for the session's live pours: full-precision in
503
- * memory, 2-bit on disk, so within-session accumulate-then-compare never
504
- * round-trips through the quantizer. Regenerable — a miss reads the durable
505
- * 2-bit row. */
506
- protected _haloExact: BoundedMap<NodeId, Vec>;
507
- /** NORMALIZED halo read cache — the decoded, normalized vector {@link halo}
508
- * returns, cached by id so repeat reads skip the per-call 2-bit decode and
509
- * normalize of a full D-element row (measured on a trained store: ~15K
510
- * halo() calls per deep query over ~50 distinct ids — all but the first
511
- * per id pure re-decode). Point-invalidated by {@link pourHalo}, the one
512
- * halo mutation site. Callers receive a COPY, so the cached vector is
513
- * never aliased. Regenerable — a miss re-decodes the durable row. */
514
- protected _haloNorm: BoundedMap<NodeId, Vec>;
515
- /** Interiors deliberately SKIPPED by indexSubtree (unique nodes with 1 parent
516
- * that bridge nothing). Remembered so subsequent visits prune the subtree
517
- * without re-checking parent count. LRU-bounded: an evicted entry is
518
- * re-checked on next visit — if it gained parents in the meantime, it will
519
- * be promoted to the index. */
520
- protected _coveredIds: BoundedMap<NodeId, true>;
521
- /** Live content-index id set, LRU-bounded so a massive ingest never leaks
522
- * memory; an evicted entry is still indexed (the row is durable), so the
523
- * only cost of an eviction is a duplicate index probe on next visit. */
524
- protected _indexedIds: BoundedMap<NodeId, true>;
525
- /** ANN read cache for {@link resonate} — keyed by vecKey(v) + ":" + k;
526
- * lazily initialised, dropped on any index mutation. */
527
- protected _resonateCache: Map<string, Hit[]> | null;
528
- /** ANN read cache for {@link resonateHalo} — same scheme. */
529
- protected _resonateHaloCache: Map<string, Hit[]> | null;
530
- /** Content (gist) index write buffer. */
531
- protected _contentBuffer: Array<{
532
- id: NodeId;
533
- vector: Float32Array;
534
- }>;
535
- /** Halo index write buffer — keyed by id so repeats within a batch coalesce. */
536
- protected _haloBuffer: Map<number, Float32Array<ArrayBufferLike>>;
537
- /** Containment write buffer: child → new parents, merged on flush cadence. */
538
- protected _containBuf: Map<number, Set<number>>;
539
- /** Dedup-target candidates still in the write buffer (keyed by id). Only
540
- * roots that have gained an edge/halo are targets; a fresh intermediate
541
- * branch is never folded onto. */
542
- protected _nearDedupBuf: Map<number, Float32Array<ArrayBufferLike>>;
543
- /** Ids currently in `_contentBuffer` (not yet flushed) — O(1) membership. */
544
- protected _bufferedIds: Set<number>;
545
- /** {@link Store.chainRun} results, valid for the store's lifetime BETWEEN
546
- * writes: a chain is a pure function of the kid and edge tables, so any
547
- * write that could break a node's transparency (a fresh mint inserting kid
548
- * rows, a link inserting an edge) drops the whole cache — see the two
549
- * invalidation sites. Regenerable; a miss re-walks. */
550
- protected _chainMemo: BoundedMap<NodeId, NodeId[]>;
551
- /** Distinct edge-source count — the store's DOCUMENT COUNT (how many
552
- * learnt contexts predict a continuation), the N of every
553
- * inverse-document-frequency read. −1 until first asked for; from then
554
- * on maintained INCREMENTALLY by {@link link} (edges are never deleted),
555
- * so a read is O(1) — never a table scan on the recall path. */
556
- protected _edgeSrcCount: number;
557
- constructor(config: StoreConfig, D: number, maxGroup: number);
558
- get D(): number;
559
- /** Await the async initialisation performed by the concrete constructor. */
560
- protected _ensureReady(): Promise<void>;
561
- has(id: NodeId): boolean;
562
- protected mintId(): NodeId;
563
- nodeCount(): number;
564
- size(): Promise<number>;
565
- get(id: NodeId): NodeRec | null;
566
- /** Reconstruct the bytes a node spans by traversing the DAG bottom-up.
567
- * Iterative post-order on an explicit stack — the call stack never sees the
568
- * tree depth, so even an adversarial chain of nodes stays safe. */
569
- /** How many reads hit a MISSING node record this session (a dangling edge
570
- * or kid id). Zero in a healthy store; a growing count means references
571
- * outlive their records — the read degrades safely to empty bytes, this
572
- * counter is what keeps that degradation observable. */
573
- danglingReads: number;
574
- /** {@link Store.meter} — the per-response work accumulator, or null when
575
- * nothing is profiling. Every read below bumps it through `?.`, so an
576
- * unprofiled store pays one null check per read and allocates nothing. */
577
- meter: Meter | null;
578
- bytes(id: NodeId): Uint8Array;
579
- /** First `maxLen` bytes of a node. Walks only the leftmost branch,
580
- * stopping at `maxLen` — so a 1 MB document root costs the same as a
581
- * 4-byte leaf. Recursive, but tree depth is logarithmic.
582
- *
583
- * IMMUTABILITY CONTRACT (applies to {@link bytes} too): returned arrays
584
- * may be shared with the byte cache and with other callers — treat them
585
- * as read-only. Mutating one would corrupt every subsequent read. */
586
- bytesPrefix(id: NodeId, maxLen: number): Uint8Array;
587
- /** {@link bytesPrefix}'s recursive body — uncharged; see the metering
588
- * boundary note there. */
589
- private _prefix;
590
- contentLen(id: NodeId, cap?: number): number;
591
- findLeaf(bytes: Uint8Array): NodeId | null;
592
- /** {@link findBranch} for a run of SINGLE-BYTE leaves, addressed by the
593
- * bytes themselves — no kid array, no key string, no copy.
594
- *
595
- * A flat branch stores its children as {@link flatKidsBytes}, and that
596
- * encoding is the identity on single-byte leaves: kid id −(b+1) IS byte b.
597
- * So for such a run the kid array and the byte span are the same object in
598
- * two spellings, and `findBranch(leafIds.slice(i, j))` and this call are
599
- * the same lookup — except that the array path allocates the slice, then
600
- * `kids.join(",")`, then the flat bytes, all O(span), for a probe whose
601
- * answer is usually "no". The bloom filter behind `_dbFindBranchByLeaf`
602
- * answers most of those with no I/O at all, so the allocations dominated.
603
- *
604
- * Pass a subarray: it is a view, so a caller scanning spans of a query
605
- * allocates nothing per probe. Deliberately NOT memoized — its callers
606
- * probe many spans that miss, and a key string per probe is the cost this
607
- * exists to remove. */
608
- findFlatBranch(bytes: Uint8Array): NodeId | null;
609
- findBranch(kids: NodeId[]): NodeId | null;
610
- parents(id: NodeId): NodeId[];
611
- parentsFirst(id: NodeId, limit: number): NodeId[];
612
- hasParents(id: NodeId): boolean;
613
- chainRun(id: NodeId): readonly NodeId[];
614
- /** {@link Store.chainRun}'s walk, node at a time through the existing
615
- * probes. Adapters with a set-based query engine should override with a
616
- * single server-side descent (the SQLite adapter uses a recursive CTE). */
617
- protected _chainWalk(id: NodeId, cap: number): NodeId[];
618
- addContainer(child: NodeId, parent: NodeId): void;
619
- hasContainers(child: NodeId): boolean;
620
- containersSlice(child: NodeId, offset: number, limit: number): NodeId[];
621
- containers(child: NodeId): NodeId[];
622
- private flatLeafIds;
623
- /** On a dedup HIT, keep the node's gist available for lazy indexing —
624
- * EXACTLY when it is not already indexed. Replaces the old id-range
625
- * "recency" heuristic (id ≥ nextId − cacheWindow), which conflated an LRU
626
- * entry COUNT with an id RANGE and permanently refused to index any node
627
- * that first became a resonance target long after it was minted (an early
628
- * interior later reused as an edge/halo-bearing deposit root was silently
629
- * unreachable by resonance). The durable index itself is the arbiter:
630
- * one point query, cached in `_indexedIds` on a hit so repeats are O(1). */
631
- private captureIfUnindexed;
632
- /** If `id` structurally bridges ≥2 experiences (the post-hoc compaction
633
- * criterion), promote its gist into the content index NOW — the exact
634
- * moment it becomes useful for multi-experience recall. The 1→2 parent
635
- * transition fires on {@link _dbInsertKid} during mint, and nodes that
636
- * were already bridges but missed indexing (gist evicted, pre-transition
637
- * store) are recaptured in {@link captureIfUnindexed}.
638
- *
639
- * A no-op when the node is already indexed or its gist is evicted from
640
- * the pending cache — a future re-encounter will retry. */
641
- private promoteBridge;
642
- /** Re-index a node under a DIFFERENT gist for the same content.
643
- *
644
- * Normally a node's gist is a pure function of its id, so indexGist skips
645
- * anything already indexed. Step 1b of {@link intern} breaks that: it
646
- * reuses an id for the same BYTES folded a different way, and the two
647
- * foldings have different gists. The index holds one vector per id, so
648
- * the node must carry the gist a direct query of those bytes will present
649
- * — otherwise it is unreachable from exactly the query that names it.
650
- *
651
- * A no-op when the gists agree, so the ordinary path pays one comparison
652
- * and nothing else. */
653
- private recaptureGist;
654
- private intern;
655
- /** Whether the byte content under `kids` and the byte content of `targetId`
656
- * are identical except for ONE local span of at most `W` bytes on each side
657
- * — the near dedup's byte-grain definition of a near-duplicate. A
658
- * common-prefix / common-suffix trim: whatever remains after both trims is
659
- * the single differing span (substitution, insertion or deletion), and both
660
- * remainders must fit the budget. Scattered differences leave a wide
661
- * middle and are rejected. */
662
- private differsByOneWindow;
663
- putLeaf(bytes: Uint8Array, gist: Vec): Promise<NodeId>;
664
- putBranch(kids: NodeId[], gist: Vec): Promise<NodeId>;
665
- /** Promote a node's captured gist into the content (resonance) index, once.
666
- * Called the first time a node becomes a target — i.e. from `link` (it bears
667
- * or receives a continuation edge) or `pourHalo` (it gains distributional
668
- * company). Idempotent: a node already indexed, or whose gist has been evicted
669
- * from the bounded pending map, is a no-op.
670
- *
671
- * `dedupTarget` marks the node a candidate the near dedup may fold a fresh
672
- * near-gist branch ONTO. Only a genuine target — an edge/halo-bearing ROOT —
673
- * is one; a climb-only interior is reach-indexed but never a dedup sink. */
674
- protected indexGist(id: NodeId, dedupTarget: boolean): void;
675
- /** {@link Store.indexTarget} — the public hook for marking a deposit root a
676
- * resonance target, the one target `link`/`pourHalo` do not cover. A deposit
677
- * root is a genuine target (a whole experience), so it is a dedup target
678
- * too. */
679
- indexTarget(id: NodeId): void;
680
- /** Index a node and its interior forms as resonance targets. A node that
681
- * gains an edge is a learnt EXPERIENCE, and the consensus climb
682
- * ({@link Mind.climbAttention}) answers a query naming only a PORTION of it by
683
- * resonating its SUB-REGIONS — branch nodes within the experience — and
684
- * climbing their parents back to it.
685
- *
686
- * EVERY interior branch is indexed unconditionally, and this is
687
- * LOAD-BEARING: indexing only structural bridges (nodes with ≥2 parents,
688
- * the post-hoc compaction criterion) was tried and REJECTED by the test
689
- * suite — partial recall of an experience's interior slices, multi-topic
690
- * attention, and counterfactual anchoring all resonate to SINGLE-parent
691
- * interiors (13 tests fail without them). Post-hoc structural compaction
692
- * ({@link compactContentIndex}) may still remove them, but that is a
693
- * storage/recall trade-off for archived stores, not a free optimisation.
694
- * The store's hash-cons bounds the index by the number of DISTINCT byte
695
- * patterns in the corpus — not by the number of deposits.
696
- *
697
- * Only the ROOT is a DEDUP TARGET — the whole experience a fresh near-gist
698
- * branch may legitimately fold onto. Interior nodes are REACH-ONLY: they
699
- * let a partial query resonate and climb, but a fresh branch must never
700
- * merge onto an interior node of another experience.
701
- *
702
- * Iterative explicit-queue walk: the call stack never sees tree depth. */
703
- protected indexSubtree(root: NodeId): void;
704
- resonate(v: Vec, k: number, exhaustive?: boolean): Promise<Hit[]>;
705
- indexedVectorCount(): number;
706
- lastResonateReads(): number;
707
- /** How many physical compaction attempts have failed this session. Zero in
708
- * a healthy store; a growing count means tombstones are accumulating and
709
- * index query cost is drifting up (the first failure also warns once). */
710
- compactFailures: number;
711
- /** Meta key holding the incremental scan watermark of
712
- * {@link compactContentIndex}: "minParents:maxInternalIdScanned". KEEP
713
- * decisions are MONOTONE — parents, edges and halos only ever grow, so an
714
- * entry once kept can never become removable — and removed entries are
715
- * gone, so a pass only ever needs to examine entries indexed AFTER the
716
- * previous pass. Internal ids are monotone and survive the index's
717
- * splice compaction; the watermark is reset whenever a PHYSICAL index
718
- * compaction runs (id reuse after a dropped top row would otherwise hide
719
- * new entries behind it). */
720
- protected static readonly COMPACT_WATERMARK_KEY = "contentCompact.watermark";
721
- /** {@link Store.compactContentIndex} */
722
- compactContentIndex(minParents?: number): Promise<number>;
723
- /** {@link Store.repairContentIndex} */
724
- repairContentIndex(regenerateGist: (id: NodeId) => Promise<Vec | null>, minParents?: number): Promise<number>;
725
- link(from: NodeId, to: NodeId): Promise<void>;
726
- next(id: NodeId): NodeId[];
727
- /** {@link Store.hasNext} — one indexed point probe, never a range read. */
728
- hasNext(id: NodeId): boolean;
729
- prev(id: NodeId): NodeId[];
730
- nextFirst(id: NodeId, limit: number): NodeId[];
731
- prevFirst(id: NodeId, limit: number): NodeId[];
732
- /** {@link Store.prevCount}. Subclasses with an indexed reverse-edge count
733
- * should override; this default materialises (correct, not optimal). */
734
- prevCount(id: NodeId): number;
735
- edgeSourceCount(): number;
736
- haloMass(id: NodeId): number;
737
- halo(id: NodeId): Vec | null;
738
- /** {@link Store.hasHalo} — MUST mirror {@link halo}'s null condition
739
- * exactly (row present AND mass ≥ minHaloMass), minus the decode. */
740
- hasHalo(id: NodeId): boolean;
741
- pourHalo(id: NodeId, add: Vec): Promise<void>;
742
- resonateHalo(v: Vec, k: number): Promise<Hit[]>;
743
- private pending;
744
- protected flushContent(): number;
745
- protected flushHalos(): number;
746
- /** Append the buffered containment pairs, inside the deferred transaction.
747
- * Pure appends: durable dedup lives in the adapter (the pair PK), so a
748
- * flush never reads a child's stored list back — the old packed-blob
749
- * read-merge-rewrite was O(fan-in) per touched child per flush, quadratic
750
- * over a long training run on a hot window. */
751
- protected flushContain(): void;
752
- /** Flush all three buffers; compact vector indices on a write-volume cadence. */
753
- protected flush(): void;
754
- protected maybeFlush(): Promise<void>;
755
- setMeta(key: string, val: string): Promise<void>;
756
- getMeta(key: string): Promise<string | null>;
757
- deleteMeta(key: string): Promise<void>;
758
- saveSnapshot(bytes: Uint8Array): Promise<void>;
759
- loadSnapshot(): Promise<Uint8Array | null>;
760
- commit(): void;
761
- close(): Promise<void>;
762
- }