@hviana/sema 0.1.3 → 0.1.5

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 (123) hide show
  1. package/dist/example/demo.js +25 -25
  2. package/dist/example/train_base.d.ts +21 -19
  3. package/dist/example/train_base.js +1952 -1684
  4. package/dist/src/alphabet.d.ts +3 -3
  5. package/dist/src/alphabet.js +27 -24
  6. package/dist/src/alu/src/alu.d.ts +194 -169
  7. package/dist/src/alu/src/alu.js +414 -374
  8. package/dist/src/alu/src/expr.d.ts +43 -36
  9. package/dist/src/alu/src/expr.js +278 -252
  10. package/dist/src/alu/src/index.d.ts +98 -9
  11. package/dist/src/alu/src/index.js +63 -9
  12. package/dist/src/alu/src/kernel-arith.d.ts +5 -1
  13. package/dist/src/alu/src/kernel-arith.js +285 -195
  14. package/dist/src/alu/src/kernel-bits.d.ts +5 -1
  15. package/dist/src/alu/src/kernel-bits.js +115 -81
  16. package/dist/src/alu/src/kernel-logic.js +63 -33
  17. package/dist/src/alu/src/kernel-nd.js +195 -155
  18. package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
  19. package/dist/src/alu/src/kernel-numeric.js +331 -259
  20. package/dist/src/alu/src/operation.d.ts +132 -112
  21. package/dist/src/alu/src/operation.js +163 -149
  22. package/dist/src/alu/src/parser.d.ts +175 -168
  23. package/dist/src/alu/src/parser.js +421 -382
  24. package/dist/src/alu/src/resonance.d.ts +25 -16
  25. package/dist/src/alu/src/resonance.js +56 -49
  26. package/dist/src/alu/src/text.d.ts +7 -3
  27. package/dist/src/alu/src/text.js +37 -31
  28. package/dist/src/alu/src/value.d.ts +14 -14
  29. package/dist/src/alu/src/value.js +160 -150
  30. package/dist/src/alu/test/alu.test.js +656 -543
  31. package/dist/src/bytes.d.ts +5 -1
  32. package/dist/src/bytes.js +40 -33
  33. package/dist/src/config.d.ts +102 -102
  34. package/dist/src/config.js +83 -83
  35. package/dist/src/derive/src/deduction.d.ts +62 -60
  36. package/dist/src/derive/src/deduction.js +109 -103
  37. package/dist/src/derive/src/index.d.ts +7 -1
  38. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  39. package/dist/src/derive/src/priority-queue.js +61 -57
  40. package/dist/src/derive/src/rewrite.d.ts +18 -15
  41. package/dist/src/derive/src/rewrite.js +67 -59
  42. package/dist/src/derive/src/trie.d.ts +56 -56
  43. package/dist/src/derive/src/trie.js +179 -175
  44. package/dist/src/derive/test/derive.test.js +100 -96
  45. package/dist/src/extension.d.ts +16 -13
  46. package/dist/src/geometry.d.ts +42 -17
  47. package/dist/src/geometry.js +266 -235
  48. package/dist/src/index.d.ts +17 -2
  49. package/dist/src/index.js +7 -1
  50. package/dist/src/ingest-cache.d.ts +30 -25
  51. package/dist/src/ingest-cache.js +127 -108
  52. package/dist/src/mind/articulation.d.ts +5 -1
  53. package/dist/src/mind/articulation.js +112 -77
  54. package/dist/src/mind/attention.d.ts +108 -40
  55. package/dist/src/mind/attention.js +871 -749
  56. package/dist/src/mind/canonical.d.ts +19 -4
  57. package/dist/src/mind/canonical.js +31 -27
  58. package/dist/src/mind/graph-search.d.ts +225 -201
  59. package/dist/src/mind/graph-search.js +821 -742
  60. package/dist/src/mind/index.d.ts +10 -2
  61. package/dist/src/mind/junction.d.ts +58 -31
  62. package/dist/src/mind/junction.js +237 -172
  63. package/dist/src/mind/learning.d.ts +52 -16
  64. package/dist/src/mind/learning.js +165 -143
  65. package/dist/src/mind/match.d.ts +69 -20
  66. package/dist/src/mind/match.js +318 -259
  67. package/dist/src/mind/mechanisms/alu.js +16 -16
  68. package/dist/src/mind/mechanisms/cast.d.ts +13 -9
  69. package/dist/src/mind/mechanisms/cast.js +456 -363
  70. package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
  71. package/dist/src/mind/mechanisms/confluence.js +183 -152
  72. package/dist/src/mind/mechanisms/cover.d.ts +8 -2
  73. package/dist/src/mind/mechanisms/cover.js +210 -148
  74. package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
  75. package/dist/src/mind/mechanisms/extraction.js +288 -234
  76. package/dist/src/mind/mechanisms/recall.d.ts +10 -6
  77. package/dist/src/mind/mechanisms/recall.js +185 -126
  78. package/dist/src/mind/mind.d.ts +154 -129
  79. package/dist/src/mind/mind.js +288 -258
  80. package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
  81. package/dist/src/mind/pipeline-mechanism.js +172 -161
  82. package/dist/src/mind/pipeline.d.ts +14 -4
  83. package/dist/src/mind/pipeline.js +189 -125
  84. package/dist/src/mind/primitives.d.ts +32 -8
  85. package/dist/src/mind/primitives.js +117 -99
  86. package/dist/src/mind/rationale.d.ts +98 -86
  87. package/dist/src/mind/rationale.js +121 -113
  88. package/dist/src/mind/reasoning.d.ts +13 -2
  89. package/dist/src/mind/reasoning.js +166 -129
  90. package/dist/src/mind/recognition.d.ts +4 -1
  91. package/dist/src/mind/recognition.js +208 -183
  92. package/dist/src/mind/resonance.d.ts +22 -5
  93. package/dist/src/mind/resonance.js +0 -0
  94. package/dist/src/mind/trace.d.ts +25 -6
  95. package/dist/src/mind/trace.js +58 -50
  96. package/dist/src/mind/traverse.d.ts +58 -16
  97. package/dist/src/mind/traverse.js +357 -304
  98. package/dist/src/mind/types.d.ts +127 -120
  99. package/dist/src/mind/types.js +69 -60
  100. package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
  101. package/dist/src/rabitq-hnsw/src/database.js +359 -342
  102. package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
  103. package/dist/src/rabitq-hnsw/src/heap.js +80 -75
  104. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
  105. package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
  106. package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
  107. package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
  108. package/dist/src/rabitq-hnsw/src/prng.js +27 -25
  109. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
  110. package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
  111. package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
  112. package/dist/src/rabitq-hnsw/src/store.js +843 -752
  113. package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
  114. package/dist/src/sema.d.ts +35 -0
  115. package/dist/src/sema.js +77 -0
  116. package/dist/src/store-sqlite.d.ts +166 -144
  117. package/dist/src/store-sqlite.js +660 -578
  118. package/dist/src/store.d.ts +630 -593
  119. package/dist/src/store.js +1581 -1440
  120. package/dist/src/vec.d.ts +9 -5
  121. package/dist/src/vec.js +73 -61
  122. package/package.json +2 -2
  123. package/src/sema.ts +102 -0
@@ -1,5 +1,13 @@
1
1
  export { Mind } from "./mind.js";
2
2
  export type { Input, Response } from "./mind.js";
3
3
  export type { ComputedSpan, ExtensionHost } from "./mind.js";
4
- export type { MechanismResult, PipelineMechanism, Precomputed, } from "./pipeline-mechanism.js";
5
- export type { InspectRationale, RationaleItem, RationaleStep, } from "./rationale.js";
4
+ export type {
5
+ MechanismResult,
6
+ PipelineMechanism,
7
+ Precomputed,
8
+ } from "./pipeline-mechanism.js";
9
+ export type {
10
+ InspectRationale,
11
+ RationaleItem,
12
+ RationaleStep,
13
+ } from "./rationale.js";
@@ -1,10 +1,10 @@
1
1
  import type { MindContext } from "./types.js";
2
2
  export interface Junction {
3
- /** The node whose learnt bytes evidence this junction (a container form,
4
- * a continuation, or a context). */
5
- id: number;
6
- /** The bytes that belong between left and right. */
7
- interior: Uint8Array;
3
+ /** The node whose learnt bytes evidence this junction (a container form,
4
+ * a continuation, or a context). */
5
+ id: number;
6
+ /** The bytes that belong between left and right. */
7
+ interior: Uint8Array;
8
8
  }
9
9
  /** Seed node ids to ascend from for one side of a junction: the side's own
10
10
  * node when it is a stored form, plus — when the node has no structural
@@ -15,7 +15,10 @@ export interface Junction {
15
15
  * the window-id path is only taken when the node alone cannot ascend.
16
16
  * Exported for callers (synonym junctions) that hold one side FIXED across
17
17
  * several calls and so compute its seeds once instead of per call. */
18
- export declare function junctionSeeds(ctx: MindContext, b: Uint8Array): number[];
18
+ export declare function junctionSeeds(
19
+ ctx: MindContext,
20
+ b: Uint8Array,
21
+ ): number[];
19
22
  /** Per-response cache of the identity walks' pure reads (capped bytes,
20
23
  * parent pages, container pages), keyed by the response lifecycle object
21
24
  * (ctx.climbMemo). One response issues many walks whose ancestries overlap
@@ -24,17 +27,22 @@ export declare function junctionSeeds(ctx: MindContext, b: Uint8Array): number[]
24
27
  * so every one of these reads is a pure function of the id — repeats cost a
25
28
  * Map hit instead of a SQL statement or a byte reconstruction. */
26
29
  export interface WalkCache {
27
- /** id → prefix bytes read so far + whether they are the COMPLETE bytes
28
- * (shorter than the cap that read them). */
29
- reads: Map<number, {
30
- b: Uint8Array;
31
- complete: boolean;
32
- }>;
33
- parents: Map<number, number[]>;
34
- containers: Map<number, number[]>;
30
+ /** id → prefix bytes read so far + whether they are the COMPLETE bytes
31
+ * (shorter than the cap that read them). */
32
+ reads: Map<number, {
33
+ b: Uint8Array;
34
+ complete: boolean;
35
+ }>;
36
+ parents: Map<number, number[]>;
37
+ containers: Map<number, number[]>;
35
38
  }
36
39
  export declare function walkCache(ctx: MindContext): WalkCache | null;
37
- export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id: number, cap: number): Uint8Array;
40
+ export declare function cachedRead(
41
+ ctx: MindContext,
42
+ cache: WalkCache | null,
43
+ id: number,
44
+ cap: number,
45
+ ): Uint8Array;
38
46
  /** Tier 1 body, parameterised on already-resolved seed lists so a caller
39
47
  * holding one side FIXED across several calls (synonym junctions) pays for
40
48
  * that side's seeds once, not once per call. The byte-containment check
@@ -59,26 +67,39 @@ export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id
59
67
  * • per-node hub guards — parent fan-outs beyond √N are hubs (not
60
68
  * expanded); each node contributes at most one √N page of containers;
61
69
  * √N collected candidates decide. */
62
- export declare function junctionContainersFrom(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, leftSeeds: number[], rightSeeds: number[],
63
- /** Shared expansion budget — a TIER's √N pops, not each walk's, when one
64
- * tier issues several walks (synonym junctions try up to 2·haloQueryK
65
- * siblings; without a shared budget each sibling would spend its own √N). */
66
- budget?: {
70
+ export declare function junctionContainersFrom(
71
+ ctx: MindContext,
72
+ left: Uint8Array,
73
+ right: Uint8Array,
74
+ maxContainer: number,
75
+ leftSeeds: number[],
76
+ rightSeeds: number[],
77
+ /** Shared expansion budget — a TIER's √N pops, not each walk's, when one
78
+ * tier issues several walks (synonym junctions try up to 2·haloQueryK
79
+ * siblings; without a shared budget each sibling would spend its own √N). */
80
+ budget?: {
67
81
  n: number;
68
- },
69
- /** ORDER-FREE containment: also accept containers holding right-then-left.
70
- * A junction is evidence that the two forms were LEARNT TOGETHER; which
71
- * one the query happened to mention first is a fact about the query, not
72
- * about the learnt whole. The walk is identical (the seed ascent does not
73
- * depend on order) — only the byte-containment test gains a second probe,
74
- * so order-freedom costs two indexOf calls per visited node, never a
75
- * second walk. */
76
- unordered?: boolean): Junction[];
82
+ },
83
+ /** ORDER-FREE containment: also accept containers holding right-then-left.
84
+ * A junction is evidence that the two forms were LEARNT TOGETHER; which
85
+ * one the query happened to mention first is a fact about the query, not
86
+ * about the learnt whole. The walk is identical (the seed ascent does not
87
+ * depend on order) — only the byte-containment test gains a second probe,
88
+ * so order-freedom costs two indexOf calls per visited node, never a
89
+ * second walk. */
90
+ unordered?: boolean,
91
+ ): Junction[];
77
92
  /** Tier 1 entry point: every learnt whole that literally contains
78
93
  * left-then-right, found by ascending the structural DAG (parents +
79
94
  * containment links) from the two sides' content-addressed identities.
80
95
  * Both sides' seeds resolved fresh, one call. */
81
- export declare function junctionContainers(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, unordered?: boolean): Junction[];
96
+ export declare function junctionContainers(
97
+ ctx: MindContext,
98
+ left: Uint8Array,
99
+ right: Uint8Array,
100
+ maxContainer: number,
101
+ unordered?: boolean,
102
+ ): Junction[];
82
103
  /** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
83
104
  * halo siblings of left and right. When a distributional synonym of one
84
105
  * form participates in a learnt whole with the other form, the container
@@ -92,4 +113,10 @@ export declare function junctionContainers(ctx: MindContext, left: Uint8Array, r
92
113
  * cost is bounded at √N·W pops total regardless of how many siblings are
93
114
  * tried. A sibling whose bytes exceed `maxInterior` is skipped (it
94
115
  * cannot be junction-sized). */
95
- export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean): Promise<Junction[]>;
116
+ export declare function junctionSynonyms(
117
+ ctx: MindContext,
118
+ left: Uint8Array,
119
+ right: Uint8Array,
120
+ maxInterior: number,
121
+ unordered?: boolean,
122
+ ): Promise<Junction[]>;
@@ -26,59 +26,69 @@ import { indexOf } from "../bytes.js";
26
26
  * Exported for callers (synonym junctions) that hold one side FIXED across
27
27
  * several calls and so compute its seeds once instead of per call. */
28
28
  export function junctionSeeds(ctx, b) {
29
- const r = resolve(ctx, b);
30
- if (r !== null) {
31
- if (ctx.store.parentsFirst(r, 1).length > 0)
32
- return [r];
33
- const wids = [...windowIds(ctx, b).values()];
34
- return [r, ...wids];
29
+ const r = resolve(ctx, b);
30
+ if (r !== null) {
31
+ if (ctx.store.parentsFirst(r, 1).length > 0) {
32
+ return [r];
35
33
  }
36
34
  const wids = [...windowIds(ctx, b).values()];
37
- if (wids.length <= 2)
38
- return wids;
39
- return [wids[0], wids[wids.length - 1]];
35
+ return [r, ...wids];
36
+ }
37
+ const wids = [...windowIds(ctx, b).values()];
38
+ if (wids.length <= 2) {
39
+ return wids;
40
+ }
41
+ return [wids[0], wids[wids.length - 1]];
40
42
  }
41
43
  const walkCaches = new WeakMap();
42
44
  export function walkCache(ctx) {
43
- if (ctx.climbMemo === null)
44
- return null;
45
- let c = walkCaches.get(ctx.climbMemo);
46
- if (c === undefined) {
47
- walkCaches.set(ctx.climbMemo, c = { reads: new Map(), parents: new Map(), containers: new Map() });
48
- }
49
- return c;
45
+ if (ctx.climbMemo === null) {
46
+ return null;
47
+ }
48
+ let c = walkCaches.get(ctx.climbMemo);
49
+ if (c === undefined) {
50
+ walkCaches.set(
51
+ ctx.climbMemo,
52
+ c = { reads: new Map(), parents: new Map(), containers: new Map() },
53
+ );
54
+ }
55
+ return c;
50
56
  }
51
57
  export function cachedRead(ctx, cache, id, cap) {
52
- if (cache === null)
53
- return read(ctx, id, cap + 1);
54
- const hit = cache.reads.get(id);
55
- // A cached COMPLETE read serves any cap; a cached truncated read serves
56
- // any cap it already covers (the caller only checks `length > cap`).
57
- if (hit !== undefined && (hit.complete || hit.b.length > cap))
58
- return hit.b;
59
- const b = read(ctx, id, cap + 1);
60
- cache.reads.set(id, { b, complete: b.length <= cap });
61
- return b;
58
+ if (cache === null) {
59
+ return read(ctx, id, cap + 1);
60
+ }
61
+ const hit = cache.reads.get(id);
62
+ // A cached COMPLETE read serves any cap; a cached truncated read serves
63
+ // any cap it already covers (the caller only checks `length > cap`).
64
+ if (hit !== undefined && (hit.complete || hit.b.length > cap)) {
65
+ return hit.b;
66
+ }
67
+ const b = read(ctx, id, cap + 1);
68
+ cache.reads.set(id, { b, complete: b.length <= cap });
69
+ return b;
62
70
  }
63
71
  function cachedParents(ctx, cache, id, limit) {
64
- if (cache === null)
65
- return ctx.store.parentsFirst(id, limit);
66
- let v = cache.parents.get(id);
67
- if (v === undefined) {
68
- v = ctx.store.parentsFirst(id, limit);
69
- cache.parents.set(id, v);
70
- }
71
- return v;
72
+ if (cache === null) {
73
+ return ctx.store.parentsFirst(id, limit);
74
+ }
75
+ let v = cache.parents.get(id);
76
+ if (v === undefined) {
77
+ v = ctx.store.parentsFirst(id, limit);
78
+ cache.parents.set(id, v);
79
+ }
80
+ return v;
72
81
  }
73
82
  function cachedContainers(ctx, cache, id, limit) {
74
- if (cache === null)
75
- return ctx.store.containersSlice(id, 0, limit);
76
- let v = cache.containers.get(id);
77
- if (v === undefined) {
78
- v = ctx.store.containersSlice(id, 0, limit);
79
- cache.containers.set(id, v);
80
- }
81
- return v;
83
+ if (cache === null) {
84
+ return ctx.store.containersSlice(id, 0, limit);
85
+ }
86
+ let v = cache.containers.get(id);
87
+ if (v === undefined) {
88
+ v = ctx.store.containersSlice(id, 0, limit);
89
+ cache.containers.set(id, v);
90
+ }
91
+ return v;
82
92
  }
83
93
  /** Tier 1 body, parameterised on already-resolved seed lists so a caller
84
94
  * holding one side FIXED across several calls (synonym junctions) pays for
@@ -104,113 +114,139 @@ function cachedContainers(ctx, cache, id, limit) {
104
114
  * • per-node hub guards — parent fan-outs beyond √N are hubs (not
105
115
  * expanded); each node contributes at most one √N page of containers;
106
116
  * √N collected candidates decide. */
107
- export function junctionContainersFrom(ctx, left, right, maxContainer, leftSeeds, rightSeeds,
108
- /** Shared expansion budget — a TIER's √N pops, not each walk's, when one
109
- * tier issues several walks (synonym junctions try up to 2·haloQueryK
110
- * siblings; without a shared budget each sibling would spend its own √N). */
111
- budget,
112
- /** ORDER-FREE containment: also accept containers holding right-then-left.
113
- * A junction is evidence that the two forms were LEARNT TOGETHER; which
114
- * one the query happened to mention first is a fact about the query, not
115
- * about the learnt whole. The walk is identical (the seed ascent does not
116
- * depend on order) only the byte-containment test gains a second probe,
117
- * so order-freedom costs two indexOf calls per visited node, never a
118
- * second walk. */
119
- unordered = false) {
120
- const bound = hubBound(ctx);
121
- const joinedLength = left.length + right.length;
122
- const seeds = [...new Set([...leftSeeds, ...rightSeeds])];
123
- if (seeds.length === 0)
124
- return [];
125
- const b = budget ?? { n: bound * ctx.space.maxGroup };
126
- // DEPTH CAP: perception trees are W-ary and a junction container is
127
- // phrase-scale, so it sits within ~log_W(maxContainer) structural levels
128
- // of its parts at most W levels for any practical W (plus the
129
- // containment hop the seeds already are). Ancestry beyond that depth is
130
- // strictly larger than any admissible container; walking it can only burn
131
- // budget, never find a junction.
132
- const maxDepth = ctx.space.maxGroup;
133
- const out = [];
134
- const cache = walkCache(ctx);
135
- const seen = new Set(seeds);
136
- const stack = seeds.map((id) => ({
137
- id,
138
- d: 0,
139
- }));
140
- while (stack.length > 0 && out.length < bound && b.n-- > 0) {
141
- const { id: x, d } = stack.pop();
142
- const f = cachedRead(ctx, cache, x, maxContainer);
143
- if (f.length > maxContainer)
144
- continue; // beyond phrase scale: prune branch
145
- if (unordered) {
146
- // Order-free containment does NOT require disjoint occurrences: two
147
- // grid-aligned fragments of the same whole legitimately OVERLAP inside
148
- // it ("red " at 0 and " cir" at 3 in `red circle`), and both being
149
- // literal substrings is the evidence. The interior is the gap between
150
- // them when they are disjoint, empty otherwise. Only the containment
151
- // test differs from the ordered form — and because occurrences may
152
- // overlap or abut, `f.length > joinedLength` is too strict (grid
153
- // fragments of one whole sum past it; "red " + "circle" exactly equals
154
- // `red circle`). The container must be a STRICT super-form of each
155
- // side, so that holding both is more than restating either.
156
- const li = indexOf(f, left, 0);
157
- const ri = li >= 0 ? indexOf(f, right, 0) : -1;
158
- if (li >= 0 && ri >= 0 && f.length > Math.max(left.length, right.length)) {
159
- const lo = Math.min(li + left.length, ri + right.length);
160
- const hi = Math.max(li, ri);
161
- out.push({
162
- id: x,
163
- interior: lo < hi ? f.subarray(lo, hi) : f.subarray(0, 0),
164
- });
165
- }
166
- }
167
- else if (f.length > joinedLength) {
168
- const li = indexOf(f, left, 0);
169
- if (li >= 0) {
170
- const ri = indexOf(f, right, li + left.length);
171
- if (ri >= 0) {
172
- out.push({ id: x, interior: f.subarray(li + left.length, ri) });
173
- }
174
- }
117
+ export function junctionContainersFrom(
118
+ ctx,
119
+ left,
120
+ right,
121
+ maxContainer,
122
+ leftSeeds,
123
+ rightSeeds,
124
+ /** Shared expansion budget a TIER's √N pops, not each walk's, when one
125
+ * tier issues several walks (synonym junctions try up to 2·haloQueryK
126
+ * siblings; without a shared budget each sibling would spend its own √N). */
127
+ budget,
128
+ /** ORDER-FREE containment: also accept containers holding right-then-left.
129
+ * A junction is evidence that the two forms were LEARNT TOGETHER; which
130
+ * one the query happened to mention first is a fact about the query, not
131
+ * about the learnt whole. The walk is identical (the seed ascent does not
132
+ * depend on order) only the byte-containment test gains a second probe,
133
+ * so order-freedom costs two indexOf calls per visited node, never a
134
+ * second walk. */
135
+ unordered = false,
136
+ ) {
137
+ const bound = hubBound(ctx);
138
+ const joinedLength = left.length + right.length;
139
+ const seeds = [...new Set([...leftSeeds, ...rightSeeds])];
140
+ if (seeds.length === 0) {
141
+ return [];
142
+ }
143
+ const b = budget ?? { n: bound * ctx.space.maxGroup };
144
+ // DEPTH CAP: perception trees are W-ary and a junction container is
145
+ // phrase-scale, so it sits within ~log_W(maxContainer) structural levels
146
+ // of its parts at most W levels for any practical W (plus the
147
+ // containment hop the seeds already are). Ancestry beyond that depth is
148
+ // strictly larger than any admissible container; walking it can only burn
149
+ // budget, never find a junction.
150
+ const maxDepth = ctx.space.maxGroup;
151
+ const out = [];
152
+ const cache = walkCache(ctx);
153
+ const seen = new Set(seeds);
154
+ const stack = seeds.map((id) => ({
155
+ id,
156
+ d: 0,
157
+ }));
158
+ while (stack.length > 0 && out.length < bound && b.n-- > 0) {
159
+ const { id: x, d } = stack.pop();
160
+ const f = cachedRead(ctx, cache, x, maxContainer);
161
+ if (f.length > maxContainer) {
162
+ continue; // beyond phrase scale: prune branch
163
+ }
164
+ if (unordered) {
165
+ // Order-free containment does NOT require disjoint occurrences: two
166
+ // grid-aligned fragments of the same whole legitimately OVERLAP inside
167
+ // it ("red " at 0 and " cir" at 3 in `red circle`), and both being
168
+ // literal substrings is the evidence. The interior is the gap between
169
+ // them when they are disjoint, empty otherwise. Only the containment
170
+ // test differs from the ordered form — and because occurrences may
171
+ // overlap or abut, `f.length > joinedLength` is too strict (grid
172
+ // fragments of one whole sum past it; "red " + "circle" exactly equals
173
+ // `red circle`). The container must be a STRICT super-form of each
174
+ // side, so that holding both is more than restating either.
175
+ const li = indexOf(f, left, 0);
176
+ const ri = li >= 0 ? indexOf(f, right, 0) : -1;
177
+ if (
178
+ li >= 0 && ri >= 0 && f.length > Math.max(left.length, right.length)
179
+ ) {
180
+ const lo = Math.min(li + left.length, ri + right.length);
181
+ const hi = Math.max(li, ri);
182
+ out.push({
183
+ id: x,
184
+ interior: lo < hi ? f.subarray(lo, hi) : f.subarray(0, 0),
185
+ });
186
+ }
187
+ } else if (f.length > joinedLength) {
188
+ const li = indexOf(f, left, 0);
189
+ if (li >= 0) {
190
+ const ri = indexOf(f, right, li + left.length);
191
+ if (ri >= 0) {
192
+ out.push({ id: x, interior: f.subarray(li + left.length, ri) });
175
193
  }
176
- if (d >= maxDepth)
177
- continue; // deeper ancestry is beyond phrase scale
178
- const parents = cachedParents(ctx, cache, x, bound + 1);
179
- if (parents.length <= bound) { // beyond √N parents: a hub, not expanded
180
- for (const p of parents) {
181
- if (!seen.has(p)) {
182
- seen.add(p);
183
- stack.push({ id: p, d: d + 1 });
184
- }
185
- }
194
+ }
195
+ }
196
+ if (d >= maxDepth) {
197
+ continue; // deeper ancestry is beyond phrase scale
198
+ }
199
+ const parents = cachedParents(ctx, cache, x, bound + 1);
200
+ if (parents.length <= bound) { // beyond √N parents: a hub, not expanded
201
+ for (const p of parents) {
202
+ if (!seen.has(p)) {
203
+ seen.add(p);
204
+ stack.push({ id: p, d: d + 1 });
186
205
  }
187
- // Containment fan-out under the SAME hub reading as parents: a node
188
- // whose containers fill a whole √N page is COMMON content — its
189
- // containment ancestry reaches a non-discriminative slice of the corpus
190
- // (the climb's saturation semantics), and walking it would spend the
191
- // entire budget discovering nothing a junction could use. Such a node
192
- // is not expanded through containment; a pair whose sides are common
193
- // abstains here in a handful of pops and falls through to the resonance
194
- // tier. Below the page bound the read IS the full container list, so
195
- // the walk stays exact exactly where identity evidence discriminates.
196
- const containers = cachedContainers(ctx, cache, x, bound);
197
- if (containers.length < bound) {
198
- for (const c of containers) {
199
- if (!seen.has(c)) {
200
- seen.add(c);
201
- stack.push({ id: c, d: d + 1 });
202
- }
203
- }
206
+ }
207
+ }
208
+ // Containment fan-out under the SAME hub reading as parents: a node
209
+ // whose containers fill a whole √N page is COMMON content — its
210
+ // containment ancestry reaches a non-discriminative slice of the corpus
211
+ // (the climb's saturation semantics), and walking it would spend the
212
+ // entire budget discovering nothing a junction could use. Such a node
213
+ // is not expanded through containment; a pair whose sides are common
214
+ // abstains here in a handful of pops and falls through to the resonance
215
+ // tier. Below the page bound the read IS the full container list, so
216
+ // the walk stays exact exactly where identity evidence discriminates.
217
+ const containers = cachedContainers(ctx, cache, x, bound);
218
+ if (containers.length < bound) {
219
+ for (const c of containers) {
220
+ if (!seen.has(c)) {
221
+ seen.add(c);
222
+ stack.push({ id: c, d: d + 1 });
204
223
  }
224
+ }
205
225
  }
206
- return out;
226
+ }
227
+ return out;
207
228
  }
208
229
  /** Tier 1 entry point: every learnt whole that literally contains
209
230
  * left-then-right, found by ascending the structural DAG (parents +
210
231
  * containment links) from the two sides' content-addressed identities.
211
232
  * Both sides' seeds resolved fresh, one call. */
212
- export function junctionContainers(ctx, left, right, maxContainer, unordered = false) {
213
- return junctionContainersFrom(ctx, left, right, maxContainer, junctionSeeds(ctx, left), junctionSeeds(ctx, right), undefined, unordered);
233
+ export function junctionContainers(
234
+ ctx,
235
+ left,
236
+ right,
237
+ maxContainer,
238
+ unordered = false,
239
+ ) {
240
+ return junctionContainersFrom(
241
+ ctx,
242
+ left,
243
+ right,
244
+ maxContainer,
245
+ junctionSeeds(ctx, left),
246
+ junctionSeeds(ctx, right),
247
+ undefined,
248
+ unordered,
249
+ );
214
250
  }
215
251
  /** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
216
252
  * halo siblings of left and right. When a distributional synonym of one
@@ -225,38 +261,67 @@ export function junctionContainers(ctx, left, right, maxContainer, unordered = f
225
261
  * cost is bounded at √N·W pops total regardless of how many siblings are
226
262
  * tried. A sibling whose bytes exceed `maxInterior` is skipped (it
227
263
  * cannot be junction-sized). */
228
- export async function junctionSynonyms(ctx, left, right, maxInterior, unordered = false) {
229
- const out = [];
230
- const lId = resolve(ctx, left);
231
- const rId = resolve(ctx, right);
232
- if (lId === null && rId === null)
233
- return out;
234
- const budget = { n: hubBound(ctx) * ctx.space.maxGroup };
235
- // Left-side synonyms: containers of sibling+right. `right`'s seeds are
236
- // FIXED across every sibling this loop tries.
237
- if (lId !== null) {
238
- const rightSeeds = junctionSeeds(ctx, right);
239
- for (const sib of await haloSiblings(ctx, lId)) {
240
- const sibBytes = read(ctx, sib.id, maxInterior + 1);
241
- if (sibBytes.length === 0 || sibBytes.length > maxInterior)
242
- continue;
243
- const containers = junctionContainersFrom(ctx, sibBytes, right, sibBytes.length + right.length + maxInterior, junctionSeeds(ctx, sibBytes), rightSeeds, budget, unordered);
244
- for (const c of containers)
245
- out.push(c);
246
- }
264
+ export async function junctionSynonyms(
265
+ ctx,
266
+ left,
267
+ right,
268
+ maxInterior,
269
+ unordered = false,
270
+ ) {
271
+ const out = [];
272
+ const lId = resolve(ctx, left);
273
+ const rId = resolve(ctx, right);
274
+ if (lId === null && rId === null) {
275
+ return out;
276
+ }
277
+ const budget = { n: hubBound(ctx) * ctx.space.maxGroup };
278
+ // Left-side synonyms: containers of sibling+right. `right`'s seeds are
279
+ // FIXED across every sibling this loop tries.
280
+ if (lId !== null) {
281
+ const rightSeeds = junctionSeeds(ctx, right);
282
+ for (const sib of await haloSiblings(ctx, lId)) {
283
+ const sibBytes = read(ctx, sib.id, maxInterior + 1);
284
+ if (sibBytes.length === 0 || sibBytes.length > maxInterior) {
285
+ continue;
286
+ }
287
+ const containers = junctionContainersFrom(
288
+ ctx,
289
+ sibBytes,
290
+ right,
291
+ sibBytes.length + right.length + maxInterior,
292
+ junctionSeeds(ctx, sibBytes),
293
+ rightSeeds,
294
+ budget,
295
+ unordered,
296
+ );
297
+ for (const c of containers) {
298
+ out.push(c);
299
+ }
247
300
  }
248
- // Right-side synonyms: containers of left+sibling. `left`'s seeds are
249
- // likewise fixed across this loop.
250
- if (rId !== null) {
251
- const leftSeeds = junctionSeeds(ctx, left);
252
- for (const sib of await haloSiblings(ctx, rId)) {
253
- const sibBytes = read(ctx, sib.id, maxInterior + 1);
254
- if (sibBytes.length === 0 || sibBytes.length > maxInterior)
255
- continue;
256
- const containers = junctionContainersFrom(ctx, left, sibBytes, left.length + sibBytes.length + maxInterior, leftSeeds, junctionSeeds(ctx, sibBytes), budget, unordered);
257
- for (const c of containers)
258
- out.push(c);
259
- }
301
+ }
302
+ // Right-side synonyms: containers of left+sibling. `left`'s seeds are
303
+ // likewise fixed across this loop.
304
+ if (rId !== null) {
305
+ const leftSeeds = junctionSeeds(ctx, left);
306
+ for (const sib of await haloSiblings(ctx, rId)) {
307
+ const sibBytes = read(ctx, sib.id, maxInterior + 1);
308
+ if (sibBytes.length === 0 || sibBytes.length > maxInterior) {
309
+ continue;
310
+ }
311
+ const containers = junctionContainersFrom(
312
+ ctx,
313
+ left,
314
+ sibBytes,
315
+ left.length + sibBytes.length + maxInterior,
316
+ leftSeeds,
317
+ junctionSeeds(ctx, sibBytes),
318
+ budget,
319
+ unordered,
320
+ );
321
+ for (const c of containers) {
322
+ out.push(c);
323
+ }
260
324
  }
261
- return out;
325
+ }
326
+ return out;
262
327
  }