@hviana/sema 0.2.2 → 0.2.3

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.
@@ -83,7 +83,7 @@ export async function articulate(ctx, answer, query) {
83
83
  s.end,
84
84
  ])),
85
85
  ]);
86
- const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
86
+ const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, ans.starts, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
87
87
  const segs = solved && solved.segs;
88
88
  tArtCover?.done(segs === null
89
89
  ? []
@@ -39,6 +39,9 @@ export declare function poolVotes(ctx: MindContext, regionVotes: readonly Region
39
39
  end: number;
40
40
  w: number;
41
41
  }>;
42
+ /** Per-anchor SCALE-INVARIANT support: Σ RegionVote.absorbed over the
43
+ * distinct contributing regions — see Attention.breadth. */
44
+ regionSupport: Map<number, number>;
42
45
  steps: DerivationStep[];
43
46
  };
44
47
  export declare function commitVotes(ctx: MindContext, pooled: {
@@ -49,6 +52,7 @@ export declare function commitVotes(ctx: MindContext, pooled: {
49
52
  end: number;
50
53
  w: number;
51
54
  }>;
55
+ regionSupport: Map<number, number>;
52
56
  steps: DerivationStep[];
53
57
  }, sat: SaturationInfo, regions: readonly Region[], regionVoter: ReadonlyArray<{
54
58
  id: number;
@@ -400,6 +400,7 @@ export function poolVotes(ctx, regionVotes, sat, N) {
400
400
  const votes = new Map();
401
401
  const votesIdf = new Map();
402
402
  const support = new Map();
403
+ const regionSupport = new Map();
403
404
  const steps = [];
404
405
  let order = 0;
405
406
  for (const pc of pool.values()) {
@@ -407,14 +408,17 @@ export function poolVotes(ctx, regionVotes, sat, N) {
407
408
  votes.set(pc.item.id, pc.cost);
408
409
  const premises = [];
409
410
  const seenRi = new Set();
411
+ let breadthSum = 0;
410
412
  for (const c of pc.contributions) {
411
413
  const p0 = c.premises[0].item;
412
414
  if (p0.kind !== "region" || seenRi.has(p0.ri))
413
415
  continue;
414
416
  seenRi.add(p0.ri);
415
417
  const rv = regionVotes[p0.ri];
418
+ breadthSum += rv.absorbed ?? 1;
416
419
  premises.push({ kind: "form", span: [rv.start, rv.end] });
417
420
  }
421
+ regionSupport.set(pc.item.id, breadthSum);
418
422
  steps.push({
419
423
  order: order++,
420
424
  move: "pool-vote",
@@ -444,18 +448,29 @@ export function poolVotes(ctx, regionVotes, sat, N) {
444
448
  }
445
449
  }
446
450
  }
447
- return { votes, votesIdf, support, steps };
451
+ return { votes, votesIdf, support, regionSupport, steps };
448
452
  }
449
453
  export function commitVotes(ctx, pooled, sat, regions, regionVoter, N) {
450
- const { votes, votesIdf, support, steps } = pooled;
454
+ const { votes, votesIdf, support, regionSupport, steps } = pooled;
451
455
  if (votes.size === 0) {
452
456
  traceAttention(ctx, regions, regionVoter, [], steps);
453
457
  return { roots: [], ranked: [] };
454
458
  }
459
+ // SCALE-INVARIANT confidence — see Attention.breadth's doc. regions.length
460
+ // is the query's OWN full candidate count (most never vote at all), the
461
+ // same denominator the "N of M sub-regions voted" rationale text already
462
+ // reports; regionSupport is that same accounting read PER ANCHOR.
463
+ const totalRegions = Math.max(1, regions.length);
455
464
  const ranked = [...votes.entries()]
456
465
  .map(([anchor, vote]) => {
457
466
  const s = support.get(anchor);
458
- return { anchor, vote, start: s.start, end: s.end };
467
+ return {
468
+ anchor,
469
+ vote,
470
+ start: s.start,
471
+ end: s.end,
472
+ breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
473
+ };
459
474
  })
460
475
  .sort((a, b) => b.vote - a.vote);
461
476
  const overlaps = (a, b) => a.start < b.end && b.start < a.end;
@@ -855,14 +870,6 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
855
870
  spanStart = Math.min(spanStart, regions[ei].start);
856
871
  spanEnd = Math.max(spanEnd, regions[ei].end);
857
872
  }
858
- out.push({
859
- start: spanStart,
860
- end: spanEnd,
861
- canonicalFailed: false, // content-addressed: never saturation-masked
862
- roots: reach.roots,
863
- w,
864
- wFocus: w,
865
- });
866
873
  consumed.add(cand[a]);
867
874
  consumed.add(cand[b]);
868
875
  for (const ei of bestExtras)
@@ -872,15 +879,32 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo) {
872
879
  // vote's bytes are literally part of the learnt whole), and FULL root
873
880
  // disjointness is the disagreement test: a vote sharing even one root
874
881
  // with the junction corroborates it and keeps its say elsewhere.
882
+ // Counted BEFORE pushing the junction's own vote below: each ORIGINAL
883
+ // region this ascent explains away is evidence the junction speaks
884
+ // for, not evidence lost — `absorbed` (RegionVote's breadth-accounting
885
+ // field) must credit the junction with all of it, not just the ONE
886
+ // pooled axiom it collapses to.
875
887
  const containerBytes = cachedRead(ctx, cache, best.id, cap);
876
888
  const jointRoots = new Set(reach.roots);
889
+ let explainedAway = 0;
877
890
  for (const rv of rvs.votes) {
878
891
  if (rv.roots.some((r) => jointRoots.has(r)))
879
892
  continue;
880
893
  const bytes = query.subarray(rv.start, rv.end);
881
- if (indexOf(containerBytes, bytes, 0) >= 0)
894
+ if (indexOf(containerBytes, bytes, 0) >= 0 && !superseded.has(rv)) {
882
895
  superseded.add(rv);
896
+ explainedAway++;
897
+ }
883
898
  }
899
+ out.push({
900
+ start: spanStart,
901
+ end: spanEnd,
902
+ canonicalFailed: false, // content-addressed: never saturation-masked
903
+ roots: reach.roots,
904
+ w,
905
+ wFocus: w,
906
+ absorbed: 1 + explainedAway,
907
+ });
884
908
  const label = [cand[a], cand[b], ...bestExtras]
885
909
  .sort((x, y) => regions[x].start - regions[y].start)
886
910
  .map((ri) => dec(query.subarray(regions[ri].start, regions[ri].end)))
@@ -54,6 +54,18 @@ export type GItem = {
54
54
  cover: boolean;
55
55
  rec: boolean;
56
56
  node?: number;
57
+ /** Set only for a {@link ComputedResult} axiom (an extension's derived
58
+ * value, e.g. ALU arithmetic) — as opposed to a genuinely RECOGNISED
59
+ * learned form. Both set `rec: true` (a computation bridges the cover
60
+ * exactly like a learned terminal answer), but they mean different
61
+ * things for `i..j`'s WIDTH: a recognised form's query-span width is
62
+ * real evidence of how much of the query's meaning it accounts for
63
+ * (liftAnswer's half-dominance framing decision); a computed span's
64
+ * width is operand digit-count, uncorrelated with meaning ("1000 - 421"
65
+ * is wider than "15 * 7" only because the numbers are bigger, not
66
+ * because subtraction is more "the point" of its query). See
67
+ * {@link liftAnswer}. */
68
+ computed?: boolean;
57
69
  };
58
70
  export declare const STEP = 1;
59
71
  export declare const CONCEPT = 10;
@@ -72,6 +84,9 @@ export interface Seg {
72
84
  bytes: Uint8Array;
73
85
  rec: boolean;
74
86
  node?: number;
87
+ /** See the `computed` field of the "out" {@link GItem} — set only for an
88
+ * extension's derived value, never a genuinely recognised learned form. */
89
+ computed?: boolean;
75
90
  }
76
91
  /** One rule application inside the cover's lightest derivation — the FINEST
77
92
  * grain of Sema's core reasoning, one node of the adapted A*LD proof tree. `move`
@@ -144,7 +159,7 @@ export declare class GraphSearch {
144
159
  * Any learnt connector between two rewrites is spliced IN by the in-search
145
160
  * connector rule (see {@link outRules}), so the returned spans already carry
146
161
  * it — there is no post-pass. */
147
- cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
162
+ cover(queryLen: number, sites: ReadonlyArray<Site>, conceptTarget: ReadonlyMap<number, number>, leaves: ReadonlyArray<Leaf>, splits: ReadonlySet<number>, starts: ReadonlySet<number>, substitutions?: ReadonlyMap<number, Uint8Array>, connectors?: ReadonlyMap<string, Uint8Array>, computedResults?: ReadonlyArray<ComputedResult>,
148
163
  /** When given, receives the lightest derivation's rule applications — the
149
164
  * full adapted A*LD proof tree as classified {@link DerivationStep}s — for the TOP
150
165
  * cover only (a recursive recompletion solves its own sub-cover and is not
@@ -63,6 +63,7 @@ function readCover(derivation) {
63
63
  bytes: out.bytes,
64
64
  rec: out.rec,
65
65
  node: out.node,
66
+ computed: out.computed,
66
67
  });
67
68
  }
68
69
  node = node.premises[0];
@@ -205,7 +206,7 @@ export class GraphSearch {
205
206
  * Any learnt connector between two rewrites is spliced IN by the in-search
206
207
  * connector rule (see {@link outRules}), so the returned spans already carry
207
208
  * it — there is no post-pass. */
208
- cover(queryLen, sites, conceptTarget, leaves, splits, substitutions, connectors, computedResults,
209
+ cover(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults,
209
210
  /** When given, receives the lightest derivation's rule applications — the
210
211
  * full adapted A*LD proof tree as classified {@link DerivationStep}s — for the TOP
211
212
  * cover only (a recursive recompletion solves its own sub-cover and is not
@@ -221,6 +222,7 @@ export class GraphSearch {
221
222
  sites,
222
223
  leaves,
223
224
  splits,
225
+ starts,
224
226
  }, conceptTarget, substitutions, connectors, computedResults, onDerivation);
225
227
  }
226
228
  /** Build the deduction system for one span and return its lightest cover's
@@ -237,7 +239,7 @@ export class GraphSearch {
237
239
  * decomposes, two recomposes, any mix — and stops only when it reaches a node
238
240
  * that leads nowhere new, never at an arbitrary count. */
239
241
  solve(spanLen, recognition, conceptTarget, substitutions, connectors, computedResults, onDerivation) {
240
- const system = this.buildSearch(spanLen, recognition.sites, conceptTarget, recognition.leaves, recognition.splits, substitutions, connectors, computedResults);
242
+ const system = this.buildSearch(spanLen, recognition.sites, conceptTarget, recognition.leaves, recognition.splits, recognition.starts, substitutions, connectors, computedResults);
241
243
  const derivation = lightestDerivation(system);
242
244
  // When covering under a substitution map (articulation), a form→out rule is
243
245
  // the form EMITTING the asker's voice, not grounding to its own answer — so
@@ -257,8 +259,15 @@ export class GraphSearch {
257
259
  * adjacent fragments toward a known leaf (findLeaf) or branch (findBranch);
258
260
  * a completion fused with its neighbour may spell a deeper learned form the
259
261
  * flat probes can't name, recovered canonically by {@link resolve}. */
260
- buildSearch(queryLen, sites, conceptTarget, leaves, splits, substitutions, connectors, computedResults) {
262
+ buildSearch(queryLen, sites, conceptTarget, leaves, splits, starts, substitutions, connectors, computedResults) {
261
263
  const W = this.maxGroup; // fusible span ceiling (shortest composite bound)
264
+ // Same corpus-scale hub floor {@link atomIsHub}/{@link atomReach} (traverse.ts)
265
+ // derive for byte atoms — duplicated here rather than imported because this
266
+ // module is deliberately host-based (no MindContext), and both inputs
267
+ // (maxGroup, edgeSourceCount) are already in scope with no ctx needed. If
268
+ // the formula ever changes, it must change in BOTH places (see canonical.ts's
269
+ // header for the same write/read-side duplication convention).
270
+ const atomsAreHubs = Math.max(1, Math.ceil((this.store.edgeSourceCount() * W) / 256)) > Math.ceil(Math.sqrt(Math.max(2, this.store.edgeSourceCount())));
262
271
  const nodeBytes = (n) => this.store.bytesPrefix(n, ALL);
263
272
  // Content-addressed probes over the store's hash-cons maps — the same keys
264
273
  // training filled. No byte-by-byte trie walk.
@@ -350,6 +359,7 @@ export class GraphSearch {
350
359
  cover: true,
351
360
  rec: true,
352
361
  node: u.node,
362
+ computed: true,
353
363
  },
354
364
  cost: STEP,
355
365
  };
@@ -388,6 +398,8 @@ export class GraphSearch {
388
398
  return this.outRules(it, {
389
399
  W,
390
400
  splits,
401
+ starts,
402
+ atomsAreHubs,
391
403
  coversDone,
392
404
  outsByStart,
393
405
  outsByEnd,
@@ -845,7 +857,23 @@ export class GraphSearch {
845
857
  * enters the graph as a form the moment it names a node. */
846
858
  *fuse(l, r, ctx) {
847
859
  const bytes = concat2(l.bytes, r.bytes);
848
- let node = bytes.length <= ctx.W ? ctx.findLeafU(bytes) : undefined;
860
+ // A PURE leaf-leaf fuse (neither side already a recognised completion)
861
+ // is opportunistic cross-leaf recovery exactly like recognition.ts's own
862
+ // canonical chain — findLeaf/findBranch here have no idea WHY these two
863
+ // leaves are adjacent, only that their concatenation happens to spell a
864
+ // trained form ("hi" recovered from "W[hi]ch"). The same corpus-scale
865
+ // caution recognition.ts's `boundary` gate applies: trust it fully when
866
+ // `l.i` is a position the query's OWN fold chose as a boundary (real
867
+ // structural evidence); past the scale where atoms themselves stop
868
+ // discriminating, an interior offset's opportunistic match is noise, not
869
+ // a genuine cross-leaf recovery. A completion-involved fuse (l.rec ||
870
+ // r.rec) is a different, legitimate case — a rewrite growing into its
871
+ // neighbour — and is exempt, same as recognition.ts's rec-derived sites.
872
+ const trusted = l.rec || r.rec || ctx.starts.has(l.i) ||
873
+ !ctx.atomsAreHubs;
874
+ let node = (trusted && bytes.length <= ctx.W)
875
+ ? ctx.findLeafU(bytes)
876
+ : undefined;
849
877
  // Whether this pair ACTUALLY forms a 2-child branch — the hard evidence
850
878
  // that the fused bytes are a learned form worth keeping alive. Derived
851
879
  // from the same findBranchU probe that sets `node`; when false, the pair
@@ -853,7 +881,8 @@ export class GraphSearch {
853
881
  // node cannot contribute to any further fusion (findBranch needs two
854
882
  // nodes, resolve needs a completion, and findLeaf already had its chance).
855
883
  let pairFormsBranch = false;
856
- if (node === undefined && l.node !== undefined && r.node !== undefined) {
884
+ if (trusted && node === undefined && l.node !== undefined &&
885
+ r.node !== undefined) {
857
886
  node = ctx.findBranchU([l.node, r.node]);
858
887
  pairFormsBranch = node !== undefined;
859
888
  }
@@ -12,7 +12,14 @@ import { unexplainedLabel } from "../rationale.js";
12
12
  export function aluToMechanism(alu) {
13
13
  return {
14
14
  name: "alu",
15
- provenance: "cover",
15
+ // Not a cover derivation: cover.ts composes an answer by walking
16
+ // recognised query STRUCTURE; the ALU evaluates a recognised expression
17
+ // to its authoritative result and hands the bytes back untouched. It
18
+ // shares cover's near-zero floor (computation always wins, masked into
19
+ // cover's own search — see mechanisms/cover.ts), but the candidate this
20
+ // produces is not one of cover's derivations, so it carries its own
21
+ // honest label, the same way extract/cast/recall each carry theirs.
22
+ provenance: "alu",
16
23
  parse: (query) => alu.parse(query),
17
24
  async floor(_ctx, _query, pre, _worthRunning) {
18
25
  return pre.computed.length > 0 ? 0 : null;
@@ -70,19 +70,37 @@ const MIN_WEAVE = 2;
70
70
  *
71
71
  * Returns the array of {@link CastResult}s that fired (possibly empty). */
72
72
  export async function counterfactualTransfer(ctx, query, pre) {
73
+ // Opened unconditionally, at entry — the same convention recall.ts's
74
+ // recallByResonance and extraction.ts's extractBySkill use, so every exit
75
+ // path (five gates below, then the schemas themselves) closes through
76
+ // ONE scope and inspectRationale never hits a silent dead end. Only the
77
+ // first two gates duplicate floor()'s own admissible bound (query length,
78
+ // ranked anchor count) — required to stay in sync per this function's own
79
+ // doc comment above, and effectively dead through the ordinary pipeline
80
+ // (floor() returning null already stops run() from being called at all),
81
+ // but this function is also exported and callable directly, so they stay
82
+ // and get the same honest trace as everything past them.
83
+ const t = ctx.trace?.enter("counterfactual", [rItem(query, "query")]);
84
+ const fail = (note) => {
85
+ t?.done([], note);
86
+ return [];
87
+ };
73
88
  const quantum = ctx.space.maxGroup;
74
89
  if (query.length < 2 * quantum || ctx.store.edgeSourceCount() === 0) {
75
- return [];
90
+ return fail("query below the two-quantum floor, or no edges learnt yet");
76
91
  }
77
92
  const { roots, ranked } = await pre.attention();
78
- if (ranked.length < 2)
79
- return [];
93
+ if (ranked.length < 2) {
94
+ return fail(`only ${ranked.length} ranked anchor(s) — CAST needs at least two`);
95
+ }
80
96
  const weave = await pre.weave();
81
97
  const points = weave.points;
82
98
  const depth = weave.depth;
83
99
  const aligned = points.length;
84
- if (aligned < 2)
85
- return [];
100
+ if (aligned < 2) {
101
+ return fail(`only ${aligned} structure(s) aligned across the query — CAST needs ` +
102
+ `at least two to transfer between`);
103
+ }
86
104
  // ── Frame gate (half-dominance, weave-local) ─────────────────────────
87
105
  // A byte is FRAME when more than MIN_WEAVE aligned structures cover it
88
106
  // AND those structures are a majority of all aligned structures.
@@ -120,14 +138,20 @@ export async function counterfactualTransfer(ctx, query, pre) {
120
138
  const isRoot = (id) => roots.some((r) => r.anchor === id);
121
139
  // The weave must touch a COMMITTED point of attention: the dominant
122
140
  // structure itself, or another aligned point the climb committed to.
123
- if (!points.some((p) => isRoot(p.anchor)))
141
+ if (!points.some((p) => isRoot(p.anchor))) {
142
+ t?.done([
143
+ ...points.map((p) => rNode(ctx, p.anchor, "aligned")),
144
+ ...roots.map((r) => rNode(ctx, r.anchor, "committed-root")),
145
+ ], `${points.length} aligned structure(s), but none is one of the climb's ` +
146
+ `${roots.length} committed root(s) — CAST refuses to transfer through ` +
147
+ `content the climb itself never settled on`);
124
148
  return [];
149
+ }
125
150
  const woven = points.some((p) => p.runs.some((r) => !pre.rec.sites.some((s) => r.qs >= s.start && r.qe <= s.end)));
126
- if (!woven)
127
- return [];
128
- const t = ctx.trace?.enter("counterfactual", [
129
- rItem(query, "query"),
130
- ]);
151
+ if (!woven) {
152
+ return fail(`every aligned run restates a recognised query site — nothing was ` +
153
+ `actually WOVEN across structures, so there is nothing to transfer`);
154
+ }
131
155
  // Each schema tried below RECORDS its candidate (when it fires) rather than
132
156
  // returning immediately — every schema that succeeds contributes its own
133
157
  // candidate, and the grounding decider's own weight comparison (not CAST's
@@ -122,11 +122,18 @@ export const coverMechanism = {
122
122
  return [];
123
123
  const connectors = await resolveConnectors(ctx, sites);
124
124
  let splits = rec.splits;
125
+ let starts = rec.starts;
125
126
  if (computed.length > 0) {
126
127
  splits = new Set(rec.splits);
128
+ starts = new Set(rec.starts);
127
129
  for (const u of computed) {
128
130
  splits.add(u.i);
129
131
  splits.add(u.j);
132
+ // A computation's own boundaries carry the same fold-level evidence
133
+ // a chunk boundary does — "computation always wins" (see the header
134
+ // comment) extends to being trusted ground for cross-leaf recovery.
135
+ starts.add(u.i);
136
+ starts.add(u.j);
130
137
  }
131
138
  }
132
139
  const concepts = await resolveConcepts(ctx, sites);
@@ -150,7 +157,7 @@ export const coverMechanism = {
150
157
  ])),
151
158
  ...computedResults.map((u) => rItem(u.bytes, "computed")),
152
159
  ], coverDeps.length ? coverDeps : undefined);
153
- const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
160
+ const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, starts, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
154
161
  const segs = solved && solved.segs;
155
162
  tCover?.done(segs === null
156
163
  ? []
@@ -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
  //
@@ -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 };
@@ -12,4 +12,12 @@ export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint
12
12
  * When the consensus climb finds more than one dominant point, each
13
13
  * independent point grounds its own answer; they are bridged together
14
14
  * by any learnt connector the graph holds between them. */
15
- export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed): Promise<Uint8Array>;
15
+ export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed,
16
+ /** True when `primary` never touched the consensus climb at all — e.g. a
17
+ * pure ALU computation, which has no anchor of its own. commitVotes
18
+ * ALWAYS admits the dominant root regardless of its vote (attention.ts:
19
+ * "roots.length === 0 || …") on the assumption a lone root already IS
20
+ * primary's own source; that assumption is exactly backwards when
21
+ * primary is unclimbed. Absent or false preserves the original
22
+ * behaviour exactly. */
23
+ unclimbed?: boolean): Promise<Uint8Array>;