@hviana/sema 0.2.7 → 0.2.8

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.
@@ -2,6 +2,7 @@ import type { DerivationStep } from "./graph-search.js";
2
2
  import type { AncestorReach, Attention, AttentionRead, DFMode, MindContext, Region, RegionVote, SaturationInfo, SaturationStop } from "./types.js";
3
3
  import { type StructuralPart } from "../geometry.js";
4
4
  import { type JunctionSynonymSides } from "./junction.js";
5
+ import type { Vec } from "../vec.js";
5
6
  /** How the newly-added graded junction ladder (junction.ts / attention.ts's
6
7
  * {@link CrossRegionTier}) is reported on a `junctionVotes` entry. The
7
8
  * instrumentation spec this implements predates that ladder and only knew
@@ -360,8 +361,12 @@ export interface StructuralResonanceProposal {
360
361
  /** Build, bound and order every mandatory structural variant (§7-8): the
361
362
  * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
362
363
  * synonym variants (single- and double-synonym combined, one shared
363
- * budget) are appended, ordered by confidence, then kind, then sibling id. */
364
- export declare function buildStructuralVariants(ctx: MindContext, ra: Region, rb: Region, sides: JunctionSynonymSides): {
364
+ * budget) are appended, ordered by confidence, then kind, then sibling id.
365
+ * Variant selection is entirely lightweight (see {@link
366
+ * buildStructuralVariantSpecs}); a sibling's bytes are read and perceived
367
+ * only for specs actually retained, and at most once per sibling id per
368
+ * climb via `siblingGistMemo`. */
369
+ export declare function buildStructuralVariants(ctx: MindContext, ra: Region, rb: Region, sides: JunctionSynonymSides, siblingGistMemo: Map<number, Vec>): {
365
370
  variants: StructuralVariant[];
366
371
  exactLeft: StructuralPart;
367
372
  exactRight: StructuralPart;
@@ -370,7 +375,7 @@ export declare function buildStructuralVariants(ctx: MindContext, ra: Region, rb
370
375
  * ANN-query each, merge proposals by candidate id, and validate the winner
371
376
  * through the SAME structural gates every other tier answers to (saturation,
372
377
  * roots, IDF, contrastive margin). Returns null when nothing survives. */
373
- export declare function structuralResonance(ctx: MindContext, query: Uint8Array, ra: Region, rb: Region, sides: JunctionSynonymSides, k: number, N: number, reachMemo: Map<number, AncestorReach>,
378
+ export declare function structuralResonance(ctx: MindContext, query: Uint8Array, ra: Region, rb: Region, sides: JunctionSynonymSides, siblingGistMemo: Map<number, Vec>, k: number, N: number, reachMemo: Map<number, AncestorReach>,
374
379
  /** Each side's OWN individual climb roots (from voteRegions), when it cast
375
380
  * one — the self-evidence backstop structural-resonance needs and the
376
381
  * exact tier gets for free from literal byte containment (§11's whole
@@ -916,67 +916,94 @@ const VARIANT_KIND_ORDER = {
916
916
  "right-synonym": 1,
917
917
  "double-synonym": 2,
918
918
  };
919
- /** A node's structural gist, read directly from its own stored bytes — the
920
- * repository's node → gist accessor: content is immutable and perception is
921
- * a pure function of bytes, so re-perceiving a node's full stored bytes
922
- * reproduces exactly the gist it was interned with. Never concatenates
923
- * the sibling's bytes with anything else. */
924
- function storedNodeGist(ctx, id) {
925
- return gistOf(ctx, read(ctx, id));
926
- }
927
- /** A halo sibling's structural part, occupying the ORIGINAL query-region
928
- * slot length — the sibling replaces only the DIRECTION, never the query's
929
- * own bytes, position, or gap (see §6 of the spec this implements). */
930
- function siblingPart(ctx, sibling, originalRegion) {
931
- return {
932
- v: storedNodeGist(ctx, sibling.id),
933
- len: originalRegion.end - originalRegion.start,
934
- };
919
+ /** Same deterministic ordering the old implementation applied to already-
920
+ * materialized variants (§8): semantic confidence desc, then kind
921
+ * (left-synonym, right-synonym, double-synonym), then sibling ids asc. */
922
+ function compareStructuralVariantSpecs(a, b) {
923
+ return b.semanticConfidence - a.semanticConfidence ||
924
+ VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
925
+ (a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
926
+ (a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1);
935
927
  }
936
- /** Build, bound and order every mandatory structural variant (§7-8): the
937
- * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
938
- * synonym variants (single- and double-synonym combined, one shared
939
- * budget) are appended, ordered by confidence, then kind, then sibling id. */
940
- export function buildStructuralVariants(ctx, ra, rb, sides) {
941
- const exactLeft = { v: ra.v, len: ra.end - ra.start };
942
- const exactRight = { v: rb.v, len: rb.end - rb.start };
943
- const synonymVariants = [];
944
- for (const ls of sides.leftSiblings) {
945
- synonymVariants.push({
946
- left: siblingPart(ctx, ls, ra),
947
- right: exactRight,
928
+ /** Every single- and double-synonym combination, as cost-free descriptors
929
+ * no `read`, `gistOf`, `perceive` or `StructuralPart` allocation. Both
930
+ * sibling lists are already bounded by `haloQueryK`, so the O(haloQueryK²)
931
+ * cross-product here is cheap; only the SELECTED specs go on to pay for
932
+ * sibling reconstruction. */
933
+ function buildStructuralVariantSpecs(sides) {
934
+ const specs = [];
935
+ for (const left of sides.leftSiblings) {
936
+ specs.push({
948
937
  kind: "left-synonym",
949
- semanticConfidence: ls.score,
950
- leftSiblingId: ls.id,
938
+ semanticConfidence: left.score,
939
+ leftSiblingId: left.id,
951
940
  });
952
941
  }
953
- for (const rs of sides.rightSiblings) {
954
- synonymVariants.push({
955
- left: exactLeft,
956
- right: siblingPart(ctx, rs, rb),
942
+ for (const right of sides.rightSiblings) {
943
+ specs.push({
957
944
  kind: "right-synonym",
958
- semanticConfidence: rs.score,
959
- rightSiblingId: rs.id,
945
+ semanticConfidence: right.score,
946
+ rightSiblingId: right.id,
960
947
  });
961
948
  }
962
- for (const ls of sides.leftSiblings) {
963
- for (const rs of sides.rightSiblings) {
964
- synonymVariants.push({
965
- left: siblingPart(ctx, ls, ra),
966
- right: siblingPart(ctx, rs, rb),
949
+ for (const left of sides.leftSiblings) {
950
+ for (const right of sides.rightSiblings) {
951
+ specs.push({
967
952
  kind: "double-synonym",
968
- semanticConfidence: Math.min(ls.score, rs.score),
969
- leftSiblingId: ls.id,
970
- rightSiblingId: rs.id,
953
+ semanticConfidence: Math.min(left.score, right.score),
954
+ leftSiblingId: left.id,
955
+ rightSiblingId: right.id,
971
956
  });
972
957
  }
973
958
  }
974
- // §8: semantic confidence desc, then kind (left-synonym, right-synonym,
975
- // double-synonym), then left sibling id asc, then right sibling id asc.
976
- synonymVariants.sort((a, b) => b.semanticConfidence - a.semanticConfidence ||
977
- VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
978
- (a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
979
- (a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1));
959
+ specs.sort(compareStructuralVariantSpecs);
960
+ return specs;
961
+ }
962
+ /** A halo sibling's structural gist, bounded to `maxBytes` of stored content
963
+ * and reused across the whole climb. `positiveMemo` (shared across every
964
+ * probe in the climb, passed in by the caller) remembers only successfully
965
+ * reconstructed complete gists — a sibling rejected here for being too
966
+ * large for THIS pair's phrase-scale bound may still be admissible for a
967
+ * larger-spanning pair later, so a rejection is never memoized globally.
968
+ * `localMemo` is scoped to one `buildStructuralVariants` call, where every
969
+ * variant shares the same bound, so a `null` there is safe to reuse. */
970
+ function loadBoundedSiblingGist(ctx, id, maxBytes, positiveMemo, localMemo) {
971
+ if (localMemo.has(id)) {
972
+ return localMemo.get(id) ?? null;
973
+ }
974
+ const cached = positiveMemo.get(id);
975
+ if (cached !== undefined) {
976
+ localMemo.set(id, cached);
977
+ return cached;
978
+ }
979
+ const length = ctx.store.contentLen(id, maxBytes + 1);
980
+ if (length <= 0 || length > maxBytes) {
981
+ localMemo.set(id, null);
982
+ return null;
983
+ }
984
+ const bytes = read(ctx, id, maxBytes + 1);
985
+ if (bytes.length === 0 || bytes.length > maxBytes) {
986
+ localMemo.set(id, null);
987
+ return null;
988
+ }
989
+ const gist = gistOf(ctx, bytes);
990
+ positiveMemo.set(id, gist);
991
+ localMemo.set(id, gist);
992
+ return gist;
993
+ }
994
+ /** Build, bound and order every mandatory structural variant (§7-8): the
995
+ * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
996
+ * synonym variants (single- and double-synonym combined, one shared
997
+ * budget) are appended, ordered by confidence, then kind, then sibling id.
998
+ * Variant selection is entirely lightweight (see {@link
999
+ * buildStructuralVariantSpecs}); a sibling's bytes are read and perceived
1000
+ * only for specs actually retained, and at most once per sibling id per
1001
+ * climb via `siblingGistMemo`. */
1002
+ export function buildStructuralVariants(ctx, ra, rb, sides, siblingGistMemo) {
1003
+ const leftLen = ra.end - ra.start;
1004
+ const rightLen = rb.end - rb.start;
1005
+ const exactLeft = { v: ra.v, len: leftLen };
1006
+ const exactRight = { v: rb.v, len: rightLen };
980
1007
  const variants = [
981
1008
  {
982
1009
  left: exactLeft,
@@ -984,8 +1011,43 @@ export function buildStructuralVariants(ctx, ra, rb, sides) {
984
1011
  kind: "exact-exact",
985
1012
  semanticConfidence: 1,
986
1013
  },
987
- ...synonymVariants.slice(0, ctx.cfg.haloQueryK),
988
1014
  ];
1015
+ // Same phrase-scale bound the cross-region junction ladder uses
1016
+ // (`maxInterior`): a sibling whose complete stored content exceeds it is
1017
+ // not materialized as a structural-resonance endpoint, keeping sibling
1018
+ // reconstruction phrase-scale even for a large deposit or conversation
1019
+ // root that merely appeared in a halo result.
1020
+ const maxSiblingBytes = (leftLen + rightLen) * ctx.space.maxGroup;
1021
+ const specs = buildStructuralVariantSpecs(sides);
1022
+ const localGistMemo = new Map();
1023
+ let retainedSynonyms = 0;
1024
+ for (const spec of specs) {
1025
+ if (retainedSynonyms >= ctx.cfg.haloQueryK)
1026
+ break;
1027
+ let left = exactLeft;
1028
+ let right = exactRight;
1029
+ if (spec.leftSiblingId !== undefined) {
1030
+ const gist = loadBoundedSiblingGist(ctx, spec.leftSiblingId, maxSiblingBytes, siblingGistMemo, localGistMemo);
1031
+ if (gist === null)
1032
+ continue;
1033
+ left = { v: gist, len: leftLen };
1034
+ }
1035
+ if (spec.rightSiblingId !== undefined) {
1036
+ const gist = loadBoundedSiblingGist(ctx, spec.rightSiblingId, maxSiblingBytes, siblingGistMemo, localGistMemo);
1037
+ if (gist === null)
1038
+ continue;
1039
+ right = { v: gist, len: rightLen };
1040
+ }
1041
+ variants.push({
1042
+ left,
1043
+ right,
1044
+ kind: spec.kind,
1045
+ semanticConfidence: spec.semanticConfidence,
1046
+ leftSiblingId: spec.leftSiblingId,
1047
+ rightSiblingId: spec.rightSiblingId,
1048
+ });
1049
+ retainedSynonyms++;
1050
+ }
989
1051
  return { variants, exactLeft, exactRight };
990
1052
  }
991
1053
  /** Deterministic best-of tie-break for two proposals ranked for the SAME
@@ -1012,7 +1074,7 @@ function betterProposal(a, b) {
1012
1074
  * ANN-query each, merge proposals by candidate id, and validate the winner
1013
1075
  * through the SAME structural gates every other tier answers to (saturation,
1014
1076
  * roots, IDF, contrastive margin). Returns null when nothing survives. */
1015
- export async function structuralResonance(ctx, query, ra, rb, sides, k, N, reachMemo,
1077
+ export async function structuralResonance(ctx, query, ra, rb, sides, siblingGistMemo, k, N, reachMemo,
1016
1078
  /** Each side's OWN individual climb roots (from voteRegions), when it cast
1017
1079
  * one — the self-evidence backstop structural-resonance needs and the
1018
1080
  * exact tier gets for free from literal byte containment (§11's whole
@@ -1021,7 +1083,7 @@ export async function structuralResonance(ctx, query, ra, rb, sides, k, N, reach
1021
1083
  * evidence of a joint whole; it is that side's resonance rediscovering
1022
1084
  * itself through a synthetic gist still dominated by its own direction. */
1023
1085
  ownRootsA, ownRootsB, trace) {
1024
- const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
1086
+ const { variants } = buildStructuralVariants(ctx, ra, rb, sides, siblingGistMemo);
1025
1087
  if (trace)
1026
1088
  trace.variantBudget = ctx.cfg.haloQueryK;
1027
1089
  const middleBytes = query.subarray(ra.end, rb.start);
@@ -1177,6 +1239,10 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
1177
1239
  //
1178
1240
  // Only MAXIMAL spans compose: a span contained in another candidate is a
1179
1241
  // fragment of that candidate's evidence, never independent of it.
1242
+ // Shared across every cross-region probe in this climb: a sibling
1243
+ // successfully reconstructed while probing one pair must not be read and
1244
+ // perceived again while probing another pair in the same climb.
1245
+ const siblingGistMemo = new Map();
1180
1246
  const votedSpans = new Set();
1181
1247
  for (const rv of rvs.votes)
1182
1248
  votedSpans.add(`${rv.start},${rv.end}`);
@@ -1425,7 +1491,7 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
1425
1491
  }
1426
1492
  const ownRootsA = rvs.votes.find((v) => v.start === ra.start && v.end === ra.end)?.roots;
1427
1493
  const ownRootsB = rvs.votes.find((v) => v.start === rb.start && v.end === rb.end)?.roots;
1428
- structuralPick = await structuralResonance(ctx, query, ra, rb, sides, k, N, reachMemo, ownRootsA, ownRootsB, resonanceTrace);
1494
+ structuralPick = await structuralResonance(ctx, query, ra, rb, sides, siblingGistMemo, k, N, reachMemo, ownRootsA, ownRootsB, resonanceTrace);
1429
1495
  }
1430
1496
  if (structuralPick === null) {
1431
1497
  pushProbe(reasons.length > 0 ? "resonance-ineligible" : "resonance-rejected");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -1549,86 +1549,137 @@ const VARIANT_KIND_ORDER: Record<StructuralVariant["kind"], number> = {
1549
1549
  "double-synonym": 2,
1550
1550
  };
1551
1551
 
1552
- /** A node's structural gist, read directly from its own stored bytes the
1553
- * repository's node gist accessor: content is immutable and perception is
1554
- * a pure function of bytes, so re-perceiving a node's full stored bytes
1555
- * reproduces exactly the gist it was interned with. Never concatenates
1556
- * the sibling's bytes with anything else. */
1557
- function storedNodeGist(ctx: MindContext, id: number): Vec {
1558
- return gistOf(ctx, read(ctx, id));
1552
+ /** A lightweight, cost-free descriptor of one candidate structural variant
1553
+ * enough to rank it, but with no bytes read and no vector materialized. */
1554
+ interface StructuralVariantSpec {
1555
+ kind: "left-synonym" | "right-synonym" | "double-synonym";
1556
+ semanticConfidence: number;
1557
+ leftSiblingId?: number;
1558
+ rightSiblingId?: number;
1559
1559
  }
1560
1560
 
1561
- /** A halo sibling's structural part, occupying the ORIGINAL query-region
1562
- * slot length the sibling replaces only the DIRECTION, never the query's
1563
- * own bytes, position, or gap (see §6 of the spec this implements). */
1564
- function siblingPart(
1561
+ /** Same deterministic ordering the old implementation applied to already-
1562
+ * materialized variants (§8): semantic confidence desc, then kind
1563
+ * (left-synonym, right-synonym, double-synonym), then sibling ids asc. */
1564
+ function compareStructuralVariantSpecs(
1565
+ a: StructuralVariantSpec,
1566
+ b: StructuralVariantSpec,
1567
+ ): number {
1568
+ return b.semanticConfidence - a.semanticConfidence ||
1569
+ VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
1570
+ (a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
1571
+ (a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1);
1572
+ }
1573
+
1574
+ /** Every single- and double-synonym combination, as cost-free descriptors —
1575
+ * no `read`, `gistOf`, `perceive` or `StructuralPart` allocation. Both
1576
+ * sibling lists are already bounded by `haloQueryK`, so the O(haloQueryK²)
1577
+ * cross-product here is cheap; only the SELECTED specs go on to pay for
1578
+ * sibling reconstruction. */
1579
+ function buildStructuralVariantSpecs(
1580
+ sides: JunctionSynonymSides,
1581
+ ): StructuralVariantSpec[] {
1582
+ const specs: StructuralVariantSpec[] = [];
1583
+
1584
+ for (const left of sides.leftSiblings) {
1585
+ specs.push({
1586
+ kind: "left-synonym",
1587
+ semanticConfidence: left.score,
1588
+ leftSiblingId: left.id,
1589
+ });
1590
+ }
1591
+
1592
+ for (const right of sides.rightSiblings) {
1593
+ specs.push({
1594
+ kind: "right-synonym",
1595
+ semanticConfidence: right.score,
1596
+ rightSiblingId: right.id,
1597
+ });
1598
+ }
1599
+
1600
+ for (const left of sides.leftSiblings) {
1601
+ for (const right of sides.rightSiblings) {
1602
+ specs.push({
1603
+ kind: "double-synonym",
1604
+ semanticConfidence: Math.min(left.score, right.score),
1605
+ leftSiblingId: left.id,
1606
+ rightSiblingId: right.id,
1607
+ });
1608
+ }
1609
+ }
1610
+
1611
+ specs.sort(compareStructuralVariantSpecs);
1612
+
1613
+ return specs;
1614
+ }
1615
+
1616
+ /** A halo sibling's structural gist, bounded to `maxBytes` of stored content
1617
+ * and reused across the whole climb. `positiveMemo` (shared across every
1618
+ * probe in the climb, passed in by the caller) remembers only successfully
1619
+ * reconstructed complete gists — a sibling rejected here for being too
1620
+ * large for THIS pair's phrase-scale bound may still be admissible for a
1621
+ * larger-spanning pair later, so a rejection is never memoized globally.
1622
+ * `localMemo` is scoped to one `buildStructuralVariants` call, where every
1623
+ * variant shares the same bound, so a `null` there is safe to reuse. */
1624
+ function loadBoundedSiblingGist(
1565
1625
  ctx: MindContext,
1566
- sibling: Hit,
1567
- originalRegion: { start: number; end: number },
1568
- ): StructuralPart {
1569
- return {
1570
- v: storedNodeGist(ctx, sibling.id),
1571
- len: originalRegion.end - originalRegion.start,
1572
- };
1626
+ id: number,
1627
+ maxBytes: number,
1628
+ positiveMemo: Map<number, Vec>,
1629
+ localMemo: Map<number, Vec | null>,
1630
+ ): Vec | null {
1631
+ if (localMemo.has(id)) {
1632
+ return localMemo.get(id) ?? null;
1633
+ }
1634
+
1635
+ const cached = positiveMemo.get(id);
1636
+ if (cached !== undefined) {
1637
+ localMemo.set(id, cached);
1638
+ return cached;
1639
+ }
1640
+
1641
+ const length = ctx.store.contentLen(id, maxBytes + 1);
1642
+ if (length <= 0 || length > maxBytes) {
1643
+ localMemo.set(id, null);
1644
+ return null;
1645
+ }
1646
+
1647
+ const bytes = read(ctx, id, maxBytes + 1);
1648
+ if (bytes.length === 0 || bytes.length > maxBytes) {
1649
+ localMemo.set(id, null);
1650
+ return null;
1651
+ }
1652
+
1653
+ const gist = gistOf(ctx, bytes);
1654
+ positiveMemo.set(id, gist);
1655
+ localMemo.set(id, gist);
1656
+ return gist;
1573
1657
  }
1574
1658
 
1575
1659
  /** Build, bound and order every mandatory structural variant (§7-8): the
1576
1660
  * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
1577
1661
  * synonym variants (single- and double-synonym combined, one shared
1578
- * budget) are appended, ordered by confidence, then kind, then sibling id. */
1662
+ * budget) are appended, ordered by confidence, then kind, then sibling id.
1663
+ * Variant selection is entirely lightweight (see {@link
1664
+ * buildStructuralVariantSpecs}); a sibling's bytes are read and perceived
1665
+ * only for specs actually retained, and at most once per sibling id per
1666
+ * climb via `siblingGistMemo`. */
1579
1667
  export function buildStructuralVariants(
1580
1668
  ctx: MindContext,
1581
1669
  ra: Region,
1582
1670
  rb: Region,
1583
1671
  sides: JunctionSynonymSides,
1672
+ siblingGistMemo: Map<number, Vec>,
1584
1673
  ): {
1585
1674
  variants: StructuralVariant[];
1586
1675
  exactLeft: StructuralPart;
1587
1676
  exactRight: StructuralPart;
1588
1677
  } {
1589
- const exactLeft: StructuralPart = { v: ra.v, len: ra.end - ra.start };
1590
- const exactRight: StructuralPart = { v: rb.v, len: rb.end - rb.start };
1591
-
1592
- const synonymVariants: StructuralVariant[] = [];
1593
- for (const ls of sides.leftSiblings) {
1594
- synonymVariants.push({
1595
- left: siblingPart(ctx, ls, ra),
1596
- right: exactRight,
1597
- kind: "left-synonym",
1598
- semanticConfidence: ls.score,
1599
- leftSiblingId: ls.id,
1600
- });
1601
- }
1602
- for (const rs of sides.rightSiblings) {
1603
- synonymVariants.push({
1604
- left: exactLeft,
1605
- right: siblingPart(ctx, rs, rb),
1606
- kind: "right-synonym",
1607
- semanticConfidence: rs.score,
1608
- rightSiblingId: rs.id,
1609
- });
1610
- }
1611
- for (const ls of sides.leftSiblings) {
1612
- for (const rs of sides.rightSiblings) {
1613
- synonymVariants.push({
1614
- left: siblingPart(ctx, ls, ra),
1615
- right: siblingPart(ctx, rs, rb),
1616
- kind: "double-synonym",
1617
- semanticConfidence: Math.min(ls.score, rs.score),
1618
- leftSiblingId: ls.id,
1619
- rightSiblingId: rs.id,
1620
- });
1621
- }
1622
- }
1678
+ const leftLen = ra.end - ra.start;
1679
+ const rightLen = rb.end - rb.start;
1623
1680
 
1624
- // §8: semantic confidence desc, then kind (left-synonym, right-synonym,
1625
- // double-synonym), then left sibling id asc, then right sibling id asc.
1626
- synonymVariants.sort((a, b) =>
1627
- b.semanticConfidence - a.semanticConfidence ||
1628
- VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
1629
- (a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
1630
- (a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1)
1631
- );
1681
+ const exactLeft: StructuralPart = { v: ra.v, len: leftLen };
1682
+ const exactRight: StructuralPart = { v: rb.v, len: rightLen };
1632
1683
 
1633
1684
  const variants: StructuralVariant[] = [
1634
1685
  {
@@ -1637,8 +1688,60 @@ export function buildStructuralVariants(
1637
1688
  kind: "exact-exact",
1638
1689
  semanticConfidence: 1,
1639
1690
  },
1640
- ...synonymVariants.slice(0, ctx.cfg.haloQueryK),
1641
1691
  ];
1692
+
1693
+ // Same phrase-scale bound the cross-region junction ladder uses
1694
+ // (`maxInterior`): a sibling whose complete stored content exceeds it is
1695
+ // not materialized as a structural-resonance endpoint, keeping sibling
1696
+ // reconstruction phrase-scale even for a large deposit or conversation
1697
+ // root that merely appeared in a halo result.
1698
+ const maxSiblingBytes = (leftLen + rightLen) * ctx.space.maxGroup;
1699
+
1700
+ const specs = buildStructuralVariantSpecs(sides);
1701
+ const localGistMemo = new Map<number, Vec | null>();
1702
+ let retainedSynonyms = 0;
1703
+
1704
+ for (const spec of specs) {
1705
+ if (retainedSynonyms >= ctx.cfg.haloQueryK) break;
1706
+
1707
+ let left = exactLeft;
1708
+ let right = exactRight;
1709
+
1710
+ if (spec.leftSiblingId !== undefined) {
1711
+ const gist = loadBoundedSiblingGist(
1712
+ ctx,
1713
+ spec.leftSiblingId,
1714
+ maxSiblingBytes,
1715
+ siblingGistMemo,
1716
+ localGistMemo,
1717
+ );
1718
+ if (gist === null) continue;
1719
+ left = { v: gist, len: leftLen };
1720
+ }
1721
+
1722
+ if (spec.rightSiblingId !== undefined) {
1723
+ const gist = loadBoundedSiblingGist(
1724
+ ctx,
1725
+ spec.rightSiblingId,
1726
+ maxSiblingBytes,
1727
+ siblingGistMemo,
1728
+ localGistMemo,
1729
+ );
1730
+ if (gist === null) continue;
1731
+ right = { v: gist, len: rightLen };
1732
+ }
1733
+
1734
+ variants.push({
1735
+ left,
1736
+ right,
1737
+ kind: spec.kind,
1738
+ semanticConfidence: spec.semanticConfidence,
1739
+ leftSiblingId: spec.leftSiblingId,
1740
+ rightSiblingId: spec.rightSiblingId,
1741
+ });
1742
+ retainedSynonyms++;
1743
+ }
1744
+
1642
1745
  return { variants, exactLeft, exactRight };
1643
1746
  }
1644
1747
 
@@ -1675,6 +1778,7 @@ export async function structuralResonance(
1675
1778
  ra: Region,
1676
1779
  rb: Region,
1677
1780
  sides: JunctionSynonymSides,
1781
+ siblingGistMemo: Map<number, Vec>,
1678
1782
  k: number,
1679
1783
  N: number,
1680
1784
  reachMemo: Map<number, AncestorReach>,
@@ -1692,7 +1796,13 @@ export async function structuralResonance(
1692
1796
  | { proposal: StructuralResonanceProposal; reach: AncestorReach; idf: number }
1693
1797
  | null
1694
1798
  > {
1695
- const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
1799
+ const { variants } = buildStructuralVariants(
1800
+ ctx,
1801
+ ra,
1802
+ rb,
1803
+ sides,
1804
+ siblingGistMemo,
1805
+ );
1696
1806
  if (trace) trace.variantBudget = ctx.cfg.haloQueryK;
1697
1807
 
1698
1808
  const middleBytes = query.subarray(ra.end, rb.start);
@@ -1864,6 +1974,10 @@ async function crossRegionVotes(
1864
1974
  //
1865
1975
  // Only MAXIMAL spans compose: a span contained in another candidate is a
1866
1976
  // fragment of that candidate's evidence, never independent of it.
1977
+ // Shared across every cross-region probe in this climb: a sibling
1978
+ // successfully reconstructed while probing one pair must not be read and
1979
+ // perceived again while probing another pair in the same climb.
1980
+ const siblingGistMemo = new Map<number, Vec>();
1867
1981
  const votedSpans = new Set<string>();
1868
1982
  for (const rv of rvs.votes) votedSpans.add(`${rv.start},${rv.end}`);
1869
1983
  const seen = new Set<string>();
@@ -2155,6 +2269,7 @@ async function crossRegionVotes(
2155
2269
  ra,
2156
2270
  rb,
2157
2271
  sides,
2272
+ siblingGistMemo,
2158
2273
  k,
2159
2274
  N,
2160
2275
  reachMemo,
@@ -381,6 +381,7 @@ test("7/8/9. buildStructuralVariants always includes exact-exact, left-synonym,
381
381
  ra,
382
382
  rb,
383
383
  sides,
384
+ new Map(),
384
385
  );
385
386
  assert.equal(variants[0].kind, "exact-exact");
386
387
  assert.equal(variants[0].semanticConfidence, 1);
@@ -410,7 +411,7 @@ test("11. synonym variants are bounded to haloQueryK, in addition to the always-
410
411
  enc("crimson"),
411
412
  enc("square"),
412
413
  );
413
- const { variants } = buildStructuralVariants(m, ra, rb, sides);
414
+ const { variants } = buildStructuralVariants(m, ra, rb, sides, new Map());
414
415
  assert.ok(variants.length <= 1 + m.cfg.haloQueryK);
415
416
  await m.store.close();
416
417
  });
@@ -428,7 +429,7 @@ test("12. a sibling's structural part keeps the ORIGINAL query-region slot lengt
428
429
  enc("crimson"),
429
430
  enc("square"),
430
431
  );
431
- const { variants } = buildStructuralVariants(m, ra, rb, sides);
432
+ const { variants } = buildStructuralVariants(m, ra, rb, sides, new Map());
432
433
  for (const v of variants) {
433
434
  assert.equal(
434
435
  v.left.len,
@@ -483,6 +484,7 @@ test("13/18. structuralResonance composes the real middle query bytes and applie
483
484
  ra,
484
485
  rb,
485
486
  sides,
487
+ new Map(),
486
488
  12,
487
489
  N,
488
490
  new Map(),
@@ -520,7 +522,7 @@ test("15. two variants proposing the SAME candidate keep only the higher-effecti
520
522
  enc("crimson"),
521
523
  enc("square"),
522
524
  );
523
- const { variants } = buildStructuralVariants(m, ra, rb, sides);
525
+ const { variants } = buildStructuralVariants(m, ra, rb, sides, new Map());
524
526
  // Directly exercise the merge logic's contract: build two synthetic
525
527
  // proposals for the SAME id with different effectiveScore and confirm the
526
528
  // higher one is what a caller keeps (mirrors betterProposal's tie-break,
@@ -533,6 +535,7 @@ test("15. two variants proposing the SAME candidate keep only the higher-effecti
533
535
  ra,
534
536
  rb,
535
537
  sides,
538
+ new Map(),
536
539
  12,
537
540
  N,
538
541
  new Map(),