@hviana/sema 0.2.6 → 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.
Files changed (133) hide show
  1. package/dist/example/demo.d.ts +1 -0
  2. package/dist/example/demo.js +39 -0
  3. package/dist/example/train_base.d.ts +87 -0
  4. package/dist/example/train_base.js +2252 -0
  5. package/dist/src/alphabet.d.ts +7 -0
  6. package/dist/src/alphabet.js +33 -0
  7. package/dist/src/alu/src/alu.d.ts +185 -0
  8. package/dist/src/alu/src/alu.js +440 -0
  9. package/dist/src/alu/src/expr.d.ts +61 -0
  10. package/dist/src/alu/src/expr.js +318 -0
  11. package/dist/src/alu/src/index.d.ts +11 -0
  12. package/dist/src/alu/src/index.js +19 -0
  13. package/dist/src/alu/src/kernel-arith.d.ts +16 -0
  14. package/dist/src/alu/src/kernel-arith.js +264 -0
  15. package/dist/src/alu/src/kernel-bits.d.ts +19 -0
  16. package/dist/src/alu/src/kernel-bits.js +152 -0
  17. package/dist/src/alu/src/kernel-logic.d.ts +4 -0
  18. package/dist/src/alu/src/kernel-logic.js +60 -0
  19. package/dist/src/alu/src/kernel-nd.d.ts +3 -0
  20. package/dist/src/alu/src/kernel-nd.js +208 -0
  21. package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
  22. package/dist/src/alu/src/kernel-numeric.js +366 -0
  23. package/dist/src/alu/src/operation.d.ts +168 -0
  24. package/dist/src/alu/src/operation.js +189 -0
  25. package/dist/src/alu/src/parser.d.ts +221 -0
  26. package/dist/src/alu/src/parser.js +577 -0
  27. package/dist/src/alu/src/resonance.d.ts +55 -0
  28. package/dist/src/alu/src/resonance.js +126 -0
  29. package/dist/src/alu/src/text.d.ts +31 -0
  30. package/dist/src/alu/src/text.js +73 -0
  31. package/dist/src/alu/src/value.d.ts +109 -0
  32. package/dist/src/alu/src/value.js +300 -0
  33. package/dist/src/alu/test/alu.test.d.ts +1 -0
  34. package/dist/src/alu/test/alu.test.js +764 -0
  35. package/dist/src/bytes.d.ts +14 -0
  36. package/dist/src/bytes.js +59 -0
  37. package/dist/src/canon.d.ts +26 -0
  38. package/dist/src/canon.js +57 -0
  39. package/dist/src/config.d.ts +111 -0
  40. package/dist/src/config.js +91 -0
  41. package/dist/src/derive/src/deduction.d.ts +125 -0
  42. package/dist/src/derive/src/deduction.js +155 -0
  43. package/dist/src/derive/src/index.d.ts +7 -0
  44. package/dist/src/derive/src/index.js +11 -0
  45. package/dist/src/derive/src/priority-queue.d.ts +20 -0
  46. package/dist/src/derive/src/priority-queue.js +73 -0
  47. package/dist/src/derive/src/rewrite.d.ts +56 -0
  48. package/dist/src/derive/src/rewrite.js +100 -0
  49. package/dist/src/derive/src/trie.d.ts +90 -0
  50. package/dist/src/derive/src/trie.js +217 -0
  51. package/dist/src/derive/test/derive.test.d.ts +1 -0
  52. package/dist/src/derive/test/derive.test.js +122 -0
  53. package/dist/src/extension.d.ts +37 -0
  54. package/dist/src/extension.js +7 -0
  55. package/dist/src/geometry.d.ts +202 -0
  56. package/dist/src/geometry.js +575 -0
  57. package/dist/src/index.d.ts +16 -0
  58. package/dist/src/index.js +18 -0
  59. package/dist/src/ingest-cache.d.ts +41 -0
  60. package/dist/src/ingest-cache.js +161 -0
  61. package/dist/src/mind/articulation.d.ts +6 -0
  62. package/dist/src/mind/articulation.js +99 -0
  63. package/dist/src/mind/attention.d.ts +405 -0
  64. package/dist/src/mind/attention.js +1829 -0
  65. package/dist/src/mind/bridge.d.ts +30 -0
  66. package/dist/src/mind/bridge.js +569 -0
  67. package/dist/src/mind/canonical.d.ts +29 -0
  68. package/dist/src/mind/canonical.js +88 -0
  69. package/dist/src/mind/graph-search.d.ts +285 -0
  70. package/dist/src/mind/graph-search.js +956 -0
  71. package/dist/src/mind/index.d.ts +7 -0
  72. package/dist/src/mind/index.js +5 -0
  73. package/dist/src/mind/junction.d.ts +124 -0
  74. package/dist/src/mind/junction.js +317 -0
  75. package/dist/src/mind/learning.d.ts +47 -0
  76. package/dist/src/mind/learning.js +248 -0
  77. package/dist/src/mind/match.d.ts +126 -0
  78. package/dist/src/mind/match.js +441 -0
  79. package/dist/src/mind/mechanisms/alu.d.ts +4 -0
  80. package/dist/src/mind/mechanisms/alu.js +36 -0
  81. package/dist/src/mind/mechanisms/cast.d.ts +89 -0
  82. package/dist/src/mind/mechanisms/cast.js +716 -0
  83. package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
  84. package/dist/src/mind/mechanisms/confluence.js +225 -0
  85. package/dist/src/mind/mechanisms/cover.d.ts +6 -0
  86. package/dist/src/mind/mechanisms/cover.js +203 -0
  87. package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
  88. package/dist/src/mind/mechanisms/extraction.js +387 -0
  89. package/dist/src/mind/mechanisms/recall.d.ts +13 -0
  90. package/dist/src/mind/mechanisms/recall.js +325 -0
  91. package/dist/src/mind/mind.d.ts +276 -0
  92. package/dist/src/mind/mind.js +572 -0
  93. package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
  94. package/dist/src/mind/pipeline-mechanism.js +213 -0
  95. package/dist/src/mind/pipeline.d.ts +20 -0
  96. package/dist/src/mind/pipeline.js +217 -0
  97. package/dist/src/mind/primitives.d.ts +70 -0
  98. package/dist/src/mind/primitives.js +287 -0
  99. package/dist/src/mind/rationale.d.ts +139 -0
  100. package/dist/src/mind/rationale.js +163 -0
  101. package/dist/src/mind/reasoning.d.ts +34 -0
  102. package/dist/src/mind/reasoning.js +240 -0
  103. package/dist/src/mind/recognition.d.ts +20 -0
  104. package/dist/src/mind/recognition.js +390 -0
  105. package/dist/src/mind/resonance.d.ts +23 -0
  106. package/dist/src/mind/resonance.js +0 -0
  107. package/dist/src/mind/trace.d.ts +15 -0
  108. package/dist/src/mind/trace.js +73 -0
  109. package/dist/src/mind/traverse.d.ts +113 -0
  110. package/dist/src/mind/traverse.js +568 -0
  111. package/dist/src/mind/types.d.ts +289 -0
  112. package/dist/src/mind/types.js +130 -0
  113. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  114. package/dist/src/rabitq-ivf/src/database.js +201 -0
  115. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  116. package/dist/src/rabitq-ivf/src/index.js +4 -0
  117. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  118. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  119. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  120. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  121. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  122. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  123. package/dist/src/sema.d.ts +31 -0
  124. package/dist/src/sema.js +63 -0
  125. package/dist/src/store-sqlite.d.ts +184 -0
  126. package/dist/src/store-sqlite.js +942 -0
  127. package/dist/src/store.d.ts +663 -0
  128. package/dist/src/store.js +1618 -0
  129. package/dist/src/vec.d.ts +31 -0
  130. package/dist/src/vec.js +109 -0
  131. package/package.json +1 -1
  132. package/src/mind/attention.ts +178 -63
  133. package/test/51-structural-resonance-ladder.test.mjs +6 -3
@@ -0,0 +1,7 @@
1
+ export { Mind } from "./mind.js";
2
+ export type { Input, Response } from "./mind.js";
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";
6
+ export type { AnchorRejectionReason, ClimbConsensusData, ConsensusAnchorTrace, ConsensusReachTrace, ConsensusRegionTrace, CrossRegionTier, JunctionVoteTrace, RegionOutcome, } from "./attention.js";
7
+ export type { AncestorReach, AttentionRead, SaturationReason, SaturationStop, } from "./types.js";
@@ -0,0 +1,5 @@
1
+ // mind/index.ts — public surface of the mind module.
2
+ //
3
+ // Re-exports the Mind class and all public types that were previously
4
+ // exported from mind/mind.ts directly.
5
+ export { Mind } from "./mind.js";
@@ -0,0 +1,124 @@
1
+ import type { Hit } from "../store.js";
2
+ import type { MindContext } from "./types.js";
3
+ export interface Junction {
4
+ /** The node whose learnt bytes evidence this junction (a container form,
5
+ * a continuation, or a context). */
6
+ id: number;
7
+ /** The bytes that belong between left and right. */
8
+ interior: Uint8Array;
9
+ }
10
+ /** Which relaxation produced a {@link SynonymJunction}: one side replaced by
11
+ * a distributional halo sibling (`single-synonym`), or both (`double-
12
+ * synonym`) — the two remaining rungs of the graded ladder below exact DAG
13
+ * containment (see the module doc atop {@link junctionSynonyms}). */
14
+ export type SynonymJunctionTier = "single-synonym" | "double-synonym";
15
+ export interface SynonymJunction extends Junction {
16
+ tier: SynonymJunctionTier;
17
+ /** Sibling score for a single-synonym junction; min(left, right) sibling
18
+ * score for a double-synonym junction. */
19
+ confidence: number;
20
+ }
21
+ /** The exact node ids and halo siblings resolved for one junction call's two
22
+ * sides — computed ONCE and reused by every ladder rung that needs them
23
+ * (junctionSynonyms' two tiers, and the structural-resonance tier beyond
24
+ * it). A failed synonym junction means only "no common DAG container was
25
+ * proven" — it does NOT mean the loaded siblings stop being useful. */
26
+ export interface JunctionSynonymSides {
27
+ leftId: number | null;
28
+ rightId: number | null;
29
+ leftSiblings: Hit[];
30
+ rightSiblings: Hit[];
31
+ }
32
+ /** Resolve `left`/`right` to their exact node ids (when known) and load each
33
+ * resolved side's halo siblings once — deterministic (haloSiblings already
34
+ * ranks nearest-first) and shared by every ladder rung that consults
35
+ * siblings, so no ladder rung repeats a halo ANN query the previous one
36
+ * already paid for. */
37
+ export declare function loadJunctionSynonymSides(ctx: MindContext, left: Uint8Array, right: Uint8Array): Promise<JunctionSynonymSides>;
38
+ /** Seed node ids to ascend from for one side of a junction: the side's own
39
+ * node when it is a stored form, plus — when the node has no structural
40
+ * parents — its canonical window ids. A non-W-aligned node may have no
41
+ * parents, but its constituent W-grams typically do; the window ids
42
+ * provide alternative ascent paths. The `parentsFirst(…, 1)` probe is a
43
+ * single indexed lookup, far cheaper than computing every window id, so
44
+ * the window-id path is only taken when the node alone cannot ascend.
45
+ * Exported for callers (synonym junctions) that hold one side FIXED across
46
+ * several calls and so compute its seeds once instead of per call. */
47
+ export declare function junctionSeeds(ctx: MindContext, b: Uint8Array): number[];
48
+ /** Per-response cache of the identity walks' pure reads (capped bytes,
49
+ * parent pages, container pages), keyed by the response lifecycle object
50
+ * (ctx.climbMemo). One response issues many walks whose ancestries overlap
51
+ * heavily (pair sides repeat across combos, and synonym walks revisit the
52
+ * same neighbourhoods); the store is read-only while a response is in flight,
53
+ * so every one of these reads is a pure function of the id — repeats cost a
54
+ * Map hit instead of a SQL statement or a byte reconstruction. */
55
+ export interface WalkCache {
56
+ /** id → prefix bytes read so far + whether they are the COMPLETE bytes
57
+ * (shorter than the cap that read them). */
58
+ reads: Map<number, {
59
+ b: Uint8Array;
60
+ complete: boolean;
61
+ }>;
62
+ parents: Map<number, number[]>;
63
+ containers: Map<number, number[]>;
64
+ }
65
+ export declare function walkCache(ctx: MindContext): WalkCache | null;
66
+ export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id: number, cap: number): Uint8Array;
67
+ /** Tier 1 body, parameterised on already-resolved seed lists so a caller
68
+ * holding one side FIXED across several calls (synonym junctions) pays for
69
+ * that side's seeds once, not once per call. The byte-containment check
70
+ * below ensures only genuine containers are returned regardless of seeds.
71
+ *
72
+ * BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
73
+ * store, where the unbounded form spent >90% of a query's CPU here):
74
+ * • PHRASE-SCALE READS — a junction container is by contract a whole the
75
+ * pair nearly exhausts (glue from a period to a phrase), so every visit
76
+ * reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
77
+ * A node whose bytes exceed the cap cannot be a junction container, and
78
+ * its ancestors are strictly larger — the branch is PRUNED, never
79
+ * reconstructing a corpus-sized deposit (an oasst2 conversation) just
80
+ * to reject it.
81
+ * • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
82
+ * frontier (the one fan-out convention) through the ~W structural
83
+ * levels that separate phrase-scale content from its containers
84
+ * (perception trees are W-ary, so a junction container lies within a
85
+ * few levels of its parts). A side too common to decide within the
86
+ * budget abstains here and falls through to the resonance tier (the
87
+ * climb's own saturation semantics).
88
+ * • per-node hub guards — parent fan-outs beyond √N are hubs (not
89
+ * expanded); each node contributes at most one √N page of containers;
90
+ * √N collected candidates decide. */
91
+ export declare function junctionContainersFrom(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, leftSeeds: number[], rightSeeds: number[],
92
+ /** Shared expansion budget — a TIER's √N pops, not each walk's, when one
93
+ * tier issues several walks (synonym junctions try up to 2·haloQueryK
94
+ * siblings; without a shared budget each sibling would spend its own √N). */
95
+ budget?: {
96
+ n: number;
97
+ },
98
+ /** ORDER-FREE containment: also accept containers holding right-then-left.
99
+ * A junction is evidence that the two forms were LEARNT TOGETHER; which
100
+ * one the query happened to mention first is a fact about the query, not
101
+ * about the learnt whole. The walk is identical (the seed ascent does not
102
+ * depend on order) — only the byte-containment test gains a second probe,
103
+ * so order-freedom costs two indexOf calls per visited node, never a
104
+ * second walk. */
105
+ unordered?: boolean): Junction[];
106
+ /** Tier 1 entry point: every learnt whole that literally contains
107
+ * left-then-right, found by ascending the structural DAG (parents +
108
+ * containment links) from the two sides' content-addressed identities.
109
+ * Both sides' seeds resolved fresh, one call. */
110
+ export declare function junctionContainers(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, unordered?: boolean): Junction[];
111
+ /** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
112
+ * halo siblings of left and right. When a distributional synonym of one
113
+ * form participates in a learnt whole with the other form, the container
114
+ * between the synonym and the other side is valid evidence for the
115
+ * original pair. The container evidence is exact (content-addressed DAG
116
+ * ascent, with window-id-enhanced seeds so non-W-aligned siblings still
117
+ * ascend); the relaxation is only in which form occupies one side — a
118
+ * distributional sibling rather than the exact form.
119
+ *
120
+ * ONE expansion budget is shared by every sibling walk in this call, so
121
+ * cost is bounded at √N·W pops total regardless of how many siblings are
122
+ * tried. A sibling whose bytes exceed `maxInterior` is skipped (it
123
+ * cannot be junction-sized). */
124
+ export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean, sides?: JunctionSynonymSides): Promise<SynonymJunction[]>;
@@ -0,0 +1,317 @@
1
+ // junction.ts — content-addressed junction search (bridge Tier 1).
2
+ //
3
+ // "Which learnt wholes ran L and R together?" answered by DAG ascent, not a
4
+ // similarity guess: hash-consing means any deposit containing L's bytes shares
5
+ // L's node (or L's canonical-window ids — position-independent identities), so
6
+ // climbing parents + containment links from L's and R's seeds reaches every
7
+ // container that literally holds L-then-R. A resonance seed (the gist of the
8
+ // bare concatenation — an object never learnt) could rank the true container
9
+ // out of its top-k; the ascent cannot.
10
+ //
11
+ // Extracted from resonance.ts so BOTH the bridge (a connector between two
12
+ // adjacent ANSWER pieces) and cross-region attention (the joint CONTEXT of two
13
+ // non-adjacent QUERY regions) ascend by the same disciplined, bounded walk.
14
+ import { read, resolve } from "./primitives.js";
15
+ import { windowIds } from "./canonical.js";
16
+ import { hubBound } from "./traverse.js";
17
+ import { haloSiblings } from "./match.js";
18
+ import { indexOf } from "../bytes.js";
19
+ /** Resolve `left`/`right` to their exact node ids (when known) and load each
20
+ * resolved side's halo siblings once — deterministic (haloSiblings already
21
+ * ranks nearest-first) and shared by every ladder rung that consults
22
+ * siblings, so no ladder rung repeats a halo ANN query the previous one
23
+ * already paid for. */
24
+ export async function loadJunctionSynonymSides(ctx, left, right) {
25
+ const leftId = resolve(ctx, left);
26
+ const rightId = resolve(ctx, right);
27
+ const leftSiblings = leftId !== null ? await haloSiblings(ctx, leftId) : [];
28
+ const rightSiblings = rightId !== null
29
+ ? await haloSiblings(ctx, rightId)
30
+ : [];
31
+ return { leftId, rightId, leftSiblings, rightSiblings };
32
+ }
33
+ /** Seed node ids to ascend from for one side of a junction: the side's own
34
+ * node when it is a stored form, plus — when the node has no structural
35
+ * parents — its canonical window ids. A non-W-aligned node may have no
36
+ * parents, but its constituent W-grams typically do; the window ids
37
+ * provide alternative ascent paths. The `parentsFirst(…, 1)` probe is a
38
+ * single indexed lookup, far cheaper than computing every window id, so
39
+ * the window-id path is only taken when the node alone cannot ascend.
40
+ * Exported for callers (synonym junctions) that hold one side FIXED across
41
+ * several calls and so compute its seeds once instead of per call. */
42
+ export function junctionSeeds(ctx, b) {
43
+ const r = resolve(ctx, b);
44
+ if (r !== null) {
45
+ if (ctx.store.parentsFirst(r, 1).length > 0)
46
+ return [r];
47
+ const wids = [...windowIds(ctx, b).values()];
48
+ return [r, ...wids];
49
+ }
50
+ const wids = [...windowIds(ctx, b).values()];
51
+ if (wids.length <= 2)
52
+ return wids;
53
+ return [wids[0], wids[wids.length - 1]];
54
+ }
55
+ const walkCaches = new WeakMap();
56
+ export function walkCache(ctx) {
57
+ if (ctx.climbMemo === null)
58
+ return null;
59
+ let c = walkCaches.get(ctx.climbMemo);
60
+ if (c === undefined) {
61
+ walkCaches.set(ctx.climbMemo, c = { reads: new Map(), parents: new Map(), containers: new Map() });
62
+ }
63
+ return c;
64
+ }
65
+ export function cachedRead(ctx, cache, id, cap) {
66
+ if (cache === null)
67
+ return read(ctx, id, cap + 1);
68
+ const hit = cache.reads.get(id);
69
+ // A cached COMPLETE read serves any cap; a cached truncated read serves
70
+ // any cap it already covers (the caller only checks `length > cap`).
71
+ if (hit !== undefined && (hit.complete || hit.b.length > cap))
72
+ return hit.b;
73
+ const b = read(ctx, id, cap + 1);
74
+ cache.reads.set(id, { b, complete: b.length <= cap });
75
+ return b;
76
+ }
77
+ function cachedParents(ctx, cache, id, limit) {
78
+ if (cache === null)
79
+ return ctx.store.parentsFirst(id, limit);
80
+ let v = cache.parents.get(id);
81
+ if (v === undefined) {
82
+ v = ctx.store.parentsFirst(id, limit);
83
+ cache.parents.set(id, v);
84
+ }
85
+ return v;
86
+ }
87
+ function cachedContainers(ctx, cache, id, limit) {
88
+ if (cache === null)
89
+ return ctx.store.containersSlice(id, 0, limit);
90
+ let v = cache.containers.get(id);
91
+ if (v === undefined) {
92
+ v = ctx.store.containersSlice(id, 0, limit);
93
+ cache.containers.set(id, v);
94
+ }
95
+ return v;
96
+ }
97
+ /** Tier 1 body, parameterised on already-resolved seed lists so a caller
98
+ * holding one side FIXED across several calls (synonym junctions) pays for
99
+ * that side's seeds once, not once per call. The byte-containment check
100
+ * below ensures only genuine containers are returned regardless of seeds.
101
+ *
102
+ * BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
103
+ * store, where the unbounded form spent >90% of a query's CPU here):
104
+ * • PHRASE-SCALE READS — a junction container is by contract a whole the
105
+ * pair nearly exhausts (glue from a period to a phrase), so every visit
106
+ * reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
107
+ * A node whose bytes exceed the cap cannot be a junction container, and
108
+ * its ancestors are strictly larger — the branch is PRUNED, never
109
+ * reconstructing a corpus-sized deposit (an oasst2 conversation) just
110
+ * to reject it.
111
+ * • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
112
+ * frontier (the one fan-out convention) through the ~W structural
113
+ * levels that separate phrase-scale content from its containers
114
+ * (perception trees are W-ary, so a junction container lies within a
115
+ * few levels of its parts). A side too common to decide within the
116
+ * budget abstains here and falls through to the resonance tier (the
117
+ * climb's own saturation semantics).
118
+ * • per-node hub guards — parent fan-outs beyond √N are hubs (not
119
+ * expanded); each node contributes at most one √N page of containers;
120
+ * √N collected candidates decide. */
121
+ export function junctionContainersFrom(ctx, left, right, maxContainer, leftSeeds, rightSeeds,
122
+ /** Shared expansion budget — a TIER's √N pops, not each walk's, when one
123
+ * tier issues several walks (synonym junctions try up to 2·haloQueryK
124
+ * siblings; without a shared budget each sibling would spend its own √N). */
125
+ budget,
126
+ /** ORDER-FREE containment: also accept containers holding right-then-left.
127
+ * A junction is evidence that the two forms were LEARNT TOGETHER; which
128
+ * one the query happened to mention first is a fact about the query, not
129
+ * about the learnt whole. The walk is identical (the seed ascent does not
130
+ * depend on order) — only the byte-containment test gains a second probe,
131
+ * so order-freedom costs two indexOf calls per visited node, never a
132
+ * second walk. */
133
+ unordered = false) {
134
+ const bound = hubBound(ctx);
135
+ const joinedLength = left.length + right.length;
136
+ const seeds = [...new Set([...leftSeeds, ...rightSeeds])];
137
+ if (seeds.length === 0)
138
+ return [];
139
+ const b = budget ?? { n: bound * ctx.space.maxGroup };
140
+ // DEPTH CAP: perception trees are W-ary and a junction container is
141
+ // phrase-scale, so it sits within ~log_W(maxContainer) structural levels
142
+ // of its parts — at most W levels for any practical W (plus the
143
+ // containment hop the seeds already are). Ancestry beyond that depth is
144
+ // strictly larger than any admissible container; walking it can only burn
145
+ // budget, never find a junction.
146
+ const maxDepth = ctx.space.maxGroup;
147
+ const out = [];
148
+ const cache = walkCache(ctx);
149
+ const seen = new Set(seeds);
150
+ const stack = seeds.map((id) => ({
151
+ id,
152
+ d: 0,
153
+ }));
154
+ while (stack.length > 0 && out.length < bound && b.n-- > 0) {
155
+ const { id: x, d } = stack.pop();
156
+ const f = cachedRead(ctx, cache, x, maxContainer);
157
+ if (f.length > maxContainer)
158
+ continue; // beyond phrase scale: prune branch
159
+ if (unordered) {
160
+ // Order-free containment does NOT require disjoint occurrences: two
161
+ // grid-aligned fragments of the same whole legitimately OVERLAP inside
162
+ // it ("red " at 0 and " cir" at 3 in `red circle`), and both being
163
+ // literal substrings is the evidence. The interior is the gap between
164
+ // them when they are disjoint, empty otherwise. Only the containment
165
+ // test differs from the ordered form — and because occurrences may
166
+ // overlap or abut, `f.length > joinedLength` is too strict (grid
167
+ // fragments of one whole sum past it; "red " + "circle" exactly equals
168
+ // `red circle`). The container must be a STRICT super-form of each
169
+ // side, so that holding both is more than restating either.
170
+ const li = indexOf(f, left, 0);
171
+ const ri = li >= 0 ? indexOf(f, right, 0) : -1;
172
+ if (li >= 0 && ri >= 0 && f.length > Math.max(left.length, right.length)) {
173
+ const lo = Math.min(li + left.length, ri + right.length);
174
+ const hi = Math.max(li, ri);
175
+ out.push({
176
+ id: x,
177
+ interior: lo < hi ? f.subarray(lo, hi) : f.subarray(0, 0),
178
+ });
179
+ }
180
+ }
181
+ else if (f.length > joinedLength) {
182
+ const li = indexOf(f, left, 0);
183
+ if (li >= 0) {
184
+ const ri = indexOf(f, right, li + left.length);
185
+ if (ri >= 0) {
186
+ out.push({ id: x, interior: f.subarray(li + left.length, ri) });
187
+ }
188
+ }
189
+ }
190
+ if (d >= maxDepth)
191
+ continue; // deeper ancestry is beyond phrase scale
192
+ const parents = cachedParents(ctx, cache, x, bound + 1);
193
+ if (parents.length <= bound) { // beyond √N parents: a hub, not expanded
194
+ for (const p of parents) {
195
+ if (!seen.has(p)) {
196
+ seen.add(p);
197
+ stack.push({ id: p, d: d + 1 });
198
+ }
199
+ }
200
+ }
201
+ // Containment fan-out under the SAME hub reading as parents: a node
202
+ // whose containers fill a whole √N page is COMMON content — its
203
+ // containment ancestry reaches a non-discriminative slice of the corpus
204
+ // (the climb's saturation semantics), and walking it would spend the
205
+ // entire budget discovering nothing a junction could use. Such a node
206
+ // is not expanded through containment; a pair whose sides are common
207
+ // abstains here in a handful of pops and falls through to the resonance
208
+ // tier. Below the page bound the read IS the full container list, so
209
+ // the walk stays exact exactly where identity evidence discriminates.
210
+ const containers = cachedContainers(ctx, cache, x, bound);
211
+ if (containers.length < bound) {
212
+ for (const c of containers) {
213
+ if (!seen.has(c)) {
214
+ seen.add(c);
215
+ stack.push({ id: c, d: d + 1 });
216
+ }
217
+ }
218
+ }
219
+ }
220
+ return out;
221
+ }
222
+ /** Tier 1 entry point: every learnt whole that literally contains
223
+ * left-then-right, found by ascending the structural DAG (parents +
224
+ * containment links) from the two sides' content-addressed identities.
225
+ * Both sides' seeds resolved fresh, one call. */
226
+ export function junctionContainers(ctx, left, right, maxContainer, unordered = false) {
227
+ return junctionContainersFrom(ctx, left, right, maxContainer, junctionSeeds(ctx, left), junctionSeeds(ctx, right), undefined, unordered);
228
+ }
229
+ /** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
230
+ * halo siblings of left and right. When a distributional synonym of one
231
+ * form participates in a learnt whole with the other form, the container
232
+ * between the synonym and the other side is valid evidence for the
233
+ * original pair. The container evidence is exact (content-addressed DAG
234
+ * ascent, with window-id-enhanced seeds so non-W-aligned siblings still
235
+ * ascend); the relaxation is only in which form occupies one side — a
236
+ * distributional sibling rather than the exact form.
237
+ *
238
+ * ONE expansion budget is shared by every sibling walk in this call, so
239
+ * cost is bounded at √N·W pops total regardless of how many siblings are
240
+ * tried. A sibling whose bytes exceed `maxInterior` is skipped (it
241
+ * cannot be junction-sized). */
242
+ export async function junctionSynonyms(ctx, left, right, maxInterior, unordered = false, sides) {
243
+ const s = sides ?? await loadJunctionSynonymSides(ctx, left, right);
244
+ if (s.leftId === null && s.rightId === null)
245
+ return [];
246
+ // ── Tier 2.5a: single-synonym — one side replaced by a halo sibling ──────
247
+ // ONE shared expansion budget across BOTH directions of this tier.
248
+ const singleBudget = { n: hubBound(ctx) * ctx.space.maxGroup };
249
+ const singleOut = new Map();
250
+ const keepBest = (map, j, tier, confidence) => {
251
+ const prev = map.get(j.id);
252
+ if (prev === undefined || confidence > prev.confidence) {
253
+ map.set(j.id, { ...j, tier, confidence });
254
+ }
255
+ };
256
+ // Left-side synonyms: containers of sibling+right. `right`'s seeds are
257
+ // FIXED across every sibling this loop tries.
258
+ if (s.leftId !== null) {
259
+ const rightSeeds = junctionSeeds(ctx, right);
260
+ for (const sib of s.leftSiblings) {
261
+ const sibBytes = read(ctx, sib.id, maxInterior + 1);
262
+ if (sibBytes.length === 0 || sibBytes.length > maxInterior)
263
+ continue;
264
+ const containers = junctionContainersFrom(ctx, sibBytes, right, sibBytes.length + right.length + maxInterior, junctionSeeds(ctx, sibBytes), rightSeeds, singleBudget, unordered);
265
+ for (const c of containers) {
266
+ keepBest(singleOut, c, "single-synonym", sib.score);
267
+ }
268
+ }
269
+ }
270
+ // Right-side synonyms: containers of left+sibling. `left`'s seeds are
271
+ // likewise fixed across this loop.
272
+ if (s.rightId !== null) {
273
+ const leftSeeds = junctionSeeds(ctx, left);
274
+ for (const sib of s.rightSiblings) {
275
+ const sibBytes = read(ctx, sib.id, maxInterior + 1);
276
+ if (sibBytes.length === 0 || sibBytes.length > maxInterior)
277
+ continue;
278
+ const containers = junctionContainersFrom(ctx, left, sibBytes, left.length + sibBytes.length + maxInterior, leftSeeds, junctionSeeds(ctx, sibBytes), singleBudget, unordered);
279
+ for (const c of containers) {
280
+ keepBest(singleOut, c, "single-synonym", sib.score);
281
+ }
282
+ }
283
+ }
284
+ if (singleOut.size > 0)
285
+ return [...singleOut.values()];
286
+ // ── Tier 2.5b: double-synonym — BOTH sides replaced, tried only when
287
+ // single-synonym found NOTHING. Every (leftSibling, rightSibling) pair,
288
+ // sorted deterministically, bounded to haloQueryK pairs total, ONE fresh
289
+ // shared budget for the whole tier. ─────────────────────────────────────
290
+ if (s.leftSiblings.length === 0 || s.rightSiblings.length === 0)
291
+ return [];
292
+ const pairs = [];
293
+ for (const l of s.leftSiblings) {
294
+ for (const r of s.rightSiblings) {
295
+ pairs.push({ l, r, confidence: Math.min(l.score, r.score) });
296
+ }
297
+ }
298
+ pairs.sort((a, b) => b.confidence - a.confidence ||
299
+ a.l.id - b.l.id ||
300
+ a.r.id - b.r.id);
301
+ const doubleOut = new Map();
302
+ const budget = { n: hubBound(ctx) * ctx.space.maxGroup };
303
+ const tries = Math.min(pairs.length, ctx.cfg.haloQueryK);
304
+ for (let i = 0; i < tries; i++) {
305
+ const { l, r, confidence } = pairs[i];
306
+ const lBytes = read(ctx, l.id, maxInterior + 1);
307
+ const rBytes = read(ctx, r.id, maxInterior + 1);
308
+ if (lBytes.length === 0 || lBytes.length > maxInterior ||
309
+ rBytes.length === 0 || rBytes.length > maxInterior)
310
+ continue;
311
+ const containers = junctionContainersFrom(ctx, lBytes, rBytes, lBytes.length + rBytes.length + maxInterior, junctionSeeds(ctx, lBytes), junctionSeeds(ctx, rBytes), budget, unordered);
312
+ for (const c of containers) {
313
+ keepBest(doubleOut, c, "double-synonym", confidence);
314
+ }
315
+ }
316
+ return [...doubleOut.values()];
317
+ }
@@ -0,0 +1,47 @@
1
+ import { Sema } from "../sema.js";
2
+ import type { Input, MindContext } from "./types.js";
3
+ /** Intern a perceived tree into node ids, bottom-up, sharing equal subtrees.
4
+ * Returns the root node id and a map from tree nodes to their ids.
5
+ *
6
+ * Memoized by NODE IDENTITY (ctx._internIds): the pyramid fold shares a
7
+ * prefix's subtree OBJECTS across an accumulated context's deposits, and a
8
+ * node already interned needs nothing again — its id is permanent
9
+ * (content-addressed) and its intern-time side effects (gist capture, kid
10
+ * rows) fired at first mint; re-interning was pure lookups. A memo hit
11
+ * therefore skips the WHOLE shared subtree, making the intern walk
12
+ * O(new nodes) per deposit instead of O(context). Only the hit node
13
+ * itself enters `ids`; descendants stay reachable via the memo (see
14
+ * idOf in indexSubSpans and the changedNodes prune). */
15
+ export declare function internTreeIds(ctx: MindContext, node: Sema, ids: Map<Sema, number>): Promise<number>;
16
+ /** Index flat branches for sub-spans of a deposit's byte stream, linked to
17
+ * their structural chunks via durable CONTAINMENT edges. */
18
+ export declare function indexSubSpans(ctx: MindContext, tree: Sema, ids: Map<Sema, number>): Promise<boolean>;
19
+ /** Perceive, intern, and index a single input. Returns the perceived tree,
20
+ * root id, id map, and the changed (new) subtrees for halo reinforcement. */
21
+ export declare function deposit(ctx: MindContext, input: Input, track: boolean, conversational?: boolean): Promise<{
22
+ tree: Sema;
23
+ rootId: number;
24
+ ids: Map<Sema, number>;
25
+ changed: Sema[];
26
+ }>;
27
+ /** Ingest a single input (a bare experience, no continuation). */
28
+ export declare function ingestOne(ctx: MindContext, input: Input): Promise<Sema & {
29
+ id: number;
30
+ }>;
31
+ /** Ingest a pair (context, continuation) — learn an edge and pour halos. */
32
+ export declare function ingestPair(ctx: MindContext, ctxInput: Input, cont: Input): Promise<void>;
33
+ /** Dispatch the public ingest input shapes onto one-input / pair handlers —
34
+ * THE one reading of ingest's polymorphic surface (scalar, (context,
35
+ * continuation) pair, or a list mixing bare inputs and pairs). Both ingest
36
+ * paths — the direct one below and {@link CachedIngest} — route through
37
+ * this, so the shape-detection can never drift between them again (the
38
+ * ingest cache once re-implemented it and drifted). */
39
+ export declare function dispatchIngest(input: Input | (Input | [Input, Input])[], second: Input | undefined, onOne: (input: Input) => Promise<Sema & {
40
+ id: number;
41
+ }>, onPair: (ctxInput: Input, cont: Input) => Promise<void>): Promise<(Sema & {
42
+ id: number;
43
+ }) | undefined>;
44
+ /** Ingest an input or array of inputs/pairs. The public ingest entry point. */
45
+ export declare function ingest(ctx: MindContext, input: Input | (Input | [Input, Input])[], second?: Input): Promise<(Sema & {
46
+ id: number;
47
+ }) | undefined>;