@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,716 +0,0 @@
1
- // traverse.ts — Traverse primitives + disambiguation (Sections 1 & 6 of the mind).
2
- //
3
- // Traverse — node → nodes (edgeAncestors, nextOf, prevOf, contains,
4
- // guidedNext, chooseNext, chooseAmong, hubCap)
5
- //
6
- // The PROJECTIONS built on these walks (follow, conceptHop, reverseContext,
7
- // project) live in match.ts — the elementary match-and-project operation.
8
- import { cosine } from "../vec.js";
9
- import { gistOf, read } from "./primitives.js";
10
- import { leafIdRun } from "./canonical.js";
11
- //
12
- // Budgeted on the same terms as the reach memo below (AGENTS §2.12): these
13
- // three maps are cleared on every write, but a long read-only session over a
14
- // large store converges on one entry per node per map with nothing to bound
15
- // it. Past the cap all three are dropped together and re-derived, costing
16
- // cold structural probes and never a wrong answer.
17
- const STRUCT_MEMO_MAX = 100_000;
18
- const structCaches = new WeakMap();
19
- // ── The shared ancestor-reach memo ──────────────────────────────────────
20
- //
21
- // `edgeAncestors` is a pure function of (node, N) over a read-only store —
22
- // asking never writes — so its result is reusable for as long as the store
23
- // is not written to. There used to be TWO memos and they never met: the
24
- // climb built a private one per call (computeAttention), while
25
- // `Precomputed.reachMemo` — documented as "one response-scoped memo serves
26
- // every mechanism that prices commonality" — was reached only by confluence.
27
- // The climb is by far the biggest consumer.
28
- //
29
- // Keyed by the Mind's structural lifecycle identity: ordinary and
30
- // conversational asks share it, and every ingest invalidates it. A real
31
- // battery repeatedly reaches the same corpus scaffolding even when its
32
- // surface questions differ.
33
- //
34
- // Budgeted, not unbounded (AGENTS §2.12): past the cap the whole map is
35
- // dropped and re-derived, costing a cold climb and never a wrong answer.
36
- const REACH_MEMO_MAX = 100_000;
37
- const reachCaches = new WeakMap();
38
- /** The reach memo this ask should use — see the note above.
39
- *
40
- * A TRACED response always gets a fresh, empty one. `AncestorReach`'s
41
- * `visited`/`maxDepth`/`saturation` fields are populated only when a trace
42
- * is attached, so an entry deposited by an untraced earlier turn would
43
- * silently black out the reach detail of a later traced one; and the trace's
44
- * reach payload is serialised by ITERATING this map, which must therefore
45
- * hold what THIS climb consulted, not the whole conversation's history.
46
- * Consistent with AGENTS §2.11: a traced response is a different machine —
47
- * never benchmark with a trace attached. */
48
- export function sharedReachMemo(ctx) {
49
- if (ctx.trace !== null || ctx.climbMemo === null)
50
- return new Map();
51
- let m = reachCaches.get(ctx._structMemoKey);
52
- if (m === undefined)
53
- reachCaches.set(ctx._structMemoKey, m = new Map());
54
- else if (m.size >= REACH_MEMO_MAX)
55
- m.clear();
56
- return m;
57
- }
58
- function getStructCache(ctx) {
59
- if (ctx.climbMemo === null)
60
- return null;
61
- let c = structCaches.get(ctx._structMemoKey);
62
- if (c === undefined) {
63
- structCaches.set(ctx._structMemoKey, c = {
64
- hasNext: new Map(),
65
- prevCount: new Map(),
66
- hasParents: new Map(),
67
- });
68
- }
69
- else if (c.hasNext.size >= STRUCT_MEMO_MAX ||
70
- c.prevCount.size >= STRUCT_MEMO_MAX ||
71
- c.hasParents.size >= STRUCT_MEMO_MAX) {
72
- c.hasNext.clear();
73
- c.prevCount.clear();
74
- c.hasParents.clear();
75
- }
76
- return c;
77
- }
78
- /** Invalidate every session-lifetime structural read after a write. */
79
- export function invalidateStructuralCaches(ctx) {
80
- reachCaches.delete(ctx._structMemoKey);
81
- structCaches.delete(ctx._structMemoKey);
82
- }
83
- /** Cached {@link Store.hasNext} — pure during one respond(). */
84
- function cachedHasNext(ctx, id, cache) {
85
- if (cache === null)
86
- return ctx.store.hasNext(id);
87
- let v = cache.hasNext.get(id);
88
- if (v === undefined) {
89
- v = ctx.store.hasNext(id);
90
- cache.hasNext.set(id, v);
91
- }
92
- return v;
93
- }
94
- /** Cached {@link Store.prevCount} — pure during one respond(). */
95
- function cachedPrevCount(ctx, id, cache) {
96
- if (cache === null)
97
- return ctx.store.prevCount(id);
98
- let v = cache.prevCount.get(id);
99
- if (v === undefined) {
100
- v = ctx.store.prevCount(id);
101
- cache.prevCount.set(id, v);
102
- }
103
- return v;
104
- }
105
- /** Cached {@link Store.hasParents} — pure during one respond(). */
106
- function cachedHasParents(ctx, id, cache) {
107
- if (cache === null)
108
- return ctx.store.hasParents(id);
109
- let v = cache.hasParents.get(id);
110
- if (v === undefined) {
111
- v = ctx.store.hasParents(id);
112
- cache.hasParents.set(id, v);
113
- }
114
- return v;
115
- }
116
- // ── Graph climbing ───────────────────────────────────────────────────────
117
- /** Climb the structural DAG from a node to its edge-bearing ancestor contexts.
118
- * Ascent stops at hub nodes (parents > √N) — their reach is non-discriminative.
119
- * When the start node has no structural parents, climbs from containment parents
120
- * (sub-span flat branches inheriting their chunks' context).
121
- *
122
- * `memo`, when given, caches whole climbs by start id for the duration of ONE
123
- * query (the store is read-only while a query is in flight, so a climb is a
124
- * pure function of the id). The consensus pipeline climbs the SAME anchors
125
- * repeatedly — regions sharing a chunk, and canonicalChunkId probing each
126
- * chunk's prefixes — so without the memo every repeat re-pays the full
127
- * fan-out reads. */
128
- export function edgeAncestors(ctx, id, contextCount, memo) {
129
- const hit = memo?.get(id);
130
- if (hit !== undefined)
131
- return hit;
132
- // BYTE-ATOM COMMONALITY. A single-byte leaf (implicit negative id) has no
133
- // structural parents BY CONSTRUCTION — atoms are never linked into the kid
134
- // or contain tables — so this climb cannot observe its containment at all.
135
- // The walk below would see only the atom's own direct edges and report
136
- // contextsReached ≈ 1, turning the MOST common content in the store into
137
- // the MOST discriminative voter (observed on a 325K-context store: every
138
- // recognised single-letter site voted full ln N for the one fact whose
139
- // continuation is that letter, and their pooled sum out-voted every
140
- // genuine anchor). An unmeasurable containment must not default to
141
- // "maximally rare": it is bounded below by the uniform expectation over
142
- // the byte alphabet — N contexts, each at least one chunk of up to W of
143
- // the 256 possible atoms, reach ≥ N·W/256 contexts per atom on average
144
- // (see {@link atomReach}). When that floor itself exceeds the hub bound
145
- // √N the atom is a hub at this corpus scale and the climb abstains
146
- // (saturated) — the atom's own edges remain fully traversable (tier-0
147
- // exact recall, chooseNext, project); only its say as a consensus voter
148
- // is withdrawn. On a small store the floor stays ≤ √N and the atom
149
- // climbs exactly as before, so single-letter facts keep working.
150
- if (id < 0 && atomIsHub(ctx, contextCount)) {
151
- const bound0 = boundFor(contextCount);
152
- const reach = {
153
- roots: [],
154
- contextsReached: 0,
155
- saturated: true,
156
- ...(ctx.trace
157
- ? {
158
- saturation: {
159
- reason: "byte-atom-commonality",
160
- node: id,
161
- observed: atomReach(ctx, contextCount),
162
- limit: bound0,
163
- },
164
- visited: 0,
165
- maxDepth: 0,
166
- }
167
- : {}),
168
- };
169
- memo?.set(id, reach);
170
- return reach;
171
- }
172
- const bound = boundFor(contextCount);
173
- const roots = [];
174
- const seen = new Set([id]);
175
- const ctxSeen = new Set();
176
- let saturated = false;
177
- // Provenance of the FIRST decision that saturated this climb — allocated
178
- // only when a trace is requested (see AncestorReach.saturation's doc); the
179
- // climb itself never reads it back.
180
- let satStop;
181
- // EXPAND-UNTIL-DECIDED: a reach is consumed either as a VOTE (which needs
182
- // contextsReached exactly, and only while ≤ √N — beyond that the region is
183
- // non-discriminative) or as an ABSTENTION (saturated — whose roots and
184
- // counts no consumer reads). So the climb may STOP the moment the answer
185
- // is decided:
186
- // • a node whose prev fan-in alone exceeds √N decides it (its
187
- // predecessors are √N+ distinct contexts) — no read needed, prevCount
188
- // is an indexed O(1);
189
- // • distinct contexts crossing √N decides it;
190
- // • a node with more than √N parents decides its own expansion (the
191
- // classic hub guard; the walk aborts rather than continue, which no
192
- // consumer can distinguish — saturated reaches are never voted).
193
- // Below every decision threshold the walk is EXACT — identical roots and
194
- // contexts to the unbounded climb — because prevFirst(√N) IS the full prev
195
- // list and parentsFirst(√N+1) IS the full parent list whenever they do not
196
- // decide. Work is bounded by √N contexts × the climb's local structure,
197
- // never by the corpus.
198
- const structCache = getStructCache(ctx);
199
- // LATERAL-BRANCH ACCOUNT — the cumulative dual of the per-node hub guard.
200
- // Within one deposit the ascent is a CHAIN (each node's first parent);
201
- // every parent BEYOND a node's first is an entry into another containing
202
- // structure (hash-consing: a shared subtree's extra parents are other
203
- // deposits' chunks). The per-node guard already declares a node with more
204
- // than √N parents non-discriminative; a climb whose ACCUMULATED lateral
205
- // entries exceed √N has spread across just as many distinct containing
206
- // structures — the same commonness, distributed along the cone instead of
207
- // concentrated at one node — and is decided: saturated. A deep chain in
208
- // ONE structure accrues no laterals, so legitimate deep scaffolding (a
209
- // fragment far down a long cumulative context) still climbs to its root
210
- // at any depth; what dies is the cross-structure drift that visited tens
211
- // of thousands of edge-free interiors (profiled on a 17.7M-node store:
212
- // ~20K distinct nodes per climb family, >95% unique — not memoisable)
213
- // while the context account never decided.
214
- let lateral = 0;
215
- // CLIMB READ-OUT (pure instrumentation, same contract as satStop): the
216
- // parallel `depths` stack mirrors every push/pop of `stack`, so a node's
217
- // ascent distance is known at its pop. Allocated only when a trace is
218
- // requested; the climb itself never reads any of these back.
219
- const depths = ctx.trace ? [] : null;
220
- let curDepth = 0;
221
- let visitedCount = 0;
222
- let maxDepth = 0;
223
- const visit = (x) => {
224
- if (ctx.meter)
225
- ctx.meter.ancestorVisits++;
226
- if (depths) {
227
- visitedCount++;
228
- if (curDepth > maxDepth)
229
- maxDepth = curDepth;
230
- }
231
- const hasNx = cachedHasNext(ctx, x, structCache);
232
- const pc = cachedPrevCount(ctx, x, structCache);
233
- if (hasNx || pc > 0) {
234
- roots.push(x);
235
- if (hasNx)
236
- ctxSeen.add(x);
237
- if (pc > bound) {
238
- // decided: ≥ pc > √N distinct contexts
239
- if (ctx.trace) {
240
- satStop = {
241
- reason: "predecessor-fan-in",
242
- node: x,
243
- observed: pc,
244
- limit: bound,
245
- };
246
- }
247
- return false;
248
- }
249
- for (const p of ctx.store.prevFirst(x, bound))
250
- ctxSeen.add(p);
251
- if (ctxSeen.size > bound) {
252
- // decided
253
- if (ctx.trace) {
254
- satStop = {
255
- reason: "distinct-context-limit",
256
- node: x,
257
- observed: ctxSeen.size,
258
- limit: bound,
259
- };
260
- }
261
- return false;
262
- }
263
- }
264
- const parents = ctx.store.parentsFirst(x, bound + 1);
265
- if (parents.length > bound) {
266
- // decided: hub
267
- if (ctx.trace) {
268
- satStop = {
269
- reason: "parent-fan-out",
270
- node: x,
271
- observed: parents.length,
272
- limit: bound,
273
- };
274
- }
275
- return false;
276
- }
277
- let fresh = 0;
278
- for (const p of parents) {
279
- if (!seen.has(p)) {
280
- seen.add(p);
281
- stack.push(p);
282
- depths?.push(curDepth + 1);
283
- fresh++;
284
- }
285
- }
286
- if (fresh > 1) {
287
- lateral += fresh - 1;
288
- if (lateral > bound) {
289
- // decided: cone-wide hub
290
- if (ctx.trace) {
291
- satStop = {
292
- reason: "lateral-cone-limit",
293
- node: x,
294
- observed: lateral,
295
- limit: bound,
296
- };
297
- }
298
- return false;
299
- }
300
- }
301
- return true;
302
- };
303
- const stack = [];
304
- const containment = !cachedHasParents(ctx, id, structCache);
305
- if (!containment) {
306
- stack.push(id);
307
- depths?.push(0);
308
- }
309
- // The containment seed is STREAMED in pages of √N: a distinctive window's
310
- // containers (which converge on one or two contexts, however many chunks
311
- // of one deposit repeat it) are walked IN FULL — exact — while a common
312
- // window's corpus-sized container list is abandoned at the first decision
313
- // above, after O(√N) pages at most (each page adds containers whose climbs
314
- // add contexts; √N distinct contexts decide).
315
- let containerOff = 0;
316
- let containersExhausted = !containment;
317
- climb: for (;;) {
318
- if (stack.length === 0) {
319
- if (containersExhausted)
320
- break;
321
- const page = ctx.store.containersSlice(id, containerOff, bound);
322
- containerOff += page.length;
323
- if (page.length < bound)
324
- containersExhausted = true;
325
- for (const c of page) {
326
- if (!seen.has(c)) {
327
- seen.add(c);
328
- stack.push(c);
329
- depths?.push(1);
330
- }
331
- }
332
- if (stack.length === 0) {
333
- if (containerOff === 0) {
334
- stack.push(id); // no containers at all
335
- depths?.push(0);
336
- }
337
- else
338
- break;
339
- }
340
- }
341
- while (stack.length > 0) {
342
- let x = stack.pop();
343
- if (depths)
344
- curDepth = depths.pop();
345
- // TRANSPARENT-CHAIN HOP: a node with no edges in or out and exactly one
346
- // parent contributes nothing here — no root, no context, no lateral
347
- // entry — so the run to its first non-transparent ancestor is skipped
348
- // in ONE store read (Store.chainRun) instead of three probes per node.
349
- // The interior nodes still enter `seen`, exactly as a node-at-a-time
350
- // ascent would have recorded them at push time, so sibling entries into
351
- // the same chain keep identical fresh/lateral accounting; and if the
352
- // terminal was already seen (another chain merged into this one first),
353
- // it is not visited twice — the same dedup the push-time seen-check
354
- // used to provide.
355
- const run = ctx.store.chainRun(x);
356
- if (run.length > 1) {
357
- const top = run[run.length - 1];
358
- const dup = seen.has(top);
359
- for (let i = 1; i < run.length; i++)
360
- seen.add(run[i]);
361
- if (dup)
362
- continue;
363
- x = top;
364
- // The chain's interior hops are part of the terminal's ascent
365
- // distance — count them exactly as a node-at-a-time ascent would.
366
- if (depths)
367
- curDepth += run.length - 1;
368
- }
369
- if (!visit(x)) {
370
- saturated = true;
371
- break climb;
372
- }
373
- }
374
- }
375
- const reach = {
376
- roots,
377
- contextsReached: ctxSeen.size,
378
- saturated,
379
- ...(saturated && satStop ? { saturation: satStop } : {}),
380
- ...(depths ? { visited: visitedCount, maxDepth } : {}),
381
- };
382
- memo?.set(id, reach);
383
- return reach;
384
- }
385
- /** Convenience: forward edges of a node. */
386
- export function nextOf(ctx, id) {
387
- return ctx.store.next(id);
388
- }
389
- /** Convenience: reverse edges of a node. */
390
- export function prevOf(ctx, id) {
391
- return ctx.store.prev(id);
392
- }
393
- /** The uniform-expectation floor on a byte atom's corpus commonality: N
394
- * learnt contexts, each at least one perception chunk of up to W of the 256
395
- * possible byte values, contain a given atom in ≥ N·W/256 contexts on
396
- * average. An atom's TRUE containment is unmeasurable (atoms carry no
397
- * kid/contain links by construction), so this floor is the honest stand-in:
398
- * derived entirely from the corpus scale N, the perception window W, and
399
- * the alphabet size — never tuned. */
400
- export function atomReach(ctx, contextCount) {
401
- return Math.max(1, Math.ceil((contextCount * ctx.space.maxGroup) / 256));
402
- }
403
- /** Whether a byte atom is a hub at this corpus scale — its commonality floor
404
- * {@link atomReach} exceeds the hub bound √N. Below it (small stores) an
405
- * atom votes and is recognised exactly as any stored form; above it the
406
- * alphabet is scaffolding everywhere and abstains. */
407
- export function atomIsHub(ctx, contextCount) {
408
- return atomReach(ctx, contextCount) > boundFor(contextCount);
409
- }
410
- /** Whether a node LEADS SOMEWHERE — it bears a continuation edge or a halo.
411
- * The admission predicate recognition filters sites with (HOW_IT_WORKS
412
- * §15.3): a form that leads nowhere contributes nothing to any derivation.
413
- * Runs once per candidate span on the recognition hot path — `hasNext` is
414
- * cached per response (the same flat-branch ids are probed across prefix
415
- * variants by canonicalChunkId). `hasHalo` is not cached: it's a single
416
- * indexed point probe per candidate, and the candidates that reach this
417
- * check have already been filtered by hasNext above in edgeAncestors. */
418
- export function leadsSomewhere(ctx, id) {
419
- const memo = getStructCache(ctx);
420
- if (cachedHasNext(ctx, id, memo))
421
- return true;
422
- return ctx.store.hasHalo(id);
423
- }
424
- /** The structural IDF read of ONE node: how many distinct learnt contexts
425
- * its containment/edge climb reaches, or Infinity when it reaches none or
426
- * saturates (no usable identity evidence). The number every
427
- * discriminative-vs-scaffolding decision derives from — paired with the
428
- * half-dominance convention (geometry.dominates(reach, N)): content
429
- * reaching a corpus MINORITY of contexts discriminates (an entity, a
430
- * filler); content reaching a majority is frame scaffolding. */
431
- export function reachOf(ctx, id, contextCount, memo) {
432
- const r = edgeAncestors(ctx, id, contextCount, memo);
433
- if (r.saturated || r.roots.length === 0)
434
- return Infinity;
435
- return Math.max(1, r.contextsReached);
436
- }
437
- /** The corpus scale N — the count of DISTINCT learnt contexts, floored at 2
438
- * so its derived readings (ln N in the consensus floor, √N in the hub bound)
439
- * stay meaningful on a near-empty store. The one definition every consumer
440
- * of "how big is this corpus?" reads. */
441
- export function corpusN(ctx) {
442
- return Math.max(2, ctx.store.edgeSourceCount());
443
- }
444
- /** The hub bound √N itself (≥ 2 always, since N is floored at 2) — for
445
- * consumers that pass it to the store's LIMITed reads instead of capping a
446
- * materialised list. {@link hubCap} is the list-side reading of the same
447
- * convention. */
448
- export function hubBound(ctx) {
449
- return boundFor(corpusN(ctx));
450
- }
451
- /** √N for an EXPLICIT context count — the ctx-free reading of {@link
452
- * hubBound}, for the callers inside this module that are handed a count
453
- * rather than a context ({@link edgeAncestors}, {@link atomIsHub}). The
454
- * floor at 2 matches {@link corpusN}'s, so both readings agree for every
455
- * input: the two used to be spelled out inline, once WITH the floor and
456
- * once without, in the same function. */
457
- function boundFor(contextCount) {
458
- return Math.ceil(Math.sqrt(Math.max(2, contextCount)));
459
- }
460
- /** Cap a candidate list at the hub bound √N (insertion order) — the ONE
461
- * fan-out convention every walk and disambiguation uses (see HOW_IT_WORKS
462
- * §8.6). A node connected to more than √N others is a hub whose individual
463
- * connections carry ~no discriminative information; materialising or scoring
464
- * them all would make single decisions scale with the corpus. */
465
- export function hubCap(ctx, ids) {
466
- const bound = hubBound(ctx);
467
- return ids.length > bound ? ids.slice(0, bound) : ids;
468
- }
469
- /** Whether `descendant` lies within `ancestor`'s subtree — a structural DAG
470
- * relation read off the hash-consed `kids` lists, by a bounded explicit-stack
471
- * descent. Used by articulation to keep a voice from revoicing a fragment
472
- * OF that voice. */
473
- export function contains(ctx, ancestor, descendant) {
474
- if (ancestor === descendant)
475
- return true;
476
- const seen = new Set([ancestor]);
477
- const stack = [ancestor];
478
- while (stack.length > 0) {
479
- const rec = ctx.store.get(stack.pop());
480
- if (!rec?.kids)
481
- continue;
482
- for (const k of rec.kids) {
483
- if (k === descendant)
484
- return true;
485
- if (!seen.has(k)) {
486
- seen.add(k);
487
- stack.push(k);
488
- }
489
- }
490
- }
491
- return false;
492
- }
493
- // ── Edge disambiguation (Section 6) ──────────────────────────────────────
494
- /** The best-scoring item by cosine against `query`, among items scoring at
495
- * or above `threshold` — the shared arg-max every Pattern-A "which of these
496
- * resonates best" decision reduces to. `strict` picks the tie-break a
497
- * caller needs: `true` keeps the first-seen leader on a tie (`>`), the
498
- * default lets a later equal score take it (`>=`). */
499
- export function argmaxBy(items, scoreOf, threshold, strict = false) {
500
- let best = null;
501
- for (const item of items) {
502
- const score = scoreOf(item);
503
- const bar = best?.score ?? threshold;
504
- if (strict ? score > bar : score >= bar)
505
- best = { item, score };
506
- }
507
- return best;
508
- }
509
- export function argmaxCosine(query, items, vecOf, threshold, strict = false) {
510
- return argmaxBy(items, (item) => {
511
- const v = vecOf(item);
512
- return v ? cosine(query, v) : -Infinity;
513
- }, threshold, strict);
514
- }
515
- /** The guided-or-first continuation of a node, as answer-shaped bytes source:
516
- * chooseNext under the response guide, falling back to the FIRST-inserted
517
- * edge — the one no-guide convention chooseNext, project() and the search's
518
- * formRules all share. undefined when the node has no continuation. */
519
- export function guidedFirst(ctx, id) {
520
- const pick = guidedNext(ctx, id);
521
- if (pick !== undefined)
522
- return pick;
523
- // No guide in flight (or nothing chosen): the first-inserted edge, read
524
- // with LIMIT 1 — never the full fan-out.
525
- const nx = ctx.store.nextFirst(id, 1);
526
- return nx.length > 0 ? nx[0] : undefined;
527
- }
528
- export function guidedNext(ctx, node) {
529
- if (ctx._edgeGuide === null)
530
- return undefined;
531
- // The pick memo is BYPASSED while a rationale trace is attached — the same
532
- // policy climbMemo and recogniseMemo follow (every mechanism must emit its
533
- // own steps; a memo hit would swallow the repeat's `disambiguate` step).
534
- // Consistency does not need the memo: chooseNext is a pure function of the
535
- // (read-only) store and the guide, so recomputation yields the same pick.
536
- if (!ctx.trace) {
537
- const memo = ctx._edgeChoice.get(node);
538
- if (memo !== undefined)
539
- return memo === -1 ? undefined : memo;
540
- }
541
- const pick = chooseNext(ctx, node, ctx._edgeGuide);
542
- if (!ctx.trace)
543
- ctx._edgeChoice.set(node, pick ?? -1);
544
- return pick;
545
- }
546
- /** Disambiguate among a node's learnt continuations by distributional
547
- * support. NOTE the `guide` contract: its VALUE is deliberately unused —
548
- * only its PRESENCE gates disambiguation (a null guide means no query is in
549
- * flight, so structural walkers keep plain first-edge behaviour). The
550
- * gist-cosine of short answer candidates against a query guide is dominated
551
- * by accidental byte-pattern correlations, not semantic relatedness, so the
552
- * evidence consulted is structural: each candidate's reverse-edge support
553
- * count (see below). Contrast {@link chooseAmong}, the REVERSE-direction
554
- * disambiguator, whose candidates are whole learnt contexts — long enough
555
- * that their perceived gists ARE semantically meaningful — and which
556
- * therefore scores by guide cosine. The two directions consult different
557
- * halves of the evidence on purpose. */
558
- export function chooseNext(ctx, id, guide) {
559
- // CAPPED read: only the first √N continuations are ever candidates (the
560
- // documented hub trade), so only they are read — a hub context's full
561
- // fan-out is corpus-sized and must never be materialised. hubBound ≥ 2,
562
- // so the single-continuation fast path below stays exact.
563
- const nx = ctx.store.nextFirst(id, hubBound(ctx));
564
- if (nx.length === 0)
565
- return undefined;
566
- if (nx.length === 1 || !guide)
567
- return nx[0];
568
- // Cap candidates at √N — the same bound the original chooseAmong used.
569
- // A hub context can accumulate thousands of continuations; the best-fit
570
- // one is among the first √N by insertion order (edges are never deleted,
571
- // so the oldest are the most established). A strongly-supported edge
572
- // inserted beyond the cap is invisible here — the deliberate trade
573
- // against paying O(fan-out) count reads on every disambiguation.
574
- const capped = nx; // already the hub-capped prefix, by the read above
575
- // Distributional-evidence disambiguation, consulting BOTH read-outs of the
576
- // evidence the training poured:
577
- // 1. prevCount — how many DISTINCT contexts predict this candidate (one
578
- // indexed COUNT; never a materialisation — a common continuation's
579
- // reverse fan-in is corpus-sized). Diversity of independent evidence
580
- // is the primary signal: three different formulations agreeing beat
581
- // one formulation repeated.
582
- // 2. haloMass — how many episode signatures were poured into the
583
- // candidate's halo (repetition counts). The tie-break among equally
584
- // diverse candidates: a fact reinforced across many episodes is more
585
- // corroborated than one seen once, and this is the DIRECT measure of
586
- // that — consulting only the structural count would leave poured
587
- // evidence on the table.
588
- // When both are equal, first-inserted wins (backward compatible).
589
- let best = capped[0];
590
- let bestSupport = ctx.store.prevCount(best);
591
- let bestMass = ctx.store.haloMass(best);
592
- for (let i = 1; i < capped.length; i++) {
593
- const support = ctx.store.prevCount(capped[i]);
594
- if (support < bestSupport)
595
- continue;
596
- const mass = ctx.store.haloMass(capped[i]);
597
- if (support > bestSupport || mass > bestMass) {
598
- best = capped[i];
599
- bestSupport = support;
600
- bestMass = mass;
601
- }
602
- }
603
- // NO consensusFloor gate here (tried and reverted — see
604
- // test/40-choosenext-scale-guard.test.mjs): that floor is calibrated for
605
- // POOLED, IDF-weighted CLIMB VOTES (recallByResonance, commitVotes), where
606
- // each corroborating region contributes at most ln N and the floor grows
607
- // with N exactly as that per-region ceiling does (HOW_IT_WORKS.md §8.6).
608
- // `bestSupport` here is a different kind of quantity — a raw prevCount of
609
- // how many training contexts predicted ONE destination, bounded by how
610
- // often that specific fact was retold, never by corpus size N. Gating an
611
- // N-invariant count against an N-growing threshold guarantees failure
612
- // once N is large enough, discarding genuinely, structurally dominant
613
- // edges (observed: a fact corroborated 2-to-1-1-1 refused at N≈325K,
614
- // falling back to a noisy concept-hop). The loop above already IS the
615
- // "genuinely competing" test: a tie leaves first-inserted as the pick
616
- // (test/30's own pinned behaviour); a strict winner is real evidence
617
- // regardless of corpus scale. Matches HOW_IT_WORKS.md §25's own
618
- // chooseNext pseudocode, which has no such floor.
619
- // Trace is built lazily — the filter + map below only execute when a
620
- // trace listener is attached, so the common (no-trace) path pays only
621
- // for the prevCount calls in the loop above, never for extra rItemShort
622
- // byte-reads.
623
- if (ctx.trace) {
624
- const others = capped.filter((c) => c !== best);
625
- ctx.trace.step("disambiguate", [rItemShort(ctx, best, "halo-evidence", bestSupport)], others.map((c) => rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))), `${capped.length} continuations — distributional evidence selects ` +
626
- `the most corroborated (distinct contexts ${bestSupport}, ` +
627
- `poured mass ${bestMass})`);
628
- }
629
- return best;
630
- }
631
- /** The perceived gist of a candidate node, through the session gist cache.
632
- * Re-gisting a candidate is a full river fold of its bytes — the measured
633
- * recall bottleneck (a hub context offers up to √N continuations, EACH
634
- * re-perceived per pick). A node's bytes are immutable and perception is
635
- * pure, so the cached gist is valid for the store's lifetime. Exported for
636
- * every "score node ids against a guide" decision (chooseAmong here, the
637
- * bridge's junction pick) so they share ONE cache and one convention. */
638
- export function candidateGist(ctx, c) {
639
- const hit = ctx._gistCache.get(c);
640
- if (hit !== undefined)
641
- return hit;
642
- const b = read(ctx, c);
643
- if (b.length === 0)
644
- return null;
645
- const g = gistOf(ctx, b);
646
- ctx._gistCache.set(c, g);
647
- return g;
648
- }
649
- export function chooseAmong(ctx, candidates, guide) {
650
- const capped = hubCap(ctx, candidates);
651
- const found = argmaxCosine(guide, capped, (c) => candidateGist(ctx, c), -Infinity, true);
652
- return found
653
- ? { id: found.item, score: found.score }
654
- : { id: candidates[0], score: -Infinity };
655
- }
656
- // ── Trace shim (used by chooseNext before trace module is loaded) ────────
657
- import { decodeText } from "./rationale.js";
658
- function rItemShort(ctx, id, role, score) {
659
- return {
660
- text: decodeText(read(ctx, id)),
661
- node: id,
662
- role,
663
- score,
664
- };
665
- }
666
- /** True when NO window of `query` discriminates anything — every stored
667
- * W-window it spells is contained by more places than the hub bound allows,
668
- * i.e. the whole query is corpus-global scaffolding.
669
- *
670
- * WHAT IT IS FOR. Several mechanisms ground a query through the literal
671
- * spans it did NOT explain, and those spans are the whole of their evidence.
672
- * When every one of them is a hub, the query says nothing the corpus can be
673
- * held to, and grounding it means picking one of thousands of continuations
674
- * it gives no evidence for — a fabrication whatever the answer happens to be.
675
- * Answering with silence there is the honest degradation contract (§2.13).
676
- *
677
- * MEASURED SEPARATION (trained store, hubBound 571) — this is categorical,
678
- * not marginal, and it is why the predicate lives here rather than being
679
- * spelled twice:
680
- * "What is the capital of" ALL saturated ("What":572) → fabricated
681
- * "What is the capital " ALL saturated ("What":572) → fabricated
682
- * "what is the capital of france" min "f fr":248 → correct
683
- * "What is the capitol of France?" min "f Fr":114 → correct
684
- * "WHAT IS THE CAPITAL OF FRANCE?" min "HE C":1 → correct
685
- * "What is the capital of France?" min "t i":4 → correct
686
- * "Who wrote Romeo and Juliet?" min "iet?":26 → correct
687
- * "What is the capital of Zamunda?" min "Zamu":3 → silent anyway
688
- * Note the last: the honest-silence probes are already refused on other
689
- * evidence and sit on the SAME side as the correct ones, so this predicate
690
- * is not what makes them silent and cannot be credited for them.
691
- *
692
- * NO NEW THRESHOLD (§2.2): `hubBound` is the √N reading of "hub" used
693
- * everywhere, and the containment read is clamped to it exactly as every
694
- * other fan-out read is (§2.8). A query with no stored window at all is NOT
695
- * scaffolding-only — it has no evidence either way, and its callers already
696
- * refuse it on their own terms. */
697
- export function allWindowsAreScaffolding(ctx, query) {
698
- const W = ctx.space.maxGroup;
699
- const bound = hubBound(ctx);
700
- let sawOne = false;
701
- for (let o = 0; o + W <= query.length; o++) {
702
- const ids = leafIdRun(ctx, query, o, o + W);
703
- if (ids === null)
704
- continue;
705
- const id = ctx.store.findBranch(ids);
706
- if (id === null)
707
- continue;
708
- const rarity = ctx.store.containersSlice(id, 0, bound + 1).length;
709
- if (rarity === 0)
710
- continue;
711
- if (rarity <= bound)
712
- return false;
713
- sawOne = true;
714
- }
715
- return sawOne;
716
- }