@hviana/sema 0.2.7 → 0.2.9

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
@@ -357,11 +358,23 @@ export interface StructuralResonanceProposal {
357
358
  leftSiblingId?: number;
358
359
  rightSiblingId?: number;
359
360
  }
361
+ /** A sibling gist cached in the shared, climb-wide memo alongside the
362
+ * COMPLETE stored byte length it was reconstructed from — the length is
363
+ * required to tell whether a cache hit is still valid under a probe whose
364
+ * `maxSiblingBytes` bound is smaller than the one that first cached it. */
365
+ interface CachedSiblingGist {
366
+ gist: Vec;
367
+ length: number;
368
+ }
360
369
  /** Build, bound and order every mandatory structural variant (§7-8): the
361
370
  * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
362
371
  * 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): {
372
+ * budget) are appended, ordered by confidence, then kind, then sibling id.
373
+ * Variant selection is entirely lightweight (see {@link
374
+ * buildStructuralVariantSpecs}); a sibling's bytes are read and perceived
375
+ * only for specs actually retained, and at most once per sibling id per
376
+ * climb via `siblingGistMemo`. */
377
+ export declare function buildStructuralVariants(ctx: MindContext, ra: Region, rb: Region, sides: JunctionSynonymSides, siblingGistMemo: Map<number, CachedSiblingGist>): {
365
378
  variants: StructuralVariant[];
366
379
  exactLeft: StructuralPart;
367
380
  exactRight: StructuralPart;
@@ -370,7 +383,7 @@ export declare function buildStructuralVariants(ctx: MindContext, ra: Region, rb
370
383
  * ANN-query each, merge proposals by candidate id, and validate the winner
371
384
  * through the SAME structural gates every other tier answers to (saturation,
372
385
  * 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>,
386
+ export declare function structuralResonance(ctx: MindContext, query: Uint8Array, ra: Region, rb: Region, sides: JunctionSynonymSides, siblingGistMemo: Map<number, CachedSiblingGist>, k: number, N: number, reachMemo: Map<number, AncestorReach>,
374
387
  /** Each side's OWN individual climb roots (from voteRegions), when it cast
375
388
  * one — the self-evidence backstop structural-resonance needs and the
376
389
  * exact tier gets for free from literal byte containment (§11's whole
@@ -916,67 +916,99 @@ 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 together with their complete byte length —
966
+ * a sibling rejected here for being too large for THIS pair's phrase-scale
967
+ * bound may still be admissible for a larger-spanning pair later, so a
968
+ * rejection is never memoized globally, and a sibling cached by a LARGER
969
+ * probe is only reused here when its length still fits THIS probe's
970
+ * (possibly smaller) bound — eligibility must never depend on which probe
971
+ * happened to cache the sibling first. `localMemo` is scoped to one
972
+ * `buildStructuralVariants` call, where every variant shares the same
973
+ * bound, so a `null` there is safe to reuse. */
974
+ function loadBoundedSiblingGist(ctx, id, maxBytes, positiveMemo, localMemo) {
975
+ if (localMemo.has(id)) {
976
+ return localMemo.get(id) ?? null;
977
+ }
978
+ const cached = positiveMemo.get(id);
979
+ if (cached !== undefined) {
980
+ const result = cached.length <= maxBytes ? cached.gist : null;
981
+ localMemo.set(id, result);
982
+ return result;
983
+ }
984
+ const length = ctx.store.contentLen(id, maxBytes + 1);
985
+ if (length <= 0 || length > maxBytes) {
986
+ localMemo.set(id, null);
987
+ return null;
988
+ }
989
+ const bytes = read(ctx, id, maxBytes + 1);
990
+ if (bytes.length === 0 || bytes.length > maxBytes) {
991
+ localMemo.set(id, null);
992
+ return null;
993
+ }
994
+ const gist = gistOf(ctx, bytes);
995
+ positiveMemo.set(id, { gist, length });
996
+ localMemo.set(id, gist);
997
+ return gist;
998
+ }
999
+ /** Build, bound and order every mandatory structural variant (§7-8): the
1000
+ * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
1001
+ * synonym variants (single- and double-synonym combined, one shared
1002
+ * budget) are appended, ordered by confidence, then kind, then sibling id.
1003
+ * Variant selection is entirely lightweight (see {@link
1004
+ * buildStructuralVariantSpecs}); a sibling's bytes are read and perceived
1005
+ * only for specs actually retained, and at most once per sibling id per
1006
+ * climb via `siblingGistMemo`. */
1007
+ export function buildStructuralVariants(ctx, ra, rb, sides, siblingGistMemo) {
1008
+ const leftLen = ra.end - ra.start;
1009
+ const rightLen = rb.end - rb.start;
1010
+ const exactLeft = { v: ra.v, len: leftLen };
1011
+ const exactRight = { v: rb.v, len: rightLen };
980
1012
  const variants = [
981
1013
  {
982
1014
  left: exactLeft,
@@ -984,8 +1016,43 @@ export function buildStructuralVariants(ctx, ra, rb, sides) {
984
1016
  kind: "exact-exact",
985
1017
  semanticConfidence: 1,
986
1018
  },
987
- ...synonymVariants.slice(0, ctx.cfg.haloQueryK),
988
1019
  ];
1020
+ // Same phrase-scale bound the cross-region junction ladder uses
1021
+ // (`maxInterior`): a sibling whose complete stored content exceeds it is
1022
+ // not materialized as a structural-resonance endpoint, keeping sibling
1023
+ // reconstruction phrase-scale even for a large deposit or conversation
1024
+ // root that merely appeared in a halo result.
1025
+ const maxSiblingBytes = (leftLen + rightLen) * ctx.space.maxGroup;
1026
+ const specs = buildStructuralVariantSpecs(sides);
1027
+ const localGistMemo = new Map();
1028
+ let retainedSynonyms = 0;
1029
+ for (const spec of specs) {
1030
+ if (retainedSynonyms >= ctx.cfg.haloQueryK)
1031
+ break;
1032
+ let left = exactLeft;
1033
+ let right = exactRight;
1034
+ if (spec.leftSiblingId !== undefined) {
1035
+ const gist = loadBoundedSiblingGist(ctx, spec.leftSiblingId, maxSiblingBytes, siblingGistMemo, localGistMemo);
1036
+ if (gist === null)
1037
+ continue;
1038
+ left = { v: gist, len: leftLen };
1039
+ }
1040
+ if (spec.rightSiblingId !== undefined) {
1041
+ const gist = loadBoundedSiblingGist(ctx, spec.rightSiblingId, maxSiblingBytes, siblingGistMemo, localGistMemo);
1042
+ if (gist === null)
1043
+ continue;
1044
+ right = { v: gist, len: rightLen };
1045
+ }
1046
+ variants.push({
1047
+ left,
1048
+ right,
1049
+ kind: spec.kind,
1050
+ semanticConfidence: spec.semanticConfidence,
1051
+ leftSiblingId: spec.leftSiblingId,
1052
+ rightSiblingId: spec.rightSiblingId,
1053
+ });
1054
+ retainedSynonyms++;
1055
+ }
989
1056
  return { variants, exactLeft, exactRight };
990
1057
  }
991
1058
  /** Deterministic best-of tie-break for two proposals ranked for the SAME
@@ -1012,7 +1079,7 @@ function betterProposal(a, b) {
1012
1079
  * ANN-query each, merge proposals by candidate id, and validate the winner
1013
1080
  * through the SAME structural gates every other tier answers to (saturation,
1014
1081
  * roots, IDF, contrastive margin). Returns null when nothing survives. */
1015
- export async function structuralResonance(ctx, query, ra, rb, sides, k, N, reachMemo,
1082
+ export async function structuralResonance(ctx, query, ra, rb, sides, siblingGistMemo, k, N, reachMemo,
1016
1083
  /** Each side's OWN individual climb roots (from voteRegions), when it cast
1017
1084
  * one — the self-evidence backstop structural-resonance needs and the
1018
1085
  * exact tier gets for free from literal byte containment (§11's whole
@@ -1021,7 +1088,7 @@ export async function structuralResonance(ctx, query, ra, rb, sides, k, N, reach
1021
1088
  * evidence of a joint whole; it is that side's resonance rediscovering
1022
1089
  * itself through a synthetic gist still dominated by its own direction. */
1023
1090
  ownRootsA, ownRootsB, trace) {
1024
- const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
1091
+ const { variants } = buildStructuralVariants(ctx, ra, rb, sides, siblingGistMemo);
1025
1092
  if (trace)
1026
1093
  trace.variantBudget = ctx.cfg.haloQueryK;
1027
1094
  const middleBytes = query.subarray(ra.end, rb.start);
@@ -1177,6 +1244,10 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
1177
1244
  //
1178
1245
  // Only MAXIMAL spans compose: a span contained in another candidate is a
1179
1246
  // fragment of that candidate's evidence, never independent of it.
1247
+ // Shared across every cross-region probe in this climb: a sibling
1248
+ // successfully reconstructed while probing one pair must not be read and
1249
+ // perceived again while probing another pair in the same climb.
1250
+ const siblingGistMemo = new Map();
1180
1251
  const votedSpans = new Set();
1181
1252
  for (const rv of rvs.votes)
1182
1253
  votedSpans.add(`${rv.start},${rv.end}`);
@@ -1425,7 +1496,7 @@ async function crossRegionVotes(ctx, query, regions, rvs, k, N, reachMemo, td) {
1425
1496
  }
1426
1497
  const ownRootsA = rvs.votes.find((v) => v.start === ra.start && v.end === ra.end)?.roots;
1427
1498
  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);
1499
+ structuralPick = await structuralResonance(ctx, query, ra, rb, sides, siblingGistMemo, k, N, reachMemo, ownRootsA, ownRootsB, resonanceTrace);
1429
1500
  }
1430
1501
  if (structuralPick === null) {
1431
1502
  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.9",
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,151 @@ 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
+ }
1560
+
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;
1559
1614
  }
1560
1615
 
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(
1616
+ /** A sibling gist cached in the shared, climb-wide memo alongside the
1617
+ * COMPLETE stored byte length it was reconstructed from the length is
1618
+ * required to tell whether a cache hit is still valid under a probe whose
1619
+ * `maxSiblingBytes` bound is smaller than the one that first cached it. */
1620
+ interface CachedSiblingGist {
1621
+ gist: Vec;
1622
+ length: number;
1623
+ }
1624
+
1625
+ /** A halo sibling's structural gist, bounded to `maxBytes` of stored content
1626
+ * and reused across the whole climb. `positiveMemo` (shared across every
1627
+ * probe in the climb, passed in by the caller) remembers only successfully
1628
+ * reconstructed complete gists together with their complete byte length —
1629
+ * a sibling rejected here for being too large for THIS pair's phrase-scale
1630
+ * bound may still be admissible for a larger-spanning pair later, so a
1631
+ * rejection is never memoized globally, and a sibling cached by a LARGER
1632
+ * probe is only reused here when its length still fits THIS probe's
1633
+ * (possibly smaller) bound — eligibility must never depend on which probe
1634
+ * happened to cache the sibling first. `localMemo` is scoped to one
1635
+ * `buildStructuralVariants` call, where every variant shares the same
1636
+ * bound, so a `null` there is safe to reuse. */
1637
+ function loadBoundedSiblingGist(
1565
1638
  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
- };
1639
+ id: number,
1640
+ maxBytes: number,
1641
+ positiveMemo: Map<number, CachedSiblingGist>,
1642
+ localMemo: Map<number, Vec | null>,
1643
+ ): Vec | null {
1644
+ if (localMemo.has(id)) {
1645
+ return localMemo.get(id) ?? null;
1646
+ }
1647
+
1648
+ const cached = positiveMemo.get(id);
1649
+ if (cached !== undefined) {
1650
+ const result = cached.length <= maxBytes ? cached.gist : null;
1651
+ localMemo.set(id, result);
1652
+ return result;
1653
+ }
1654
+
1655
+ const length = ctx.store.contentLen(id, maxBytes + 1);
1656
+ if (length <= 0 || length > maxBytes) {
1657
+ localMemo.set(id, null);
1658
+ return null;
1659
+ }
1660
+
1661
+ const bytes = read(ctx, id, maxBytes + 1);
1662
+ if (bytes.length === 0 || bytes.length > maxBytes) {
1663
+ localMemo.set(id, null);
1664
+ return null;
1665
+ }
1666
+
1667
+ const gist = gistOf(ctx, bytes);
1668
+ positiveMemo.set(id, { gist, length });
1669
+ localMemo.set(id, gist);
1670
+ return gist;
1573
1671
  }
1574
1672
 
1575
1673
  /** Build, bound and order every mandatory structural variant (§7-8): the
1576
1674
  * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
1577
1675
  * synonym variants (single- and double-synonym combined, one shared
1578
- * budget) are appended, ordered by confidence, then kind, then sibling id. */
1676
+ * budget) are appended, ordered by confidence, then kind, then sibling id.
1677
+ * Variant selection is entirely lightweight (see {@link
1678
+ * buildStructuralVariantSpecs}); a sibling's bytes are read and perceived
1679
+ * only for specs actually retained, and at most once per sibling id per
1680
+ * climb via `siblingGistMemo`. */
1579
1681
  export function buildStructuralVariants(
1580
1682
  ctx: MindContext,
1581
1683
  ra: Region,
1582
1684
  rb: Region,
1583
1685
  sides: JunctionSynonymSides,
1686
+ siblingGistMemo: Map<number, CachedSiblingGist>,
1584
1687
  ): {
1585
1688
  variants: StructuralVariant[];
1586
1689
  exactLeft: StructuralPart;
1587
1690
  exactRight: StructuralPart;
1588
1691
  } {
1589
- const exactLeft: StructuralPart = { v: ra.v, len: ra.end - ra.start };
1590
- const exactRight: StructuralPart = { v: rb.v, len: rb.end - rb.start };
1692
+ const leftLen = ra.end - ra.start;
1693
+ const rightLen = rb.end - rb.start;
1591
1694
 
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
- }
1623
-
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
- );
1695
+ const exactLeft: StructuralPart = { v: ra.v, len: leftLen };
1696
+ const exactRight: StructuralPart = { v: rb.v, len: rightLen };
1632
1697
 
1633
1698
  const variants: StructuralVariant[] = [
1634
1699
  {
@@ -1637,8 +1702,60 @@ export function buildStructuralVariants(
1637
1702
  kind: "exact-exact",
1638
1703
  semanticConfidence: 1,
1639
1704
  },
1640
- ...synonymVariants.slice(0, ctx.cfg.haloQueryK),
1641
1705
  ];
1706
+
1707
+ // Same phrase-scale bound the cross-region junction ladder uses
1708
+ // (`maxInterior`): a sibling whose complete stored content exceeds it is
1709
+ // not materialized as a structural-resonance endpoint, keeping sibling
1710
+ // reconstruction phrase-scale even for a large deposit or conversation
1711
+ // root that merely appeared in a halo result.
1712
+ const maxSiblingBytes = (leftLen + rightLen) * ctx.space.maxGroup;
1713
+
1714
+ const specs = buildStructuralVariantSpecs(sides);
1715
+ const localGistMemo = new Map<number, Vec | null>();
1716
+ let retainedSynonyms = 0;
1717
+
1718
+ for (const spec of specs) {
1719
+ if (retainedSynonyms >= ctx.cfg.haloQueryK) break;
1720
+
1721
+ let left = exactLeft;
1722
+ let right = exactRight;
1723
+
1724
+ if (spec.leftSiblingId !== undefined) {
1725
+ const gist = loadBoundedSiblingGist(
1726
+ ctx,
1727
+ spec.leftSiblingId,
1728
+ maxSiblingBytes,
1729
+ siblingGistMemo,
1730
+ localGistMemo,
1731
+ );
1732
+ if (gist === null) continue;
1733
+ left = { v: gist, len: leftLen };
1734
+ }
1735
+
1736
+ if (spec.rightSiblingId !== undefined) {
1737
+ const gist = loadBoundedSiblingGist(
1738
+ ctx,
1739
+ spec.rightSiblingId,
1740
+ maxSiblingBytes,
1741
+ siblingGistMemo,
1742
+ localGistMemo,
1743
+ );
1744
+ if (gist === null) continue;
1745
+ right = { v: gist, len: rightLen };
1746
+ }
1747
+
1748
+ variants.push({
1749
+ left,
1750
+ right,
1751
+ kind: spec.kind,
1752
+ semanticConfidence: spec.semanticConfidence,
1753
+ leftSiblingId: spec.leftSiblingId,
1754
+ rightSiblingId: spec.rightSiblingId,
1755
+ });
1756
+ retainedSynonyms++;
1757
+ }
1758
+
1642
1759
  return { variants, exactLeft, exactRight };
1643
1760
  }
1644
1761
 
@@ -1675,6 +1792,7 @@ export async function structuralResonance(
1675
1792
  ra: Region,
1676
1793
  rb: Region,
1677
1794
  sides: JunctionSynonymSides,
1795
+ siblingGistMemo: Map<number, CachedSiblingGist>,
1678
1796
  k: number,
1679
1797
  N: number,
1680
1798
  reachMemo: Map<number, AncestorReach>,
@@ -1692,7 +1810,13 @@ export async function structuralResonance(
1692
1810
  | { proposal: StructuralResonanceProposal; reach: AncestorReach; idf: number }
1693
1811
  | null
1694
1812
  > {
1695
- const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
1813
+ const { variants } = buildStructuralVariants(
1814
+ ctx,
1815
+ ra,
1816
+ rb,
1817
+ sides,
1818
+ siblingGistMemo,
1819
+ );
1696
1820
  if (trace) trace.variantBudget = ctx.cfg.haloQueryK;
1697
1821
 
1698
1822
  const middleBytes = query.subarray(ra.end, rb.start);
@@ -1864,6 +1988,10 @@ async function crossRegionVotes(
1864
1988
  //
1865
1989
  // Only MAXIMAL spans compose: a span contained in another candidate is a
1866
1990
  // fragment of that candidate's evidence, never independent of it.
1991
+ // Shared across every cross-region probe in this climb: a sibling
1992
+ // successfully reconstructed while probing one pair must not be read and
1993
+ // perceived again while probing another pair in the same climb.
1994
+ const siblingGistMemo = new Map<number, CachedSiblingGist>();
1867
1995
  const votedSpans = new Set<string>();
1868
1996
  for (const rv of rvs.votes) votedSpans.add(`${rv.start},${rv.end}`);
1869
1997
  const seen = new Set<string>();
@@ -2155,6 +2283,7 @@ async function crossRegionVotes(
2155
2283
  ra,
2156
2284
  rb,
2157
2285
  sides,
2286
+ siblingGistMemo,
2158
2287
  k,
2159
2288
  N,
2160
2289
  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(),