@hviana/sema 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/package.json +1 -1
  2. package/src/mind/attention.ts +355 -19
  3. package/test/53-cross-region-probe-instrumentation.test.mjs +356 -0
  4. package/dist/example/demo.d.ts +0 -1
  5. package/dist/example/demo.js +0 -39
  6. package/dist/example/train_base.d.ts +0 -87
  7. package/dist/example/train_base.js +0 -2252
  8. package/dist/src/alphabet.d.ts +0 -7
  9. package/dist/src/alphabet.js +0 -33
  10. package/dist/src/alu/src/alu.d.ts +0 -185
  11. package/dist/src/alu/src/alu.js +0 -440
  12. package/dist/src/alu/src/expr.d.ts +0 -61
  13. package/dist/src/alu/src/expr.js +0 -318
  14. package/dist/src/alu/src/index.d.ts +0 -11
  15. package/dist/src/alu/src/index.js +0 -19
  16. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  17. package/dist/src/alu/src/kernel-arith.js +0 -264
  18. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  19. package/dist/src/alu/src/kernel-bits.js +0 -152
  20. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  21. package/dist/src/alu/src/kernel-logic.js +0 -60
  22. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  23. package/dist/src/alu/src/kernel-nd.js +0 -208
  24. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  25. package/dist/src/alu/src/kernel-numeric.js +0 -366
  26. package/dist/src/alu/src/operation.d.ts +0 -168
  27. package/dist/src/alu/src/operation.js +0 -189
  28. package/dist/src/alu/src/parser.d.ts +0 -221
  29. package/dist/src/alu/src/parser.js +0 -577
  30. package/dist/src/alu/src/resonance.d.ts +0 -55
  31. package/dist/src/alu/src/resonance.js +0 -126
  32. package/dist/src/alu/src/text.d.ts +0 -31
  33. package/dist/src/alu/src/text.js +0 -73
  34. package/dist/src/alu/src/value.d.ts +0 -109
  35. package/dist/src/alu/src/value.js +0 -300
  36. package/dist/src/alu/test/alu.test.d.ts +0 -1
  37. package/dist/src/alu/test/alu.test.js +0 -764
  38. package/dist/src/bytes.d.ts +0 -14
  39. package/dist/src/bytes.js +0 -59
  40. package/dist/src/canon.d.ts +0 -26
  41. package/dist/src/canon.js +0 -57
  42. package/dist/src/config.d.ts +0 -111
  43. package/dist/src/config.js +0 -91
  44. package/dist/src/derive/src/deduction.d.ts +0 -125
  45. package/dist/src/derive/src/deduction.js +0 -155
  46. package/dist/src/derive/src/index.d.ts +0 -7
  47. package/dist/src/derive/src/index.js +0 -11
  48. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  49. package/dist/src/derive/src/priority-queue.js +0 -73
  50. package/dist/src/derive/src/rewrite.d.ts +0 -56
  51. package/dist/src/derive/src/rewrite.js +0 -100
  52. package/dist/src/derive/src/trie.d.ts +0 -90
  53. package/dist/src/derive/src/trie.js +0 -217
  54. package/dist/src/derive/test/derive.test.d.ts +0 -1
  55. package/dist/src/derive/test/derive.test.js +0 -122
  56. package/dist/src/extension.d.ts +0 -37
  57. package/dist/src/extension.js +0 -7
  58. package/dist/src/geometry.d.ts +0 -202
  59. package/dist/src/geometry.js +0 -575
  60. package/dist/src/index.d.ts +0 -16
  61. package/dist/src/index.js +0 -18
  62. package/dist/src/ingest-cache.d.ts +0 -41
  63. package/dist/src/ingest-cache.js +0 -161
  64. package/dist/src/mind/articulation.d.ts +0 -6
  65. package/dist/src/mind/articulation.js +0 -99
  66. package/dist/src/mind/attention.d.ts +0 -315
  67. package/dist/src/mind/attention.js +0 -1549
  68. package/dist/src/mind/bridge.d.ts +0 -30
  69. package/dist/src/mind/bridge.js +0 -569
  70. package/dist/src/mind/canonical.d.ts +0 -29
  71. package/dist/src/mind/canonical.js +0 -88
  72. package/dist/src/mind/graph-search.d.ts +0 -285
  73. package/dist/src/mind/graph-search.js +0 -956
  74. package/dist/src/mind/index.d.ts +0 -7
  75. package/dist/src/mind/index.js +0 -5
  76. package/dist/src/mind/junction.d.ts +0 -124
  77. package/dist/src/mind/junction.js +0 -317
  78. package/dist/src/mind/learning.d.ts +0 -47
  79. package/dist/src/mind/learning.js +0 -248
  80. package/dist/src/mind/match.d.ts +0 -126
  81. package/dist/src/mind/match.js +0 -441
  82. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  83. package/dist/src/mind/mechanisms/alu.js +0 -36
  84. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  85. package/dist/src/mind/mechanisms/cast.js +0 -716
  86. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  87. package/dist/src/mind/mechanisms/confluence.js +0 -225
  88. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  89. package/dist/src/mind/mechanisms/cover.js +0 -203
  90. package/dist/src/mind/mechanisms/extraction.d.ts +0 -67
  91. package/dist/src/mind/mechanisms/extraction.js +0 -387
  92. package/dist/src/mind/mechanisms/recall.d.ts +0 -13
  93. package/dist/src/mind/mechanisms/recall.js +0 -325
  94. package/dist/src/mind/mind.d.ts +0 -276
  95. package/dist/src/mind/mind.js +0 -572
  96. package/dist/src/mind/pipeline-mechanism.d.ts +0 -142
  97. package/dist/src/mind/pipeline-mechanism.js +0 -213
  98. package/dist/src/mind/pipeline.d.ts +0 -20
  99. package/dist/src/mind/pipeline.js +0 -217
  100. package/dist/src/mind/primitives.d.ts +0 -70
  101. package/dist/src/mind/primitives.js +0 -287
  102. package/dist/src/mind/rationale.d.ts +0 -139
  103. package/dist/src/mind/rationale.js +0 -163
  104. package/dist/src/mind/reasoning.d.ts +0 -34
  105. package/dist/src/mind/reasoning.js +0 -240
  106. package/dist/src/mind/recognition.d.ts +0 -20
  107. package/dist/src/mind/recognition.js +0 -390
  108. package/dist/src/mind/resonance.d.ts +0 -23
  109. package/dist/src/mind/resonance.js +0 -0
  110. package/dist/src/mind/trace.d.ts +0 -15
  111. package/dist/src/mind/trace.js +0 -73
  112. package/dist/src/mind/traverse.d.ts +0 -113
  113. package/dist/src/mind/traverse.js +0 -568
  114. package/dist/src/mind/types.d.ts +0 -289
  115. package/dist/src/mind/types.js +0 -130
  116. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  117. package/dist/src/rabitq-ivf/src/database.js +0 -201
  118. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  119. package/dist/src/rabitq-ivf/src/index.js +0 -4
  120. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  121. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  122. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  123. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  124. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  125. package/dist/src/rabitq-ivf/src/rabitq.js +0 -283
  126. package/dist/src/sema.d.ts +0 -31
  127. package/dist/src/sema.js +0 -63
  128. package/dist/src/store-sqlite.d.ts +0 -184
  129. package/dist/src/store-sqlite.js +0 -942
  130. package/dist/src/store.d.ts +0 -663
  131. package/dist/src/store.js +0 -1618
  132. package/dist/src/vec.d.ts +0 -31
  133. package/dist/src/vec.js +0 -109
@@ -1,1549 +0,0 @@
1
- // attention.ts — Consensus climb / attention pipeline (Section 4 of the mind).
2
- //
3
- // Every region of the query's perceived tree casts a resonance vote for the
4
- // context (learnt fact) it best climbs to. Votes are pooled through the very
5
- // deduction engine (lightestDerivation) that GraphSearch covers with — so a
6
- // pooled-evidence decision is one weighted rule of the SAME deduction system,
7
- // not a hand-rolled tally. The result is one or more independent points of
8
- // attention for the rest of the pipeline to follow.
9
- import { isChunk } from "../sema.js";
10
- import { lightestDerivation, } from "../derive/src/index.js";
11
- import { composeStructuralGist, consensusFloor, dominates, estimatorNoise, } from "../geometry.js";
12
- import { foldTree, gistOf, latin1Key, perceive, read } from "./primitives.js";
13
- import { recognise } from "./recognition.js";
14
- import { leafIdRun } from "./canonical.js";
15
- import { corpusN, edgeAncestors, hubBound } from "./traverse.js";
16
- import { cachedRead, junctionContainersFrom, junctionSeeds, junctionSynonyms, loadJunctionSynonymSides, walkCache, } from "./junction.js";
17
- import { indexOf } from "../bytes.js";
18
- import { rItem, rNode, traceDerivation } from "./trace.js";
19
- function newTraceDraft(perceivedCount) {
20
- return {
21
- perceivedCount,
22
- regions: [],
23
- crossRegionJunctionVotes: [],
24
- supersededOrdinaryVotes: 0,
25
- anchors: [],
26
- };
27
- }
28
- /** Serialise the shared `reachMemo` into the plain, authoritative saturation
29
- * profile (spec §5) — every distinct node any tier's `edgeAncestors` call
30
- * climbed from during this response, in insertion (first-consulted) order. */
31
- function serialiseReaches(reachMemo) {
32
- const out = [];
33
- for (const [node, r] of reachMemo) {
34
- out.push({
35
- node,
36
- roots: [...r.roots],
37
- contextsReached: r.contextsReached,
38
- saturated: r.saturated,
39
- ...(r.saturation ? { saturation: r.saturation } : {}),
40
- });
41
- }
42
- return out;
43
- }
44
- // ── Public entry points ───────────────────────────────────────────────────
45
- /** Climb the query's perceived byte regions up the structural DAG via
46
- * resonance, pool the evidence, and return only the ROOT points of
47
- * attention — those that cleared commitVotes' significance floor. */
48
- export async function climbAttention(ctx, query, k, mode = "inverse") {
49
- return (await climbAttentionAll(ctx, query, k, mode)).roots;
50
- }
51
- /** Full read-out of one consensus climb: both the roots (dominant points of
52
- * attention) and the entire ranked list. Cached via ctx.climbMemo, ALWAYS —
53
- * see {@link recognise} for why this memo (and recognise()'s own) must
54
- * never be skipped while tracing: computeAttention's collectRegions walks
55
- * the query's perceived tree via the same foldTree whose subtree-resolution
56
- * fast path makes a second call on identical bytes non-idempotent once
57
- * ctx._resolvedSubtrees is warm (which a multi-turn conversation's shared
58
- * prefix subtrees guarantee by the second turn). A cache hit still emits
59
- * a trace step — abbreviated, since the full per-sub-region voting detail
60
- * {@link traceAttention} builds isn't preserved by the cached read-out —
61
- * so a traced response is never silently blacked out for a repeated
62
- * query. */
63
- export async function climbAttentionAll(ctx, query, k, mode = "inverse") {
64
- // Content-keyed memo — works for both single-turn respond() and multi-turn
65
- // respondTurn().
66
- if (ctx.climbMemo) {
67
- const contentKey = latin1Key(query);
68
- const modeKey = `${k}:${mode}`;
69
- let byRead = ctx.climbMemo.get(contentKey);
70
- if (byRead === undefined) {
71
- ctx.climbMemo.set(contentKey, byRead = new Map());
72
- }
73
- const hit = byRead.get(modeKey);
74
- if (hit !== undefined) {
75
- // Cache-hit exit (spec §9): the abbreviated payload shape — only what
76
- // is actually stored in the cached AttentionRead is reported. No
77
- // candidate, reach, saturation, pooling or anchor detail is fabricated
78
- // (that per-region detail was never retained by the memo).
79
- const data = ctx.trace
80
- ? {
81
- version: 1,
82
- cache: { hit: true, detailAvailable: false },
83
- config: { annK: k, crossRegionProbeLimit: k, mode },
84
- candidates: { perceived: 0, recognised: 0, total: 0 },
85
- result: hit,
86
- }
87
- : undefined;
88
- ctx.trace?.step("climbConsensus", [rItem(query, "query")], hit.roots.map((r) => rNode(ctx, r.anchor, "anchor", r.vote)), `(cached) consensus already computed for this query — ` +
89
- `${hit.roots.length} point(s) of attention`, undefined, data);
90
- return hit;
91
- }
92
- const read = await computeAttention(ctx, query, k, mode);
93
- byRead.set(modeKey, read);
94
- return read;
95
- }
96
- return computeAttention(ctx, query, k, mode);
97
- }
98
- // ── Pipeline ──────────────────────────────────────────────────────────────
99
- export async function computeAttention(ctx, query, k, mode) {
100
- const regions = collectRegions(ctx, query);
101
- const perceivedCount = regions.length;
102
- // Recognised sites carry structural evidence that perceived sub-regions
103
- // miss: a word crossing a W-boundary is split into chunks whose partial
104
- // gists may not resonate distinctively, but the SITE (content-addressed,
105
- // exact) names the whole form. Adding sites as climb regions lets the
106
- // consensus vote with the full word, at zero cost — recognition is already
107
- // memoised per response (ctx.recogniseMemo), and gistOf for short sites is
108
- // O(|span|·D). Sites that overlap perceived regions add corroborating
109
- // evidence; sites in gaps (like cross-boundary words) fill them.
110
- const rec = recognise(ctx, query);
111
- for (const s of rec.sites) {
112
- regions.push({
113
- v: gistOf(ctx, query.subarray(s.start, s.end)),
114
- start: s.start,
115
- end: s.end,
116
- chunk: false,
117
- known: true, // a recognised site IS a stored form
118
- });
119
- }
120
- // The trace draft (spec §9): allocated ONLY when a trace was requested —
121
- // every downstream consumer gates its own writes on `td?` / `if (td)`, so
122
- // an untraced climb pays zero allocation for this instrumentation.
123
- const td = ctx.trace
124
- ? newTraceDraft(perceivedCount)
125
- : undefined;
126
- const cfg0 = {
127
- k,
128
- mode,
129
- perceivedCount,
130
- totalRegions: regions.length,
131
- };
132
- if (regions.length === 0) {
133
- traceAttention(ctx, [], [], [], undefined, td, cfg0);
134
- return { roots: [], ranked: [] };
135
- }
136
- const N = corpusN(ctx);
137
- // One climb per distinct anchor for the WHOLE query: regions sharing a
138
- // chunk, and canonicalChunkId's prefix probes, all hit this memo instead of
139
- // re-reading the anchor's full edge fan-out from the store.
140
- const reachMemo = new Map();
141
- const rvs = await voteRegions(ctx, query, regions, k, mode, N, reachMemo, td);
142
- // ── Cross-region: DIRECT region-to-region interaction ─────────────────
143
- // Two regions whose individual climbs land on DIFFERENT contexts leave
144
- // their JOINT context — the learnt whole that contains BOTH — with no
145
- // vote. crossRegionVotes recovers it by the bridge's content-addressed
146
- // junction ascent (see the note above the function).
147
- const cross = await crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td);
148
- // A vote SUPERSEDED by exact joint evidence (its bytes literally live
149
- // inside the joint container, yet it climbed elsewhere — grid aliasing)
150
- // is dropped, not down-weighted: the joint container explains it away.
151
- const allVotes = cross.votes.length > 0
152
- ? [
153
- ...rvs.votes.filter((v) => !cross.superseded.has(v)),
154
- ...cross.votes,
155
- ]
156
- : rvs.votes;
157
- // Mark, on the per-region trace, the source region of every superseded
158
- // ordinary vote (spec §4's final rule) — an explicit pass over the exact
159
- // set crossRegionVotes' explaining-away logic removed, never inferred
160
- // from `absorbed`.
161
- if (td && cross.superseded.size > 0) {
162
- for (const rv of cross.superseded) {
163
- const region = td.regions.find((r) => r.span[0] === rv.start && r.span[1] === rv.end);
164
- if (region)
165
- region.superseded = true;
166
- }
167
- }
168
- // ──────────────────────────────────────────────────────────────────────
169
- const cfg = { ...cfg0, N, reachMemo };
170
- if (allVotes.length === 0) {
171
- traceAttention(ctx, regions, rvs.voters, [], undefined, td, cfg);
172
- return { roots: [], ranked: [] };
173
- }
174
- const sat = detectSaturated(ctx, regions, rvs.saturated);
175
- if (td) {
176
- td.saturation = {
177
- regionIntervals: sat.intervals.map((iv) => ({ ...iv })),
178
- hasLeading: sat.hasLeading,
179
- leadingEnd: sat.leadingEnd,
180
- };
181
- }
182
- const pooled = poolVotes(ctx, allVotes, sat, N, td);
183
- return commitVotes(ctx, pooled, sat, regions, rvs.voters, N, td, cfg);
184
- }
185
- export function collectRegions(ctx, query) {
186
- const regions = [];
187
- // A region that DOMINATES the query (covers more than half — the shared
188
- // {@link dominates} test liftAnswer uses for a span that swallows its
189
- // surroundings) can never itself discriminate between several topics the
190
- // query weaves; voting with it only when it is the sole structure (no
191
- // narrower region exists) keeps a flat/short query's single point of
192
- // attention intact without letting a broad, non-discriminative wrapper
193
- // dilute a multi-topic query's vote or masquerade as a genuine second
194
- // point of attention.
195
- // foldTree (not walkTree): the same post-order walk, but each node also
196
- // resolves content-addressed against the store — `known` is what lets the
197
- // climb keep exact evidence at full weight while margin-damping the
198
- // approximate kind (see voteRegions). One findLeaf/findBranch per tree
199
- // node, the same lookups a deposit pays.
200
- foldTree(ctx, perceive(ctx, query), 0, (n, start, end, node) => {
201
- if (n.kids === null)
202
- return;
203
- if (!dominates(end - start, query.length) || regions.length === 0) {
204
- regions.push({
205
- v: n.v,
206
- start,
207
- end,
208
- chunk: isChunk(n),
209
- known: node !== null,
210
- });
211
- }
212
- });
213
- return regions;
214
- }
215
- export async function voteRegions(ctx, query, regions, k, mode, N, reachMemo, td) {
216
- const regionSaturated = new Array(regions.length).fill(false);
217
- const regionVotes = [];
218
- const regionVoter = ctx.trace ? regions.map(() => null) : [];
219
- for (let ri = 0; ri < regions.length; ri++) {
220
- const { v, start, end, chunk, known } = regions[ri];
221
- // Trace-only bookkeeping for this region — allocated only under `td`
222
- // (i.e. only when ctx.trace is set); see ConsensusRegionTrace/
223
- // RegionOutcome (spec §4). `examinedIds` tracks distinct ANN hits
224
- // whose edgeAncestors reach was actually CONSULTED here (not merely
225
- // returned by resonate) — the fallback/margin loops below add to it.
226
- const examinedIds = td ? new Set() : undefined;
227
- let annQueried = false;
228
- let fallbackKind;
229
- const recordRegion = (outcome, extra = {}) => {
230
- if (!td)
231
- return;
232
- td.regions[ri] = {
233
- index: ri,
234
- source: ri < td.perceivedCount ? "perceived" : "recognised",
235
- span: [start, end],
236
- chunk,
237
- known,
238
- canonicalId: canonicalId ?? undefined,
239
- canonicalUsable,
240
- canonicalFailed,
241
- annQueried,
242
- annHitsReturned: hits ? hits.length : 0,
243
- annHitsExamined: examinedIds ? examinedIds.size : 0,
244
- outcome,
245
- ordinaryVoteProduced: outcome === "voted",
246
- superseded: false,
247
- ...extra,
248
- };
249
- };
250
- // EXACT-FIRST: a chunk whose canonical anchor is content-addressed needs
251
- // no estimator — identity is exact, so its score is 1 BY DEFINITION (the
252
- // estimated cosine of a form with itself, minus quantisation noise, and
253
- // the caveat atop geometry.ts forbids trusting the estimate over the
254
- // exact resolution anyway). The ANN query is deferred behind
255
- // `ensureHits` and paid only when actually consulted: the orphan
256
- // fallback, the contrastive margin (approximate regions only), or a
257
- // region with no usable canonical. On chunk-heavy queries this removes
258
- // the resonate() call for most exact regions — the single largest
259
- // remaining inference sink — with the anchor choice unchanged (the
260
- // canonical branch already ignored hits[0]).
261
- const canonicalId = chunk
262
- ? canonicalChunkId(ctx, query.subarray(start, end), N, reachMemo)
263
- : null;
264
- const canonicalUsable = canonicalId !== null &&
265
- (ctx.store.hasParents(canonicalId) ||
266
- ctx.store.hasContainers(canonicalId));
267
- let hits = null;
268
- const ensureHits = async () => {
269
- if (hits === null) {
270
- hits = await ctx.store.resonate(v, k);
271
- annQueried = true;
272
- }
273
- return hits;
274
- };
275
- const canonicalFailed = chunk && canonicalId === null;
276
- let voterId;
277
- let score;
278
- let scoreId; // the node the score was measured against
279
- let selectedSource;
280
- if (canonicalUsable) {
281
- voterId = canonicalId;
282
- score = 1;
283
- scoreId = canonicalId;
284
- selectedSource = "canonical";
285
- }
286
- else {
287
- const h = await ensureHits();
288
- if (h.length === 0) {
289
- recordRegion("no-ann-hit");
290
- continue;
291
- }
292
- voterId = h[0].id;
293
- score = h[0].score;
294
- scoreId = h[0].id;
295
- selectedSource = "ann";
296
- examinedIds?.add(voterId);
297
- }
298
- let reach = edgeAncestors(ctx, voterId, N, reachMemo);
299
- // A region's vote must not die with the TOP hit: `hits[1..k]` were
300
- // already fetched, and the top-ranked anchor being a structural orphan
301
- // (no edge-bearing ancestors) is an accident of the approximate ranking,
302
- // not evidence the region relates to nothing. Walk the remaining hits —
303
- // nearest first, climbs memoised — until one climbs. A SATURATED reach
304
- // is not an orphan: it is a deliberate abstention, kept as-is.
305
- if (reach.roots.length === 0 && !reach.saturated) {
306
- for (const h of await ensureHits()) {
307
- if (h.id === voterId)
308
- continue;
309
- const r2 = edgeAncestors(ctx, h.id, N, reachMemo);
310
- examinedIds?.add(h.id);
311
- if (r2.saturated || r2.roots.length > 0) {
312
- ctx.trace?.step("anchorFallback", [rNode(ctx, voterId, "orphan-anchor", score)], [rNode(ctx, h.id, "anchor", h.score)], "the top-ranked anchor climbs to no context — a lower-ranked hit votes instead");
313
- reach = r2;
314
- voterId = h.id;
315
- score = h.score;
316
- scoreId = h.id;
317
- selectedSource = "ann";
318
- fallbackKind = "orphan";
319
- break;
320
- }
321
- }
322
- }
323
- else if (!canonicalUsable && reach.saturated) {
324
- // TIE-BAND saturation fallback. A saturated top hit abstains the whole
325
- // region (a hub's reach concludes nothing) — but the hub may only CLAIM
326
- // that abstention when it is DISTINGUISHABLY the nearest anchor. The
327
- // resonance ranking is an estimate: the difference between two scores
328
- // against the same query carries √2× the estimator's per-score error,
329
- // ≈ 1/√D ({@link estimatorNoise}) — so any hit within that band of the
330
- // top is the SAME rank at measurement resolution, and letting the hub
331
- // win the tie decides the region by quantisation accident (observed:
332
- // a 0.1σ rank inversion flipped a pinned behaviour when the query
333
- // estimator sharpened from 4 to 8 bits). Walk the tied hits, nearest
334
- // first; the first that climbs somewhere non-saturated votes for the
335
- // region. Beyond the band the hub is genuinely nearest and its
336
- // abstention stands. A KNOWN (content-addressed) region never enters:
337
- // its anchor is exact, not an estimate.
338
- const band = estimatorNoise(ctx.store.D);
339
- for (const h of await ensureHits()) {
340
- if (h.id === voterId)
341
- continue;
342
- if (h.score < score - band)
343
- break; // hits are nearest-first
344
- const r2 = edgeAncestors(ctx, h.id, N, reachMemo);
345
- examinedIds?.add(h.id);
346
- if (!r2.saturated && r2.roots.length > 0) {
347
- ctx.trace?.step("anchorFallback", [rNode(ctx, voterId, "saturated-anchor", score)], [rNode(ctx, h.id, "anchor", h.score)], "the top-ranked anchor is a saturated hub tied within estimator noise — the tied hit votes instead");
348
- reach = r2;
349
- voterId = h.id;
350
- score = h.score;
351
- scoreId = h.id;
352
- selectedSource = "ann";
353
- fallbackKind = "saturated-tie";
354
- break;
355
- }
356
- }
357
- }
358
- regionSaturated[ri] = reach.saturated;
359
- const selected = !td
360
- ? undefined
361
- : (() => {
362
- const rank = selectedSource === "ann"
363
- ? hits?.findIndex((h) => h.id === voterId)
364
- : undefined;
365
- return {
366
- source: selectedSource,
367
- node: voterId,
368
- score,
369
- ...(rank !== undefined ? { rank } : {}),
370
- ...(fallbackKind ? { fallback: fallbackKind } : {}),
371
- };
372
- })();
373
- if (reach.roots.length === 0) {
374
- recordRegion("no-structural-reach", { selected, reachNode: voterId });
375
- continue;
376
- }
377
- if (reach.saturated) {
378
- recordRegion("saturated-abstention", { selected, reachNode: voterId });
379
- continue;
380
- }
381
- // One IDF per region — dfWeight() and the focus weight used to compute
382
- // the same logarithm independently.
383
- const idf = Math.log(N / Math.max(1, reach.contextsReached));
384
- const df = Math.log(1 + reach.contextsReached);
385
- const wf = mode === "direct" ? df : mode === "combined" ? idf + df : idf;
386
- if (wf <= 0) {
387
- recordRegion("nonpositive-df-weight", {
388
- selected,
389
- reachNode: voterId,
390
- idf,
391
- dfWeight: wf,
392
- });
393
- continue;
394
- }
395
- // CONTRASTIVE-MARGIN GATE — the compensation the linear (byte-proportional)
396
- // fold demands, applied to APPROXIMATE evidence only. Under the linear
397
- // fold a resonance score reads "fraction of aligned shared bytes", so a
398
- // NOVEL span sharing a frame with several stored exemplars scores high
399
- // against each of them without being evidence of ANY of them: the shared
400
- // scaffolding, not the span's own content, carries the similarity. Such a
401
- // frame region resonates ~equally to every framed exemplar, so its top hit
402
- // barely beats the best DIFFERENT-conclusion rival (a different climb
403
- // root-set) — its discriminative margin, score MINUS that rival, collapses
404
- // toward zero. A region votes only when that margin clears the estimator's
405
- // own noise floor (1/√D — see {@link estimatorNoise}); below it the margin
406
- // is quantisation noise, not evidence. A KNOWN region (content-addressed,
407
- // exact) skips the contrast: it IS learnt content, not an approximation.
408
- //
409
- // The margin GATES; it does NOT scale the weight. A surviving region votes
410
- // at its genuine strength (score²·wf) — the SAME scale {@link
411
- // consensusFloor} is derived for. Using the margin as a MULTIPLIER
412
- // (score·margin) conflated "discriminative" with "strong": a genuinely
413
- // discriminative span whose frame-rival happened to score close got a tiny
414
- // vote, systematically compressing correct scaffolding-dominated groundings
415
- // (reordered / paraphrased queries) below the floor so they grounded
416
- // nothing. Gating at the noise floor keeps frame-echo suppression (a frame
417
- // region's margin ≈ 0 is gated out) without penalising honest evidence.
418
- let contrastiveMargin;
419
- if (!known) {
420
- let margin = score;
421
- for (const h of await ensureHits()) {
422
- if (h.id === voterId)
423
- continue;
424
- const r2 = edgeAncestors(ctx, h.id, N, reachMemo);
425
- examinedIds?.add(h.id);
426
- if (r2.saturated || r2.roots.length === 0)
427
- continue; // concludes nothing
428
- if (sameRoots(r2.roots, reach.roots))
429
- continue; // same conclusion
430
- margin = score - h.score; // hits are nearest-first: the best rival
431
- break;
432
- }
433
- contrastiveMargin = margin;
434
- const noiseFloor = estimatorNoise(ctx.store.D);
435
- if (margin <= noiseFloor) {
436
- recordRegion("contrastive-margin-rejection", {
437
- selected,
438
- reachNode: voterId,
439
- idf,
440
- dfWeight: wf,
441
- contrastiveMargin: margin,
442
- contrastiveNoiseFloor: noiseFloor,
443
- });
444
- continue;
445
- }
446
- }
447
- // MUTUAL-EXPLANATION WEIGHT (angle + magnitude). Under the linear fold
448
- // cos = shared/(‖r‖·‖h‖) with ‖·‖² = content bytes, so the old score²
449
- // was already — implicitly — (shared/len_r)·(shared/len_h): the fraction
450
- // of the REGION the hit explains times the fraction of the HIT the
451
- // region pins down. Made explicit, each factor is computed from the two
452
- // magnitudes (the region's own span; the hit's, read from the store —
453
- // contentLen, √bytes being the linear fold's gist norm) and CAPPED at 1:
454
- // the estimated cosine can imply more shared content than the smaller
455
- // side even holds, and the uncapped square silently credited that
456
- // impossible surplus — a small region echoing inside a large context, or
457
- // the reverse, voted above its physical evidence. In the uncapped
458
- // regime this is exactly score², the scale {@link consensusFloor} is
459
- // derived for. (The margin gate above deliberately stays in raw cosine
460
- // units: it tests the ESTIMATOR's noise floor, which lives in cosine
461
- // space; converting each side by its own hit's magnitude would compare
462
- // noise floors of different scales.)
463
- const lenR = Math.max(1, end - start);
464
- // Cap the magnitude read at lenR·D: past it s/ratio ≤ s/√D — below the
465
- // estimator's own noise floor — so the mutual weight is ~0 regardless
466
- // and the clamped value yields exactly that; no full walk of a huge hit.
467
- const ratio = Math.sqrt(Math.max(1, ctx.store.contentLen(scoreId, lenR * ctx.store.D)) / lenR);
468
- const mutual = Math.min(1, score * ratio) * Math.min(1, score / ratio);
469
- const w = (mutual * wf) / reach.roots.length;
470
- const wFocus = (mutual * idf) / reach.roots.length;
471
- regionVotes.push({
472
- start,
473
- end,
474
- canonicalFailed,
475
- roots: reach.roots,
476
- w,
477
- wFocus,
478
- });
479
- if (ctx.trace) {
480
- regionVoter[ri] = { id: voterId, score, w: wf };
481
- }
482
- recordRegion("voted", {
483
- selected,
484
- reachNode: voterId,
485
- idf,
486
- dfWeight: wf,
487
- ...(contrastiveMargin !== undefined
488
- ? {
489
- contrastiveMargin,
490
- contrastiveNoiseFloor: estimatorNoise(ctx.store.D),
491
- }
492
- : {}),
493
- mutualWeight: mutual,
494
- voteWeightPerRoot: w,
495
- focusWeightPerRoot: wFocus,
496
- });
497
- }
498
- return {
499
- votes: regionVotes,
500
- saturated: regionSaturated,
501
- voters: regionVoter,
502
- };
503
- }
504
- /** The consensus vote as EVIDENCE POOLING, not shortest path: each surviving
505
- * region is an axiom; it contributes to every root it climbed to (or, for a
506
- * terminal answer node, to the contexts that lead to it) by a `combine:
507
- * "sum"` rule, so independent regions corroborating the same anchor ADD
508
- * rather than compete to be the cheapest route (see {@link Rule.combine} in
509
- * derive/src/deduction.ts). Run through the very engine {@link
510
- * GraphSearch} covers with — `lightestDerivation` — so a pooled-evidence
511
- * decision is, like a followed edge or a spliced connector, one weighted
512
- * rule of the SAME deduction system, not a separate hand-rolled tally that
513
- * merely logs alongside it. `votesIdf`/`support` are the same two
514
- * read-outs {@link commitVotes} always gated on; only how they accumulate
515
- * changed. */
516
- export function poolVotes(ctx, regionVotes, sat, N, td) {
517
- const eligible = [];
518
- for (let ri = 0; ri < regionVotes.length; ri++) {
519
- const rv = regionVotes[ri];
520
- if (rv.canonicalFailed &&
521
- sat.intervals.some((iv) => rv.start >= iv.start && rv.end <= iv.end)) {
522
- continue;
523
- }
524
- eligible.push(ri);
525
- }
526
- if (td) {
527
- td.pooling = {
528
- inputVotes: regionVotes.length,
529
- eligibleVotes: eligible.length,
530
- saturationMaskedVotes: regionVotes.length - eligible.length,
531
- };
532
- }
533
- const key = (it) => it.kind === "region"
534
- ? `r${it.ri}`
535
- : it.kind === "anchor"
536
- ? `a${it.id}`
537
- : `x${it.id}`;
538
- const pool = new Map();
539
- const system = {
540
- key,
541
- *axioms() {
542
- for (const ri of eligible) {
543
- yield { item: { kind: "region", ri }, cost: 0 };
544
- }
545
- },
546
- isGoal: () => false, // exhaust every axiom; there is no single goal to stop at
547
- // Every region axiom ties at cost 0, so the agenda's pop order among them
548
- // is otherwise unspecified; ordering by `ri` here only steers the HEAP
549
- // (never added to a stored cost — see relax's use of h) so pooling fires
550
- // in exactly the regionVotes array order the original loop used, byte-for-
551
- // byte reproducing its accumulation and tie-break order.
552
- heuristic: (it) => it.kind === "region" ? it.ri : 0,
553
- *rules(it) {
554
- if (it.kind !== "region")
555
- return;
556
- const rv = regionVotes[it.ri];
557
- // The same hub bound the rest of the system uses (edgeAncestors' parent
558
- // cutoff, chooseNext's candidate cap): a terminal answer followed by
559
- // more than √N contexts is a non-discriminative hub — spreading a
560
- // region's vote across its FULL corpus-sized fan-in yields O(corpus)
561
- // rule applications per region and near-zero per-target weight anyway.
562
- // Cap the redistribution at the first √N contexts (insertion order,
563
- // the same convention chooseNext caps by).
564
- const hubBound = Math.ceil(Math.sqrt(N));
565
- for (const r of rv.roots) {
566
- // CAPPED read: only the first hubBound targets are ever credited, so
567
- // only they are read — a common continuation's full reverse fan-in
568
- // is corpus-sized and is never materialised.
569
- const pv = ctx.store.prevFirst(r, hubBound);
570
- const isAnswer = pv.length > 0 && !ctx.store.hasNext(r);
571
- const targets = isAnswer ? pv : [r];
572
- for (const t of targets) {
573
- yield {
574
- premises: [it],
575
- conclusion: { kind: "anchor", id: t },
576
- cost: rv.w / targets.length,
577
- combine: "sum",
578
- };
579
- yield {
580
- premises: [it],
581
- conclusion: { kind: "anchorFocus", id: t },
582
- cost: rv.wFocus / targets.length,
583
- combine: "sum",
584
- };
585
- }
586
- }
587
- },
588
- pool,
589
- };
590
- lightestDerivation(system);
591
- const votes = new Map();
592
- const votesIdf = new Map();
593
- const support = new Map();
594
- const regionSupport = new Map();
595
- const regionSpans = new Map();
596
- const steps = [];
597
- let order = 0;
598
- for (const pc of pool.values()) {
599
- if (pc.item.kind === "anchor") {
600
- votes.set(pc.item.id, pc.cost);
601
- const premises = [];
602
- const seenRi = new Set();
603
- let breadthSum = 0;
604
- const spans = [];
605
- for (const c of pc.contributions) {
606
- const p0 = c.premises[0].item;
607
- if (p0.kind !== "region" || seenRi.has(p0.ri))
608
- continue;
609
- seenRi.add(p0.ri);
610
- const rv = regionVotes[p0.ri];
611
- breadthSum += rv.absorbed ?? 1;
612
- premises.push({ kind: "form", span: [rv.start, rv.end] });
613
- spans.push([rv.start, rv.end]);
614
- }
615
- regionSupport.set(pc.item.id, breadthSum);
616
- regionSpans.set(pc.item.id, spans);
617
- steps.push({
618
- order: order++,
619
- move: "pool-vote",
620
- premises,
621
- conclusion: { kind: "form", span: [-1, -1], node: pc.item.id },
622
- cost: pc.cost,
623
- producers: [],
624
- });
625
- }
626
- else if (pc.item.kind === "anchorFocus") {
627
- votesIdf.set(pc.item.id, pc.cost);
628
- let bestRv = null;
629
- for (const c of pc.contributions) {
630
- const p0 = c.premises[0].item;
631
- if (p0.kind !== "region")
632
- continue;
633
- const rv = regionVotes[p0.ri];
634
- if (!bestRv || rv.wFocus > bestRv.wFocus)
635
- bestRv = rv;
636
- }
637
- if (bestRv) {
638
- support.set(pc.item.id, {
639
- start: bestRv.start,
640
- end: bestRv.end,
641
- w: bestRv.wFocus,
642
- });
643
- }
644
- }
645
- }
646
- return { votes, votesIdf, support, regionSupport, regionSpans, steps };
647
- }
648
- /** The number of DISTINCT clusters a root's contributing regions form —
649
- * see Attention.clusters. Two regions belong to the same cluster iff the
650
- * gap between them is strictly less than one river-fold quantum W: at
651
- * that distance there is no room for a genuinely separate, independently
652
- * perceivable unit of content between them (the same "smallest meaningful
653
- * distinction" quantum {@link reachThreshold}'s own doc invokes). A gap
654
- * of a full quantum or more means real, separate structure could sit
655
- * between the two spans, so they count as independent corroboration.
656
- * Strict `<` (not `<=`): verified against gap 3.1's own "gender equality"
657
- * root, whose two genuine clusters sit EXACTLY W bytes apart — `<= W`
658
- * would wrongly merge them into one and break that pinned requirement. */
659
- function countClusters(spans, W) {
660
- if (spans.length === 0)
661
- return 0;
662
- const sorted = [...spans].sort((a, b) => a[0] - b[0]);
663
- let clusters = 1;
664
- let curEnd = sorted[0][1];
665
- for (let i = 1; i < sorted.length; i++) {
666
- const [s, e] = sorted[i];
667
- if (s - curEnd < W) {
668
- curEnd = Math.max(curEnd, e);
669
- }
670
- else {
671
- clusters++;
672
- curEnd = e;
673
- }
674
- }
675
- return clusters;
676
- }
677
- export function commitVotes(ctx, pooled, sat, regions, regionVoter, N, td, cfg) {
678
- const { votes, votesIdf, support, regionSupport, regionSpans, steps } = pooled;
679
- if (votes.size === 0) {
680
- traceAttention(ctx, regions, regionVoter, [], steps, td, cfg);
681
- return { roots: [], ranked: [] };
682
- }
683
- // SCALE-INVARIANT confidence — see Attention.breadth's doc. regions.length
684
- // is the query's OWN full candidate count (most never vote at all), the
685
- // same denominator the "N of M sub-regions voted" rationale text already
686
- // reports; regionSupport is that same accounting read PER ANCHOR.
687
- const totalRegions = Math.max(1, regions.length);
688
- const ranked = [...votes.entries()]
689
- .map(([anchor, vote]) => {
690
- const s = support.get(anchor);
691
- return {
692
- anchor,
693
- vote,
694
- start: s.start,
695
- end: s.end,
696
- breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
697
- clusters: countClusters(regionSpans.get(anchor) ?? [], ctx.space.maxGroup),
698
- };
699
- })
700
- .sort((a, b) => b.vote - a.vote);
701
- const overlaps = (a, b) => a.start < b.end && b.start < a.end;
702
- const idfDesc = [...votesIdf.values()].sort((a, b) => b - a);
703
- const rootCut = naturalBreak(idfDesc);
704
- // A FURTHER point of attention (beyond the dominant one, which always
705
- // grounds) must clear the same absolute significance floor
706
- // recallByResonance trusts a climb anchor with — log(N) + 1/2, three-ish
707
- // halvings of confidence above pure chance at this corpus scale — not
708
- // merely beat whatever its immediate neighbour in the ratio happens to be.
709
- // Without it, naturalBreak's ratio is scale-free but not FLOOR-free: on a
710
- // large, topic-diverse corpus the steepest ratio in a long noise tail can
711
- // sit far below any real signal, admitting scaffolding echoes as if they
712
- // were genuine further topics.
713
- const floor = consensusFloor(N);
714
- const placed = [];
715
- const roots = [];
716
- const recordAnchor = (point, rank, status, dominant, passesNaturalBreak, passesConsensusFloor, pastLeadingSaturation, rejectionReasons) => {
717
- if (!td)
718
- return;
719
- td.anchors.push({
720
- anchor: point.anchor,
721
- rank,
722
- pooledVote: point.vote,
723
- idfVote: votesIdf.get(point.anchor) ?? 0,
724
- candidateBreadth: regions.length,
725
- contributingVotes: regionSpans.get(point.anchor)?.length ?? 0,
726
- contributingEvidence: regionSupport.get(point.anchor) ?? 0,
727
- breadth: point.breadth,
728
- contributingSpans: regionSpans.get(point.anchor) ?? [],
729
- clusters: point.clusters,
730
- commit: {
731
- status,
732
- dominant,
733
- passesNaturalBreak,
734
- passesConsensusFloor,
735
- pastLeadingSaturation,
736
- rejectionReasons,
737
- },
738
- });
739
- };
740
- for (let rank = 0; rank < ranked.length; rank++) {
741
- const point = ranked[rank];
742
- const absorbed = placed.some((p) => overlaps(point, p));
743
- // Commit decisions are recorded LIVE, inside this loop, in the exact
744
- // shape the gates below apply them — never reconstructed afterward from
745
- // the final `roots` (spec §8's explicit requirement).
746
- let status;
747
- let dominant = false;
748
- let passesNaturalBreak;
749
- let passesConsensusFloor;
750
- let pastLeadingSaturation;
751
- const rejectionReasons = [];
752
- if (absorbed) {
753
- status = "overlap";
754
- }
755
- else {
756
- const pastLeading = !sat.hasLeading ||
757
- roots.length === 0 || point.start >= sat.leadingEnd;
758
- pastLeadingSaturation = pastLeading;
759
- const vote = votesIdf.get(point.anchor) ?? 0;
760
- if (roots.length === 0) {
761
- // The first non-overlapping root is DOMINANT and bypasses the two
762
- // vote thresholds (it always grounds) — only the leading-saturation
763
- // gate still applies to it.
764
- dominant = true;
765
- if (pastLeading) {
766
- status = "root";
767
- }
768
- else {
769
- status = "rejected";
770
- rejectionReasons.push("leading-saturation");
771
- }
772
- }
773
- else {
774
- passesNaturalBreak = vote >= rootCut;
775
- passesConsensusFloor = vote >= floor;
776
- if (passesNaturalBreak && passesConsensusFloor && pastLeading) {
777
- status = "root";
778
- }
779
- else {
780
- status = "rejected";
781
- if (!passesNaturalBreak)
782
- rejectionReasons.push("below-natural-break");
783
- if (!passesConsensusFloor) {
784
- rejectionReasons.push("below-consensus-floor");
785
- }
786
- if (!pastLeading)
787
- rejectionReasons.push("leading-saturation");
788
- }
789
- }
790
- if (status === "root") {
791
- roots.push(point);
792
- }
793
- else {
794
- recordAnchor(point, rank, status, dominant, passesNaturalBreak, passesConsensusFloor, pastLeadingSaturation, rejectionReasons);
795
- continue;
796
- }
797
- }
798
- recordAnchor(point, rank, status, dominant, passesNaturalBreak, passesConsensusFloor, pastLeadingSaturation, rejectionReasons);
799
- placed.push(point);
800
- }
801
- traceAttention(ctx, regions, regionVoter, roots, steps, td, cfg ? { ...cfg, naturalBreak: rootCut, consensusFloor: floor } : undefined, ranked);
802
- return { roots, ranked };
803
- }
804
- export function detectSaturated(ctx, regions, saturated) {
805
- // Intervals are built from CHUNK regions only. collectRegions emits the
806
- // tree in POST-ORDER — a parent region arrives AFTER its children and
807
- // shares its first child's `start` — so the raw array is not monotone in
808
- // byte position, and a saturated parent would fuse with a later saturated
809
- // chunk into an interval swallowing a NON-saturated child. Chunk regions
810
- // (leaf-parents) are disjoint and already in byte order, and saturation
811
- // masking exists to drop canonicalFailed CHUNK votes (see poolVotes), so
812
- // chunks are both the sufficient and the safe basis. A region without a
813
- // `chunk` flag (a bare {start,end} from a direct caller) is treated as a
814
- // chunk.
815
- const intervals = [];
816
- let intStart = -1;
817
- let intEnd = -1;
818
- let totalLen = 0;
819
- for (let ri = 0; ri < regions.length; ri++) {
820
- const r = regions[ri];
821
- totalLen = Math.max(totalLen, r.end);
822
- if (r.chunk === false)
823
- continue;
824
- if (saturated[ri]) {
825
- if (intStart === -1)
826
- intStart = r.start;
827
- intEnd = r.end;
828
- }
829
- else {
830
- if (intStart !== -1) {
831
- intervals.push({ start: intStart, end: intEnd });
832
- intStart = -1;
833
- }
834
- }
835
- }
836
- if (intStart !== -1) {
837
- intervals.push({ start: intStart, end: intEnd });
838
- }
839
- const leading = intervals.length > 0 && intervals[0].start === 0
840
- ? intervals[0]
841
- : null;
842
- const hasLeading = leading !== null &&
843
- leading.end >= ctx.space.maxGroup &&
844
- leading.end < totalLen;
845
- const leadingEnd = leading !== null ? leading.end : 0;
846
- return { leadingEnd, hasLeading, intervals };
847
- }
848
- /** Set equality of two climb root lists (the "same conclusion" test the
849
- * contrastive margin skips rivals by). */
850
- function sameRoots(a, b) {
851
- if (a.length !== b.length)
852
- return false;
853
- const s = new Set(a);
854
- for (const x of b)
855
- if (!s.has(x))
856
- return false;
857
- return true;
858
- }
859
- export function canonicalChunkId(ctx, regionBytes, N, reachMemo) {
860
- const len = Math.min(regionBytes.length, ctx.space.maxGroup);
861
- for (let off = 0; off + len <= regionBytes.length; off++) {
862
- const ids = leafIdRun(ctx, regionBytes, off, off + len);
863
- if (ids === null)
864
- return null;
865
- const flatId = ctx.store.findBranch(ids);
866
- if (flatId === null)
867
- continue;
868
- if (len < 2)
869
- return flatId;
870
- let bestId = flatId;
871
- let bestReach = edgeAncestors(ctx, flatId, N, reachMemo);
872
- for (let k2 = 1; k2 < len; k2++) {
873
- const shortIds = ids.slice(0, len - k2);
874
- const shortId = ctx.store.findBranch(shortIds);
875
- if (shortId === null)
876
- continue;
877
- const shortReach = edgeAncestors(ctx, shortId, N, reachMemo);
878
- if (shortReach.saturated ||
879
- shortReach.contextsReached > bestReach.contextsReached) {
880
- bestId = shortId;
881
- bestReach = shortReach;
882
- }
883
- }
884
- return bestId;
885
- }
886
- return null;
887
- }
888
- export function naturalBreak(votes) {
889
- if (votes.length <= 1)
890
- return votes[0] ?? 0;
891
- let breakAt = 1;
892
- let steepest = Infinity;
893
- for (let i = 1; i < votes.length; i++) {
894
- if (votes[i - 1] <= 0)
895
- break;
896
- const ratio = votes[i] / votes[i - 1];
897
- if (ratio < steepest) {
898
- steepest = ratio;
899
- breakAt = i;
900
- }
901
- }
902
- return votes[breakAt - 1];
903
- }
904
- const VARIANT_KIND_ORDER = {
905
- "exact-exact": -1,
906
- "left-synonym": 0,
907
- "right-synonym": 1,
908
- "double-synonym": 2,
909
- };
910
- /** A node's structural gist, read directly from its own stored bytes — the
911
- * repository's node → gist accessor: content is immutable and perception is
912
- * a pure function of bytes, so re-perceiving a node's full stored bytes
913
- * reproduces exactly the gist it was interned with. Never concatenates
914
- * the sibling's bytes with anything else. */
915
- function storedNodeGist(ctx, id) {
916
- return gistOf(ctx, read(ctx, id));
917
- }
918
- /** A halo sibling's structural part, occupying the ORIGINAL query-region
919
- * slot length — the sibling replaces only the DIRECTION, never the query's
920
- * own bytes, position, or gap (see §6 of the spec this implements). */
921
- function siblingPart(ctx, sibling, originalRegion) {
922
- return {
923
- v: storedNodeGist(ctx, sibling.id),
924
- len: originalRegion.end - originalRegion.start,
925
- };
926
- }
927
- /** Build, bound and order every mandatory structural variant (§7-8): the
928
- * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
929
- * synonym variants (single- and double-synonym combined, one shared
930
- * budget) are appended, ordered by confidence, then kind, then sibling id. */
931
- export function buildStructuralVariants(ctx, ra, rb, sides) {
932
- const exactLeft = { v: ra.v, len: ra.end - ra.start };
933
- const exactRight = { v: rb.v, len: rb.end - rb.start };
934
- const synonymVariants = [];
935
- for (const ls of sides.leftSiblings) {
936
- synonymVariants.push({
937
- left: siblingPart(ctx, ls, ra),
938
- right: exactRight,
939
- kind: "left-synonym",
940
- semanticConfidence: ls.score,
941
- leftSiblingId: ls.id,
942
- });
943
- }
944
- for (const rs of sides.rightSiblings) {
945
- synonymVariants.push({
946
- left: exactLeft,
947
- right: siblingPart(ctx, rs, rb),
948
- kind: "right-synonym",
949
- semanticConfidence: rs.score,
950
- rightSiblingId: rs.id,
951
- });
952
- }
953
- for (const ls of sides.leftSiblings) {
954
- for (const rs of sides.rightSiblings) {
955
- synonymVariants.push({
956
- left: siblingPart(ctx, ls, ra),
957
- right: siblingPart(ctx, rs, rb),
958
- kind: "double-synonym",
959
- semanticConfidence: Math.min(ls.score, rs.score),
960
- leftSiblingId: ls.id,
961
- rightSiblingId: rs.id,
962
- });
963
- }
964
- }
965
- // §8: semantic confidence desc, then kind (left-synonym, right-synonym,
966
- // double-synonym), then left sibling id asc, then right sibling id asc.
967
- synonymVariants.sort((a, b) => b.semanticConfidence - a.semanticConfidence ||
968
- VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
969
- (a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
970
- (a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1));
971
- const variants = [
972
- {
973
- left: exactLeft,
974
- right: exactRight,
975
- kind: "exact-exact",
976
- semanticConfidence: 1,
977
- },
978
- ...synonymVariants.slice(0, ctx.cfg.haloQueryK),
979
- ];
980
- return { variants, exactLeft, exactRight };
981
- }
982
- /** Deterministic best-of tie-break for two proposals ranked for the SAME
983
- * candidate id — effectiveScore, then annScore, then semanticConfidence,
984
- * then variant kind, then sibling ids (§10). */
985
- function betterProposal(a, b) {
986
- if (a.effectiveScore !== b.effectiveScore) {
987
- return a.effectiveScore > b.effectiveScore;
988
- }
989
- if (a.annScore !== b.annScore)
990
- return a.annScore > b.annScore;
991
- if (a.semanticConfidence !== b.semanticConfidence) {
992
- return a.semanticConfidence > b.semanticConfidence;
993
- }
994
- if (VARIANT_KIND_ORDER[a.variant] !== VARIANT_KIND_ORDER[b.variant]) {
995
- return VARIANT_KIND_ORDER[a.variant] < VARIANT_KIND_ORDER[b.variant];
996
- }
997
- if ((a.leftSiblingId ?? -1) !== (b.leftSiblingId ?? -1)) {
998
- return (a.leftSiblingId ?? -1) < (b.leftSiblingId ?? -1);
999
- }
1000
- return (a.rightSiblingId ?? -1) < (b.rightSiblingId ?? -1);
1001
- }
1002
- /** The final approximate tier: compose every retained structural variant,
1003
- * ANN-query each, merge proposals by candidate id, and validate the winner
1004
- * through the SAME structural gates every other tier answers to (saturation,
1005
- * roots, IDF, contrastive margin). Returns null when nothing survives. */
1006
- export async function structuralResonance(ctx, query, ra, rb, sides, k, N, reachMemo,
1007
- /** Each side's OWN individual climb roots (from voteRegions), when it cast
1008
- * one — the self-evidence backstop structural-resonance needs and the
1009
- * exact tier gets for free from literal byte containment (§11's whole
1010
- * premise: recover a JOINT context neither side votes for alone). A
1011
- * candidate whose reach is exactly one side's own conclusion is not new
1012
- * evidence of a joint whole; it is that side's resonance rediscovering
1013
- * itself through a synthetic gist still dominated by its own direction. */
1014
- ownRootsA, ownRootsB) {
1015
- const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
1016
- const middleBytes = query.subarray(ra.end, rb.start);
1017
- const middlePart = middleBytes.length === 0
1018
- ? null
1019
- : { v: perceive(ctx, middleBytes).v, len: middleBytes.length };
1020
- const proposals = new Map();
1021
- for (const variant of variants) {
1022
- const parts = [variant.left];
1023
- if (middlePart)
1024
- parts.push(middlePart);
1025
- parts.push(variant.right);
1026
- const synthetic = composeStructuralGist(ctx.space, parts);
1027
- const hits = await ctx.store.resonate(synthetic, k);
1028
- for (const hit of hits) {
1029
- const candidate = {
1030
- id: hit.id,
1031
- annScore: hit.score,
1032
- semanticConfidence: variant.semanticConfidence,
1033
- effectiveScore: hit.score * variant.semanticConfidence,
1034
- variant: variant.kind,
1035
- leftSiblingId: variant.leftSiblingId,
1036
- rightSiblingId: variant.rightSiblingId,
1037
- };
1038
- const prev = proposals.get(hit.id);
1039
- if (prev === undefined || betterProposal(candidate, prev)) {
1040
- proposals.set(hit.id, candidate);
1041
- }
1042
- }
1043
- }
1044
- if (proposals.size === 0)
1045
- return null;
1046
- const sorted = [...proposals.values()].sort((a, b) => b.effectiveScore - a.effectiveScore || a.id - b.id);
1047
- let selected = null;
1048
- let selectedReach = null;
1049
- let selectedIdf = 0;
1050
- let rival = null;
1051
- for (const p of sorted) {
1052
- const reach = edgeAncestors(ctx, p.id, N, reachMemo);
1053
- if (reach.saturated || reach.roots.length === 0)
1054
- continue;
1055
- const idf = Math.log(N / Math.max(1, reach.contextsReached));
1056
- if (idf <= 0)
1057
- continue;
1058
- // Self-evidence backstop (see the param doc above): a candidate that is
1059
- // exactly one side's own already-voted conclusion carries no JOINT
1060
- // evidence — skip it as if it never survived.
1061
- if ((ownRootsA && sameRoots(reach.roots, ownRootsA)) ||
1062
- (ownRootsB && sameRoots(reach.roots, ownRootsB)))
1063
- continue;
1064
- if (selected === null) {
1065
- selected = p;
1066
- selectedReach = reach;
1067
- selectedIdf = idf;
1068
- }
1069
- else if (!sameRoots(reach.roots, selectedReach.roots)) {
1070
- rival = p;
1071
- break;
1072
- }
1073
- }
1074
- if (selected === null || selectedReach === null)
1075
- return null;
1076
- const margin = rival
1077
- ? selected.effectiveScore - rival.effectiveScore
1078
- : selected.effectiveScore;
1079
- if (margin <= estimatorNoise(ctx.store.D))
1080
- return null;
1081
- return { proposal: selected, reach: selectedReach, idf: selectedIdf };
1082
- }
1083
- async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
1084
- // Candidate regions: every region that ALREADY CAST ITS OWN VOTE in
1085
- // voteRegions — individually idf > 0, genuinely discriminative on its own,
1086
- // just not necessarily for the SAME context as its partner. This is the
1087
- // exact shape of the binding problem: "red" alone votes for `red square`,
1088
- // "circle" alone for `circle` — each independently informative, disagreeing
1089
- // on the conclusion — and only their CONJUNCTION resolves to the one
1090
- // context, `red circle`, that actually holds both.
1091
- //
1092
- // A region that never voted (idf == 0 — e.g. a repeated system-prompt
1093
- // prefix shared by every deposit) carries NO individual signal, and must be
1094
- // excluded here too: ascending from a non-discriminative fragment's seeds
1095
- // can still land on some deeper, incidentally-unique DESCENDANT container —
1096
- // its rarity would come entirely from context OUTSIDE the fragments
1097
- // actually composed, manufacturing confidence the query gave no reason to
1098
- // have. Requiring a prior individual vote is the same discipline the noise
1099
- // drop already applies to single regions, extended to compositions — with
1100
- // one graded relaxation: a KNOWN region that did NOT vote (saturated, or
1101
- // idf ≤ 0) may still serve as the WEAK side of a pair whose other side DID
1102
- // vote. Saturation is an abstention about where the region CLIMBS; its
1103
- // content-addressed identity is still exact, and the junction asks a
1104
- // different question — "which whole holds both?" — whose conclusion the
1105
- // container's own idf gate below still guards. Two non-voting regions
1106
- // never pair (that is exactly the shared-prefix trap above), so at least
1107
- // one side is always individually discriminative.
1108
- //
1109
- // Only MAXIMAL spans compose: a span contained in another candidate is a
1110
- // fragment of that candidate's evidence, never independent of it.
1111
- const votedSpans = new Set();
1112
- for (const rv of rvs.votes)
1113
- votedSpans.add(`${rv.start},${rv.end}`);
1114
- const seen = new Set();
1115
- const eligible = [];
1116
- const strong = new Set();
1117
- for (let ri = 0; ri < regions.length; ri++) {
1118
- const r = regions[ri];
1119
- const key = `${r.start},${r.end}`;
1120
- const isStrong = votedSpans.has(key);
1121
- if ((!isStrong && !r.known) || seen.has(key))
1122
- continue;
1123
- seen.add(key);
1124
- eligible.push(ri);
1125
- if (isStrong)
1126
- strong.add(ri);
1127
- }
1128
- const cand = eligible.filter((x) => !eligible.some((y) => y !== x &&
1129
- regions[y].start <= regions[x].start &&
1130
- regions[x].end <= regions[y].end &&
1131
- regions[y].end - regions[y].start > regions[x].end - regions[x].start));
1132
- const none = { votes: [], superseded: new Set() };
1133
- if (td) {
1134
- td.crossRegionSummary = {
1135
- eligibleRegions: eligible.length,
1136
- maximalRegions: cand.length,
1137
- probeLimit: k,
1138
- probesAttempted: 0, // updated below as probes accrue
1139
- };
1140
- }
1141
- if (cand.length < 2)
1142
- return none;
1143
- cand.sort((x, y) => regions[x].start - regions[y].start || regions[x].end - regions[y].end);
1144
- const dec = (b) => new TextDecoder().decode(b).replace(/\s+/g, " ").trim();
1145
- const cache = walkCache(ctx);
1146
- // One junctionSeeds per candidate for the WHOLE pairing loop — a candidate
1147
- // recurs in up to |cand|−1 pairs, and its seeds are a pure function of its
1148
- // bytes.
1149
- const seedsMemo = new Map();
1150
- const seedsOf = (ri) => {
1151
- let s = seedsMemo.get(ri);
1152
- if (s === undefined) {
1153
- const r = regions[ri];
1154
- s = junctionSeeds(ctx, query.subarray(r.start, r.end));
1155
- seedsMemo.set(ri, s);
1156
- }
1157
- return s;
1158
- };
1159
- const overlapsSpan = (e, s) => e.start < s.end && s.start < e.end;
1160
- const out = [];
1161
- const superseded = new Set();
1162
- // A candidate consumed by one junction does not seed another: its evidence
1163
- // is already composed at full joint strength, and re-pairing it would vote
1164
- // the same container (or a sub-container of it) twice.
1165
- const consumed = new Set();
1166
- let probes = 0;
1167
- for (let a = 0; a < cand.length && probes < k; a++) {
1168
- if (consumed.has(cand[a]))
1169
- continue;
1170
- const ra = regions[cand[a]];
1171
- for (let b = a + 1; b < cand.length && probes < k; b++) {
1172
- if (consumed.has(cand[b]))
1173
- continue;
1174
- const rb = regions[cand[b]];
1175
- if (!strong.has(cand[a]) && !strong.has(cand[b]))
1176
- continue;
1177
- if (ra.end >= rb.start)
1178
- continue; // overlap or adjacent — nothing between
1179
- // Candidates strictly BETWEEN ra and rb (cand is sorted by start, so
1180
- // that is exactly cand[a+1 .. b-1]) that already cast their OWN vote —
1181
- // genuine, individually-corroborated evidence about what fills the gap
1182
- // — gate the container search below: a joint container is binding
1183
- // evidence only when it is CONSISTENT with that evidence, i.e. its own
1184
- // bytes actually contain what the between-region says. This is the
1185
- // n-ary composition's normal shape (a between-attribute's bytes DO
1186
- // recur inside the joint container, credited as an "extra" below) as
1187
- // opposed to a container that silently substitutes something else for
1188
- // it (e.g. bridging past "Italy" to a container whose interior is
1189
- // "Japan" — a different, contradicting learnt whole).
1190
- // Only a KNOWN (content-addressed, exact) between-region qualifies —
1191
- // an approximate region's resonance climbing "somewhere" is ordinary
1192
- // noise (any ANN query returns SOME nearest neighbour), not evidence
1193
- // this specific gap already means something specific.
1194
- const between = [];
1195
- for (let m = a + 1; m < b; m++) {
1196
- if (strong.has(cand[m]) && !consumed.has(cand[m]) &&
1197
- regions[cand[m]].known)
1198
- between.push(cand[m]);
1199
- }
1200
- // A single KNOWN region covering both: the whole form is already a
1201
- // stored identity that votes directly; its pieces add nothing.
1202
- if (regions.some((r) => r.known && r.start <= ra.start && rb.end <= r.end))
1203
- continue;
1204
- probes++;
1205
- if (td?.crossRegionSummary) {
1206
- td.crossRegionSummary.probesAttempted = probes;
1207
- }
1208
- const left = query.subarray(ra.start, ra.end);
1209
- const right = query.subarray(rb.start, rb.end);
1210
- // Phrase-scale contract, exactly as the bridge: the glue between the two
1211
- // forms may be up to W× the content it joins.
1212
- const maxInterior = (left.length + right.length) * ctx.space.maxGroup;
1213
- const cap = left.length + right.length + maxInterior;
1214
- // The graded ladder (spec §1): exact DAG junction, then single-synonym,
1215
- // then double-synonym, then — only when every DAG tier found nothing —
1216
- // structural-resonance. `sides` (the two halo sibling lists) is loaded
1217
- // ONCE and reused by junctionSynonyms AND structural-resonance, so no
1218
- // ladder rung repeats a halo ANN query an earlier rung already paid for.
1219
- const sides = await loadJunctionSynonymSides(ctx, left, right);
1220
- let tier = "exact";
1221
- let containers = junctionContainersFrom(ctx, left, right, cap, seedsOf(cand[a]), seedsOf(cand[b]), undefined, true);
1222
- if (containers.length === 0) {
1223
- // Tiers 2-4 — synonym containers (junctionSynonyms itself runs
1224
- // single-synonym first, falling to double-synonym only when
1225
- // single-synonym found nothing — see junction.ts).
1226
- const syn = await junctionSynonyms(ctx, left, right, maxInterior, true, sides);
1227
- if (syn.length > 0) {
1228
- containers = syn;
1229
- tier = syn[0].tier;
1230
- }
1231
- }
1232
- // Tier 5 — structural-resonance ANN, the FINAL approximate proposal
1233
- // path. Only reached when every DAG tier found NOTHING, and only when
1234
- // there is no already-corroborated region between the endpoints (a
1235
- // between-region with its own vote is evidence the gap already means
1236
- // something specific — an ANN guess must not override it).
1237
- let structuralPick = null;
1238
- if (containers.length === 0) {
1239
- // Structural-resonance composes each side's OWN gist directly (no
1240
- // byte-containment truth backs it, unlike the DAG tiers) — so, unlike
1241
- // the DAG ladder (which tolerates one approximate side because byte
1242
- // containment cannot lie), the ANN tier requires BOTH sides to be
1243
- // KNOWN (content-addressed, exact identities): an approximate chunk
1244
- // fragment's own resonance is noise at any tier, and composing noise
1245
- // into a synthetic gist only manufactures a plausible-looking but
1246
- // spurious ANN neighbour, not evidence of a genuine joint whole.
1247
- // PHRASE-SCALE CONTRACT — the same one the DAG tiers hold their glue
1248
- // to (see maxInterior above): a junction, exact or approximate, is a
1249
- // whole the two forms nearly exhaust, not two arbitrary landmarks
1250
- // anywhere in a long, multi-topic query. Without this, structural-
1251
- // resonance would pair opposite ends of an unrelated scaffolding-
1252
- // dominated query and manufacture a plausible-looking ANN neighbour
1253
- // for a "gap" that never was a phrase.
1254
- // BOTH sides must be independently DISCRIMINATIVE (individually
1255
- // voted — `strong`, not merely a content-addressed `known` chunk):
1256
- // a shared, non-discriminative scaffolding run (a repeated system
1257
- // preamble) can be `known` without ever being distinctive evidence
1258
- // of anything, and composing its own gist into a synthetic query
1259
- // manufactures a plausible-looking but spurious ANN neighbour. The
1260
- // DAG tiers can tolerate one merely-`known` side because byte
1261
- // containment cannot lie; structural-resonance has no such
1262
- // backstop, so both sides earn their place here the same way an
1263
- // ordinary approximate region earns its individual vote.
1264
- const gap = rb.start - ra.end;
1265
- if (between.length === 0 &&
1266
- strong.has(cand[a]) && strong.has(cand[b]) &&
1267
- ra.known && rb.known &&
1268
- gap <= maxInterior) {
1269
- const ownRootsA = rvs.votes.find((v) => v.start === ra.start && v.end === ra.end)?.roots;
1270
- const ownRootsB = rvs.votes.find((v) => v.start === rb.start && v.end === rb.end)?.roots;
1271
- structuralPick = await structuralResonance(ctx, query, ra, rb, sides, k, N, reachMemo, ownRootsA, ownRootsB);
1272
- }
1273
- if (structuralPick === null)
1274
- continue;
1275
- tier = "structural-resonance";
1276
- }
1277
- let best = null;
1278
- let bestExtras = [];
1279
- let bestCov = -1;
1280
- let reach;
1281
- let idf;
1282
- let confidence;
1283
- if (structuralPick !== null) {
1284
- // A resonance proposal is NOT a Junction — there is no container to
1285
- // read bytes from, so the self-evidence/contradiction/N-ary
1286
- // machinery below (byte-verified against a real container) does not
1287
- // apply; per spec §13, no N-ary extra-region coverage for resonance
1288
- // proposals.
1289
- best = { id: structuralPick.proposal.id, interior: new Uint8Array(0) };
1290
- bestExtras = [];
1291
- bestCov = rb.end - ra.start;
1292
- reach = structuralPick.reach;
1293
- idf = structuralPick.idf;
1294
- confidence = structuralPick.proposal.effectiveScore;
1295
- }
1296
- else {
1297
- // N-ARY selection: the container covering the MOST remaining candidate
1298
- // forms wins (then tightest interior, then lowest id). Reads are
1299
- // cache hits — every container's bytes were already read by the walk.
1300
- //
1301
- // SELF-EVIDENCE GUARD: a junction is BINDING evidence only when the
1302
- // container joins forms the query mentions APART. When the container's
1303
- // own joined occurrence (left..right including its interior) is a
1304
- // literal substring of the query, the query already spells that phrase
1305
- // out contiguously — perception already voted with it, and grid shards
1306
- // of one phrase pairing "around" a gap chunk would merely rediscover
1307
- // the phrase they are shards of, then explain away its rivals.
1308
- for (const c of containers) {
1309
- const bytes = cachedRead(ctx, cache, c.id, cap);
1310
- const li = indexOf(bytes, left, 0);
1311
- const ri = indexOf(bytes, right, 0);
1312
- if (li >= 0 && ri >= 0) {
1313
- const joined = bytes.subarray(Math.min(li, ri), Math.max(li + left.length, ri + right.length));
1314
- if (indexOf(query, joined, 0) >= 0)
1315
- continue; // query says it itself
1316
- }
1317
- // CONTRADICTION GUARD: a between-region already carrying its own
1318
- // vote must actually recur in this container's bytes — otherwise
1319
- // the container is a different learnt whole that happens to share
1320
- // ra/rb, and letting it stand in for the gap would silently
1321
- // override evidence the query itself already resolved there.
1322
- if (between.some((bi) => indexOf(bytes, query.subarray(regions[bi].start, regions[bi].end), 0) < 0)) {
1323
- continue;
1324
- }
1325
- let cov = left.length + right.length;
1326
- const extras = [];
1327
- for (const ei of cand) {
1328
- if (ei === cand[a] || ei === cand[b] || consumed.has(ei))
1329
- continue;
1330
- const e = regions[ei];
1331
- if (overlapsSpan(e, ra) || overlapsSpan(e, rb))
1332
- continue;
1333
- const eb = query.subarray(e.start, e.end);
1334
- if (indexOf(bytes, eb, 0) >= 0) {
1335
- extras.push(ei);
1336
- cov += eb.length;
1337
- }
1338
- }
1339
- if (cov > bestCov ||
1340
- (cov === bestCov && best !== null &&
1341
- (c.interior.length < best.interior.length ||
1342
- (c.interior.length === best.interior.length && c.id < best.id)))) {
1343
- best = c;
1344
- bestExtras = extras;
1345
- bestCov = cov;
1346
- }
1347
- }
1348
- if (best === null)
1349
- continue; // every container was self-evidence
1350
- const r = edgeAncestors(ctx, best.id, N, reachMemo);
1351
- if (r.saturated || r.roots.length === 0)
1352
- continue;
1353
- const df = Math.log(N / Math.max(1, r.contextsReached));
1354
- if (df <= 0)
1355
- continue;
1356
- reach = r;
1357
- idf = df;
1358
- // Confidence used by voting (spec §13): exact junction = 1;
1359
- // single/double-synonym = the sibling(s)' score(s), carried on the
1360
- // SynonymJunction the ladder selected.
1361
- confidence = "confidence" in best ? best.confidence : 1;
1362
- }
1363
- // MUTUAL-EXPLANATION WEIGHT — the same formula for every tier, with
1364
- // `confidence` collapsed to certainty (1) for exact evidence: under
1365
- // that collapse this is byte-for-byte the old exact-only formula
1366
- // (min(1,ratio)·min(1,1/ratio)). For structural-resonance,
1367
- // `confidence` is already annScore·semanticConfidence — never
1368
- // multiplied a second time.
1369
- const lenR = Math.max(1, bestCov);
1370
- const ratio = Math.sqrt(Math.max(1, ctx.store.contentLen(best.id, lenR * ctx.store.D)) / lenR);
1371
- const mutual = Math.min(1, confidence * ratio) *
1372
- Math.min(1, confidence / ratio);
1373
- const w = (mutual * idf) / reach.roots.length;
1374
- let spanStart = ra.start;
1375
- let spanEnd = rb.end;
1376
- for (const ei of bestExtras) {
1377
- spanStart = Math.min(spanStart, regions[ei].start);
1378
- spanEnd = Math.max(spanEnd, regions[ei].end);
1379
- }
1380
- consumed.add(cand[a]);
1381
- consumed.add(cand[b]);
1382
- for (const ei of bestExtras)
1383
- consumed.add(ei);
1384
- // EXPLAINING AWAY — see the block comment above the function. Byte
1385
- // containment in the joint container is the relatedness test (the
1386
- // vote's bytes are literally part of the learnt whole), and FULL root
1387
- // disjointness is the disagreement test: a vote sharing even one root
1388
- // with the junction corroborates it and keeps its say elsewhere.
1389
- // Counted BEFORE pushing the junction's own vote below: each ORIGINAL
1390
- // region this ascent explains away is evidence the junction speaks
1391
- // for, not evidence lost — `absorbed` (RegionVote's breadth-accounting
1392
- // field) must credit the junction with all of it, not just the ONE
1393
- // pooled axiom it collapses to.
1394
- // Only EXACT DAG evidence may explain away ordinary votes (spec §15).
1395
- // Single-synonym, double-synonym, and structural-resonance may ADD
1396
- // supporting evidence but never remove it: their evidence is itself
1397
- // approximate (a sibling substitution, or an ANN guess), so treating
1398
- // their byte-containment the way exact containment is treated would
1399
- // let an approximation override a genuine, independently-voted region.
1400
- let explainedAway = 0;
1401
- // Exact set of ORIGINAL region indices this junction explained away —
1402
- // recorded live as `superseded.add` fires (spec §3's explicit rule:
1403
- // never inferred from `absorbed` afterward).
1404
- const explainedAwayIndices = [];
1405
- if (tier === "exact") {
1406
- const containerBytes = cachedRead(ctx, cache, best.id, cap);
1407
- const jointRoots = new Set(reach.roots);
1408
- for (const rv of rvs.votes) {
1409
- if (rv.roots.some((r) => jointRoots.has(r)))
1410
- continue;
1411
- const bytes = query.subarray(rv.start, rv.end);
1412
- if (indexOf(containerBytes, bytes, 0) >= 0 && !superseded.has(rv)) {
1413
- superseded.add(rv);
1414
- explainedAway++;
1415
- if (td) {
1416
- const idx = regions.findIndex((r) => r.start === rv.start && r.end === rv.end);
1417
- if (idx >= 0)
1418
- explainedAwayIndices.push(idx);
1419
- }
1420
- }
1421
- }
1422
- }
1423
- out.push({
1424
- start: spanStart,
1425
- end: spanEnd,
1426
- canonicalFailed: false, // content-addressed: never saturation-masked
1427
- roots: reach.roots,
1428
- w,
1429
- wFocus: w,
1430
- absorbed: 1 + explainedAway,
1431
- });
1432
- if (td) {
1433
- td.crossRegionJunctionVotes.push({
1434
- container: best.id,
1435
- span: [spanStart, spanEnd],
1436
- roots: [...reach.roots],
1437
- sourceRegionIndices: [cand[a], cand[b], ...bestExtras],
1438
- explainedAwayRegionIndices: explainedAwayIndices,
1439
- absorbed: 1 + explainedAway,
1440
- tier,
1441
- });
1442
- }
1443
- const label = [cand[a], cand[b], ...bestExtras]
1444
- .sort((x, y) => regions[x].start - regions[y].start)
1445
- .map((ri) => dec(query.subarray(regions[ri].start, regions[ri].end)))
1446
- .join(" ▸ ");
1447
- const tierNote = tier === "exact"
1448
- ? `junction node ${best.id}` +
1449
- (best.interior.length === 0
1450
- ? " (adjacent)"
1451
- : ` (interior "${dec(best.interior)}")`) +
1452
- ", by content-addressed ascent"
1453
- : tier === "structural-resonance"
1454
- ? `structurally-composed ANN proposal, node ${best.id} — the query ` +
1455
- `structurally composed the endpoint regions, the real middle-` +
1456
- `query structure, and the selected halo-sibling endpoint ` +
1457
- `direction(s) (variant ${structuralPick.proposal.variant}, ` +
1458
- `annScore ${structuralPick.proposal.annScore.toFixed(3)} × ` +
1459
- `semanticConfidence ${structuralPick.proposal.semanticConfidence.toFixed(3)} = effectiveScore ${structuralPick.proposal.effectiveScore.toFixed(3)}); it did not concatenate endpoint bytes or rewrite the query`
1460
- : `${tier} junction node ${best.id}` +
1461
- (best.interior.length === 0
1462
- ? " (adjacent)"
1463
- : ` (interior "${dec(best.interior)}")`) +
1464
- `, by halo-sibling DAG ascent (confidence ${confidence.toFixed(3)})`;
1465
- ctx.trace?.step("crossRegion", [{ text: label, role: "pair" }], reach.roots.map((r) => ({
1466
- text: dec(read(ctx, r)).slice(0, 60),
1467
- node: r,
1468
- role: "joint-context",
1469
- })), `${label} → ${tierNote} → ${reach.roots.length} context(s)` +
1470
- (superseded.size > 0
1471
- ? `; ${superseded.size} aliasing vote(s) explained away`
1472
- : ""));
1473
- break; // ra is consumed — move to the next unconsumed candidate
1474
- }
1475
- }
1476
- if (td)
1477
- td.supersededOrdinaryVotes = superseded.size;
1478
- return { votes: out, superseded };
1479
- }
1480
- /** Emit the "climbConsensus" step — the human-readable note this always
1481
- * produced, now paired (when `ctx.trace` and `cfg` are both present) with
1482
- * the structured {@link ClimbConsensusData} payload on the SAME step's
1483
- * `data` field. Every exit of {@link computeAttention} funnels through
1484
- * here, so instrumentation and the existing rationale text can never drift
1485
- * apart — see the instrumentation spec's §9 "every exit path". */
1486
- export function traceAttention(ctx, regions, regionVoter, roots, steps = [], td, cfg, ranked = roots) {
1487
- if (!ctx.trace)
1488
- return;
1489
- const voters = [];
1490
- for (let i = 0; i < regions.length; i++) {
1491
- const rv = regionVoter[i];
1492
- if (rv == null)
1493
- continue;
1494
- const item = rNode(ctx, rv.id, "sub-region", rv.score);
1495
- item.text = `${item.text} (df-w ${rv.w.toFixed(2)})`;
1496
- voters.push(item);
1497
- }
1498
- const t = ctx.trace.enter("climbConsensus", voters);
1499
- // The pooled-evidence decision, one DerivationStep per anchor — the same
1500
- // shape {@link GraphSearch}'s own cover steps take (see traceDerivation).
1501
- if (steps.length > 0)
1502
- traceDerivation(ctx, steps);
1503
- const data = (td && cfg)
1504
- ? {
1505
- version: 1,
1506
- cache: { hit: false, detailAvailable: true },
1507
- config: {
1508
- annK: cfg.k,
1509
- crossRegionProbeLimit: cfg.k,
1510
- mode: cfg.mode,
1511
- ...(cfg.N !== undefined ? { corpusN: cfg.N } : {}),
1512
- dimension: ctx.store.D,
1513
- ...(cfg.N !== undefined ? { hubBound: hubBound(ctx) } : {}),
1514
- estimatorNoise: estimatorNoise(ctx.store.D),
1515
- ...(cfg.naturalBreak !== undefined
1516
- ? { naturalBreak: cfg.naturalBreak }
1517
- : {}),
1518
- ...(cfg.consensusFloor !== undefined
1519
- ? { consensusFloor: cfg.consensusFloor }
1520
- : {}),
1521
- },
1522
- candidates: {
1523
- perceived: cfg.perceivedCount,
1524
- recognised: cfg.totalRegions - cfg.perceivedCount,
1525
- total: cfg.totalRegions,
1526
- },
1527
- ...(td.regions.length > 0 ? { regions: td.regions } : {}),
1528
- ...(cfg.reachMemo ? { reaches: serialiseReaches(cfg.reachMemo) } : {}),
1529
- ...(td.crossRegionSummary
1530
- ? {
1531
- crossRegion: {
1532
- ...td.crossRegionSummary,
1533
- junctionVotes: td.crossRegionJunctionVotes,
1534
- supersededOrdinaryVotes: td.supersededOrdinaryVotes,
1535
- },
1536
- }
1537
- : {}),
1538
- ...(td.saturation ? { saturation: td.saturation } : {}),
1539
- ...(td.pooling ? { pooling: td.pooling } : {}),
1540
- ...(td.anchors.length > 0 ? { anchors: td.anchors } : {}),
1541
- result: { roots: [...roots], ranked: [...ranked] },
1542
- }
1543
- : undefined;
1544
- t.done(roots.map((r) => rNode(ctx, r.anchor, "anchor", r.vote)), roots.length === 0
1545
- ? `${regions.length} sub-regions climbed the DAG, but none agreed on a context`
1546
- : roots.length === 1
1547
- ? `${voters.length} of ${regions.length} sub-regions voted; IDF-weighted consensus picked one context (vote ${roots[0].vote.toFixed(2)})`
1548
- : `${voters.length} of ${regions.length} sub-regions voted; consensus ordered ${roots.length} INDEPENDENT points of attention (votes ${roots.map((r) => r.vote.toFixed(2)).join(", ")})`, data);
1549
- }