@hviana/sema 0.2.2 → 0.2.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 (60) hide show
  1. package/dist/src/mind/articulation.js +1 -1
  2. package/dist/src/mind/attention.d.ts +18 -2
  3. package/dist/src/mind/attention.js +88 -18
  4. package/dist/src/mind/bridge.d.ts +30 -0
  5. package/dist/src/mind/bridge.js +569 -0
  6. package/dist/src/mind/graph-search.d.ts +16 -1
  7. package/dist/src/mind/graph-search.js +34 -5
  8. package/dist/src/mind/match.d.ts +15 -2
  9. package/dist/src/mind/match.js +3 -8
  10. package/dist/src/mind/mechanisms/alu.js +8 -1
  11. package/dist/src/mind/mechanisms/cast.d.ts +54 -0
  12. package/dist/src/mind/mechanisms/cast.js +303 -48
  13. package/dist/src/mind/mechanisms/cover.js +24 -32
  14. package/dist/src/mind/mechanisms/extraction.js +75 -30
  15. package/dist/src/mind/mechanisms/recall.js +66 -0
  16. package/dist/src/mind/mind.d.ts +1 -0
  17. package/dist/src/mind/mind.js +6 -1
  18. package/dist/src/mind/pipeline.js +34 -2
  19. package/dist/src/mind/reasoning.d.ts +20 -1
  20. package/dist/src/mind/reasoning.js +84 -6
  21. package/dist/src/mind/recognition.js +157 -13
  22. package/dist/src/mind/traverse.js +16 -0
  23. package/dist/src/mind/types.d.ts +65 -2
  24. package/dist/src/mind/types.js +53 -7
  25. package/dist/src/store.d.ts +12 -3
  26. package/dist/src/store.js +9 -3
  27. package/package.json +1 -1
  28. package/src/mind/articulation.ts +1 -0
  29. package/src/mind/attention.ts +105 -17
  30. package/src/mind/bridge.ts +596 -0
  31. package/src/mind/graph-search.ts +59 -2
  32. package/src/mind/match.ts +19 -5
  33. package/src/mind/mechanisms/alu.ts +8 -1
  34. package/src/mind/mechanisms/cast.ts +336 -46
  35. package/src/mind/mechanisms/cover.ts +31 -36
  36. package/src/mind/mechanisms/extraction.ts +101 -40
  37. package/src/mind/mechanisms/recall.ts +79 -0
  38. package/src/mind/mind.ts +7 -1
  39. package/src/mind/pipeline.ts +37 -2
  40. package/src/mind/reasoning.ts +97 -5
  41. package/src/mind/recognition.ts +160 -12
  42. package/src/mind/traverse.ts +17 -0
  43. package/src/mind/types.ts +110 -6
  44. package/src/store.ts +19 -5
  45. package/test/35-attention-confidence.test.mjs +139 -0
  46. package/test/36-already-answered-fusion.test.mjs +128 -0
  47. package/test/37-cluster-dispersion-fusion.test.mjs +190 -0
  48. package/test/38-reason-restate-guard.test.mjs +94 -0
  49. package/test/39-cast-restate-guard.test.mjs +102 -0
  50. package/test/40-choosenext-scale-guard.test.mjs +75 -0
  51. package/test/41-seatofnode-direction.test.mjs +85 -0
  52. package/test/42-recognise-trace-idempotence.test.mjs +106 -0
  53. package/test/43-cast-analog-seat.test.mjs +244 -0
  54. package/test/44-recognise-edge-whitespace.test.mjs +63 -0
  55. package/test/45-liftanswer-restated-trim.test.mjs +60 -0
  56. package/test/46-recognise-multibyte-edge.test.mjs +85 -0
  57. package/test/47-cast-comparison-coverage.test.mjs +134 -0
  58. package/test/48-recognise-turn-connective.test.mjs +125 -0
  59. package/test/49-natural-units-synonym-bridge.test.mjs +175 -0
  60. package/test/50-cast-analog-consensus-floor.test.mjs +179 -0
@@ -49,32 +49,86 @@ export async function extractBySkill(ctx, query, pre) {
49
49
  if (ranked.length === 0) {
50
50
  return fail("no consensus anchor — no skill to apply");
51
51
  }
52
- let exemplar = null;
53
- let chosenAnchor = null;
54
- let skipped = 0;
55
- for (const cand of ranked) {
56
- const ex = await pre.spanShapedOf(cand.anchor);
57
- if (ex) {
58
- exemplar = ex;
59
- chosenAnchor = cand.anchor;
60
- break;
52
+ // Try ranked anchors IN ORDER until one yields a USABLE extraction — not
53
+ // merely a span-shaped exemplar, but one whose extracted span clears the
54
+ // same one-river-fold quantum (W) cover.ts's restatedSpan gate already
55
+ // treats as the floor below which byte overlap is chance, not evidence.
56
+ // isSpanShaped (spanShapedOf) is a deliberately permissive sparse-
57
+ // subsequence check — see the section note below — so it accepts exemplars
58
+ // whose relation to the query is coincidental gap-matching, not genuine
59
+ // structure. Stopping at the FIRST such exemplar let a coincidental match
60
+ // early in the ranked list win outright and read out a sub-quantum
61
+ // fragment (observed: a 3-byte "Hel" pulled from an unrelated exemplar,
62
+ // while a later ranked anchor would have read the query's own "Hello…"
63
+ // correctly). Trying further anchors when one produces nothing usable is
64
+ // the same idiom this loop already uses for non-exemplars — extended to
65
+ // cover a bad extraction, not just a structural non-match.
66
+ //
67
+ // The retry is bounded at pre.k — the SAME evidence-breadth constant every
68
+ // other consumer of a ranked list already self-limits to (resonance, the
69
+ // weave, the climb itself; see Precomputed.k's own doc comment) — not the
70
+ // full ranked list. locate()'s frame match has an EXACT-byte tier with no
71
+ // significance correction of its own (short W-byte frames are cheap to
72
+ // match by pure chance), so trying every ranked anchor turns that per-
73
+ // anchor chance into a near-certainty over enough attempts: on a pure-
74
+ // gibberish query, 170 anchors deep found an unrelated Zulu exemplar whose
75
+ // short frame happened to byte-match, producing "xyzzy pl" — a coincidence
76
+ // no different in kind from the "RaBitQ estimate overshot the reach bar
77
+ // and grounded pure gibberish" failure recall.ts's own significance
78
+ // correction exists to prevent (see recallByResonance's reach-threshold
79
+ // comment). Bounding the search to the ranked list's own top-k restores
80
+ // the "genuinely relevant but not root-significant" exemplars this loop
81
+ // was built for, without the unbounded tail's chance collisions.
82
+ const W = ctx.space.maxGroup;
83
+ const searched = ranked.slice(0, pre.k);
84
+ let shapeMisses = 0;
85
+ let subQuantum = 0;
86
+ for (const cand of searched) {
87
+ const exemplar = await pre.spanShapedOf(cand.anchor);
88
+ if (!exemplar) {
89
+ shapeMisses++;
90
+ continue;
91
+ }
92
+ const built = buildFromExemplar(ctx, query, pre, exemplar);
93
+ if (built === null || built.bytes.length < W) {
94
+ subQuantum++;
95
+ continue;
96
+ }
97
+ if (shapeMisses > 0 || subQuantum > 0) {
98
+ ctx.trace?.step("trySkillAnchors", [
99
+ rItem(query.subarray(0, 0), `skipped ${shapeMisses + subQuantum}`),
100
+ rNode(ctx, cand.anchor, "chosen"),
101
+ ], [], `skipped ${shapeMisses} non-exemplar and ${subQuantum} sub-quantum ` +
102
+ `anchor(s) before one yielded a usable extraction`);
61
103
  }
62
- skipped++;
104
+ t?.done([rItem(built.bytes, "extracted")], built.pieces === 1
105
+ ? `apply a learnt extraction skill — read the analogous span of the query` +
106
+ ` framed like "${decodeText(exemplar.answerBytes)}" sits in its exemplar`
107
+ : `apply a learnt MULTI-PIECE skill — read ${built.pieces} analogous` +
108
+ ` pieces of the query and synthesize them like "${decodeText(exemplar.answerBytes)}"`);
109
+ return {
110
+ bytes: built.bytes,
111
+ accounted: built.accounted,
112
+ unexplained: unexplainedLabel(query, built.accounted),
113
+ };
63
114
  }
64
- if (exemplar === null) {
65
- ctx.trace?.step("trySkillAnchors", [], [], `none of ${ranked.length} ranked anchor(s) is a span-shaped skill exemplar`);
115
+ if (shapeMisses === searched.length) {
116
+ ctx.trace?.step("trySkillAnchors", [], [], `none of the top ${searched.length} ranked anchor(s) (of ${ranked.length} total) ` +
117
+ `is a span-shaped skill exemplar`);
66
118
  return fail("no consensus root is a span-shaped skill exemplar");
67
119
  }
68
- if (skipped > 0) {
69
- ctx.trace?.step("trySkillAnchors", [
70
- rItem(query.subarray(0, 0), `skipped ${skipped}`),
71
- rNode(ctx, chosenAnchor, "chosen"),
72
- ], [], `skipped ${skipped} anchor(s) that are not skill exemplars`);
73
- }
120
+ return fail("no ranked anchor yielded an extraction at or above the quantum floor");
121
+ }
122
+ /** Build the extracted bytes for ONE already-accepted span-shaped exemplar —
123
+ * factored out of {@link extractBySkill} so its anchor loop can try
124
+ * successive ranked candidates instead of committing to the first
125
+ * structural match. Null when the exemplar's answer does not decompose
126
+ * against its context, or no piece's frame locates in the query. */
127
+ function buildFromExemplar(ctx, query, pre, exemplar) {
74
128
  const { contextBytes, answerBytes } = exemplar;
75
129
  const ansCtxRuns = answerRunsInContext(ctx, contextBytes, answerBytes);
76
130
  if (ansCtxRuns === null || ansCtxRuns.length === 0) {
77
- return fail("answer is not a subsequence of the context");
131
+ return null;
78
132
  }
79
133
  if (ansCtxRuns.length > 1) {
80
134
  ctx.trace?.step("decomposeAnswer", [rItem(answerBytes, "multi-piece-answer")], ansCtxRuns.map((r) => rItem(contextBytes.subarray(r.start, r.end), "piece", undefined, [
@@ -142,19 +196,10 @@ export async function extractBySkill(ctx, query, pre) {
142
196
  accounted.push([start, end]);
143
197
  }
144
198
  if (pieces.length === 0) {
145
- return fail("no answer piece's frame located in the query");
199
+ return null;
146
200
  }
147
201
  const out = pieces.length === 1 ? pieces[0] : concatBytes(pieces);
148
- t?.done([rItem(out, "extracted")], pieces.length === 1
149
- ? `apply a learnt extraction skill — read the analogous span of the query` +
150
- ` framed like "${decodeText(answerBytes)}" sits in its exemplar`
151
- : `apply a learnt MULTI-PIECE skill — read ${pieces.length} analogous` +
152
- ` pieces of the query and synthesize them like "${decodeText(answerBytes)}"`);
153
- return {
154
- bytes: out,
155
- accounted,
156
- unexplained: unexplainedLabel(query, accounted),
157
- };
202
+ return { bytes: out, accounted, pieces: pieces.length };
158
203
  }
159
204
  // ── The two span-shape readings: OPEN acceptance vs. STRONG decomposition ──
160
205
  //
@@ -12,6 +12,7 @@ import { follow, project, reverseContext } from "../match.js";
12
12
  import { CONCEPT, STEP } from "../graph-search.js";
13
13
  import { unexplainedLabel } from "../rationale.js";
14
14
  import { rItem, rNode } from "../trace.js";
15
+ import { substitutionBridge } from "../bridge.js";
15
16
  /** Recall the answer by resonating the whole query against the content index. */
16
17
  export async function recallByResonance(ctx, query, pre) {
17
18
  const t = ctx.trace?.enter("recallByResonance", [
@@ -209,6 +210,71 @@ export async function recallByResonance(ctx, query, pre) {
209
210
  }
210
211
  }
211
212
  }
213
+ // 3b. Corroborated-substitution bridge — refusal-path only (bridge.ts).
214
+ // Every gist-based tier has failed; before refusing, align the query
215
+ // byte-for-byte against the trained contexts its own stored windows
216
+ // anchor, accepting mismatches only as corpus-attested, concept-bar
217
+ // substitutions. A bridged context grounds exactly like any hit —
218
+ // projected through its learnt edges — under the same restated-fragment
219
+ // guard tiers 0b/2 apply. Costs nothing on any answering path.
220
+ {
221
+ // The resonance hits already ranked above are handed to the bridge as
222
+ // PROPOSED candidates alongside its own anchor climbs: on a corpus this
223
+ // size a W-byte window is far too common for the clamped climb to
224
+ // single out the right trained context, while the whole-query gist
225
+ // already ranked it nearest (observed live: "what is the capital of
226
+ // france" resonating straight to "What is the capital of France?" yet
227
+ // refusing on the reach bar). Approximate scores propose; the bridge's
228
+ // byte-exact alignment and attestation gates decide.
229
+ //
230
+ // The proposal breadth here is widened PAST `k` — first by requesting
231
+ // hubBound(ctx) candidates instead of `k` (recall's own tiers above
232
+ // stay at `k`; this re-resonates only on the refusal path, exactly
233
+ // where the bridge itself already runs), AND by asking the index to
234
+ // search EXHAUSTIVELY. Both matter: the IVF only ever probes
235
+ // ⌈√clusters⌉ of them (store.ts's efFor) REGARDLESS of k — widening k
236
+ // alone just returns more hits from the SAME already-probed clusters,
237
+ // never a hit whose vector lives in an unprobed one. Measured live:
238
+ // "What is the chemical symbol for water?" needs "What is the
239
+ // chemical formula for water?", scoring only 0.58 against the
240
+ // query's gist (a MIDDLE-of-string word swap perturbs the river-fold
241
+ // tree hash far more than a same-length TAIL swap like the "carbon"/
242
+ // "oxygen" neighbours that outrank it at 0.87+) — absent from the
243
+ // resonance list even at k=5000, present and byte-exact-verified the
244
+ // moment it's force-fed to the bridge directly. `exhaustive` is the
245
+ // natural, tuning-free ceiling (probe every cluster) for a call that
246
+ // is ALREADY refusal-path-only and must not miss a candidate hiding
247
+ // behind an unlucky structural distance.
248
+ const wide = k < hubBound(ctx)
249
+ ? await ctx.store.resonate(queryGist, hubBound(ctx), true)
250
+ : whole;
251
+ const bridged = await substitutionBridge(ctx, query, wide.map((h) => h.id));
252
+ if (bridged !== null) {
253
+ const g = await project(ctx, bridged.id, queryGist);
254
+ // A projection contained in a substituted candidate-side span is the
255
+ // substitution RESTATED as if it were knowledge — the exact failure
256
+ // observed live: "Darwin was born in England." bridged to the
257
+ // Einstein fact through " England." → " Germany." and would have
258
+ // voiced "Germany", an answer the substitution itself manufactured.
259
+ // The same principle as the restated-fragment guards above, extended
260
+ // to the bridge's own substitutions.
261
+ const cBytes = ctx.store.bytes(bridged.id);
262
+ const manufactured = g !== null &&
263
+ bridged.subs.some((s) => indexOf(cBytes.subarray(s.cs, s.ce), g, 0) >= 0);
264
+ if (g !== null && g.length > 0 && !restates(g) && !manufactured &&
265
+ !(g.length < query.length && indexOf(query, g, 0) >= 0)) {
266
+ return ground(g, `substitution bridge — a trained context accounts for the query ` +
267
+ `up to ${bridged.subs.length} corroborated substitution(s)`,
268
+ // Accounted NOTHING — the same epistemic humility as the echo
269
+ // tier below: a substitution-bridged grounding is a last resort
270
+ // that must lose to ANY mechanism that actually explained the
271
+ // query (observed: pricing the aligned spans here outweighed
272
+ // extraction's correct answer in the grounding decider), while
273
+ // still beating silence when everything else refused.
274
+ [], CONCEPT * bridged.subs.length + STEP);
275
+ }
276
+ }
277
+ }
212
278
  // The refusal/echo decision. The echo returns a stored form's bytes AS
213
279
  // the answer — a near-identity claim about the query — and identity-grade
214
280
  // decisions are never made on an estimated score ("approximate scores may
@@ -122,6 +122,7 @@ export declare class Mind implements MindContext {
122
122
  sites: ReadonlyArray<Site>;
123
123
  leaves: ReadonlyArray<Leaf>;
124
124
  splits: ReadonlySet<number>;
125
+ starts: ReadonlySet<number>;
125
126
  };
126
127
  /** Disambiguate among multiple learnt continuations of the same context node.
127
128
  * Required by {@link GraphSearchHost} — the graph search calls this through the
@@ -95,7 +95,12 @@ export class Mind {
95
95
  // recogniseSpan wraps recognise
96
96
  recogniseSpan(bytes) {
97
97
  const r = recognise(this, bytes);
98
- return { sites: r.sites, leaves: r.leaves, splits: r.splits };
98
+ return {
99
+ sites: r.sites,
100
+ leaves: r.leaves,
101
+ splits: r.splits,
102
+ starts: r.starts,
103
+ };
99
104
  }
100
105
  /** Disambiguate among multiple learnt continuations of the same context node.
101
106
  * Required by {@link GraphSearchHost} — the graph search calls this through the
@@ -177,8 +177,40 @@ export async function think(ctx, query, mechs) {
177
177
  ? new Set()
178
178
  : new Set(recognise(ctx, answer).sites.map((s) => s.payload));
179
179
  const reasoned = await reason(ctx, query, answer, preConsumed, pre);
180
- const fused = provenance === "recall" || provenance === "recall-echo"
181
- ? await fuseAttention(ctx, query, reasoned, pre)
180
+ // Fuse only when the query has a genuine REMAINDER no mechanism's
181
+ // structural evidence touched at all. `decided.accounted` alone
182
+ // undercounts this: it is a COST-LADDER quantity (cover.ts prices its
183
+ // masked/computed spans at near-zero and deliberately leaves them out of
184
+ // `accounted` so PASS-bridged bytes are still charged), not a coverage
185
+ // one — a query fully explained by one computed span plus bridged
186
+ // connectors can report `accounted: []` while nothing is actually left
187
+ // unexplained. The genuine remainder is what NEITHER the winning
188
+ // candidate's accounted spans NOR any recognised extension's computed
189
+ // span (`pre.computed` — every mechanism's parse() output, ALU included)
190
+ // ever touched. A remainder under one river-fold quantum (W, the same
191
+ // floor cover.ts's restatedSpan and the honesty-density bar above both
192
+ // use) is bridging punctuation/whitespace, never a second topic —
193
+ // observed: a single space between two fully-computed arithmetic spans
194
+ // ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
195
+ // corpus fact, corrupting "4 6" into "4 63".
196
+ const explained = [
197
+ ...decided.accounted,
198
+ ...pre.computed.map((u) => [u.i, u.j]),
199
+ ];
200
+ const remainder = unaccounted(explained);
201
+ // Whether the winning candidate's entire recognised substance is
202
+ // COMPUTED — every accounted span exactly a pre.computed span, nothing
203
+ // from a genuinely recognised/climbed site. fuseAttention's lone-root
204
+ // shortcut assumes a single point of attention already IS primary's own
205
+ // source; that assumption is exactly backwards for a pure computation
206
+ // (an ALU result has no anchor of its own) — see fuseAttention's
207
+ // `unclimbed` parameter, gated there by Attention.breadth so a
208
+ // coincidental echo (which this flag alone cannot distinguish) is still
209
+ // rejected.
210
+ const unclimbed = decided.accounted.length > 0 &&
211
+ decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
212
+ const fused = remainder >= ctx.space.maxGroup
213
+ ? await fuseAttention(ctx, query, reasoned, pre, unclimbed)
182
214
  : reasoned;
183
215
  done(fused, "grounded, reasoned forward, fused across points of attention");
184
216
  return { bytes: fused, provenance };
@@ -1,5 +1,16 @@
1
1
  import type { MindContext } from "./types.js";
2
2
  import type { Precomputed } from "./pipeline-mechanism.js";
3
+ /** Whether `bytes` is a proper byte-subspan of `query` — already present in
4
+ * the question, so voicing it back only restates part of what was asked,
5
+ * never answers it. The exact guard recallByResonance already applies to
6
+ * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
7
+ * check, tier 0b's argument-binding subspan check) — every mechanism that
8
+ * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
9
+ * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
10
+ * substitution — see cast.ts) needs the same guard applied to what the
11
+ * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
12
+ * the query at all — only of what structurally continues what. */
13
+ export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean;
3
14
  /** Extend a grounded answer forward across facts (multi-hop reasoning).
4
15
  * Pivots on the longest unconsumed learnt context each answer contains,
5
16
  * then follows the pivot's continuation to the next fact. Repeats up
@@ -12,4 +23,12 @@ export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint
12
23
  * When the consensus climb finds more than one dominant point, each
13
24
  * independent point grounds its own answer; they are bridged together
14
25
  * by any learnt connector the graph holds between them. */
15
- export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed): Promise<Uint8Array>;
26
+ export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed,
27
+ /** True when `primary` never touched the consensus climb at all — e.g. a
28
+ * pure ALU computation, which has no anchor of its own. commitVotes
29
+ * ALWAYS admits the dominant root regardless of its vote (attention.ts:
30
+ * "roots.length === 0 || …") on the assumption a lone root already IS
31
+ * primary's own source; that assumption is exactly backwards when
32
+ * primary is unclimbed. Absent or false preserves the original
33
+ * behaviour exactly. */
34
+ unclimbed?: boolean): Promise<Uint8Array>;
@@ -8,6 +8,19 @@ import { resolve } from "./primitives.js";
8
8
  import { corpusN } from "./traverse.js";
9
9
  import { follow, haloSiblings, project } from "./match.js";
10
10
  import { joinWithBridge, pivotInto } from "./resonance.js";
11
+ /** Whether `bytes` is a proper byte-subspan of `query` — already present in
12
+ * the question, so voicing it back only restates part of what was asked,
13
+ * never answers it. The exact guard recallByResonance already applies to
14
+ * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
15
+ * check, tier 0b's argument-binding subspan check) — every mechanism that
16
+ * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
17
+ * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
18
+ * substitution — see cast.ts) needs the same guard applied to what the
19
+ * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
20
+ * the query at all — only of what structurally continues what. */
21
+ export function restatesQuery(query, bytes) {
22
+ return bytes.length < query.length && indexOf(query, bytes, 0) >= 0;
23
+ }
11
24
  /** Extend a grounded answer forward across facts (multi-hop reasoning).
12
25
  * Pivots on the longest unconsumed learnt context each answer contains,
13
26
  * then follows the pivot's continuation to the next fact. Repeats up
@@ -81,7 +94,8 @@ export async function reason(ctx, query, answer, preConsumed, pre) {
81
94
  const fwd = await follow(ctx, curId, qv);
82
95
  const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
83
96
  if (fwd !== null && !bytesEqual(fwd, cur) &&
84
- (fwdId === null || !consumed.has(fwdId))) {
97
+ (fwdId === null || !consumed.has(fwdId)) &&
98
+ !restatesQuery(query, fwd)) {
85
99
  consumeAll(curId);
86
100
  t ??= ctx.trace?.enter("reason", [
87
101
  rItem(startedFrom, "grounded"),
@@ -98,7 +112,7 @@ export async function reason(ctx, query, answer, preConsumed, pre) {
98
112
  break;
99
113
  const fc = await follow(ctx, pivot, qv);
100
114
  consumeAll(pivot);
101
- if (fc === null || bytesEqual(fc, cur))
115
+ if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc))
102
116
  break;
103
117
  t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
104
118
  ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
@@ -111,7 +125,15 @@ export async function reason(ctx, query, answer, preConsumed, pre) {
111
125
  * When the consensus climb finds more than one dominant point, each
112
126
  * independent point grounds its own answer; they are bridged together
113
127
  * by any learnt connector the graph holds between them. */
114
- export async function fuseAttention(ctx, query, primary, pre) {
128
+ export async function fuseAttention(ctx, query, primary, pre,
129
+ /** True when `primary` never touched the consensus climb at all — e.g. a
130
+ * pure ALU computation, which has no anchor of its own. commitVotes
131
+ * ALWAYS admits the dominant root regardless of its vote (attention.ts:
132
+ * "roots.length === 0 || …") on the assumption a lone root already IS
133
+ * primary's own source; that assumption is exactly backwards when
134
+ * primary is unclimbed. Absent or false preserves the original
135
+ * behaviour exactly. */
136
+ unclimbed = false) {
115
137
  // When the answer is structurally drawn from the query itself
116
138
  // (extraction), it already spans all the query's pieces — fusion
117
139
  // would only add noise from unrelated stored contexts. The gate is
@@ -125,17 +147,73 @@ export async function fuseAttention(ctx, query, primary, pre) {
125
147
  // query, same k, same DF mode) — read them from Precomputed instead of
126
148
  // re-climbing, so even a traced response pays for the climb once.
127
149
  const forest = (await pre.attention()).roots;
128
- if (forest.length <= 1)
150
+ // A LONE root is ordinarily primary's own source — nothing to fuse. But
151
+ // when primary is unclimbed, the lone root was never checked against
152
+ // anything: it is admitted by commitVotes unconditionally, so it may be
153
+ // genuine consensus (Attention.breadth dominates — most of the query's
154
+ // OWN regions corroborate it) or a coincidental echo (breadth does not
155
+ // dominate — see test/35-attention-confidence). breadth is the SCALE-
156
+ // INVARIANT read of exactly this question: the raw IDF vote cannot serve
157
+ // here, since it is an absolute ln(N)-scaled quantity (a genuine root on
158
+ // a large store can score BELOW its own floor while a coincidental echo
159
+ // on a small one scores comfortably above its own, smaller, floor).
160
+ const lonePromotes = unclimbed && forest.length === 1 &&
161
+ forest[0].breadth > 0.5;
162
+ if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
129
163
  return primary;
164
+ }
130
165
  const pieces = [
131
166
  { start: forest[0].start, bytes: primary },
132
167
  ];
133
168
  const qv = pre.guide; // once, not per root
169
+ const rest = lonePromotes ? forest : forest.slice(1);
134
170
  const t = ctx.trace?.enter("fuseAttention", [
135
171
  rItem(primary, "primary"),
136
- ...forest.slice(1).map((r) => rNode(ctx, r.anchor, "point", r.vote)),
172
+ ...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
137
173
  ]);
138
- for (const root of forest.slice(1)) {
174
+ for (const root of rest) {
175
+ // DISPERSION: this root's contributing regions are confined to a
176
+ // single cluster (see Attention.clusters) — one local neighbourhood of
177
+ // the query, not several separate places. Raw region count already
178
+ // failed to discriminate a coincidental match from a genuine further
179
+ // topic (test/24 gap 3.1 vs test/35's echo); dispersion is a different
180
+ // question — not how MUCH evidence, but how many separate PLACES in the
181
+ // query corroborate it — and a coincidental match is structurally
182
+ // confined to one cluster no matter how strongly it resonates.
183
+ //
184
+ // EXCEPTION: crossRegionVotes' own joint conclusions (a query naming
185
+ // two attributes that were only ever learnt TOGETHER — test/34's own
186
+ // binding corpus) are inherently ONE fused context and are pooled from
187
+ // a single synthetic region, so they always read as one cluster even
188
+ // though they already, by construction, account for both original
189
+ // mentions. `breadth` (dominates — the same > half-the-query bar used
190
+ // everywhere else) still correctly recognises these: a genuine joint
191
+ // binding explains the MAJORITY of the query's regions on its own,
192
+ // which a coincidental echo never does (verified: test/35's echo tops
193
+ // out at 0.40). So a root is trusted when EITHER measure alone
194
+ // indicates real signal — excluded only when BOTH are weak. Cheap and
195
+ // synchronous — checked before the async already-answered walk below.
196
+ if (root.clusters < 2 && root.breadth <= 0.5) {
197
+ ctx.trace?.step("singleCluster", [rNode(ctx, root.anchor, "point", root.vote)], [], "this point's evidence is confined to one local neighbourhood of the query — not trusted as an independent topic");
198
+ continue;
199
+ }
200
+ // ALREADY ANSWERED: this root's own learnt continuation — the same
201
+ // content-addressed walk reason()'s echo guard already trusts
202
+ // (`ctx.store.prevCount(qId) > 0`), here applied per-candidate instead
203
+ // of to the whole query — is VERBATIM present later in the query. A
204
+ // query that embeds both an exchange's ask and its own already-given
205
+ // reply (a conversation's turn plus its own prior answer, concatenated
206
+ // raw by addTurn — or any caller pasting a transcript into one
207
+ // respond() call; the check is Mind-bookkeeping-free, so it treats both
208
+ // identically) has already spoken this root's answer — fusing it in
209
+ // again would only restate it. Deliberately NOT a magnitude measure:
210
+ // it fires on exact content-addressed recurrence, not on how strongly
211
+ // the root resonates.
212
+ const cont = await follow(ctx, root.anchor, qv);
213
+ if (cont !== null && cont.length > 0 && indexOf(query, cont, root.end) >= 0) {
214
+ ctx.trace?.step("alreadyAnswered", [rNode(ctx, root.anchor, "point", root.vote)], [rItem(cont, "continuation")], "this point's own learnt continuation already appears later in the query — already answered, not fused");
215
+ continue;
216
+ }
139
217
  const g = await project(ctx, root.anchor, qv);
140
218
  if (g === null || g.length === 0)
141
219
  continue;